Está en la página 1de 1143

lndice

. .
Agradecimientos ....................................................................................................................... 6
Contactar con el autor .............................................................................................................. 7

Siete versiones y contando ..................................................................................................... 29


La estructura del libro ............................................................................................................ 31
Normas usadas en este libro ............................................................................................ 32
.
Parte I Bases ......................................................................................................................... 33
1. Delphi 7 y su IDE .............................................................................................................. 35
Ediciones de Delphi ................................................................................................................ 36
Una vision global del IDE ..................................................................................................... 37
Un IDE para dos bibliotecas ............................................................................................ 38
. .
Configuration del escritorio ..................................................................................... 38
Environment Options ....................................................................................................... 40
Sobre 10s menus ................................................................................................................. 40
El cuadro de dialog0 Environment Options .............................................................. 41
TO-DOList .......................................................................................................................... 41
Mensajes ampliados del compilador y resultados de busqueda en Delphi 7 .............. 43
El editor de Delphi ................................................................................................................. 44
El Code Explorer ............................................................................................................... 46
. .
Exploracion en el editor ................................................................................................... 48
Class Completion .............................................................................................................. 49
Code Insight ...................................................................................................................... 50
Code Completion ......................................................................................................... 50
Code Templates ............................................................................................................ 52
Code Parameters .......................................................................................................... 52
Tooltip Expression Evaluation ................................................................................... 53
Mas teclas de metodo abreviado del editor .................................................................... 53
Vistas que se pueden cargar ............................................................................................. 54
Diagram View .............................................................................................................. 54
Form Designer ......................................................................................................................... 56
Object Inspector ................................................................................................................ 58
Categorias de propiedades .......................................................................................... 60
Object TreeView ................................................................................................................ 61
Secretos de la Component Palette ......................................................................................... 63
Copiar y pegar componentes ............................................................................................ 64
De las plantillas de componentes a 10s marcos ............................................................. 65
Gestionar proyectos ................................................................................................................ 67
Opciones de proyecto ........................................................................................................ 69
Compilar y crear proyectos .............................................................................................. 71
Ayudante para mensajes del compilador y advertencias ......................................... 73
Exploracion de las clases de un proyecto ....................................................................... 74
Herramientas Delphi adicionales y externas .............................. ........................................ 75
Los archivos creados por el sistema ..................................................................................... 76
Un vistazo a 10s archivos de codigo fuente .................................................................... 82
El Object Repository ............................................................................................................... 84
Actualizaciones del depurador en Delphi 7 ......................................................................... 87

.
2 El lenguaje de programaci6n Delphi ..........................................................................89
Caracteristicas centrales del lenguaje .................................................................................. 90
Clases y objetos ....................................................................................................................... 91
Mas sobre metodos ............................................................................................................ 93
Creacion de componentes de forma dinamica ............................................................ 94
Encapsulado ............................................................................................................................ 95
Privado, protegido y public0 ............................................................................................ 96
Encapsulado con propiedades ......................................................................................... 97
Propiedades de la clase TDate ................................................................................... 99
Caracteristicas avanzadas de las propiedades ........................................................ 100
Encapsulado y formularios ............................................................................................. 101
Aiiadir propiedades a formularios ........................................................................... 101
Constructores ......................................................................................................................... 103
Destructores y el metodo Free ....................................................................................... 104
El modelo de referencia a objetos de Delphi ..................................................................... 104
Asignacion de objetos ..................................................................................................... 105
Objetos y memoria ........................................................................................................... 107
Destruir objetos una sola vez ................................................................................... 108
Herencia de 10s tipos existentes .......................................................................................... 109
Campos protegidos y encapsulado ................................................................................ 111
Herencia y compatibilidad de tipos ............................................................................... 113
Enlace posterior y polimorfismo ......................................................................................... 114
Sobrescribir y redefinir metodos ................................................................................... 115
Metodos virtuales frente a metodos dinamicos ............................................................ 117
Manejadores de mensajes ......................................................................................... 117
Metodos abstractos .......................................................................................................... 118
Conversion descendiente con seguridad de tipos .............................................................. 119
Uso de interfaces ................................................................................................................... 121
Trabajar con excepciones ..................................................................................................... 124
Flujo de programa y el bloque finally ........................................................................... 125
Clases de excepciones ..................................................................................................... 127
Registro de errores .......................................................................................................... 129
Referencias de clase .............................................................................................................. 130
Crear. componentes usando referencias de clase ....................................................... 132
.
3 La biblioteca en tiempo de ejecuc~on
.. ......................................................................... 135
Las unidades de la RTL ........................................................................................................ 136 .
Las unidades System y SysInit ...................................................................................... 139
Cambios recientes en la unidad System .................................................................. 140
Las unidades SysUtils y SysConst ................................................................................. 141
Nuevas hnciones de SysUtils .................................................................................. 142
Rutinas extendidas de formato de cadenas en Delphi 7 ........................................ 144
La unidad Math ............................................................................................................... 145
Nuevas funciones matematicas ................................................................................ 145
Redondeo y dolores de cabeza .................................................................................. 147
Las unidades ConvUtils y StdConvs .............................................................................148
La unidad DateUtils ........................................................................................................ 148
La unidad StrUtils ........................................................................................................... 149
De Pos a PosEx .......................................................................................................... 150
La unidad Types .............................................................................................................. 151
La unidad Variants y VarUtils ....................................................................................... 151
Variantes personalizadas y numeros complejos ..................................................... 152
Las unidades DelphiMM y ShareMem ......................................................................... 154
Unidades relacionadas con COM .................................................................................. 154
Convertir datos ...................................................................................................................... 154
iConversiones de divisas? ................................................................................................... 158
Gestion de archivos con SysUtils ........................................................................................162
La clase TObject ................................................................................................................... 163
Mostrar information de clase ........................................................................................ 167
.
4 La biblioteca de clases principales .............................................................................169
El paquete RTL. VCL y CLX ..............................................................................................170
Partes tradicionales de la VCL ...................................................................................... 171
La estructura de CLX ..................................................................................................... 172
Partes especificas de VCL de la biblioteca ................................................................... 173
La clase TPersistent ..............................................................................................................173
La palabra clave published .............................................................................................176
Acceso a las propiedades por su nombre ...................................................................... 177
La clase TComponent ...........................................................................................................180
Posesion ............................................................................................................................ 180
La matriz Components ......................................................................................... 181
Cambio de propietario .............................................................................................. 182
La propiedad Name ......................................................................................................... 184
. .
Elimination de campos del formulario ......................................................................... 185
Ocultar campos del formulario ...................................................................................... 186
La propiedad personalizada Tag .................................................................................... 188
Eventos ...................................................................................................... :............................ 188
Eventos en Delphi ........................................................................................................... 188
Punteros a metodo ....................................................................................................... 189
Los eventos son propiedades ......................................................................................... 190
Listas y clases contenedores ............................................................................................... 193
Listas y listas de cadena ............................................................................................... 193
Pares nombre-valor (y extensiones de Delphi 7) ................................................... 194
Usar listas de objetos ................................................................................................. 195
Colecciones ...................................................................................................................... 196
Clases de contenedores ............................................................................................. 196
. . . .
Listas asociativas de verification ............................................................................ 198
Contenedores y listas con seguridad de tipos .......................................................... 199
Streaming ............................................................................................................................... 202
La clase TStream ............................................................................................................. 202
Clases especificas de streams ......................................................................................... 204
Uso de streams de archivo .............................................................................................. 205
Las clases TReader y TWriter ........................................................................................ 206
Streams y permanencia ................................................................................................... 207
Compresion de streams con ZLib .................................................................................. 213
Resumen sobre las unidades principales de la VCL y la unidad BaseCLX ................... 215
La unidad Classes ........................................................................................................... 215
Novedades en la unidad Classes .............................................................................. 216
. .
Otras unidades prlncipales ............................................................................................. 217

.
5 Controles visuales ........................................................................................................... 219
VCL frente a VisualCLX ...................................................................................................... 220
Soporte dual de bibliotecas en Delphi .......................................................................... 222
Clases iguales, unidades diferentes ......................................................................... 223
DFM y XFM ............................................................................................................... 224
Sentencias uses .......................................................................................................... 226
Inhabilitar el soporte de ayuda a la biblioteca dual ............................................... 226
Eleccion de una biblioteca visual .................................................................................. 226
Ejecucion en Linux ................................................................................................... 227
Compilacion condicional de las bibliotecas ........................................................... 228
Conversion de aplicaciones existentes .......................................................................... 229
Las clases TControl y derivadas ......................................................................................... 230
Parent y Controls ............................................................................................................ 231
Propiedades relacionadas con el tamafio y la posicion del control ........................... 232
Propiedades de activation y visibilidad ........................................................................ 232
Fuentes ............................................................................................................................. 233
Colores ............................................................................................................................. 233
La clase TWinControl (VCL) ........................................................................................ 235
La clase TWidgetControl (CLX) ................................................................................... 236
Abrir la caja de herramientas de componentes ................................................................. 236
Los componentes de entrada de texto ........................................................................... 237
El componente Edit ................................................................................................... 237
El control LabeledEdit ............................................................................................. 238
El componente MaskEdit .......................................................................................... 238
Los componentes Memo y RichEdit ........................................................................ 239
El control CLX Textviewer ...................................................................................... 240
Seleccion de opciones ..................................................................................................... 240
Los componentes CheckBox y RadioButton ........................................................... 241
Los componentes GroupBox ..................................................................................... 241
El componente RadioGroup ..................................................................................... 241
Listas .................'............................................................................................................... 242
El componente ListBox ............................................................................................. 242
El componente ComboBox ....................................................................................... 243
El componente CheckListBox .................................................................................. 244
Los cuadros combinados extendidos: ComboBoxEx y ColorBox ......................... 245
Los componentes Listview y TreeView .................................................................. 246
El componente ValueListEditor ............................................................................... 246
Rangos .............................................................................................................................. 248
El componente ScrollBar .......................................................................................... 248
Los componentes TrackBar y ProgressBar ............................................................. 249
El componente UpDown ........................................................................................... 249
El componente PageScroller .................................................................................... 249
El componente ScrollBox ......................................................................................... 250
Comandos ......................................................................................................................... 250
Comandos y acciones ................................................................................................ 251
Menu Designer .......................................................................................................... 251
Menus contextuales y el evento OncontextPopup .............................................. 252
Tecnicas relacionadas con 10s controles ............................................................................ 254
Gestion del foco de entrada ............................................................................................ 254
Anclajes de control ......................................................................................................... 257
Uso del componente Splitter .......................................................................................... 258
Division en sentido horizontal ................................................................................. 260
Teclas aceleradoras ......................................................................................................... 261
Sugerencias flotantes ...................................................................................................... 262
Personalizacion de las sugerencias .......................................................................... 263
Estilos y controles dibujados por el propietario .......................................................... 264
Elementos del menu dibujados por el usuario ........................................................ 265
Una ListBox de colores ............................................................................................. 267
Controles ListView y TreeView ........................................................................................... 270
Una lista de referencias grafica ..................................................................................... 270
Un arb01 de datos ............................................................................................................ 275
La version adaptada de DragTree ............................................................................ 278
Nodos de arb01 personalizados ...................................................................................... 280
. ..
6 Creac~onde la interfaz de usuario ..............................................................................283
Formularios de varias paginas ............................................................................................ 284
Pagecontrols y Tabsheets .............................................................................................. 285
Un visor de imagenes con solapas dibujadas por el propietario ................................ 290
La interfaz de usuario de un asistente .......................................................................... 294
El control ToolBar ................................................................................................................ 297
El ejemplo RichBar ......................................................................................................... 298
Un menu y un cuadro combinado en una barra de herramientas .............................. 300
Una barra de estado simple ............................................................................................ 301
Temas y estilos ...................................................................................................................... 304
Estilos CLX ..................................................................................................................... 305
Temas de Windows XP ................................................................................................... 305
El Componente ActionList .................................................................................................. 308
Acciones predefinidas en Delphi ................................................................................... 310
Las acciones en la practica ............................................................................................ 312
La barra de herramientas y la lista de acciones de un editor ..................................... 316
Los contenedores de barra de herramientas .......................................................................318
ControlBar ....................................................................................................................... 320
Un menu en una barra de control ............................................................................323
Soporte de anclaje en Delphi ......................................................................................... 323
Anclaje de barras de herramientas en barras de control ............................................ 324
Control de las operaciones de anclaje ..................................................................... 325
Anclaje a un Pagecontrol ..............................................................................................329
La arquitectura de ActionManager ..................................................................................... 331
Construir una sencilla demostracion ............................................................................ 332
Objetos del menu utilizados con menos frecuencia .....................................................336
Modificar un programa existente .................................................................................. 339
Emplear las acciones de las listas ................................................................................. 340
.
7 Trabajo con formularios ................................................................................................345
La clase TForm ..................................................................................................................... 346
Usar formularios normales ............................................................................................. 346
El estilo del formulario .................................................................................................. 348
El estilo del borde ........................................................................................................... 349
Los iconos del borde .......................................................................................................352
Definicion de mas estilos de ventana ............................................................................ 354
Entrada directa en un formulario ........................................................................................ 356
Supervision de la entrada del teclado ........................................................................... 356
Obtener una entrada de raton ........................................................................................ 358
Los parametros de 10s eventos de raton ............................................................... 359
Arrastrar y dibujar con el raton ..................................................................................... 359
Pintar sobre formularios ...................................................................................................... 364
Tecnicas inusuales: Canal Alpha, Color Key y la API Animate ..................................... 366
Posicion, tamaiio, desplazamiento y ajuste de escala ....................................................... 367
..
La posicion del formulario ............................................................................................. 368
Ajuste a la ventana (en Delphi 7) ................................................................................. 368
El tamafio de un formulario y su zona de cliente ........................................................ 369
Restricciones del formulario .......................................................................................... 370
Desplazar un formulario ................................................................................................ 370
Un ejemplo de prueba de desplazamiento ............................................................... 371
Desplazamiento automatico ..................................................................................... 373
Desplazamiento y coordenadas del formulario ...................................................... 374
Escalado de formularios ................................................................................................. 376
Escalado manual del formulario .............................................................................. 377
Ajuste automatic0 de la escala del formulario ............................................................. 378
Crear y cerrar formularios ................................................................................................... 379
Eventos de creacion de formularios .............................................................................. 381
Cerrar un formulario ...................................................................................................... 382
Cuadros de dialog0 y otros formularios secundarios ........................................................ 383
Afiadir un formulario secundario a un programa ........................................................ 384
Crear formularios secundarios en tiempo de ejecucion .............................................. 385
Crear un unica instancia de formularios secundarios ........................................... 386
. .
Creacion de un cuadro de d~alogo....................................................................................... 387
El cuadro de dialogo del ejemplo RefList .................................................................... 388
Un cuadro de dialog0 no modal ..................................................................................... 390
. .
Cuadros de dialog0 predefinidos ......................................................................................... 393
Dialogos comunes de Windows ................................................................................... 394
Un desfile de cuadros de mensaje ................................................................................. 395
Cuadros "Acerca den y pantallas iniciales ......................................................................... 396
.,
Creacion de una pantalla inicial ................................................................................... 397
.
Parte I1 Arquitecturas orientadas a objetos en Delphi ...............................................401
8. La arquitectura de las aplicaciones Delphi ...............................................................403

. .
El objeto Application ............................................................................................................ 404
Mostrar la ventana de la aplicacion .............................................................................. 406
Activacion de aplicaciones y formularios .................................................................... 407
Seguimiento de formularios con el objeto Screen ..................................................... 407
De eventos a hilos ................................................................................................................. 412
Programacion guiada por eventos ................................................................................. 412
Entrega de mensajes Windows ...................................................................................... 414
Proceso secundario y multitarea .................................................................................... 414
Multihilo en Delphi ........................................................................................................ 415
Un ejemplo con hilos ................................................................................................ 416
Verificando si existe una instancia previa de una aplicacion .......................................... 418
Buscando una copia de la ventana principal ................................................................ 418
Uso de un mutex .............................................................................................................. 419
Buscar en una lista de ventanas .................................................................................... 420
Controlar mensajes de ventana definidos por el usuario ............................................ 421
Creacion de aplicaciones MDI ............................................................................................ 422
MDI en Windows: resumen tecnico ............................................................................. 422
Ventanas marco y ventanas hijo en Delphi ........................................................................ 423
Crear un menu Window completo ................................................................................. 424
El ejemplo MdiDemo ...................................................................................................... 426
Aplicaciones MDI con distintas ventanas hijo .................................................................. 428
Formularios hijo y mezcla de menus ............................................................................ 428
El formulario principal ................................................................................................... 429
Subclasificacion de la ventana MdiClient .................................................................... 430
Herencia de formularios visuales ........................................................................................ 432
Herencia de un formulario base .................................................................................... 433
Formularios polimorficos ............................................................................................... 436
Entender 10s marcos ............................................................................................................. 439
Marcos y fichas ............................................................................................................... 442
Varios marcos sin fichas ................................................................................................. 444
Formularios base e interfaces .............................................................................................. 446
Uso de una clase de formulario base ............................................................................. 447
Un truco adicional: clases de interposition ............................................................ 450
Uso de interfaces ............................................................................................................. 451
El gestor de memoria de Delphi .......................................................................................... 452
. ..
9 Creac~onde componentes Delphi ................................................................................. 455
Ampliacion de la biblioteca de Delphi ............................................................................... 456
Paquetes de componentes .............................................................................................. 4 5 6
Normas para escribir componentes ............................................................................... 458
Las clases basicas de componentes ............................................................................... 459
..
Creacion de nuestro primer componente ........................................................................... 460
El cuadro combinado Fonts ............................................................................................ 460
Creacion de un paquete .................................................................................................. 465
~ Q u Chay detras de un paquete? ............................................................................... 466
Uso del cuadro combinado Fonts ................................................................................... 469
Los mapas de bits de la Component Palette ................................................................. 469
Creacion de componentes compuestos ............................................................................... 471
Componentes internos .................................................................................................... 471
Publicacion de subcomponentes .................................................................................... 472
Componentes externos .................................................................................................... 475
Referencias a componentes mediante interfaces .......................................................... 477
Un componente grafico complejo ........................................................................................ 481
Definition de una propiedad enumerada ...................................................................... 482
Escritura del metodo Paint ............................................................................................. 484
Adicion de las propiedades TPersistent ........................................................................ 486
Definition de un nuevo evento personalizado ............................................................. 488
:
Uso de llamadas de bajo nivel a la API de Windows ..... ....................................... 489
La version CLX: Llamadas a funciones Qt nativas ............................................... 490
Registro de las categorias de propiedades .................................................................... 490
Personalizacion de 10s controles de Windows ................................................................... 492
El cuadro de edicion numeric0 ...................................................................................... 494
Un editor numeric0 con separador de millares ...................................................... 495
El boton Sound ................................................................................................................ 496
Control de mensaje internos: El boton Active ........................................................... 498
Mensajes de componente y notificaciones .................................................................... 499
Mensajes de componentes ........................................................................................ 499
Notificaciones a componentes .................................................................................. 503
Un ejemplo de mensajes de componente ................................................................. 503
Un cuadro de dialog0 en un componente ........................................................................... 504
Uso del componente no visual ....................................................................................... 508
Propiedades de coleccion ............................................................................................... 508
Definicion de acciones personalizadas ........................................................................ 512
Escritura de editores de propiedades .................................................................................. 516
Un editor para las propiedades de sonido ................................................................ 517
Instalacion del editor de propiedades ..................................................................... 520
Creacion de un editor de componentes ............................................................................... 521
Subclasificacion de la clase TComponentEditor ......................................................... 522
Un editor de componentes para ListDialog .................................................................. 522
Registro del editor de componentes ........................................................................ 524

.
10 Bibliotecas y paquetes ................................................................................................. 527
La funcion de las DLL en Windows ............................................................................ 528
El enlace dinamico .......................................................................................................... 528
Uso de las DLL ................................................................................................................ 529
Normas de creacion de DLL en Delphi ....................................................................... 530
Uso de las DLL existentes .................................................................................................... 531
Usar una DLL de C++ .................................................................................................... 532
Creacion de una DLL en Delphi ......................................................................................... 534
La primera DLL en Delphi ...................................................................................... 535
Funciones sobrecargadas en las DLL de Delphi ................................................... 537
Exportar cadenas de una DLL ................................................................................. 537
Llamada a la DLL de Delphi ...................................................................................... 539
Caracteristicas avanzadas de las DLL en Delphi ............................................................ 540
Cambiar nombres de proyecto y de biblioteca ............................................................. 540
Llamada a una funcion DLL en tiempo de ejecucion .................................................. 542
Un formulario de Delphi en una DLL .......................................................................... 544
Bibliotecas en memoria: codigo y datos ............................................................................. 546
Compartir datos con archivos proyectados en memoria ............................................. 548
Uso de paquetes Delphi ........................................................................................................ 550
Versiones de paquetes .................................................................................................... 551
Formularios dentro de paquetes .......................................................................................... 553
Carga de paquetes en tiempo de ejecucion ................................................................ 555
Uso de interfaces en paquetes .................................................................................. 558
Estructura de un paquete ..................................................................................................... 561

.
11 Modelado y programacih orientada a objetos (con ModelMaker) ...................567
Comprension del modelo interno de ModelMaker ............................................................ 568
Modelado y UML .................................................................................................................. 569
Diagramas de clase ........................................................................................................ 569
Diagramas de secuencia ............................................................................................. 571
Casos de uso y otros diagramas ..................................................................................... 572
Diagramas no W ........................................................................................................ 574
Elementos comunes de 10s diagramas ........................................................................... 575
Caracteristicas de codification de ModelMaker .......................................................... 576
Integracion Delphi / ModelMaker ................................................................................. 576
Gestion del modelo de codigo ........................................................................................ 578
El editor Unit Code Editor ............................................................................................. 580
El editor Method Implementation Code Editor ........................................................... 582
La vista de diferencias .................................................................................................... 582
La vista Event Types View ............................................................................................. 584
Documentacion y macros ..................................................................................................... 585
Documentacion frente a comentarios ............................................................................ 585
Trabajo con macros ......................................................................................................... 587
Reingenieria de codigo ......................................................................................................... 587
..
Aplicacion de patrones de diseiio .................................................................................. 590
Plantillas de codigo ......................................................................................................... 593
Detallitos poco conocidos ................................................................................................... 595

.
12 De COM a COM+ ..................................................................................................... 597
Una breve historia de OLE y COM ..................................................................................... 598
Implementacion de IUnknow .............................................................................................. 599
Identificadores globalmente unicos ............................................................................... 601
El papel de las fabricas de clases .................................................................................. 603
Un primer sewidor COM ..................................................................................................... 604
Interfaces y objetos COM ............................................................................................... 605
Inicializacion del objeto COM ....................................................................................... 608
Prueba del sewidor COM ............................................................................................... 609
Uso de las propiedades de la interfaz ........................................................................... 610
Llamada a metodos virtuales ......................................................................................... 611
Automatization ..................................................................................................................... 612
Envio de una llamada Automatizacion ......................................................................... 614
Creacion de un sewidor de Automatizacion ...................................................................... 617
El editor de bibliotecas de tipos .................................................................................... 618
El codigo del sewidor ..................................................................................................... 619
Registro del sewidor de autornatizacion ...................................................................... 621
Creacion de un cliente para el sewidor ........................................................................ 622
El alcance de 10s objetos de automatizacion ................................................................ 624
El senidor en un componente ...................................................................................... 626
Tipos de datos COM ....................................................................................................... 627
Exponer listas de cadenas y fuentes ....................................................................... 627
Us0 de programas Office ................................................................................................ 628
Uso de documentos compuestos .......................................................................................... 629
El componente Container ............................................................................................... 630
Uso del objeto interno .................................................................................................... 633
Controles ActiveX ................................................................................................................. 633
Controles ActiveX frente a componentes Delphi ........................................................ 635
Uso de controles ActiveX en Delphi ............................................................................. 636
Uso del control WebBrowser .................................................................................... 636
Creacion de controles ActiveX ............................................................................................ 638
Creacion de una flecha ActiveX .................................................................................... 639
Afiadir Nuevas Propiedades ........................................................................................... 640
Adicibn de una ficha de propiedades ............................................................................ 642
ActiveForms ..................................................................................................................... 644
Interioridades de ActiveForm ................................................................................... 644
El control ActiveX XClock ...................................................................................... 645
ActiveX en paginas Web ................................................................................................ 646
COM+ .................................................................................................................................... 648
Creacion de un componente COM+ .............................................................................. 649
Modulos de datos transaccionales ................................................................................. 651
Eventos COM+ ................................................................................................................ 653
COM y .NET en Delphi 7 .................................................................................................... 656
.
Parte I11 Arquitecturas orientadas a bases de datos en Delphi ................................ 659
13. Arquitectura de bases de datos Delphi .....................................................................661
Acceso a bases de datos: dbExpress. datos locales y otras alternativas .......................... 662
La biblioteca dbExpress .................................................................................................. 662
Borland Database Engine (BDE) .................................................................................. 664
InterBase Express (IBX) ................................................................................................ 664
MyBase y el componente ClientDataSet ....................................................................... 665
dbGo para ADO ............................................................................................................... 665
MyBase: ClientDataSet independiente ............................................................................... 666
Conexion a una tabla local ya existente ....................................................................... 667
De la DLL Midas a la unidad MidasLib ....................................................................... 669
Formatos XML y CDS .................................................................................................... 669
Definition de una tabla local nueva .............................................................................. 670
Indexado ........................................................................................................................... 671
Filtrado ............................................................................................................................. 672
Busqueda de registros ..................................................................................................... 673
Deshacer y Savepoint ................................................................................................ 674
Activar y desactivar el registro ................................................................................ 675
Uso de controles data-aware ................................................................................................ 675
Datos en una cuadricula ................................................................................................. 676
DBNavigator y acciones sobre el conjunto de datos ................................................... 676
Controles data-aware de texto ....................................................................................... 677
Controles data-aware de lista ........................................................................................ 677
El ejemplo DbAware ................................................................................................. 678
Uso de controles de busqueda ........................................................................................ 679
Controles grAficos data-aware ....................................................................................... 681
El componente DataSet ........................................................................................................ 681
El estado de un Dataset .................................................................................................. 686
Los campos de un conjunto de datos .................................................................................. 687
Uso de objetos de campo ................................................................................................ 690
Una jerarquia de clases de campo ................................................................................. 692
.,
Adicion de un campo calculado ..................................................................................... 695
Campos de busqueda ....................................................................................................... 699
Control de 10s valores nulos con eventos de campo .................................................... 701
Navegacion por un conjunto de datos ................................................................................. 702
El total de una columna de tabla ...................................................................................703
Uso de marcadores .......................................................................................................... 704
Edicion de una columna de tabla ................................................................................. 707
Personalizacion de la cuadricula de una base de datos .................................................... 707
Pintar una DBGrid ...................................................................................................... 708
Una cuadricula que permite la seleccion multiple ................................................. 710
Arrastre sobre una cuadricula ........................................................................................ 712
Aplicaciones de bases de datos con controles estandar .................................................... 713
Imitacion de 10s controles data-aware de Delphi ....................................................... 713
Envio de solicitudes a la base de datos ......................................................................... 716
Agrupacion y agregados ....................................................................................................... 718
Agrupacion ...................................................................................................................... 718
Definicion de agregados ................................................................................................. 719
Estructuras maestroldetalles ................................................................................................ 721
Maestro/detalle con 10s ClientDataSet ..................................................................... 722
Control de errores de la base de datos ............................................................................ 723

.
14 Clientelsemidor con dbExpress ............................................................................ 727
La arquitectura clientelservidor .......................................................................................... 728
Elementos del disefio de bases de datos .......................................................................... 730
Entidades y relaciones .................................................................................................... 730
Reglas de normalizacion ........................................................................................ 731
De las claves primarias a 10s OID ................................................................................. 731
Claves externas e integridad referencial ................................................................. 733
. .
Mas restricciones ............................................................................................................ 734
Cursores unidireccianales ....................................................................................... 734
Introduccion a InterBase ...................................................................................................... 736
Uso de IBConsole ............................................................................................................ 738
Programacion de servidor en InterBase ........................................................................ 740
Procedimientos almacenados ................................................................................. 740
Disparadores (y generadores) ................................................................................... 741
La biblioteca dbExpress ....................................................................................................... 743
Trabajo con cursores unidireccionales ..................................................................... 743
Plataformas y bases de datos ........................................................................................ 744
Problemas con las versiones de controladores e inclusion de unidades .................... 745
Los componentes dbExpress ................................................................................................ 746
El componente SQLConnection .................................................................................... 747
Los componentes de conjuntos de datos de dbExpress ............................................... 751
El componente SimpleDataSet de Delphi 7 ........................................................... 752
El componente SQLMonitor .......................................................................................... 753
Algunos ejemplos de dbExpress .................................................................................... 754
Uso de un componente unico o de varios ..................................................................... 755
Aplicacion de actualizaciones .................................................................................. 755
. .
Seguimiento de la conexion ..................................................................................... 756
Control del codigo SQL de actualizacion ............................................................... 757
Acceso a metadatos de la base de datos con SetSchemaInfo ................................ 758
Una consulta parametrica ............................................................................................... 760
Cuando basta una sola direccion: imprimir datas ....................................................... 762
Los paquetes y la cache ........................................................................................................ 765
Manipulacion de actualizaciones .................................................................................. 766
El estado de 10s registros ....................................................................................... 766
Acceso a Delta ........................................................................................................... 767
Actualizar 10s datos .................................................................................................... 768
Uso de transacciones ....................................................................................................... 771
Uso de InterBase Express ............................................................................................... 774
Componentes de conjunto de datos IBX ..................................................................... 776
Componentes administrativos IBX .......................................................................... 777
Creacion de un ejemplo IBX ....................................................................................... 777
Creacion de una consulta en vivo .................................................................................. 779
Control en InterBase Express ........................................................................................ 783
Obtencion de mas datos de sistema ............................................................................... 784
Bloques del mundo real ....................................................................................................... 785
Generadores e identificadores ........................................................................................ 786
Busquedas sin distincion entre mayusculas y minusculas .......................................... 788
Manejo de ubicaciones y personas ........................................................................... 790
Creacion de una interfaz de usuario .......................................................................... 792
Reserva de clases ............................................................................................................. 795
Creacion de un dialogo de busqueda ............................................................................. 798
Adicion de un formulario de consulta libre ................................................................ 800

.
15 Trabajo con ADO .......................................................................................................... 803
Microsoft Data Access Componentes (MDAC) ............................................................. 805
Proveedores de OLE DB .............................................................................................805
Uso de componentes dbGo ................................................................................................... 807
Un ejemplo practico ................................................................................................... 808
El componente ADOConnection ............................................................................. 811
Archivos de enlace de datos ......................................................................................... 811
Propiedades dinamicas ....................................................................................................... 812
Obtencion de information esquematica ............................................................................ 813
Uso del motor Jet ............................................................................................................. 815
Paradox a traves de Jet ................................................................................................... 816
Excel a traves de Jet ....................................................................................................... 817
Archivos de texto a traves de Jet ............................................................................. 819
.,
Importaclon y exportation ............................................................................................ 821
Trabajo con cursores ............................................................................................................. 822
. .
Ubicacion de cursor ................................................................................................... 822
Tipo de cursor .................................................................................................................. 823
Pedir y no recibir ............................................................................................................. 825
Sin recuento de registros ................................................................................................ 826
Indices de cliente ............................................................................................................. 826
. . .
Repllcaclon ...................................................................................................................... 827
Procesamiento de transacciones .................................................................................... 829
Transacciones anidadas ........................................................................................... 830
Atributos de ADOConnection ................................................................................... 830
Tipos de bloqueo ............................................................................................................. 831
. .
El bloqueo peslmlsta ............................................................................................. 832
Actualizacion de 10s datos ................................................................................................... 832
Actualizaciones por lotes ............................................................................................... 834
Bloqueo optimists ........................................................................................................... 836
Resolution de conflictos de actualizacion .................................................................... 839
Conjuntos de registros desconectados ................................................................................ 840
Pooling de conexiones .......................................................................................................... 841
Conjuntos de registros permanentes ............................................................................. 843
El modelo de maletin ...................................................................................................... 844
Unas palabras sobre ALIO.NET........................................................................................... 845

.
16 Aplicaciones DataSnap multicapa ............................................................................. 847
Niveles uno. dos y tres en la historia de Delphi ................................................................ 848
Fundamento tecnico de DataSnap ................................................................................. 850
La interfaz AppSener .................................................................................................... 850
Protocolo de conexion ..................................................................................................... 851
Proporcionar paquetes de datos ..................................................................................... 853
Componentes de soporte Delphi (entorno cliente) .................................................... 854
Componentes de soporte Delphi (entorno senidor) .................................................... 856
Construction de una aplicacion de ejemplo ...................................................................... 856
El primer senidor de aplicacion ................................................................................... 856
El primer cliente ligero .................................................................................................. 858
Adicion de restricciones a1 senidor .................................................................................... 860
Restricciones de campo y conjuntos de datos .............................................................. 860
Inclusion de propiedades de campo .............................................................................. 862
Eventos de campo y tabla ............................................................................................... 862
Adicion de caracteristicas a1 cliente ................................................................................... 863
Secuencia de actualization ............................................................................................ 864
Refresco de datos ............................................................................................................. 865
Caracteristicas avanzadas de DataSnap ............................................................................. 867
Consultas por parametros ............................................................................................... 868
Llamadas a metodos personalizados ............................................................................. 868
Relaciones maestroldetalle ............................................................................................. 870
Uso del agente de conexion ............................................................................................ 871
Mas opciones de proveedor ............................................................................................ 872
Agente simple de objetos ................................................................................................ 873
Pooling de objetos ........................................................................................................... 874
Personalizacion de paquetes de datos ...........................................................................874

17. CreacMn de componentes de bases de datos ........................................................... 877


El enlace de datos ................................................................................................................. 878
La clase TDataLink ......................................................................................................... 878
Clases de enlaces de datos derivadas ............................................................................ 879
Creacion de controles data-aware orientados a campos ..................................................880
Una ProgressBar de solo lectura ...................................................................................880
Una TrackBar de lectura y escritura .............................................................................884
Creacion de enlaces de datos personalizados ....................................................................887
Un componente visualizador de registros ....................................................................888
Personalizacion del componente DBGrid .......................................................................... 893
Construir conjuntos de datos personalizados .................................................................... 897
La definicion de las clases ............................................................................................. 898
Apartado I: Inicio. apertura y cierre ............................................................................. 902
Apartado 11: Movimiento y gestion de marcadores ..................................................... 907
Apartado 111: Buffers de registro y gestion de campos ............................................... 911
Apartado IV: De buffers a campos ................................................................................ 915
Comprobacion el conjunto de datos basado en streams .............................................. 917
Un directorio en un conjunto de datos ............................................................................... 918
Una lista como conjunto de datos .................................................................................. 919
Datos del directorio ......................................................................................................... 920
Un conjunto de datos de objetos .......................................................................................... 924

. ..
18 Generation de informes con Rave ............................................................................. 931

Presentation de Rave ............................................................................................................ 932


Rave: el entorno visual de creacion de informes ......................................................... 933
El Page Designer y el Event Editor ..................................................................... 934
El panel Property .................................................................................................. 934
El panel Project Tree ................................................................................................. 934
Barras de herramientas y la Toolbar Palette .......................................................... 935
La barra de estado ..................................................................................................... 936
Uso del componente RvProject ...................................................................................... 936
Formatos de representacion ........................................................................................... 938
Conexiones de datos ....................................................................................................... 939
Componentes del Rave Designer ........................................................................................ 941
Componentes basicos ...................................................................................................... 942
Componentes Text y Memo ...................................................................................... 942
El componente Section ............................................................................................. 942
Componentes grhficos ............................................................................................... 943
El componente FontMaster ...................................................................................... 943
Numeros de pagina .................................................................................................... 944
Componentes de dibujo ............................................................................................. 944
Componentes de codigo de barras ........................................................................... 944
Objetos de acceso a datos ............................................................................................... 945
Regiones y bandas ........................................................................................................... 946
El Band Style Editor ................................................................................................. 947
Componentes data-aware ............................................................................................... 949
El Data Text Editor ................................................................................................... 949
De Text a Memo ........................................................................................................ 950
Calculo de totales ...................................................................................................... 951
Repeticion de datos en paginas ................................................................................ 951
Rave avanzado ....................................................................................................................... 951
Informes maestro-detalle ................................................................................................ 952
Guiones de informes ....................................................................................................... 953
Espejos .............................................................................................................................. 954
Calculos a tope ................................................................................................................ 955
CalcTotal .................................................................................................................... 955
. ..............................................................................................959
Parte IV Delphi e Internet
19. Programacidn para Internet: sockets e Indy .........................................................961
Creacion de aplicaciones con sockets ................................................................................. 962
Bases de la programacion de sockets ............................................................................ 963
Configuracion de una red local: direcciones IP ................................................ 964
Nombres de dominio local ........................................................................................ 964
Puertos TCP ............................................................................................................... 964
Protocolos de alto nivel ............................................................................................ 965
Conexiones de socket ................................................................................................ 965
Uso de componentes TCP de Indy ................................................................................. 966
Envio de datos de una base de datos a traves de una conexion de socket ................ 970
Envio y recepcion de correo electronic0 ....................................................................... 973
Correo recibido y enviado .............................................................................................. 975
Trabajo con HTTP ................................................................................................................ 977
Obtencion de contenido HTTP ................................................................................. 978
La M I WinInet .......................................................................................................... 982
Un navegador propio ...................................................................................................... 983
Un sencillo servidor HTTP ............................................................................................ 985
Generacion de HTML ........................................................................................................... 987
Los componentes productores de codigo HTML de Delphi ........................................ 987
Generacion de paginas HTML ....................................................................................... 988
Creacion de paginas de datos ................................................................................. 990
Produccion de tablas HTML .......................................................................................... 991
Uso de hojas de estilo ..................................................................................................... 993
Paginas dinamicas de un servidor personalizado ........................................................ 994

.
20 Programacidn Web con WebBroker y WebSnap ....................................................997
Paginas Web dinarnicas .................................................................................................. 998
Un resumen de CGI ........................................................................................................ 999
Uso de bibliotecas dinamicas ....................................................................................... 1000
Tecnologia WebBroker de Delphi ..................................................................................... 1001
Depuracion con Web App Debugger ...................................................................... 1004
Creacion de un WebModule multiproposito ............................................................... 1007
Informes dinamicos de base de datos .......................................................................... 1009
Consultas y formularios ................................................................................................ 1010
Trabajo con Apache ...................................................................................................... 1014
Ejemplos practices .............................................................................................................. 1016
Un contador Web grafico de visitas ............................................................................ 1017
Busquedas con un motor Web de busquedas .............................................................. 1019
WebSnap ............................................................................................................................ 1021
., , .
Gestion de varias paglnas ........................................................................................ 1025
Guiones de servidor ...................................................................................................... 1027
Adaptadores ................................................................................................................... 1030
Campos de adaptadores .......................................................................................... 1030
Componentes de adaptadores ................................................................................. 1031
Uso del Adapterpageproducer ........................................................................... 1031
Guiones en lugar de codigo .................................................................................... 1034
Encontrar archivos ........................................................................................................ 1035
WebSnap y bases de datos .................................................................................................. 1036
Un modulo de datos WebSnap ..................................................................................... 1036
El DataSetAdapter ........................................................................................................ 1036
Edicion de 10s datos en un formulario ........................................................................ 1039
Maestro/Detalle en WebSnap ................................................................................... 1041
Sesiones, usuarios y permisos ........................................................................................... 1043
Uso de sesiones .............................................................................................................. 1043
Peticion de entrada en el sistema ............................................................................ 1045
Derechos de acceso a una unica pagina .............................................................. 1047

.
21 Programacibn Web con IntraWeb ...........................................................................1049
Introduccion a IntraWeb ............................................................................................... 1050
De sitios Web a aplicaciones Web ........................................................................... 1051
Un primer vistazo interior ...................................................................................... 1054
Arquitecturas IntraWeb .......................................................................................... 1057
Creacion del aplicaciones IntraWeb ............................................................................ 1058
Escritura de aplicaciones de varias paginas .......................................................... 1060
Gestion de sesiones ................................................................................................. 1064
Integracion con WebBroker (y WebSnap) .............................................................. 1066
Control de la estructura ................................................................................................ 1068
Aplicaciones Web de bases de datos ................................................................................. 1070
Enlaces con detalles ...................................................................................................... 1072
Transporte de datos a1 cliente ...................................................................................... 1076

.
22 Uso de tecnologias XML ............................................................................................ 1079
Presentacion de XML ......................................................................................................... 1080
Sintaxis XML basica .................................................................................................. 1080
XML bien formado ........................................................................................................ 1082
Trabajo con XML .......................................................................................................... 1083
Manejo de documentos XML en Delphi .............................................................. 1084
Programacion con DOM .................................................................................................... 1085
Un documento XML en una TreeView ................................................................... 1087
Creacion de documentos utilizando DOM ................................................................. 1090
Interfaces de enlace de datos XML ......................................................................... 1094
Validacion y esquemas ............................................................................................ 1098
Uso de la API de SAX .................................................................................................. 1099
Proyeccion de XML con transformaciones ................................................................. 1103
XML e Internet Express ..................................................................................................... 1108
El componente XMLBroker ......................................................................................... 1109
Soporte de JavaScript ................................................................................................... 1110
Creacion de un ejemplo ........................................................................................... 1111
Uso de XSLT ....................................................................................................................... 1116
Uso de XPath ................................................................................................................. 1117
XSLT en la practica ...................................................................................................... 1118
XSLT con WebSnap ...................................................................................................... 1119
Transformaciones XSL directas con DOM ................................................................. 1121
Procesamiento de grandes documentos XML ........................................................... 1123
De un ClientDataSet a un documento XML ............................................................ 1123
De un documento XML a un ClientDataSet ............................................................ 1125
.
23 Semicios Web y SOAP ............................................................................................... 1129
Servicios Web ................................................................................................................... 1130
SOAP y WSDL .............................................................................................................. 1130
Traducciones BabelFish ........................................................................................ 1131
Creacion de un servicio Web ....................................................................................... 1134
Un servicio Web de conversion de divisas ............................................................... 1135
Publicacion del WSDL ............................................................................................ 1136
Creacion de un cliente personalizado ............................................................... 1137
Peticion de datos de una base de datos ................................................................... 1139
Acceso a 10s datos ................................................................................................... 1139
Paso de documentos XML ...................................................................................... 1140
El programa cliente (con proyeccion XML) ......................................................... 1142
Depuracion de las cabeceras SOAP ............................................................................ 1143
Exponer una clase ya existente como un servicio Web ............................................. 1144
DataSnap sobre SOAP ........................................................................................................ 1145
Creacion del semidor SOAP DataSnap ...................................................................... 1145
Creacion del cliente SOAP DataSnap ......................................................................... 1148
SOAP frente a otras conexion con DataSnap ............................................................. 1148
Manejo de adjuntos ............................................................................................................. 1149
Soporte de UDDI ................................................................................................................. 1151
~ Q u Ces UDDI? .............................................................................................................. 1151
UDDI en Delphi 7 ......................................................................................................... 1153
.
Parte V ApCndices............................................................................................................ 1157
ApCndice A. Herramientas Delphi del autor ............................................................... 1159
CanTools Wizards ............................................................................................................... 1159
Programa de conversion VclToClx ................................................................................... 1162
Object Debugger ................................................................................................................. 1162
Memory Snap ...................................................................................................................... 1163
Licencias y contribuciones ................................................................................................. 1164
.
ApCndice B Contenido del CD-ROM ........................................................................... 1165
lntroduccion

La primera vez que Zack Urlocker me enseiio un product0 aun sin publicar
denominado Delphi, me di cuenta de que cambiaria mi trabajo (y el trabajo de
muchos otros desarrolladores de software). Solia pelearme con bibliotecas de
C++ para Windows y, Delphi era, y todavia es, la mejor combinacion de progra-
macion orientada a objetos y programacion visual no solo para este sistema ope-
rativo sino tambien para Linux y pronto para .NET.
Delphi 7 simplemente se suma a esta tradicion, sobre las solidas bases de la
VCL, para proporcionar otra impresionante herramienta de desarrollo de soft-
ware que lo coordina todo. iEsta buscando soluciones de bases de datos, clientel
servidor, multicapa (multitier), Intranet o Internet? iBusca control y potencia?
~ B U SunaC ~rapida productividad? Con Delphi y la multitud de tecnicas y trucos
que se presentan en este libro, sera capaz de conseguir todo eso.

Siete versiones y contando


Algunas de las propiedades originales de Delphi que me atrajeron heron su
enfoque orientado a objetos y basado en formularios, su compilador extremada-
mente rapido, su gran soporte para bases de datos, su estrecha integracion con la
programacion para Windows y su tecnologia de componentes. Pero el elemento
mas importante era el lenguaje Pascal orientado a objetos, que es la base de todo
lo demas.
iDelphi 2 era incluso mejor! Entre sus propiedades aiiadidas mas importantes
estaban las siguientes: El Multi Record Object y la cuadricula para bases de datos
mejorada, el soporte para Automatizacion OLE y el tipo de datos variantes, el
soporte e integracion totales de Windows 95, el tip0 de datos de cadena larga y la
herencia de formulario visual. Delphi 3 aiiadio la tecnologia Code Insight, el
soporte de depuracion DLL, las plantillas de componentes, el Teechart, el Decision
Cube, la tecnologia WebBroker, 10s paquetes de componentes, 10s ActiveForms y
una sorprendente integracion con COM, gracias a las interfaces.
Delphi 4 nos trajo el editor AppBrowser, nuevas propiedades de Windows 98,
mejor soporte OLE y COM, componentes de bases de datos ampliados y muchas
mas clases principales de la VCL aiiadidas, como el soporte para acoplamiento,
restriccion y anclaje de 10s controles. Delphi 5 aiiadio a este cuadro muchas
mejoras en el IDE (demasiadas para enumerarlas aqui), soporte ampliado para
bases de datos (con conjuntos de datos especificos de ADO e InterBase), una
version mejorada de MIDAS con soporte para Internet, la herramienta de control
de versiones Teamsource, capacidades de traduccion, el concept0 de marcos y
nuevos componentes.
Delphi 6 aiiadio a todas estas propiedades el soporte para el desarrollo
multiplataforma con la nueva biblioteca de componentes para multiplataforma
(CLX), una biblioteca en tiempo de ejecucion ampliada, el motor para base de
datos dbExpress, un soporte excepcional de servicios Web y XML, un poderoso
marco de trabajo de desarrollo Web, mas mejoras en el IDE y multitud de compo-
nentes y clases, que siguen comentandose en las paginas siguientes.
Delphi 7 proporciono mas robustez a estas nuevas tecnologias con mejoras y
arreglos (el soporte de SOAP y DataSnap es lo primer0 en lo que puedo pensar) y
ofrece soporte para tecnologias m h novedosas (como 10s temas de Windows XP
o UDDI), per0 lo mas importante es que permite disponer rapidamente de un
interesante conjunto de herramientas de terceras partes: el motor de generacion de
informes RAVE, la tecnologia de desarrollo de aplicaciones Web IntraWeb y el
entorno de diseiio ModelMaker. Finalmente, abre las puertas aun mundo nuevo a1
ofrecer (aunque sea como prueba) el primer compilador de Borland para el len-
guaje PascallDelphi no orientado a la CPU de Intel, si no a la plataforma CIL de
.NET.
Delphi es una gran herramienta, per0 es tambien un entorno de programacion
completo en el que hay muchos elementos involucrados. Este libro le ayudara a
dominar la programacion en Delphi, incluidos el lenguaje Delphi, 10s componen-
tes (a usar 10s existentes y crear otros propios), el soporte de bases de datos y
clientelservidor, 10s elementos clave de programacion en Windows y COM y el
desarrollo para Web e Internet.
No necesita tener un amplio conocimiento de estos temas para leer el libro,
per0 es necesario que conozca las bases de la programacion. Le ayudara conside-
rablemente el estar familiarizado con el lenguaje Delphi, sobre todo despues de
10s capitulos introductorios. El libro comienza a tratar 10s temas con detenimiento
de forma inmediata; se ha eliminado gran parte del material introductorio incluido
en otros textos.

La estructura del libro


El libro se divide en cinco partes:
Parte I: Bases. Introduce las nuevas propiedades del entorno de desarrollo
integrado (IDE) de Delphi 7 en el capitulo 1, a continuacion pasa a1 len-
guaje Delphi y a la biblioteca en tiempo de ejecucion (RTL) y la biblioteca
de componentes visuales (VCL). Cuatro capitulos proporcionan las bases
y explicaciones avanzadas sobre 10s controles mas usados, el desarrollo de
interfaces de usuario avanzadas y el uso de formularies.
Parte 11: Arquitecturas orientadas a objetos en Delphi. Trata las aplicacio-
nes Delphi, el desarrollo de componentes personalizados, el uso de biblio-
tecas y paquetes, el uso de ModelMaker y COM+.
Parte 111: Arquitecturas orientadas a bases de datos en Delphi. Trata sobre
el acceso simple a las bases de datos, la explicacion pormenorizada de 10s
controles data-aware, la programacion clientelservidor, dbExpress,
InterBase, ADO, Datasnap, el desarrollo de controles data-aware y con-
juntos de datos personalizados y la generacion de informes.
Parte IV: Delphi e Internet. Trata en primer lugar sobre 10s sockets TCPI
IP, 10s protocolos de Internet e Indy, y despues pasa a areas especificas
como las extensiones del lado del servidor Web (con WebBroker, WebSnap
e IntraWeb) y acaba con XML y el desarrollo de servicios Web.
Parte V: Apendices. Describe las herramientas extra de Delphi y el conte-
nido del CD-ROM que acompaiia a1 libro.
Tal como sugiere este breve resumen, el libro trata muchos temas de interes
para 10s usuarios de Delphi con casi cualquier nivel de experiencia en programa-
cion, desde "principiantes avanzados" a desarrolladores de componentes.
En el libro, he intentado eludir el material de referencia casi por completo y me
he centrado, en cambio, en las tecnicas para utilizar Delphi de forma efectiva.
Dado que Delphi ofrece amplia documentacion electronica, incluir listas sobre
metodos y propiedades de componentes en el libro resultaria superfluo y haria que
la obra quedase obsoleta en cuanto el software sufriese pequeiios cambios. Para
tener material de referencia disponible, le sugiero que lea el libro con 10s archivos
de Ayuda de Delphi a mano. Sin embargo, he hecho todo lo posible para que el
libro se pueda leer lejos del ordenador, si asi se prefiere. Las capturas de pantalla
y 10s fragmentos clave de 10s listados deberian ayudarle en ese sentido. El libro
utiliza unicamente unas cuantas convenciones para resultar mas legible.

Normas usadas en este libro


En este libro se usan las siguientes convenciones tipograficas:
Las opciones de menus se indican en orden jerarquico, con cada instruc-
cion de menu separada por el signo "mayor que" y en un tip0 de letra Arial.
Por ejemplo, File>Open quiere decir hacer clic en el comando File en la
barra de menu y luego seleccionar Open.
Todos 10s elementos del codigo fuente, como las palabras clave, las pro-
piedades, las clases y las funciones, aparecen en un tipo de l e t r a c o u r i e r
y 10s fragmentos de codigo poseen el mismo formato que el utilizado en el
editor Delphi, a saber, las palabras claves en negrita y 10s comentarios y
cadenas en cursiva.
Las combinaciones de teclas se indican de esta forma: Control-C.
A lo largo del libro encontrara unos rectangulos sombreados que resaltan
la informacion especial o importante, por ejemplo:

ADVERTENCIA:Indica un procedimiento que, en teoriq podria causar


dificultades o incluso la ptr&da de datos.
-
NOTA: Resalta la informacion interesante o erdicional y suele contener
pequefios trozos extra de informaci6n tecnica sobrc dn terna.
-- - -- - --

TRUCO:Llamm la atenci6n sobre habiles sugerencias, pistas recomenda-


bles y consejos 6tiles.
Bases
Delphi 7

En una herramienta de programacion visual como Delphi, el papel del Entorno


de Desarrollo Integrado (IDE, Integrated Development Environment) resulta a
veces mas importante que el lenguaje de programacion. Delphi 7 ofrece algunas
nuevas caracteristicas muy interesantes sobre el maravilloso IDE de Delphi 6. En
este capitulo examinaremos estas nuevas caracteristicas, a1 igual que las caracte-
risticas aiiadidas en otras versiones recientes de Delphi. Tambien comentaremos
unas cuantas caracteristicas tradicionales de Delphi que no son bien conocidas u
obvias a 10s recien llegados. Este capitulo no es un tutorial completo sobre el
IDE, que necesitaria mucho mas espacio; principalmente es un conjunto de conse-
jos y sugerencias dirigidas a1 usuario medio de Delphi. Si se trata de un progra-
mador novato, no se preocupe. El IDE de Delphi es bastante intuitivo. El propio
Delphi incluye un manual (disponible en formato Acrobat en el CD Delphi
Companion Tools) con un tutorial que presenta el desarrollo de aplicaciones en
Delphi. Puede encontrar una introduccion mas sencilla a Delphi y su IDE en otros
textos. Pero en este libro asumiremos que ya sabe como llevar a cab0 las opera-
ciones basicas del IDE; todos 10s capitulos despues de este se centraran en cues-
tiones y tecnicas de programacion. Este capitulo trata 10s siguientes temas:
Navegacion del IDE.
El editor.
La tecnologia Code Insight.
Diseiio de formularios.
El Project Manager
Archivos de Delphi.

Ediciones de Delphi
Antes de pasar a 10s pormenores del entorno de programacion de Delphi, resal-
taremos dos ideas clave. En primer lugar, no hay una unica edicion de Delphi,
sino muchas. En segundo lugar, cualquier entorno Delphi se puede personalizar.
Por dichas razones, las pantallas de Delphi que aparecen en este capitulo pueden
ser distintas a las que vea en su ordenador. Las ediciones de Delphi actuales son
las siguientes:
La edicion "Personal": Dirigida a quienes empiezan a utilizar Delphi y a
programadores esporadicos. No soporta programacion de bases de datos ni
ninguna de las caracteristicas avanzadas de Delphi.
La edicion "Professional Studio": Dirigida a desarrolladores profesiona-
les. Posee todas las caracteristicas basicas, mas soporte para programa-
cion de bases de datos (corno soporte ADO), soporte basico para servidores
Web (WebBroker) y algunas herramientas externas como ModelMaker e
IntraWeb. En el libro se asume que el lector trabaja como minimo con la
edicion Professional.
La edici6n "Enterprise Studio": Esta dirigida a desarrolladores que crean
aplicaciones para empresas. Incluye todas las tecnologias XML y de servi-
cios Web avanzados, soporte de CORBA, internacionalizacion, arquitec-
tura en tres niveles y muchas otras herramientas. Algunos capitulos del
libro tratan sobre caracteristicas que solo posee esta version de Delphi y
asi se ha especificado en esos casos.
La edicihn "Architect Studio": Aiiade a la edicion Enterprise el soporte
de Bold, un entorno para la creacion de aplicaciones dirigidas en tiempo de
ejecucion por un modelo UML y capaces de proyectar sus objetos tanto
sobre una base de datos como sobre una interfaz de usuarios, gracias a una
gran cantidad de componentes avanzados. El soporte de Bold no se trata en
este libro.
Ademas de las distintas versiones disponibles, existen varias formas de perso-
nalizar el entorno Delphi. En las capturas de pantalla presentadas a lo largo del
libro, se ha intentado utilizar una interfaz estandar (corno la que resulta de la
instalacion tal cual). Sin embargo, en ciertos ejemplos, pueden aparecer refleja-
das algunas preferencias del autor como la instalacion de muchos aiiadidos, que
pueden reflejarse en el aspect0 de las pantallas. La version Professional y supe-
riores de Delphi 7 incluyen una copia funcional de Kylix 3, en la edicion de
lenguaje Delphi. Ademas de referencias a la biblioteca CLX y a las caracteristi-
cas multiplataforma de Delphi, este libro no trata Kylix ni el desarrollo sobre
Linux. Puede buscar otras obras para conseguir mas informacion sobre este tema.
(No hay muchas diferencias entre Kylix 2 y Kylix 3 en la version de lenguaje
Delphi. La caracteristica nueva mas importante de Kylix 3 es su soporte del
lenguaje C++.)

Una vision global del IDE


Cuando se trabaja con un entorno de desarrollo visual, el tiempo se emplea en
dos partes distintas de la aplicacion: en 10s asistentes de disefio visual y en el
editor de codigo. Los asistentes de diseiio permiten trabajar con componentes a un
nivel visual (como cuando se coloca un boton sobre un formulario) o a un nivel no
visual (como cuando se situa un componente DataSet sobre un modulo de datos).
La figura 1.1 muestra un formulario y un modulo de datos en accion. En ambos
casos, 10s asistentes de diseiio permiten escoger 10s componentes necesarios y
fijar el valor inicial de las propiedades de 10s componentes.

Figura 1.1. Un formulario y un modulo de datos en el IDE de Delphi 7.

rn
El editor de codigo es donde se escribe el codigo. El mod0 mas obvio de
escribir codigo en un entorno visual implica responder a eventos, comenzando por
10s eventos enlazados con las operaciones realizadas por 10s usuarios del progra-
ma, como hacer clic sobre un boton o escoger un elemento de un cuadro de lista.
Puede usarse el mismo enfoque para manejar eventos internos, como 10s eventos
que implican cambios en bases de datos o notificaciones del sistema operativo.
A medida que 10s programadores adquieren un mayor conocimiento sobre
Delphi, suelen comenzar escribiendo basicamente codigo gestor de eventos y des-
pues escriben sus propias clases y componentes y, normalmente, acaban invir-
tiendo la mayor parte de su tiempo en el editor. Ya que este libro trata mas
conceptos que la programacion visual e intenta ayudar a dominar toda la potencia
de Delphi, a medida que el testo avance se vera mas codigo y menos formularios.

Un IDE para dos bibliotecas


Por primera vez en Delphi 6 aparecio un importante cambio. El IDE permite
ahora utilizar dos bibliotecas de componentes distintas: la VCL (Visual Cornpo-
nente Library, Biblioteca de componentes visuales) y la CLX (Component Library
for Cross-Platform, Biblioteca de componentes para multiplataforma). Cuando
creamos un nuevo proyecto, sencillamente escogemos cual de las dos bibliotecas
queremos emplear, con las opciones de menu File>New>Application en el caso
de un clasico programa Windows basado en la VCL y con las opciones
File>New>CLX Application en el caso de una nueva aplicacion que se puede
transportar basada en la CLX.

blpbi w-pefliiik,~mmpi&p 91cbdi& +g G&para, qvfwnicqk bajc


Fioux. Re- ipte~eaante:vt&w CLS De& 7, ya que ip versi6n para
lenguaje lM@idc K y b se dkstribuyejunto con el pv&tr;topam ~ind'ms,

Al crear un nuevo proyecto o abrir uno que ya existe, la Component Palette


se reorganiza para mostrar solo 10s controles relacionados con la biblioteca en
uso (aunque en realidad la mayoria de 10s controles son compartidos). Cuando se
traba con un diseiiador no visual (como un modulo de datos), las pestaiias de la
Component Palette que muestran solo 10s componentes visuales se ocultan de
la vista.

Configuracion del escritorio


Los programadores pueden personalizar el IDE de Delphi de varias maneras
(tipicamente abriendo muchas ventanas, reordenandolas, y acoplandolas entre si).
Sin embargo, normalmente sera necesario abrir un conjunto de ventanas en tiem-
po de diseiio y un conjunto distinto en tiempo de depuracion. Del mismo modo,
podria necesitarse una disposicion cuando se trabaje con formularios y otra com-
pletamente diferente cuando se escriban componentes o codigo de bajo nivel me-
diante el unico uso del editor. Reorganizar el IDE para cada una de estas
necesidades es una tarea tediosa.
Por este motivo, Delphi permite almacenar una determinada disposicion de las
ventanas del IDE (llamada escritorio o escritorio global (Global Desktop) para
distinguirlo de un escritorio de proyecto (Project Desktop) con un nombre y recu-
perarla rapidamente. Tambien se puede convertir a una de estas agrupaciones en
la configuracion predeterminada para la depuracion, de manera que se recuperara
automaticamente cuando se inicie el depurador. Todas estas caracteristicas estan
disponibles en la barra de herramientas Desktops. Tambien puede trabajar con las
configuraciones de escritorio mediante el menu View>Desktops.
La informacion de configuracion de escritorio se guarda en archivos DST
(dentro del directorio b i n de Delphi), que en realidad son archivos INI. Los
parametros guardados incluyen la posicion de la ventana principal, el Project
Manager, la Alignment Palette, el Object Inspector (incluida su configura-
cion de categorias de propiedades), el editor de ventanas (con el estado del Code
Explorer y la Message View) y muchos otros, ademb del estado de anclaje de
las diversas ventanas. Este es un pequeiio extract0 de un archivo DST, que debe-
ria resultar facil de leer:
[Main Window]
Create=l
Visible=l
State=O
Left=O
Top=O
Width=1024
Height=105
ClientWidth=1016
ClientHeight=78

[Alignmentpalette]
Create=l
Visible=O

Las configuraciones de escritorio tienen mas fierza que las configuraciones de


proyecto, que se guardan en un archivo DSK con una estructura similar. Las
configuraciones de escritorio ayudan a eliminar problemas que pueden suceder
cuando se traslada un proyecto de una maquina a otra (o de un desarrollador a
otro) y es necesario reorganizar las ventanas a1 gusto. Delphi separa las configu-
raciones de escritorio globales por usuario y las configuraciones de escritorio por
proyecto, para ofrecer un mejor soporte a equipos de desarrollo.
- ---- 7 ---- -
TRUCO:Si se abre Delphi y no se puede ver el formulario u otras vent.-
nas, es recornendable cornprobar (o borrar) las configuraciones dti 'escrito-
rio (en el directorio bin de Delphi). Si se abre un proyecto recibido de un
usuario clistinto y no se pueden ver algunas de las ventanas o no gusta la
disposici6n del escritorio, lo mejor es volver a cargar las c o n f i s e i o n e s
de 10s escritorios globales o borrar el archivo DSK del proyccto. .

Environment Options
Unas cuantas de las ultimas mejoras tienen que ver con el habitual cuadro de
dialogo Environment Options. Las paginas de este cuadro de dialogo se reorga-
nizaron en Delphi 6, desplazando las opciones del Form Designer de la pagina
Preferences a la nueva pagina Designer. En Delphi 6 tambien existian unas
cuantas opciones y paginas nuevas:
La pagina Preferences del cuadro de dialogo: Time una casilla de veri-
ficacion que impide que las ventanas de Delphi se acoplen automaticamente
entre si.
La pagina Environment Variables: Permite inspeccionar las variables
del entorno del sistema (como las rutas predefinidas y parametros del SO)
y establecer variables definidas por el usuario. Lo bueno es que se pueden
utilizar ambos tipos de variable en cada uno de 10s cuadros de dialogo del
IDE (por ejemplo, se puede evitar escribir explicitamente rutas usadas
habitualmente, sustituyendolas por una variable). En otras palabras, las
variables del entorno funcionan de manera similar a la variable $DELPHI,
que hace referencia a1 directorio base de Delphi per0 puede ser definida
por el usuario.
L a pagina Internet: En ella se pueden escoger cuales son las extensiones
de archivo predefinidas para 10s archivos HTML y SML (basicamente por
el marco de trabajo WebSnap) y tambien asociar un editor externo con
cada extension.

Sobre 10s menus


La principal barra de menu de Delphi (que en Delphi 7 tiene un aspect0 mas
moderno) es un metodo importante de interaccion con el IDE, aunque probable-
mcnte la mayoria de las tareas se realizaran mediante atajos de teclado y de menu.
La barra de menu no cambia demasiado como reaccion a la operacion actual: se
necesita hacer clic con el boton derecho del raton para conseguir una lista de las
operaciones que se pueden realizar en la ventana o componente actual.
La barra de menu cambia en gran medida segun las herramientas y asistentes
de terceras partes que se hayan instalado. En Delphi 7, ModelMaker dispone de
su propio menu. Si se instalan modulos adicionales como GExperts se pueden
contemplar otros menus.
Un importante menu afiadido a Delphi en las versiones mas recientes es el
menu Window del IDE. Este menu muestra la lista de las ventanas abiertas; antes,
se podia obtener esta lista mediante la combinacion de teclas Alt-0 o la opcion de
menu View>Window List. El menu Window resulta realmente practico, ya que
las ventanas suelen acabar detras de otras y son dificiles de encontrar. Puede
controlarse el orden alfabetico de este menu mediante un parametro del Registro
de Windows: hay que encontrar la subclave Main Window de Delphi (dentro de
HKEY CURRENT USER\Software\Borland\Delphi\7.0). Esta c l a w
del ~ e g i s t r outiliz; una cadena (en lugar de valores booleanos), donde -1 y
True indican verdadero y 0 y False indican falso.

TRUCO: En Delphi 7, el menu Windows finaliza con un comando nuevo:


Next Window. Este comando resulta particularmente util como atajo,
ALGF6p. Se pueden recorrer las diversas ventanas del IDE de manera muy
sencilla olediante este comando.

El cuadro de dialogo Environment Options


Como ya se ha comentado, algunos de 10s parametros del IDE necesitan que se
edite directamente el Registro. Por supuesto, 10s parametros mas comunes pueden
ajustarse simplemente mediante el cuadro de dialogo Environment Options, que
esta disponible a traves del menu TOOISjunto con Editor Options y Debugger
Options. La mayor parte de 10s parametros resultan bastante intuitivos y estan
bien descritos en el archivo de ayuda de Delphi. La figura 1.2 muestra mis
parametros estandar para la pagina Preferences de este cuadro de dialogo.

TO-DOList
Otra caracteristica aiiadida en Delphi 5 pero que aun sigue sin usarse como
deberia es la lista de tareas pendientes. Se trata de una lista de tareas que aun se
debe realizar para completar un proyecto (es un conjunto de notas para el progra-
mador o programadores, que resulta una herramienta muy util en un equipo).
Aunque la idea no es novedosa, el concept0 clave de la lista de tareas pendientes
en Delphi es que funciona como una herramienta de dos vias.
Figura 1.2. La pagina Preferences del cuadro de dialogo Environment Options.

Se pueden aiiadir o modificar elementos pendientes a esta lista afiadiendo co-


mentarios TODO al codigo fuente de cualquier archivo de un proyecto; se pueden
ver las entradas correspondientes en la lista. Ademas, se pueden editar visualmente
10s elementos de la lista para modificar el comentario correspondiente en el codi-
go fuente. Por ejemplo, este es el aspect0 que mostraria un elemento de la lista de
tareas pendientes en el codigo fuente:
procedure TForml.FormCreate(Sender: TObject);
begin
/ / TODO - o M a r c o : A i i a d i r c d d i g o d e creacidn
end;

El mismo elemento puede editarse visualmente en la ventana que muestra la


figura 1.3, dentro de la ventana To-Do List.
La excepcion a esta regla de las dos vias es la definition de elementos pendien-
tes en el ambito del proyecto. Debe aiiadir directamente estos elementos a la lista.
Para hacer esto, puede utilizar la combinacion de teclas Control-A dentro de la
ventana To-Do List o hacer clic con el boton derecho sobre la ventana y seleccio-
nar la opcion Add en el menu desplegable. Estos elementos se guardan en un
archivo especial con el mismo nombre raiz que el archivo del proyecto y una
extension .TODO.
Pueden utilizarse diversas opciones con un comentario TODO.Puede usarse
-0 (como en el ejemplo anterior) para indicar el propietario (el programador que
escribio el comentario), la opcion -c para indicar una categoria, o simplemente
un numero de 1 a 5 para indicar la prioridad ( 0 , o ningun numero, indica que no
se establece ningun nivel de prioridad). Por ejemplo, a1 usar el comando A d d
To-Do I t e m del menu desplegable del editor (o la combinacion Control-Mayus-
T ) se genero este comentario:
TODO 2 - o M a r c o : B u t t o n p r e s s e d }

Delphi trata todo lo que aparezca tras 10s dos puntos (hasta el final de la linea
o hasta la Have de cierre, segun el tipo de comentario), como el texto del elemento
de tarea pendiente.
---.
_.
1 *
I!
A c m llcm
.-
I ~ o a ~ e 10-
--'

IWWY
-A
7 Check comp~lerfelhngs 1 Marco

Figura 1.3. La ventana Edit To-Do Item puede usarse para modificar un elemento de
tarea pendiente, una operacion que tambien puede realizarse directamente en el
codigo fuente.

Finalmente, en la ventana TO-DOList se puede elirninar la marca de un ele-


mento para indicar que se ha completado. El comentario del codigo fuente cam-
biara de T O D O a DONE.Tambien se puede cambiar manualmente el comentario
en el codigo fuente.
Uno de 10s elementos mas potentes de esta arquitectura es la ventana principal
TO-DOList, que puede recopilar automaticamente informacion de tareas pendien-
tes a partir de archivos de codigo fuente a medida que se escribe, ordenarla,
filtrarla y esportarla a1 Portapapeles como texto simple o una tabla HTML. To-
das estas opciones estan disponibles en el menu de contesto.

Mensajes ampliados del compilador


y resultados de busqueda en Delphi 7
De manera predeterminada aparece una pequeiia ventana Messages bajo el
editor, muestra tanto 10s mensajes del compilador como 10s resultados de las
busquedas. Esta ventana se ha modificado de manera importante en Delphi 7. En
primer lugar, 10s resultados de busqueda se muestran en una pestafia distinta para
que no se mezclen con 10s mensajes del compilador como solia suceder. En segun-
do lugar, cada vez que se realiza una busqueda distinta se puede pedir que Delphi
muestre 10s resultados en una pagina diferente, para que 10s resultados de la
operacion de busqueda anterior sigan disponibles:

Se pueden utilizar las combinaciones Alt-Av Pag y Alt-Re Pag para recorrer
de manera ciclica las pestaiias de esta ventana. (Los mismos comandos sirven
para otras vistas con pestaiias.)
Si suceden errores de compilador, puede activarse otra ventana nueva median-
te el comando View>Additional Message Info. A medida que se compila un
programa, esta ventana Message Hints proporcionara informacion adicional para
algunos mcnsajes de error frecuentes, proporcionando sugerencias sobre como
solucionar estos errores:

Este tipo de ayuda esta destinada mas a programadores novatos, per0 podria
ser practico tener presente esta ventana. Es importante darse cuenta de que esta
informacion cs bastante facil de personalizar: un director de desarrollo de un
proyccto pucdc introducir descripciones apropiadas de errores comunes en un
formulario que signifiquen algo especifico para nuevos desarrolladores. Para ha-
cer esto, siga las instrucciones del archivo que guarda los parametros de esta
caracteristica, el archivo msginfo70,ini que se encuentra en la carpeta b i n de
Delphi.

El editor de Delphi
Aparentemente el editor de Delphi no ha cambiado mucho en la version 7 del
IDE. Sin embargo, en el fondo, se trata de una herramienta completamente nueva.
Ademas de emplearlo para trabajar con archivo escritos en lenguaje Pascal orien-
tad0 a objetos (o-en lenguaje Delphi, como prefiere llamarlo ahora Borland), se
puede usar ahora para trabajar con otros archivos relacionados con el desarrollo
en Delphi (como archivos SQL, XML, HTML y XSL), al igual que con archivos
de otros lenguajes (entre 10s que se incluyen C++ y C#). La edicion de XML y
HTML ya estaba disponible en Delphi 6, per0 10s cambios en esta version son
importantes. Por ejemplo, durante la edicion de un archivo HTML se tiene sopor-
te tanto para resaltado de sintaxis como para acabado de codigo.
Las configuraciones el editor para cada archivo (incluido el comportamiento
de teclas como Tab) dependen de la estension del archivo que se abra. Se pueden
configurar estos parametros mediante la nueva pagina Source Options del cua-
dro de dialogo Editor Properties, que muestra la figura 1.4. Esta caracteristica
se ha ampliado y abierto aun mas para que incluso pueda configurarse el editor
mediante un DTD para formatos de archivo basados en XML o mediante un
asistente personalizado que proporcione el resaltado de sintaxis para otros len-
guajes de programacion. Otra caracteristica del editor, las plantillas de codigo,
son ahora especificas del lenguaje (las plantillas predefinidas para Delphi tendran
poco sentido en HTML o C # ) .

Figura 1.4. Los diversos lenguajes soportados por el IDE de Delphi se pueden
asociar con varias extensiones de archivo mediante la pagina Source Options del
cuadro de dialogo Editor Properties.

NOTA:C#es el nuevo lenguaje que present6 Micmsofkjunto con su arqui-


tectura .NET. Borland espera soportar C# en su propio entorno .NET,que
actualmente tiene el nombre en codigo de Galileo.

Si solo se considera el lenguaje Delphi, el editor incluido en el IDE no ha


cambiado mucho en las versiones recientes. Sin embargo, tiene unas cuantas ca-
racteristicas que muchos programadores de Delphi desconocen y no utilizan, asi
que se merece un poco de analisis.
El editor de Delphi nos permite trabajar con varios archivos a la vez, usando
una metafora de "bloc de notas con fichas". Se pasa de una ficha del editor a la
siguiente pulsando Control-Tab (o Mayus-Control-Tab para movernos en la
direccion opuesta).
Podemos pasar de una ficha del editor a la siguiente pulsando Control-Tab (o
Mayus-Control-Tab para movernos en la direccion opuesta). Se puede arrastrar y
soltar las solapas con 10s nombres de unidad situadas en la parte superior del
editor para cambiar su orden, para que se pueda usar un simple Control-Tab
para moverse entre las unidades en que se trabaje en un momento dado. El menu
local del editor posee tambien un comando Pages, que lista todas las fichas dispo-
nibles en un submenu, muy util cuando se cargan muchas unidades.
Tambien se pueden abrir varias ventanas del editor, cada una de ellas con
multiples fichas o pestaiias. Hacer esto es la unica manera de inspeccionar el
codigo fuente de dos unidas a la vez. (Realmente, cuando es necesario comparar
dos unidades de Delphi, tambien se puede utilizar Beyond Compare, una herra-
mienta de comparacion de archivos muy barata y maravillosa escrita en Delphi y
disponible a traves de www.scootersoftware.com.)
En el cuadro de dialogo Editor Properties, hay algunas opciones que afectan
a1 editor. Sin embargo, para definir la propiedad AutoSave del editor, que guarda
10s archivos del codigo fuente cada vez que se ejecuta el programa (y evita que se
pierdan 10s datos en caso de que el programa sufra daiios importantes en el depu-
rador), tenemos que ir a la ficha Preferences del cuadro de dialogo Environment
Options.
El editor de Delphi proporciona muchos comandos, incluyendo algunos que se
remontan a sus ancestros de emulacion de WordStar (de 10s primeros compiladores
Turbo Pascal). N o vamos a comentar 10s distintos parametros del editor, ya que
son bastante intuitivos y estan bien descritos en la ayuda disponible. Aun asi,
fijese en que la pagina de ayuda que describe 10s atajos de teclado es accesible de
una sola vez solo si se busca el elemento del indice shortcuts.

I TRUCO: Un tmco que debemos recordar es que emplear ias 6rdenes C u t


y p a s t e no cs la &ca forma de mover el cb&o &to,, siop qqe fambi6n
podemos s e l ~ i o n a yr pnastrar las palabras, expresicihes o lineas enteras
de c6dig0, ademis de wpiar el texto en lugar de myerlo, mantmiendo
pulsada Ia tecla Cpatrel tnbh-asiir~astnmas.

El Code Explorer
L a ventana Code Explorer, que por lo general esta anclada en el lateral del
editor, lista sencillamente todos 10s tipos, variables y rutinas definidas en una
unidad, mas otras unidades que aparecen en sentencias u s e s . En el caso de tipos
complejos, como las clases, el Code Explorer puede listar informacion
pormenorizada, como una lista de campos, propiedades y metodos. Cuando co-
menzamos a teclear en el editor, toda la informacion se actualizara.
Podemos usar el Code Explorer para desplazarnos por el editor. A1 hacer
doble clic sobre una de las entradas del Code Explorer, el editor pasa a la
declaracion correspondiente. Tambien podemos modificar nombres de variables,
propiedades y m6todos directamente en el Code Explorer. Sin embargo, si se
desea utilizar una herramienta visual para trabajar con las clases, ModelMaker
ofrece muchas mas caracteristicas.
Aunque todo esto resulta bastante obvio a 10s cinco minutos de comenzar a
usar Delphi, algunas caracteristicas del Code Explorer no se pueden utilizar de
una forma tan intuitiva. Lo importante es que el usuario tiene control total sobre
el mod0 en que aparece dispuesta la informacion y que se puede reducir la profun-
didad del arbol que aparece en esta ventana cuando se personaliza el Code
Explorer. Si reducimos el arbol, podremos realizar las elecciones con mayor
rapidez. Podemos configurar el Code Explorer mediante la pagina de Environment
Options correspondiente, como se muestra en la figura 1.5.

Figura 1.5. Se puede configurar el Code Explorer mediante el cuadro de dialogo


Environment Options.

Fijese en que a1 eliminar la seleccion de uno de 10s elementos de Explorer


Categories situados en la parte derecha de esta pagina del cuadro de dialogo, el
Explorer no elimina 10s elementos correspondientes, simplemente aiiade el nodo
a1 arbol. Por ejemplo, si se elimina la seleccion de la casilla Uses, Delphi no
oculta la lista de unidades usadas; a1 contrario, las unidad usadas aparecen en la
lista como nodos principales en lugar de permanecer en la carpeta Uses. Es una
buena idea eliminar la seleccion de Types, Classes y VariablesIConstants.
Dado que cada elemento del arbol Code Explorer tiene un icono que indica su
tipo, la organizacion por campo y metodo parece menos importante que la organi-
zacion por especificador de acceso. Es preferible mostrar todos 10s elementos en
un grupo unico, puesto asi no es necesario pulsar el raton tantas veces para llegar
a cada uno de 10s elementos. En realidad, la posibilidad de seleccionar elementos
en el Code Explorer supone una forma muy comoda de desplazarnos por el
codigo fuente de una unidad amplia. Cuando hacemos doble clic sobre un metodo
en el Code Explorer, el foco se desplaza a la definicion de la declaracion de
clase (en la parte de interfaz de la unidad). Se puede usar la combinacion Con-
trol-Mayus junto con las teclas de cursor arriba y abajo para saltar de la defini-
cion de un metodo o procedimiento en la parte de interfaz de una unidad a su
definicion completa en la parte de implernentacion o volver hacia atras (es lo que
se llaman Module Navigation).
.-

NOTA: Algunas de las categorias del explorador que aparecen en la figura


1.5 son mas utilizadas por el Project Explorer que por el Code Explorer.
Entre estas se encuentran las opciones de agrupamiento vi r t ua 1s,
!d e Introduced.

Exploracion en el editor
Otra caracteristica del editor es la Tooltip symbol insight (Ventanas de suge-
rencia sobre simbolos). A1 mover el raton sobre un simbolo del editor, una venta-
na de sugerencia nos mostrara el lugar en el que se declara el identificador. Esta
caracteristica puede resultar especialmente importante para realizar el seguimien-
to de identificadores, clases y funciones de una aplicacion que estamos escribien-
do y tambien para consultar el codigo fuente de la biblioteca de componentes
visuales (VCL).
-
ADVERTENCIA: Aunque pueda parecer buena idea en principio, no po-
demos usar la ventana de sugerencia sobre simbolos para averiguar que
unidad declara un identificador que queremos emplear. En realidad. la ven-
tana de sugerencia no aparece, si no se ha incluido todavia la unidad corres-
pondiente.

Sin embargo, la autentica ventaja de esta funcion, es que el usuario puede


transformarla en un instrumento auxiliar para desplazarse. Si mantenemos pulsa-
da la tecla Control y movemos el raton sobre el identificador, Delphi creara un
enlace activo con la definicion, en lugar de mostrar la ventana de sugerencia.
Dichos enlaces aparecen en color azul y estan subrayados, estilo tipico de 10s
exploradores Web, y el punter0 se transforma en una mano siempre que se situa
sobre el enlace.
Podemos, por ejemplo, pulsar Control y hacer clic sobre el identificador
TLabel para abrir su definition en el codigo de la VCL. Cuando seleccionamos
referencias, el editor conserva la pista de las diversas posiciones a las que se ha
movido y gracias a ella podemos pasar de una referencia a otra (de nuevo como en
un explorador Web mediante 10s botones Browse Back y Browse Forward que se
encuentran en la esquina superior derecha de las ventanas o mediante las combi-
naciones Alt-Flecha izda. o Alt-Flecha dcha.). Tambien podemos hacer clic so-
bre las flechas desplegables proximas a los botones Back y Forward para ver
una lista pormenorizada de las lineas de 10s archivos de codigo fuente a las que ya
hemos accedido, para tener mayor control sobre 10s movimientos adelante y atras.
Ahora cabe preguntarse como podemos saltar directamente al codigo fuente de
la VCL si no forma parte de nuestro proyecto. El editor no solo puede encontrar
las unidades de la ruta de busqueda (Search, que se compila como parte del
proyecto), sino tambien aquellas que estan en las rutas Debug Source, Browsing
y Library de Delphi. La busqueda se realiza en estos directorios en el mismo
orden en que aparecen aqui enumerados y podemos definirlos en la ficha
Directories/Conditionals del cuadro de dialogo Project Options y en la ficha
Library del cuadro de dialogo Environment Options. Por defecto, Delphi aiiade
10s directorios de codigo fuente de la VCL a la ruta Browsing del entorno.

Class Completion
El editor de Delphi tambien puede generar parte del codigo fuente, completan-
do lo que ya se haya escrito. Esta caracteristica se llama Class Completion, y se
activa a1 pulsar la combinacion de teclas Control-Mayus-C. Aiiadir un controla-
dor de eventos a una aplicacion es una operacion rapida, porque Delphi aiiade
automaticamente la declaracion de un nuevo metodo que controle el evento y nos
proporciona el esquema del metodo en la seccion de implementacion de la unidad.
Esto forma parte del soporte para programacion visual de Delphi.
De un mod0 similar, las ultimas versiones de Delphi han conseguido facilitar
el trabajo de 10s programadores que escriben codigo extra detras de 10s
controladores de evento. De hecho, la nueva caracteristica de creacion de codigo
afecta a 10s metodos generales, a 10s metodos de control de mensajes y a las
propiedades. Por ejemplo, si tecleamos el siguiente codigo en la declaracion de
clase:
public
procedure Hello (MessageText: string) ;

y a continuacion, pulsamos Control-Mayus-C, Delphi nos ofrecera la defini-


cion del metodo en la parte de implementacion de la unidad y crea las siguientes
lineas de codigo:
{ TForml )
procedure TForml.Hello(MessageText: string);
begin
end;

Esto resulta mas comodo que copiar y pegar una o mas declaraciones, aiiadir
10s nombres de clase y por ultimo duplicar el codigo begin. . . end en cada
metodo copiado. La funcion C 1ass C omp1etio n tambien puede funcionar a
la inversa: podemos escribir la implementacion del metodo directamente con su
codigo y despues pulsar Control-Mayus-C para crear la entrada necesaria en la
declaracion de clase.
El ejemplo mas importante y util de esta funcion de completitud de clases es la
generacion automatica de codigo para dar soporte a las propiedades declaradas en
las clases. Por ejemplo, si en una clase se escribe
p r o p e r t y Value: Integer;

y se pulsa Control-Mayus-C, Delphi convertira la linea en


p r o p e r t y Value: I n t e g e r r e a d fValue w r i t e S e t v a l u e ;

Delphi aiiadira tambien el metodo setvalue a la declaracion de clase y


proporcionara una implementacion predefinida para ese metodo.

Code Insight
Ademas del Code Explorer, la funcion de completitud de clases y las funcio-
nes de desplazamiento, el editor de Delphi soporta la tecnologia Code Insight. En
conjunto, las tecnicas Code Insight se basan en un analisis sintactico continuo en
segundo plano, tanto del codigo fuente que escribimos como del codigo fuente de
las unidades del sistema a las que se refiere nuestro codigo.
La funcion Code Insight implica cinco capacidades: Code Completion, Code
Templates, Code Parameters, Tooltip Expression Evaluation y Tooltip Symbol
Insight. Esta ultima caracteristica se trato durante la seccion sobre la exploracion
en el editor. Todas estas caracteristicas se pueden habilitar, inhabilitar y configu-
rar en la pagina Code Insight del cuadro de dialog0 Editor Properties.
Code Completion
La funcion Code Completion permite escoger la propiedad o metodo de un
objeto simplemente buscandolo en una lista o escribiendo sus letras iniciales.
Para activar esta lista, solo hay que teclear el nombre de un objeto, como Buttonl,
aiiadir el punto y esperar. Para que forzar la aparicion de la lista, hay que pulsar
Control-Barra espaciadora; para quitarla cuando no queramos verla, hay que
pulsar Esc. La funcion Code Completion permite ademas buscar un valor adecua-
do en una sentencia de asignacion.
Cuando comenzamos a teclear, la lista va filtrando su contenido de acuerdo
con la parte inicial del elemento que hemos escrito. La lista Code Completion
emplea colores y muestra mas detalles para ayudarnos a distinguir elementos
diferentes. En Delphi, se pueden personalizar estos colores mediante la pagina
Code Insight del cuadro de dialogo Editor Properties. Otra caracteristica en el
caso de funciones con parametros es la inclusion de parentesis en el codigo creado
y la aparicion inmediata de la ventana de sugerencia de la lista de parametros.
Cuando se escribe := despues de una variable o propiedad, Delphi listara
todas las demas variables u objetos del mismo tipo, ademas de 10s objetos que
tengan propiedades de ese tipo. Mientras la lista permanece visible, podemos
hacer clic con el boton derecho del raton sobre ella para modificar el orden de 10s
elementos, clasificandolos por alcance o por nombre y tambien podemos adaptar
el tamaiio de la ventana.
Desde Delphi 6, Code Completion funciona ademas en la parte de interfaz de
una unidad. Si pulsamos Control-Barra espaciadora mientras el cursor esta
dentro de la definition de clase, obtendremos una lista de 10s metodos virtuales
que se pueden sobrescribir (como por ejemplo, 10s metodos abstractos), 10s meto-
dos de las interfaces implementadas, las propiedades de clase basica y, por ulti-
mo, 10s mensajes del sistema que se pueden controlar. A1 seleccionar uno de ellos,
aiiadiremos sencillamente el metodo adecuado a la declaracion de clase. En este
caso concreto, la lista Code Completion permite la seleccion multiple.

TRUCO: Si el c6digo que hemos escrito es incorrecto, Code Insight no


funcionari y veremos un mensaje de error generic0 que nos indica dicha
situation. Es posible hacer que aparezcan errores especificos de Code Insight
en el panel Message (que debera estar ya abiertc1, porque no se abre
automaticamente para mostrar 10s errores de compilacion). Para activar
. , .. . ..
esta caracteristicas, es necesarlo estamecer una entrada del Registro
- no
.
documentada, defhendo la clave de cadena \Delphi \ 7 0\Cornpi1ing\
ShowCodeInsiteErrors con el valor 1.

Hay algunas caracteristicas avanzadas de la funcion Code Completion que no


resultan faciles de ver. Una particularmente util esta relacionada con el descubri-
miento de 10s simbolos en unidades no utilizadas por nuestro proyecto. Cuando
recurrimos a ella (con Control-Barra espaciadora) sobre una linea en blanco, la
lista incluye tambien simbolos de unidades comunes (como Math, StrUtils y
DateUtils) todavia no incluidas en la sentencia uses de la unidad en uso. A1
seleccionar uno de estos simbolos externos, Delphi aiiade la unidad a la sentencia
uses de forma automatics.
Esta caracteristica (que no funciona dentro de expresiones) esta dirigida por
una lista de unidades adicionales que puede ser personalizada, almacenada en la
clave de registro \Delphi\7.0\CodeCompletion\ExtraUnits.

L.
TRgC(r:,l&fpG 7 b capaoidad de explorar la d e c h r w i h de ele-
mentos de la fista.de campletitud de codigo a1 mantener pul'sadd la teela
Control y'hacer chc sohre cualquier identificador de la Ikta.
Code Templates
Esta caracteristica permite insertar una de las plantillas de codigo predefinidas,
como una declaracion compleja con un bloque interior b e g i n . . . . e n d . Las
plantillas de codigo deben activarse de forma manual, usando Control-J para
obtener una lista de todas ellas. Si tecleamos unas cuantas letras (como una pala-
bra clave) antes de pulsar Control-J, Delphi listara solo las plantillas que
comiencen por dichas letras.
Tambien se pueden aiiadir plantillas de codigo personalizadas, para crear me-
todos abreviados para 10s bloques de codigo que usemos normalmente. Por ejem-
plo, si empleamos con frecuencia la funcion M e s s a g e D l g , podemos aiiadir una
plantilla para la misma.
Para modificar plantillas, mediante la pagina Source Options del cuadro de
dialogo Editor Options, hay que seleccionar Pascal en la lista Source File Type
y hacer clic sobre el boton Edit Code Templates. A1 hacer esto, aparecera el
nuevo cuadro de dialogo Code Templates de Delphi 7.
En este momento, si hacemos clic sobre el boton Add, escribimos un nuevo
nombre de plantilla (por ejemplo, d e s o r d e n ) , escribimos tambien una descrip-
cion y, a continuacion, aiiadimos el siguiente texto a1 cuerpo de la plantilla en el
control memo Code:
MessageDlg (' I' , mtInformation, [mbOK] , 0) ;

Ahora, cada vez que necesitemos crear un cuadro de dialogo de mensaje, sim-
plemente escribiremos d e s o r d e n y, a continuacion, pulsaremos Control-J para
obtener el texto completo. El caracter de linea vertical indica la posicion dentro
del codigo fuente en la que estara el cursor en el editor despues de haber desplega-
do la plantilla. Deberiamos escoger la posicion en la que queremos comenzar a
teclear para completar el codigo producido por la plantilla.
Aunque pueda parecer que las plantillas de codigo, a primera vista, se corres-
ponden con palabras clave del lenguaje, estas son en realidad un mecanismo mas
general. Se guardan en el archivo DELPHI32.DC1, un archivo de texto en un
formato bastante simple que puede editarse con facilidad. Delphi 7 tambien per-
mite exportar la configuracion para un lenguaje a un archivo e importarla, lo que
facilita que 10s desarrolladores intercambien sus propias plantillas personalizadas.

Code Parameters
La funcion Code Parameters muestra, en una ventana de sugerencia, el tip0 de
datos de 10s parametros de un metodo o funcion mientras 10s tecleamos. A1 escri-
bir el nombre de la funcion o metodo y abrir el parentesis, apareceran inmediata-
mente 10s nombres y tipos de parametro en una ventana de sugerencia contextual.
Para que forzar a que aparezcan 10s parametros de codigo, podemos pulsar Con-
trol-Maylis-Barra espaciadora. Ademas, el parametro en uso aparece resaltado
en negrita.
Tooltip Expression Evaluation
La funcion Tooltip Expression Evaluation es una caracteristica en tiempo de
depuracion. Muestra el valor del identificador, la propiedad o expresion que esta
bajo el cursor del raton. En el caso de una expresion, normalmente necesitara
seleccionarla en el editor y despues mover el cursor sobre el texto seleccionado.

Mas teclas de metodo abreviado del editor


El editor tiene muchas mas teclas de metodo abreviado que dependen del estilo
de editor escogido. A continuacion, aparece una lista de las menos conocidas:
Control-Mayus mas una tecla numerica del 0 a1 9 activa un marcador,
indicado en el margen "para encuadernacion" del lateral del editor. Para
volver a1 marcador, pulsamos la tecla Control mas la tecla numerica. La
utilidad de 10s marcadores en el editor esta limitada por el hecho de que un
nuevo marcador puede sobrescribir a otro ya existente y porque 10s marca-
dores no son permanentes (se pierden cuando se cierra el archivo).
Control-E activa la busqueda incremental. Hay que pulsar Control-E y
teclear directamente la palabra que queramos buscar, sin necesidad de
pasar por un cuadro de dialogo especial ni de hacer clic sobre la tecla
E n t e r para realizar la busqueda.
Control-Mayus-I sangra diversas lineas de codigo a1 mismo tiempo. El
numero de espacios utilizado es el establecido en la opcion Block Indent
de la ficha Editor del cuadro de dialogo Environment Options. Control-
Mayus-U es la combinacion correspondiente para deshacer el sangrado del
codigo.
Control-O-U cambia el codigo seleccionado a mayusculas o minusculas.
Tambien se puede usar Control-K-E para cambiar a minuscula y Con-
trol-K-F para pasar a mayuscula.
Control-Mayus-R inicia la grabacion de una macro, que mas tarde se
puede reproducir utilizando la combinacion de teclas Control-Mayus-P.
La macro graba todas las operaciones de escritura, movimiento y borrado
realizadas en el archivo del codigo fuente. A1 reproducir la macro simple-
mente se repite la secuencia. Las macros del editor resultan bastante utiles
para repetir operaciones que constan de varios pasos, como volver a dar
formato a1 codigo fuente u organizar 10s datos de una manera mas legible
en el mismo.
Si se mantiene pulsada la tecla Alt, se puede arrastrar el raton para selec-
cionar zonas rectangulares del editor, no solo lineas consecutivas y pala-
bras.
Vistas que se pueden cargar
En la ventana del editor ha habido otra modificacion importante, presentada en
Delphi 6. Para cada uno de 10s archivos que se cargan en el IDE, el editor puede
mostrar ahora diversas vistas que podemos definir de forma programada y aiiadir
a1 sistema, y despues cargar para unos archivos determinados.
La vista mas utilizada es la pagina Diagram, que ya estaba disponible en 10s
modulos de datos de Delphi 5, aunque era menos potente. Existe otro conjunto de
vistas disponibles en las aplicaciones Web, entre las que se encuentra una vista
HTML Script, una vista previa HTML Result y muchas mas. Se pueden utilizar
las combinaciones Alt-Av Pag y Alt-Re PBg para recorrer las pestaiias inferiores
de este editor; con Control-Tab se salta entre las paginas (o archivos) que se
muestran en las pestaiias superiores.
Diagram View
La vista de diagrama muestra las dependencias entre componentes, como las
relaciones padrelhijo, de posesion, las propiedades enlazadas y las relaciones
genericas. En el caso de componentes de un conjunto de datos, tambien soporta
relaciones maestroldetalle y conexiones de busqueda. Podemos incluso aiiadir
comentarios en bloques de texto enlazados a componentes especificos.
El diagrama no se crea de forma automatica. Debemos arrastrar 10s compo-
nentes desde la vista en arb01 a1 diagrama, en el que automaticamente apareceran
las relaciones entre 10s mismos. Podemos seleccionar diversos elementos desde la
Object TreeView y arrastrarlos todos a la vez a la ficha Diagram.
Lo agradable es que podemos definir propiedades simplemente dibujando fle-
chas entre 10s componentes. Por ejemplo, despues de mover un control Edit y una
etiqueta a1 diagrama, podemos seleccionar el icono Property Connector, hacer
clic sobre la etiqueta y arrastrar el cursor del raton sobre el control Edit. Cuando
soltemos el boton del raton, el diagrama establecera una relacion de posesion
basada en la propiedad F o c u s C o n t r o 1,que es la unica propiedad de la etique-
ta que se refiere a un control Edit. Esta situacion se muestra en la figura 1.6.
Como se puede ver, la definicion de propiedades es direccional: si arrastramos
la linea de relacion de propiedad desde el control Edit a la etiqueta, en realidad,
estamos intentando usar la etiqueta como valor de una propiedad del cuadro de
edicion. Dado que eso no es posible, veremos un mensaje de error que nos indica
el problema y nos ofrece la posibilidad de conectar 10s componentes en la direc-
cion opuesta.
El Diagram View nos permite crear varios diagramas para cada unidad Delphi
(es decir, para cada formulario o modulo de datos). Simplemente se proporciona
un nombre a1 diagrama y se puede aiiadir tambien una descripcion, haciendo clic
sobre el boton New Diagram, se prepara otro diagrama y se puede pasar de un
diagrama a otro usando el cuadro combinado de la barra de herramientas de la
vista en diagrama.
--

ti-
h e

Q+fcrolim

J
Ths IS a simple version

Figura 1.6. La vista Diagram rnuestra relaciones entre cornponentes (e incluso


perrnite establecer esas relaciones).

Aunque se pueda emplear la vista en diagrama para establecer relaciones, su


funcion principal es documentar nuestro diseiio. Por esa razon: es importante que
se pueda imprimir el contenido de dicha vista. Al usar la orden estandar File>Print
mientras este activada la vista en diagrama, Delphi nos indica que seleccionemos
las opciones para personalizar la impresion, como se puede ver en la figura 1.7.

r-
M base

Figura 1.7. Las opciones de impresion de la vista en diagrama.

La informacion de la vista Diagram se guarda en un archivo separado, no


como parte del archivo DFM. Delphi 5 empleaba 10s archivos de informacion en
tiempo de diseiio (DTI), que tenian una estructura similar a 10s archivos INI.
Delphi 6 y 7 todavia pueden leer el antiguo formato .DTI, per0 usan el nuevo
formato Delphi Diagram Portfolio (.DDP).
Estos archivos utilizan aparentemente el formato binario DFM (o uno simi-
lar), por lo que se pueden editar como texto. Obviamente todos estos archivos son
inutiles en tiempo de ejecucion (no tiene sentido incluirlos en la compilacion del
archivo ejecutable).
NOTA: Si se desea experimentar con la vista en diagrama, se puede co-
menzar abriendo el proyecto DiagramDemo. El formulario del programa
, , , :+ A, A: , , , , -,- ,,,,:,J,-. ., , - ,I .I- 1, C ,.., ,1 L
LIGIIG UW3 U l i l g l i U l l i l 3 i l ? ~ U b l i l U W S .U l l U GI1 G I U G lil I l g U l i l l .V
.Y..-,,-.I,.., ,A,
U l l U 1IIUC;llU IllilJ

complejo con un menu desplegable y sus elementos.

Form Designer
Otra ventana de Delphi con la que vamos a interactuar muy a menudo es el
Form Designer; una herramienta visual para colocar componentes en 10s formu-
larios. En el Form Designer, se puede seleccionar directamente un componente
con cl raton o a traves dcl Object Inspector o la Object Treeview, mttodos
ultimos utiles en caso de quc un control quede oculto. Si un control cubre a otro
por complete, se puedc emplear la tecla Esc para seleccionar el control padre del
control actual. Se pucdc pulsar la tecla Esc una o mas veces para seleccionar el
formulario o pulsar y mantener pulsada la tecla Mayus mientras sc hace clic
sobre cl componcntc scleccionado. Esto desactivara la selection del componente
en uso y seleccionara el formulario por defecto.
Esisten dos alternativas al uso del raton para fijar la posicion de un compo-
ncnte. Se pueden definir valores para las propiedades L e f t y TOP, o bien usar
las teclas de cursor mientras se mantiene pulsada la tech Control. El uso de las
teclas de cursor resulta util sobre todo para precisar la posicion de un elemento
(cuando la opcion Snap To Grid se encuentra activada), a1 igual que mantener
pulsada la tecla Alt y utilizar el raton para mover el control. Si se pulsa la
combinacion Control-Mayus junto con una tecla de cursor, el componente se
mover6 solo a intervalos de cuadricula.
Del mismo modo, a1 pulsar las teclas de cursor mientras se mantiene pulsada
la tecla Mayus, podemos precisar el tamaiio de un componente, algo que tambien
se puede hacer mediante la tecla Alt y el raton.
Para alinear diversos componentes o hacer que tengan el mismo tamaiio, se
pueden sclcccionar todos ellos y establecer las propiedades Top, L e f t , Width
o H e i g h t de todos a1 mismo tiempo. Para seleccionar varios componentes, po-
demos haccr clic sobre ellos con el raton mientras mantenemos pulsada la tecla
Mayus o. si todos 10s componentes se encuentran en zonas rectangulares, se
puede arrastrar el raton hasta "dibujar" un rectangulo que 10s rodee. Para selec-
cionar 10s controles hijo (por ejemplo, 10s botones que se encuentran dentro de un
panel), arrastraremos el raton dcntro del panel mientras que mantendremos pulsa-
da la tecla Control, ya que de no ser asi desplazaremos el panel. Cuando ya esten
seleccionados 10s diversos componentes, tambien se puede fijar su posicion rela-
tiva utilizando el cuadro de dialog0 Alignment (con la ordcn A l i g n del menu de
metodo abreviado del formulario) o Alignment Palette (a la que accedemos
mediante la orden del menu View>Alignment Palette).
Cuando este terminado el diseiio de un formulario, podemos emplear la orden
L o c k C o n t r o l s del menu Edit para evitar cambiar por equivocacion la posi-
cion de una componente en un formulario. Esto resulta util, sobre todo teniendo
en cuenta que las operaciones Undo en 10s formularios son limitadas (solo se
puede recuperar elementos eliminados), per0 la definicion no es permanente.
Entre otras de sus caracteristicas, el Form Designer ofrece diversas ventanas
de sugerencia:
A1 mover el punter0 sobre un componente, en la sugerencia aparece el
nombre y el tipo del componente. Desde la version 6, Delphi ofrece suge-
rencias extendidas, con datos sobre la posicion del control, el tamaiio, el
orden de tabulacion y mas. Esta es una mejora de la configuracion del
entorno Show Component Captions que se puede mantener activada.
Cuando adaptamos el tamaiio de un control, en la sugerencia aparece el
tamaiio actual (las propiedades W i d t h y H e i g h t ) . Por supuesto, estas
caracteristicas estan disponibles solo para controles, no para componentes
no visuales (que estan indicados en el Form Designer mediante iconos).
A1 mover un componente, la sugerencia indica la posicion actual (las pro-
piedades L e f t y Top).
Por ultimo, se pueden guardar 10s archivos DFM (Delphi Form Module, Mo-
dulo de Formulario Delphi) en el formato de recurso binario tradicional, en lugar
de hacerlo como texto normal que es el comportamiento predeterminado. Esta
opcion se puede modificar en el caso de un formulario individual, con el menu de
metodo abreviado del Form Designer o establecer un valor predefinido para 10s
formularios nuevos que creemos en la ficha Designer del cuadro de dialogo
Environment Options. En la misma ficha, podemos especificar tambien si 10s
formularios secundarios de un programa se crearan automaticamente a1 arrancar,
una decision que siempre se podra modificar en el caso de cada formulario indivi-
dual (usando la ficha Forms del cuadro de dialogo Project Options).
Disponer de archivos DFM almacenados como texto permite trabajar de mane-
ra mas eficaz con sistemas de control de versiones. Los programadores no se
aprovecharan mucho de esta caracteristica, ya que se podria simplemente abrir el
archivo DFM binario en el editor de Delphi con un comando especifico desde el
menu de metodo abreviado del diseiiador. Por otra parte, 10s sistemas de control
de versiones necesitan guardar la version textual de 10s archivos DFM para ser
capaz de compararlos y extraer las diferencias entre dos versiones del mismo
archivo. En cualquier caso, si se utilizan archivos DFM como texto, Delphi 10s
convertira a un formato de recurso binario antes de incluirlos en el archivo ejecu-
table del programa. Los archivos DFM estan enlazados a su ejecutable en forma-
to binario para reducir el tamaiio del archivo ejecutable (aunque no esten
comprimidos) y para mejorar el rendimiento en tiempo de ejecucion (se pueden
cargar mas rapido).
NOTA: Los archivos de texto DPM resultan m b faciles de tramportar de
una version a otra de Delphi que sus versiones binarias. Aunque una ver-
- de Delphi puede no acevtar una nueva propiedad de un
sion mas antigua
control en un archivo DFM ireado por u& veni6n posterior be Delphi, la 1
version anterior si sera capaz de leer el resto del archivo de texto DFM. Sin
a
,.t,
GuIualgu, A I, L
:.,.. ,A- c~.-.:~,+, A, r\-1-L: -Z.-.Ar.
SI la YGIJIUU 111aaIGUGULG UG U G I ~ L U m a u ~
....
u u ....-.., , , :+
IIUGVU c~yu
A, A,+,.
UG uacua,

la version anterior no podra leer en absoluto 10s archivos binarios DFM


mas recientes. hcluso aunque no suene probable, recuerde que 10s sistemas
que funcionan con 64 bits e s t b a la vuelta de la esquina. Si tiene dudas,
guarde 10s archivos en formato texto DFM. Fijese tarnbien en que todas las
versiones de Delphi soportan DFM en formato texto, usando la herrarnienta
en linea de comandos convert que se encuentra en el directorio bin. Por
ultimo, tenga presente que la biblioteca CLS utiliza la extension XFM en
lugar de la extension DFM, tanto en Delphi como en Kylix.

Object lnspector
Para visualizar y modificar las propiedades dc 10s componentes de un formula-
rio (u otro disciiador) en tiempo de diseiio, se puede utilizar el Object Inspector.
En comparacion con las primeras versiones de Delphi, el Object lnspector dis-
pone de unas cuantas caracteristicas nuevas. La ultima, presentada en Delphi 7,
es cl uso de una fuente en negrita para resaltar las propiedades que tienen un valor
distinto dcl predefinido. Otro importante cambio (en Delphi 6) es la capacidad del
Object lnspector de desplegar las referencias de 10s componentes emplazados.
Las propiedades que se refieren a otros componentes aparecen en un color dife-
rcntc y pueden desplegarse seleccionado el simbolo + de la izquierda, como ocu-
rre con 10s subcomponentes internos. A continuacion, se pueden modificar las
propiedades de ese otro componente sin tener que seleccionarlo. La siguiente
figura muestra un componente conectado (un menil desplegable) expandido en el
Object lnspector mientras que se trabaja con otro componente (un cuadro de
lista):
Esta caracteristica de ampliacion de la interfaz tambien soporta subcompo-
nentes, tal y como demuestra el nuevo control LabeledEdit.Una caracteristi-
ca relacionada del Object lnspector es que podemos seleccionar el componente
a1 que hace referencia una propiedad. Para ello, hacemos doble clic sobre el valor
de la propiedad con el boton izquierdo del raton mientras mantenemos pulsada la
tecla Control. Por ejemplo, si tenemos un componenteMainMenu en un formu-
lario y estamos echando un vistazo a las propiedades del formulario en el Object
Inspector, podemos seleccionar el componente MainMenu moviendonos a la pro-
piedad MainMenu del formulario y haciendo doble clic sobre el valor de dicha
propiedad mientras mantenemos pulsada la tecla Control. Con esto se selecciona
el menu principal indicado junto con el valor de la propiedad en el Object Ins-
pector. A continuacion aparece una lista de algunos cambios recientes del Object
Inspector:
La lista situada en la parte superior del Object Inspector muestra el tip0 de
objeto y permite escoger un componente. Puede eliminarse para ahorrar
algo de espacio, ya que se puede seleccionar componentes en la Object
Treeview.
Las propiedades que hacen referencia a un objeto son ahora de un color
diferente y pueden ampliarse sin cambiar la seleccion.
Opcionalmente se pueden ver tambien las propiedades de solo lectura en el
Object Inspector. Por supuesto, estan en gris.
El Object lnspector posee un cuadro de dialog0 Properties, que permite
personalizar 10s colores de diversos tipos de propiedades y el comporta-
miento general de esta ventana.
Desde Delphi 5, la lista desplegable de una propiedad puede incluir ele-
mentos graficos. Esta caracteristica la utilizan propiedades como color
y Cursor,y es particularmente util para la propiedad ImageIndex de
10s componentes conectados a una ImageList .

NOTA: Las propiedades de la interfaz plleden coafigurarse ahora en tiem-


po de diseKo utilimdo d meet ImpMor. Este n s a s l linodeloInterfaced
Component Reference ('Rdeienciasde G~m~pobetrte por hterfaz) presenta-
do en KyIix/Delpbi 6, en &qxk IT& c?mp&&nk$ pueden implemmtar y
mantener referenciae*a b interfaces siempre qae Iw ihterfaces eat&
impl&entadas por coapmenteJ, Este mbdelb hnciicma d igrlal qae h
antiguas y simp~es.referencbr componentes, p m IBS propidides de
interfaz pueden enlazaise c m 4QUier componente que implemente la
interfaz necesaria. Las prcjpiahdes #e i n t e e no egt4.11lhfiitadas a mtip
de componentes especjfico (ma elwe o so's clases derivadu). Cuands ha-
cemos clic sobm la%&,despggableen el edihr de %& Y n s w i p a r a
. - - - . - - - . -- - . . -
I obtener una propiedad deinterfaz, aparecen todos 10s componentes de1 for-- 1
mulario actual (y formularios relacionados) que irnpleme& la interfaz.

Fuentes desplegables en el Object Inspector


El Object Inspector de Delphl tiene una lista grafica desplegable para
diversas propiedades. Si queremos aiiadir una que muestre la imagen real
de la fuente seleccionada y se corresponda a la subpropiedad Name de la
propiedad F o n t , hay que instalar en Delphi un paquete que habilite la
variable global FontName PropertyDisplayFontNames de la nue-
va unidad VCLEditors. Esta capacidad ya estaba disponible en Delphi,
pero quedaba inhabilitada ya que la mayoria de 10s ordenadores tienen
instalado un gran numero de fbentes y mostrarlas todas ralentizaria
significativamente el ordenador. En el paquete Oi FontPk, que se puede
encontrar entre 10s programas de ejemplo, se ha hecho esto.
Una vez que se haya instalado dicho paquete. podemos desplazarnos a la
propiedad F o n t de cualquier componente y emplear el menu desplegable
grafico Name, como se ve a continuacion:

I
B F d
j
1
Charsel
Cob
ITFant)
'DEFAULT-CHARSET
' W cWindowText

Existe una segunda forma, miis compteja, de personalizar el Object Ins-


. personalizada para todo el Object Inspector, para que
pector: una. fuente
P

su texto resulte mhs visible. Esta caracteristica resulta especialmente util


en el caso de presentaciones publicas.

Categorias propiedades
Delphi incluye tambien el concept0 de categorias de propiedades, activadas
mediante la opcion Arrange del mcnu local del Object Inspector. Si se activa
csta opcion, las propiedades no se listaran alfabeticamente sino que se organiza-
ran por grupos, con la posibilidad de que cada propiedad aparezca cn diversos
grupos.
Las categorias tienen la ventaja de reducir la complejidad del Object Inspec-
tor. Se puede usar el submenu View presente en cl menu de metodo abreviado
para ocultar propiedades de determinadas categorias. sea cual sea el mod0 en que
aparezcan (es decir, incluso aunque se desee el tradicional orden por nombrc, aun
asi se podran las propiedades de algunas categorias).

Object TreeView
Delphi 5 introdujo una vista en arbol para modulos de datos: en la que se
podian ver las relaciones entre 10s componentes no visuales, como 10s conjuntos
de datos, 10s campos, las acciones, etc. Delphi 6 amplio esta idea a1 proporcionar
una Object TreeView para cada diseiiador, como en el caso de 10s formularios
simples. La Object TreeView se situa por defecto sobre el Object Inspector.
La Object TreeView muestra todos 10s componentes y objetos del formulario
en un arbol en el que se representan sus relaciones.
La relacion mas obvia es la relacion padrelhijo: si colocamos un panel sobre
un formulario, un boton dentro de cste y uno fuera del panel, en el arbol aparece-
ran 10s dos botones, uno bajo el formulario y el otro bajo el panel, tal como
mucstra la figura:

Fijcse en que la vista en arbol esta sincronizada con el Object Inspector y


con el Form Designer, de tal mod0 que cuando escogemos un elemento y cam-
biamos el foco en una de estas tres herramientas, tambien cambiara en las otras
dos .
Ademas de la relacion padrelhijo, la Object TreeView muestra tambien otras
relaciones, como la de propietariolposeido, componentelsubobjeto, coleccion/ele-
mento, y otras especificas como conjunto de datos/conexion y fuente de datosl
relaciones del conjunto de datos.
A continuacion, se puede ver un ejemplo de la estructura de un menu en forma
de arbol:
L-
d h l --
+ +
-

hn New (Newl)
em, Open (Open11
bM,Save (Save1 )

A veces, en la vista en arbol aparecen tambien nodos falsos, que no correspon-


den a un objeto real sino a uno predefinido. Como ejemplo de este comportamien-
to, si desplegamos un componente Table (desde la ficha BDE), veremos dos
iconos en gris que corresponden a la sesion y a1 alias. Tecnicamente, la Object
TreeView usa iconos en gris para 10s componentes que no permanecen en tiempo
de diseiio. Son componentes reales (en tiempo de diseiio y en tiempo de ejecu-
cion), per0 como son objetos predefinidos que estan construidos en tiempo de
ejecucion y no contienen datos permanentes que se puedan editar en tiempo de
diseiio, el Data Module Designer no nos permite editar sus propiedades. Si
colocamos una Table en el formulario, veremos tambien elementos que tienen a
su lado una interrogacion en rojo dentro de un circulo amarillo. Este simbolo
indica elementos parcialmente definidos.
La Object TreeView soporta varios tipos de arrastre:
Podemos escoger un componente de la paleta (haciendo clic sobre el, no
arrastrandolo), mover el raton sobre el arbol y hacer clic sobre un compo-
nente para dejarlo ahi. Esto nos permite dejar un componente en el conte-
nedor que corresponda (formulario, panel y otros), aunque su superficie
este totalmente cubierta por otros componentes, algo que evita, a su vez,
que dejemos el componente en el diseiiador sin reorganizar primer0 10s
demas componentes.
En la vista en arbol, podemos arrastrar componentes, Ilevandolos, por ejem-
plo, de un contenedor a otro. Con el Form Designer, en cambio, solo
podemos utilizar la tecnica de cortar y pegar. Mover, en lugar de cortar,
nos ofrece la ventaja de conservar las conexiones entre 10s componentes, si
las hubiera, y de que no se pierdan como ocurre al eliminar el componente
durante la operacion de cortar.
Podemos arrastrar 10s componentes desde la vista en arbol a la vista en
diagrama.
Al pulsar con el boton derecho del raton sobre cualquier elemento de la vista
en arbol, aparece un menu de metodo abreviado, similar a1 menu de componentes
que obtenemos cuando el componente esta en un formulario (y en ambos casos, en
el menu de metodo abreviado pueden aparecer elementos relacionados con 10s
editores personalizados de componentes). Podemos incluso eliminar elementos
del arbol. La vista en arbol sirve tambien como editor de colecciones, como pode-
mos ver a continuacion en el caso de la propiedad C o l u m n s de un control
Listview. En esta ocasion, no solo podemos reorganizar y eliminar 10s elementos
existentes, sin0 tambien aiiadir elementos nuevos a la coleccion.

1Folrnl
0 Bullon2
[-I IJ
- ,: Columns
4 o .r L ~ r c o l w m
L: 1 TL~~tCalurnn
2 . TL~stCalumn
4 3 - T L~slCd.mm

TRUCO:Se pueden imprimir 10s contenidos de la Object TreeView para


documentarse. Sirnplemente hay que seleccionar la ventana y usar la orden
File>Print (no existe una orden P r i n t en el menu de metodo abreviado).

Secretos de la Component Palette


La Component Palette se utiliza para seleccionar componentes que sc de-
Sean aiiadir al diseiiador actual. Si movemos el raton sobre un componente, apare-
ccra su nombre. En Delphi 7, la sugerencia muestra tambien el nombre de la
unidad en que se define el componente.
La Component Palette tiene demasiadas pestaiias. Se pueden ocultar las
pestaiias que contienen 10s componentes que no se planea utilizar y reorganizar la
ventana para que se adecue a las necesidades del momento. En Delphi 7, tambien
se pueden arrastrar las pestaiias para reorganizarlas. Mediante la pagina Palette
del cuadro de dialog0 Environment Options, se pueden reordenar completamen-
te 10s componentes de las diversas paginas, afiadir elementos nuevos o llevarlos
de una pagina a otra. Cuando hay demasiadas fichas en la Component Palette,
sera necesario moverlas para alcanzar un componente. Esiste un truco muy senci-
110 que se puede usar en este caso: dar un nuevo nombre mas corto a cada ficha,
para que todas ellas encajen en la pantalla (es obvio, una vez que se piensa).
Delphi 7 ofrece otra caracteristica nueva. Cuando hay demasiados componen-
tes en una unica pagina, Delphi muestra una flecha abajo doble; si se hace clic
sobre ella se mostrara el resto de 10s componentes sin tener que recorrer la pagina
Palette.
El menu contextual de la Component Palette tiene un submenu Tabs que
muestra todas las paginas de la paleta en orden alfabetico. Se puede utilizar este
subrncnu para modificar la pagina activa. en particular cuando la pagina que se
neccsita no esta visible en pantalla.
L

TRUCO: Se puede establecer el orden de las entradas en el submenu Tabs


para que tengan el misrno orden que la propia paleta, en lugas de un orden
alfabetico. Para hacer esto, hay que ir a la seccion Main Window del
Registroi para Delphi (dentro de \Sof tware\Borland\Delphi\7 .0
para el usuario actual) y dar a la clave Sort Palette Tabs Menu el
valor de 0 (falso).

Una importante caracteristica no documentada de la Component Palette es la


posibilidad de activar un "seguimiento directo". Si configuramos claves especia-
les del Registro. podemos seleccionar una ficha de la paleta a1 movernos sobre la
solapa, sin tener que hacer clic con el raton. Se puede aplicar esta misma caracte-
ristica a las barras de desplazamiento de 10s componentes situadas a ambos lados
de la paleta, que aparecen cuando hay demasiados componentes en la ficha. Para
activar csta caracteristica oculta, hay que aiiadir una clave Extras dentro de la
seccion \ H K E Y C U R R E N T USER\Software\Borland\Delphi\7.0.
Bajo esta clave ST introducen-dos valores de cadena, Auto Palet teselect y
Auto Palette S c r o 11 y definimos cl valor de cada cadcna como '1'

Copiar y pegar componentes


Una caracteristica interesante del Form Designer es la posibilidad de copiar
y pegar componentes de un formulario a otro o de duplicar el componente en el
formulario. Durantc dicha operation, Delphi duplica todas las propiedades, man-
tiene 10s controladorcs dc cvcntos conectados y, si es necesario, cambia el nombre
dcl control (quc dcbc scr unico en cada formulario). Tambien es posible copiar
componentes del Form Designer a1 editor y viccversa. Cuando se copia un com-
ponente en cl Portapapelcs, Delphi coloca tambien en este su descripcion textual.
Se puede incluso editar la version textual de un componente, copiar el texto a1
Portapapeles y luego pegarlo de nuevo en el formulario como un componente
nuevo. Por ejemplo, si se coloca un boton sobre un formulario, se copia y luego se
pega en un editor (que puede ser el propio editor de codigo de Delphi o cualquier
procesador de texto), se obtendra la siguiente descripcion:
object Buttonl: T B u t t o n
Left = 152
Top = 104
W i d t h = 75
Height = 25
Caption = ' B u t t o n l
TabOrder = 0
end
Ahora, si se modifica el nombre del objeto, su etiqueta o su posicion, por
ejemplo, o se aiiade una nueva propiedad, estos cambios pueden copiarse y volver
a pegarse en un formulario. Estos son algunos cambios de muestra:
object B u t t o n l : T B u t t o n
L e f t = 152
Top = 1 0 4
Width = 75
H e i g h t = 25
Caption = ' M i Boton'
TabOrder = 0
F o n t .Name = ' Arial'
end

Copiar esta descripcion y pegarla en el formulario creara un boton en la posi-


cion especificada con la etiqueta Mi Boton con una fuente Arial.
Para utilizar esta tecnica, es necesario saber como editar la representacion
textual de un componente, que propiedades son validas para ese componente en
particular y como escribir 10s valores para las propiedades de cadena, de conjunto
y otras propiedades especiales.
Cuando Delphi interpreta la descripcion textual de un componente o formula-
rio, tambien puede cambiar 10s valores de otras propiedades relacionadas con
aquellas que se han modificado y podria cambiar la posicion del componente, de
forma que no solape con una copia previa. Por supuesto, si escribimos algo total-
mente incorrect0 e intentamos pegarlo en un formulario, Delphi mostrara un men-
saje de error indicando lo que ha fallado.
Se pueden seleccionar diversos componentes y copiarlos a otro formulario o
bien a1 editor de textos a1 mismo tiempo. Puede que esto resulte util cuando
necesitamos trabajar con una serie de componentes similares. Podemos copiar
uno en el editor, reproducirlo una serie de veces, realizar las modificaciones apro-
piadas y, a continuacion, pegar todo el grupo de nuevo en el formulario.

De las plantillas de componentes a 10s marcos


Cuando copiamos uno o mas componentes de un formulario a otro, sencilla-
mente copiamos todas sus propiedades. Una tecnica mas potente consiste en crear
una plantilla de componentes, que hace una copia de las propiedades y del codigo
fuente de 10s controladores de eventos. A1 pegar la plantilla en un nuevo formula-
rio, seleccionando el pseudocomponente desde la paleta, Delphi reproduce el co-
dig0 fuente de 10s controladores de eventos en el nuevo formulario.
Para crear una plantilla de componentes, seleccionamos uno o m b componen-
tes y activamos la orden del menu Component>Create Component Template.
Esta abre el cuadro de dialog0 Component Template Information, en el que
hay que introducir el nombre de la plantilla, la ficha de la Component Palette
en la que deberia aparecer y un icono:
De manera predeterminada, el nombre de la plantilla es el nombre del primer
componente seleccionado, seguido de la palabra Template. El icono predefinido
de la plantilla es tambien el icono del primer componente seleccionado, per0 se
puede sustituir con un archivo de icono. El nombre que se de a la plantilla del
componente sera el utilizado para describirlo en la Component Palette (cuando
Delphi muestre la sugerencia contextual).
Toda la informacion sobre las plantillas de componentes se almacena en un
unico archivo, D E L P H I 3 2 .DCT, per0 aparentemente no hay forma de recuperar
dicha informacion y editar una plantilla. Sin embargo, lo que si se puede hacer es
colocar la plantilla de componentes en un formulario completamente nuevo, edi-
tarlo e instalarlo de nuevo como una plantilla de componentes utilizando el mismo
nombre. De este mod0 se podra sobrescribir la definicion anterior.

TRUCO: Un grupo de programadores en Delphi puede compartir planti-


llas de componentes si las guarda en un directorio comb, W i e n d o a1
Registro la entrada CCLibDir bajo la claw \SoftwareABorland\
Delphi\7.0\ComponentTemplates.

La plantillas de componentes son muy comodas cuando hay distintos formula-


rios que necesitan el mismo grupo de componentes y controladores de eventos
asociados. El problema es que una vez que se ha colocado una instancia de la
plantilla en el formulario, Delphi hace una copia de 10s componentes y de su
codigo, que ya no se referira a la plantilla. No hay ninguna forma de modificar la
definicion de la propia plantilla, ni tampoco es posible realizar el mismo cambio
en todos 10s formularios que usan dicha plantilla. Pero si lo podemos hacer gra-
cias a la tecnologia de marcos de Delphi.
Un marco es una especie de panel con el que se puede trabajar en tiempo de
diseiio de un mod0 similar a un formulario. Simplemente se crea un nuevo marco,
se colocan algunos controles en el y se aiiade el codigo a 10s controladores de
eventos. Cuando el marco esta listo, se abre un formulario, se selecciona el
pseudocomponente Frame desde la ficha Standard de la Component Palette y
se escoge uno de 10s marcos disponibles (del proyecto actual). Despues de colocar
el marco en el formulario, lo veremos como si 10s componentes se hubieran copia-
do en el. Si modificamos el marco original (en su propio diseiiador), las modifica-
ciones apareceran reflejadas en cada una de las instancias del marco.
Podemos ver un ejemplo sencillo, llamado Framesl, en la figura 1.8. Una
imagen realmente no significa mucho, deberia abrir el programa o reconstruir uno
similar si desea comenzar a utilizar frames.

T
Framel

I1
IS Smt Smi
(@dad

Framel
All

I
Figura 1.8. El ejemplo Framesl demuestra el uso de marcos El marco (a la
izquierda) y su instancia en un formulario (a la derecha) permanecen en sincronia.

A1 igual que 10s formularios, 10s marcos definen clases, por lo que encajan
dentro del modelo orientado a objetos de la VCL con mayor facilidad que las
plantillas de componentes. Como cabe imaginar a partir de esta introduccion
breve, 10s marcos son una tecnica nueva realmente potente.

Gestionar proyectos
El Project Manager de Delphi (View>Project Manager) funciona con un
grupo de proyectos, que puede englobar a su vez uno o mas proyectos. Por ejem-
plo, un grupo de proyectos puede incluir una DLL y un archivo ejecutable o
varios archivos ejecutables. Todos 10s paquetes abiertos apareceran como pro-
yectos en la vista del Project Manager, incluso aunque no se hayan aiiadido a1
grupo de proyecto.
En la figura 1.9, podemos ver el Project Manager con el grupo de proyecto
del presente capitulo. Como se puede ver, el Project Manager se basa en una
vista en arbol, que muestra la estructura jerarquica del grupo de proyectos, 10s
proyectos y todos 10s formularios y unidades que lo componen. Podemos emplear
la barra de herramientas y 10s menus de metodo abreviado mas complejos del
Project Manager para trabajar con el. El menu de metodo abreviado funciona de
acuerdo con el contexto: sus opciones dependen del elemento seleccionado. Hay
elementos del menu para afiadir un nuevo proyecto o un proyecto esistente a1
grupo de proyecto, para compilar o crear un proyecto especifico o para abrir una
unidad.

Fata P&h - - - - - - -- -
C Wchnos de programa\Borland\Delph17\Prolecls
- ! 3 w e r n ~me D \rn~code\~~\~~agram~erno
d 9 OtagrarnForrn D Lnd7caJe\Ol\D1agramDemo

n
a
ttl
J
ToDoTesl exe
Fiamesl.ere
D \md7code\0l\ToDoTest
D \rnd7caJe\Ol\Fiarnesl
3
,-' Fum D hd7mde\Ol\Framesl
5 Fnm pas D \md7wdeWl\Frametl
a Form1 D \md7codeWl\Fiamesl
13 @ Frame D \rnd7codeWl \Frames1
@ Flame pas D \md7code\Ol \Frames1
a Fianel D hd7mde\O1 \Flames1

Figura 1.9. El Project Manager de Delphi.

I TRUCO: d i r d e Belphi 6; e f e e c t Manager muestra tambih 1bs pa-


qudes abiaitos, hdusoaunque no selfiayaediadido sl grupo de proyedos.
1
Un paquete es'wooleccion de componente Q & otras u n i d a h que se
compila como un archim ejccuhble 3.p- M&o se vex&& a6elante.

De todos 10s proyectos de un grupo, solo hay uno que esta activo y ese es el
proyecto sobre el que trabajamos cuando seleccionamos una orden como
Project>Compile. El menu desplegable Project posee dos ordenes para compi-
lar o crear todos 10s proyectos del grupo. Cuando tengamos que crear diversos
proyectos, podemos establecer un orden relativo usando las ordenes Build
S o o n e r y Build Later.Estas dos ordenes basicamente reorganizan 10s pro-
yectos de la lista.
Entre las caracteristicas avanzadas del Project Manager, se encuentra la
funcion de arrastre de archivos de codigo fuente desde carpetas de Windows o
desde el Windows Explorer a un proyecto de la ventana del Project Manager
para aiiadirlos a un proyecto (tambien se soporta este comportamiento para abrir
archivos en el editor de codigo). Podemos ver facilmente que proyecto esta selec-
cionado y cambiarlo utilizando el cuadro combinado de la parte superior de la
ventana o utilizando la flecha hacia abajo que se encuentra junto a1 boton Run en
la barra de herramientas de Delphi.
Ademas de aiiadir archivos y proyectos de Pascal, se pueden aiiadir archivos
de recurso de Windows a1 Project Manager; estos se compilan junto con el
proyecto. Sencillamente, hay que desplazarse a un proyecto, seleccionar Add en
el menu de metodo abreviado y escoger Resource File (*.rc) como tipo de
archivo. Este archivo de recurso se unira automaticamente a1 proyecto, incluso
aunque no haya una directiva $R correspondiente.
Delphi guarda 10s grupos de proyectos con la extension .BPG (Borland Project
Group). Esta caracteristica procede del C++ Builder y de 10s antiguos compiladores
Borland C++, un historial que resulta claramente visible a1 abrir el codigo fuente
de un grupo de proyectos, que basicamente corresponde a1 de un archivo makefile
de un entorno de desarrollo C/C++. Veamos un ejemplo:

#----------------------------------------------------------
M A K E = $ (ROOT)\bin\make.exe - $ (MAKEFLAGS) -f$**
DCC = $ (ROOT)\bin\dcc32. exe $ * *
BRCC = $ (ROOT)\bin\brcc32. exe $ * *
#----------------------------------------------------------
PROJECTS = Project1 .exe

Opciones de proyecto
El Project Manager no ofrece una forma de definir las opciones para dos
proyectos diferentes a la vez. Sin embargo, se puede recurrir a1 dialog0 Project
Options desde el Project Manager en el caso de cada proyecto. La primera
ficha de Project Options (Forms) muestra la lista de 10s formularios que se
deberian crear automaticamente cuando arranca el programa y 10s formularios
que crea el propio programa. La siguiente ficha (Application) se usa para esta-
blecer el nombre de la aplicacion y el nombre de su archivo de ayuda y para
escoger su icono. Otras posibilidades de Project Options estan relacionadas con
el compilador y el editor de enlaces de Delphi, la informacion sobre la version y el
uso de paquetes en tiempo de ejecucion.
Existen dos formas de configurar las opciones del compilador. Una es utilizar
la ficha Compiler del dialogo Project Options. La otra es definir o eliminar las
opciones individuales del codigo fuente con las ordenes { $x+} o { $x-} , en las
que se reemplazaria la X por la opcion que queramos definir. Esta segunda tecni-
ca resulta mas flexible, puesto que permite modificar una opcion solo para un
archivo de codigo fuente concreto o incluso solarnente para unas cuantas lineas de
codigo. Las opciones del nivel de fuente sobrescriben las opciones del nivel de
compilacion.
Todas las opciones de un proyecto se guardan automaticamente con el, per0 en
un archivo a parte con una extension .DOF. Este es un archivo de texto que se
puede editar facilmente. No se deberia eliminar dicho archivo si se ha modificado
alguna de las opciones predefinidas. Delphi tambien guarda las opciones del
compilador en otro formato, en un archivo CFG, para la compilacion desde la
linea de comandos. Los dos archivos poseen un contenido similar per0 tienen un
formato distinto: el compilador de la linea de comandos dcc no puede usar archi-
vos .DOF, sino que necesita el formato .CFG.
Tambien se pueden guardar las opciones del compilador pulsando Control-0-
0 (pulsar la tecla 0 dos veces mientras se mantiene pulsada la tecla Control).
Esto inserta, en la parte superior de la unidad actual, directivas de compilador
que corresponden a las opciones de proyecto en uso, como en el siguiente listado:
I$A+,B-, C+,D+, E - , F- ,G+,Ht, I t , J t , K - , L t , M - , N t , O+, P t , Q-,R-, S - , T-
,U-,vt, W-,X+,Yt,Zl)
{$MINSTACKSIZE $ 0 0 0 0 4 0 0 0 )
{$MAYSTACKSIZE $OOIOOOOO)
{$IMAGEBASE $ 0 0 4 0 0 0 0 0 )
{SAPPTYPE G U I )
ISWARN SYMBOL-DEPRECATED O N )
{$WARN S Y M B O L - L I B R A R Y ON)
{$WARN SYMBOL-PLATFORM ON)
{$WARN U N I T - L I B R A R Y O N )
{$WARN UNIT-PLATFORM ON)
{$WARN UNIT-DEPRECATED O N )
{$WARN HRESULT-COMPAT ON)
{$WARN HIDING-MEMBER O N )
{$WARN HIDDEN-VIRTUAL ON)
{$WARN GARBAGE O N )
{$WARN BOUNDS-ERROR O N )
{$WARN ZERO-NIL-COMPAT ON)
{$WARN STRING-CONST- TRUNCED O N )
{$WARN FOR-LOOP-VAR-VARPAR ON)
{$WARN TYPED-CONS T-VARPAR O N )
{$WARN ASG- TO- TYPED-CONST O N )
{$WARN CASE-LABEL-RANGE ON)
{$WARN FOR-VARIABLE ON)
{$WARN CONS TRUCTING-ABS TRACT ON) {$WARN COMPARISON-FALSE ON)
{$WARN COMPARISON- TRUE ON)
{$WARN COMPARING- S IGNED- UNSIGNED ON)
$ WARN COMBINING- S I G N E D UNSIGNED ON)
{$WARN UNSUPPORTED-CONS TRUCT ON)
{$WARN FILE-OPEN ON)
{$WARN FILE-OPEN-UNITSRC ON)
{$WARN BAD-GLOBAL-SYWBOL ON)
{$WARN DUPLICATE-CTOR-DTOR ON)
{$WARN INVALID-DIRECTIVE ON)
{$WARN PACKAGE-NO- L I N K O N )
{$WARN PACKAGED- THREADVAR ON)
{$WARN I M P L I C I T - IMPORT ON)
{$WARN HPPEMI T- IGNORED ON)
{$WARN NO-RETVAL ON)
{$WARN USE-BEFORE-DEF ON)
{$WARN FOR-LOOP-VAR-UNDEF ON)
{$WARN UNIT--MISMATCH ON)
{$WARN NO-CFG-FILE-FOUND ON)
{$WARN MESSAGE-DIRECTIVE ON)
{$WARN IMPLICIT-VARIANTS ON)
{$WARN UNICODE- TO-LOCALE ON)
{$WARN LOCALE- TO- UNICODE ON)
{$WARN IMAGEBASE-MULTIPLE ON)
{$WARN SUSPICIOUS-TYPECAST ON)
{$WARN PRIVATE-PROPACCESSOR ON)
{$WARN UNSAFE- T Y P E O F F )
{$WARN UNSAFE-CODE OFF)
ISWARN UNSAFE-CAST O F F )

Compilar y crear proyectos


Existen diversas formas de compilar un prbyecto. Si se ejecuta (pulsando F9 o
haciendo clic sobre el icono Run de la barra de herramientas), Delphi lo compila-
ra primero. Cuando Delphi compila un proyecto, compila unicamente 10s archi-
vos que han cambiado. En cambio, si seleccionamos Compile>Build>All, se
compilan todos 10s archivos, aunque no hayan cambiado. Esta segunda orden
deberia ser necesaria en raras ocasiones, puesto que Delphi puede decidir normal-
mente que archivos han cambiado y compilarlos como sea necesario. La unica
excepcion son 10s casos en 10s que se cambian algunas opciones de proyecto, en
cuyo caso debemos emplear la orden B u i l d A l l para que las nuevas opciones
Sean efectivas.
Para crear un proyecto, Delphi compila primero cada archivo de codigo fuente
y crea una unidad compilada de Delphi (DCU). (Este paso se realiza solo si el
archivo DCU no se ha actualizado aun). El segundo paso, que realiza el editor de
enlaces, consiste en mezclar todos 10s archivos DCU en un archivo ejecutable,
opcionalmente con codigo compilado de la biblioteca VCL (si no hemos decidido
utilizar paquetes en tiempo de ejecucion). El tercer paso consiste en unir a un
archivo ejecutable cualquier archivo de recurso opcional, como el archivo RES
del proyecto, que contiene su icono principal y 10s archivos DFM de 10s formula-
rios. Se pueden entender mejor 10s pasos de la compilacion y seguir el hilo de lo
que ocurre durante dicha operacion si activamos la opcion Show Compiler
Progress (en la pagina Preferences del cuadro de dialog0 Environment
Options).
- -
ADVERTENCIA: Del@ no siempre tiene claro cuhijo feconstruir las
unidadcs basadas en ah.bs unidades que se han m o d i f i d . Esto s s sobre
todo verdad en 10s casos (y son muchos) en que la i n t e r v W 4 n -deIusuario
confunde la logica del wmpilador. Por ejemplo, r m m b n u adiivas, modi-
#3catAcodigo.fuentedesde el exterior del IDE. copiar archiui,s tk &.&go
heate ~n&uos Q wchivos DCU a1 disco, o t m e r multiples ~ o p i a de s un
archiyo fuentc d c ' o n i ~
en la ruta dc busqucda puede e s t r o p k el proceso
dc compiIaclln. Ctqh vez que el compilador muestra &6n naensaje de
error estraiio, 1~primer0 que deberiamos hacer es utilizarla oFden B u i l d
XLl pard sincrohizar de nuevo l a caracteristica make (dq @mdmcci6n)
corilos uchivas acfuafes del disco. .-

La orden Compile se puede usar solo cuando se ha cargado un proyccto en el


cditor. Si no hay ningun proyecto activo y cargamos un archivo fuente Pascal, no
se puede compilar. Sin embargo, si cargamos el archivo fuente como si fuera un
proyecto, podremos compilar el archivo. Para ello, simplemente seleccionamos el
boton de la barra de herramientas Open Project y cargamos un archivo PAS.
Ahora podemos verificar su sintaxis o compilarlo, creando un DCU.
Ya mencionamos que Delphi permite el uso de paquetes en tiempo de ejecu-
cion, lo que afecta a la distribucion del programa mas que a1 proceso de compila-
cion. Los paquetes Delphi son bibliotecas de cnlace dinamico (DLL) que contienen
componentes Delphi. A1 emplear paquetes, se consigue que un archivo ejecutable
sea mucho mas pequeiio. Sin embargo, el programa no se ejecutara a no ser que
este disponible la biblioteca dinamica apropiada (corno vcl50. bpl, que es
bastante amplia) en el ordenador en el que desea ejecutar el programa.
Si sumamos el tamafio de esta biblioteca dinamica a1 del pequeiio archivo
ejecutable, la cantidad total de espacio en disco necesaria por el aparentemente
pcqueiio programa, que hemos creado con 10s paquetes en tiempo de ejecucion, es
mucho mayor que el espacio necesario por el supuestamente gran archivo ejecuta-
ble por si solo. Por supuesto, si tenemos diversas aplicaciones en un unico siste-
ma, ahorraremos mucho, tanto en espacio de disco como en consumo de memoria
en tiempo de ejecucion. El uso de paquetes suele ser recomendable, pero no siem-
pre.
En ambos casos, 10s ejecutables Delphi resultan extremadamente rapidos de
compilar y la velocidad de la aplicacion que obtenemos es comparable a la de un
programa en C o C++. El codigo compilado en Delphi se ejecuta al menos cinco
vcccs mas rapido que el codigo equivalente de herramientas interpretadas o
"scmicompiladas".
Ayudante para mensajes del compilador y advertencias
Ademas de 10s clasicos mensajes del compilador, Delphi 7 ofrece una nueva
ventana con informacion adicional sobre algunos mensajes de error. Esta ventana
se activa mediante el comando de menu View>Additional Message Info. Mues-
tra informacion almacenada en un archivo local, que puede actualizarse descar-
gando una version nueva desde el sitio Web de Borland. Otro cambio en Delphi 7
esta rclacionado con el mayor control que se tiene sobre las advertencias del
compilador.
El cuado de dialogo Project Options incluye ahora una pagina Compiler
Message en la que se pueden seleccionar muchas advertencias individuales. Esta
posibilidad se aiiadio probablemente por el hecho de que Delphi 7 tiene un nuevo
conjunto de advertencias relacionadas con la compatibilidad con la futura herra-
mienta Delphi for .NET.
Estas advertencias son bastante exhaustivas, y se pueden inhabilitar como se
muestra en la figura 1.10.

I
yarn- - - - - - - --.

?. Unit idenlifier does m t match hle name


3
% Na sonl~gu~at~an
files laund
-

i
,JJUser message
B lmpl~utuse of Varlants unit
Errol conveltrng Unicode cha~to locale charsel
@ Er~orconverl~qlocals s l i i to
~ Unicode
,4Imagebase e not a rmkiile d 64k

1 M

LI'.Unsafe typecast

- 1
Figura 1.10. La nueva pagina Compiler Messages del cuadro de dialogo Project
Options.

TambiCn se pueden habilitar o inhabilitar algunas de estas advertencias me-


diante opciones de compilador como estas:
( $Warn UNSAFE-CODE OFF)
{ $Warn UNSAFE-CAST OFF)
($Warn UNSAFE-TYPE OFF)
En general, es mejor mantener estas opciones fuera del codigo fuente del pro-
grams, algo que finalmente permite Delphi 7.

Exploracion de las clases de un proyecto


Delphi siempre ha incluido una herramienta para explorar 10s simbolos de un
programa compilado, aunque el nombre de csta herramienta haya carnbiado mu-
chas veces (desde Object Browser a Project Explorer y ahora a Project
Browser). En Delphi 7, se puede activar la ventana del Project Browser me-
diante la opcion de menu View>Browser, que muestra la misma ventana que la
figura 1 . 1 1 . El explorador permite analizar la estructura jerarquica de las clases
de un proyccto y buscar en ella 10s simbolos y lineas del codigo fuente en que se
haga referencia a ellos.

Figura 1.11. Project Browser

Al contrario que el Code Explorer, el Project Browser so10 se actualiza


cuando se vuelve a compilar el proyecto. Este explorador permite ver listas de
clases, unidades y globales, y tambien escoger si buscar so10 simbolos definidos
en el proyecto o tanto del proyecto como de la VCL. Se puede cambiar la configu-
ration del Project Browser y la del Code Explorer en la pagina Explorer de
Environment Options o mediante el comando P r o p e r t i e s del menu desple-
gable del Project Browser. Algunas de las categorias que se pueden ver en esta
ventana son especificas del Project Browser; otras estan relacionadas con am-
bas herramientas.
Herramientas Delphi adicionales y externas
Ademas del IDE, a1 instalar Delphi se consiguen otras herramientas externas.
Algunas de ellas, como el Database Desktop, el P a c k a g e Collection Editor
(PCE . e x e ) y el Image Editor ( 1 m a g E d i t . e x e ) estan disponibles desde el
menu T o o k del IDE. Ademas, la edicion Enterprise posee un enlace a1 SQL
Monitor (S qlMon .e x e ) . Otras herramientas a las que no se puede acceder di-
rectamente desde el IDE son, por ejemplo, las herramientas de linea de comandos
que se pueden encontrar en el directorio bin de Delphi. Por ejemplo, entre estas
herramientas se incluye el compilador de Delphi en linea de comandos
(DCC3 2 . e x e ) , un compilador de recursos de Borland (BRC3 2 . e x e y
BRCC3 2 .e x e ) y un visor de ejecutables (TDump . e x e ) .
Por ultimo, algunos de 10s programas de ejemplo que se incluyen con Delphi
son en realidad utiles herramientas que el usuario puede compilar y tener siempre
a mano. Aqui se presentan algunas de ellas, las de mas alto nivel, la mayoria
disponibles en la carpeta \ D e l p h i 7 \ b i n y en el menu Tools:
Web App Debugger (WebAppDbg .e x e ) : Es el servidor de depuracion
Web introducido en Delphi 6. Se utiliza para guardar la pista de segui-
miento de las solicitudes que el usuario envia a sus aplicaciones y para
depurarlas. Este depurador se rescribio en Delphi 7: ahora se trata de una
aplicacion CLX y su conectividad se basa en sockets.
XML Mapper (XmlMapper . e x e ) : Es una herramienta para crear trans-
formaciones XML aplicadas a1 formato producido por el componente
ClientDataSet.
External Translation Manager ( e t m 6 0 . e x e ) : Es la version indepen-
diente del Integrated Translation Manager. Esta herramienta externa pue-
de ofrecerse a traductores externos y esta disponible desde Delphi 6.
Borland Registry Cleanup Utility ( D 7 R e g C l e a n . e x e ) : Ayuda a eli-
minar todas las entradas de Registro aiiadidas por Delphi 7 a un ordena-
dor.
TeamSource: Es un sistema de control de versiones avanzado proporcio-
nado con Delphi, que comenzo con la version 5. La herramienta es muy
similar a su antigua encarnacion y se instala separadamente de Delphi.
Delphi 7 ofrece la version 1.0 1 de TeamSource, la misma version disponi-
ble despues de aplicar un parche disponible para la version de Delphi 6.
WinSight (Ws32 . e x e ) : Es un programa Windows "espia de mensajes"
disponible en el directorio b i n .
Database Explorer: Puede activarse desde el IDE de Delphi o como herra-
mienta independiente, usando el programa ~ B E x p l o .re x e del directo-
rio b i n . Ya que esta destinada a BDE, no se utiliza mucho actual-
mente.
OpenHelp (oh. e x e ) : Es la herramienta que podemos emplear para admi-
nistrar la estructura de 10s propios archivos de ayuda de Delphi, integran-
do archivos de otras personas en el sistema de ayuda.
Convert ( C o n v e r t .e xe): Es una herramienta de linea de comandos que
podemos usar para convertir 10s archivos DFM en su descripcion textual
equivalente y viceversa.
Turbo Grep ( G r e p . e x e ) : Es una utilidad de busqueda de lineas de orde-
nes, mucho mas rapida que el arraigado mecanismo Find In Files, per0 no
es facil de usar.
Turbo Register Server (TRegSvr .e x e ) : Es una herramienta que pode-
mos emplear para registrar bibliotecas ActiveX y servidores COM. El co-
dig0 fuente de esta herramienta esta disponible bajo \Demos \ A c t i v e X \
TRegSvr.
Resource Explorer: Es un poderoso visor de recursos (pero no un editor
de recursos propiamente dicho) que se puede encontrar bajo \Demos \
ResXplor.
Resource Workshop: Es un editor de recursos de 16 bits que puede con-
trolar archivos de recursos de Win32. El CD de instalacion de Delphi
incluye una instalacion independiente para esta herramienta. Se ofrecia
con 10s compiladores de Borland para C++ y Pascal para Windows y era
mucho mejor que 10s editores de recursos de Microsoft disponibles enton-
ces. Aunque su interfaz de usuario no se ha actualizado y no trabaja con
nombres de archivos largos, esta herramienta todavia resulta muy util para
construir recursos especiales o personalizados. Tambien le permite explo-
rar 10s recursos de 10s archivos ejecutables existentes.

Los archivos creados por el sistema


Delphi produce diversos archivos para cada proyecto y seria conveniente sa-
ber que son y como se denominan. Basicamente, hay dos elementos que influyen
en la forma de denominacion de 10s archivos: 10s nombres que se dan a un proyec-
to y a sus unidades, y las extensiones predefinidas de 10s archivos que utiliza
Delphi.
En la tabla 1.1 se listan las extensiones de 10s archivos que encontrara en el
directorio en el que se guarda un proyecto Delphi. La tabla muestra tambien
cuando o en que circunstancias se crean estos archivos y su importancia de cara a
su posterior cornpilacion.
Tabla 1.1. Extensiones de archivos de proyecto Delphi

BMP, ICO, Archivos de mapas de bits, Desarrollo: Normalmente no, per0


CUR iconos y cursores: archivos Image Editor pueden ser necesarios
estandar de Windows usa- en tiempo de ejecu-
dos para almacenar ima- cion y para una poste-
genes de mapas. rior modificacion.

BPG Borland Project Group Desarrollo Necesario para compi-


(Grupo de proyectos lar de nuevo todos 10s
Borland): archivos que usa proyectos del grupo a
el nuevo Project Manager. la vez.
Es una especie de
makefile.

BPL Borland Package Library Compilacion: Se distribuiran a otros


(Biblioteca de paquetes Enlace desarrolladores Delphi
Borland): una DLL que y opcionalmente a
contiene, entre otros, com- usuarios finales.
ponentes VCL que usa el
entorno Delphi en tiempo
de disefio o las aplicacio-
nes en tiempo de ejecu-
cion. (Estos archivos
usaban una extension
.DPL en Delphi 3.)

CAB Formato de archivo com- Compilacion Distribuido a usuarios


primido Microsoft Cabinet
usado por Delphi para el
despliegue Web. Un archi-
vo CAB puede contener
diversos archivos compri-
midos.

.CFG Archivo de configuracion Desarrollo Necesario


con las opciones de pro- han definido opciones
yecto. Similar a 10s archi- especiales de compi-
vos DOF. lacion.

.DCP Delphi Component Packa- Compilacion Necesario cuando


ge (Paquete de componen- usamos paquetes.
tes de Delphi): un archivo Solo se distribuira a
con informacion de simbo- otros desarrolladores
lo para el codigo compilado junto con 10s archivos
en el paquete. No incluye BDPL. Se puede
codigo compilador, que se compilar una aplica-
guarda en archivos DCU. cion con las unidades
de un paquete simple-
mente con el archivo
DCP y el BPL (sin
archivos DCU).

.DCU Delphi Compiled Unit (U ni- Com pilacion Solo si el codigo


dad compilada Delphi): re- fuente no esta dispo-
sultado de la compilacion nible. Los archivos
de un archivo en Pascal. DCU de las unidades
que escribimos son un
paso intermedio, por
lo que favorecen una
compilacion mas
rapida.

. DDP El n uevo Delphi Diagram Desarrollo No. Este archivo


Portfolio (Cartera de almacena informacion
diagrama Delphi) usado por "solo en tiempo de
la vista en diagrarna del disefio" no necesaria
editor (era DTI en Delphi para el programa
5). resultante per0 muy
importante para el
programador.

.DFM Delphi Form File (Archivo Desarrc Si. Todos 10s formula-
de formulario de Delphi): rios se almacenan
un archivo binario con la tanto en un archivo
descripcion de las propie- PAS como en un
dades de un formulario (o DFM.
un modulo de datos) y de
10s componentes que con-
tiene.

.-DF Copia de seguridad de Desarrollo No. Este archivo se


Delphi Form File (DFM) crea al guardar una
version de la unidad
relacionada con el
formulario y el archivo
del formulario junto
con ella.
DFN Archivo de soporte para Desarrollo (ITE) Si (para el ITE). Estos
Integrated Translation archivos contienen
Environment (hay un archi- cadenas traducidas
vo DFN para cada formula- para cada formulario
rio y cada lenguaje que editarnos en el
o bjetivo). Translation Manager

DLL Dinamic Link Library (Bi- Cornpilacion Vease .EXE


blioteca de enlace dinami- Enlace
co): otra version de un
archivo ejecutable.

DOF Delphi Option File: archivo Desarrollo Necesario solo si se


de texto con la configura- han instalado opcio-
cion actual de las opciones nes especiales del
actuales para las opciones compilador.
del proyecto.

DPK y Delphi Package: el archivo Desarrollo Si.


~ h o r atam- de codigo fuente del pro-
lien .DPKW yecto de un paquete (o un
r .DPKL archivo de proyecto espe-
cifico para Windows o
Linux).

DPR Archivo Delphi Project. Desarrollo


(Este archivo contiene en
realidad codigo fuente
Pascal.)

-DP Copia de seguridad del ar- Desarrollo No. Este archivo se


chivo Delphi Project crea automaticarnente
(.DPR). al guardar una nueva
version de un archivo
de proyecto.

DSK Desktop file (Archivo de es- Desarrollo No. En realidad debe-


critorio): contiene infor- rian eliminarse si se
macion sobre la posicion copia el proyecto en
de las ventanas Delphi, 10s un nuevo directorio.
archivos que se abren en
el editor y otras configura-
ciones del escritorio.
DSM Delphi Symbol Module (Mo- Cornpilacion No. El Object Browser
dulo de simbolos Delphi): (pero solo si se usa este archivo, en
Alrnacena toda la informa- ha activado la lugar de 10s datos en
cion de sirnbolo del explora- opcion Save memoria, cuando no
dor. Symbols) puede volver a compi-
lar un proyecto.

EXE Executable file (Archivo eje- Compilacion: No. Este archivo que
cutable): la aplicacion Enlace se distribuira incluye
Windows creada. todas las unidades
compiladas, forrnula-
rios y recursos.

HTM 0 .HTML (Hypertext Despliegue Web No. No participa en la


Markup Language, Len- de un cornpilacion del pro-
guaje de rnarcas con ActiveForrn yecto.
hipertexto): el forrnato de
archivo usado para pagi-
nas Web.

LIC Los archivos de licencia Asistente No. Es necesario usar


relacionados con un archi- ActiveX y otras el control en otro
vo OCX. herrarnientas entorno de desarrollo.

OBJ Object file (Archivo objeto) Paso intermedio Podria ser necesario
(cornpilado), tipico del de compilacion, para rnezclar Delphi
rnundo C/C++. generalmente no con codigo cornpilado
se usa en C++ en un tinico
Delphi. proyecto.

. OCX OLE Control Extension (Ex- Compilacion: Vease .EXE.


tension de control OLE): Enlace
una version especial de una
DLL, que contiene controles
ActiveX o forrnularios.

.PAS Pascal file (Archivo de Desarrollo


Pascal): El codigo fuente
de una unidad Pascal, una
unidad relacionada con un
forrnulario o una unidad
independiente.
-PA Copia de seguridad de un Desarrollo No. Este archivo lo
archivo Pascal (.PAS). crea Delphi autorna-
ticamente al guardar
una nueva version del
codigo fuente.

RES, .RC Resource file (Archivo de Cuadro de dialo- Si. Delphi crea de
recurso): el archivo binario go Development nuevo el archivo RES
asociado con el proyecto Options. El ITE principal de una apli-
de una aplicacion y que (Integrated cacion en funcion de
normalmente contiene su Translation la inforrnacion de la
icono. Podernos afiadir Environment) ficha Application del
otros archivos de este tip0 crea archivos de cuadro de dialogo
a un proyecto. Cuando recurso con Project Options.
creamos archivos de recur- comentarios
so personalizados pode- especiales.
rnos usar tambien el
forrnato textual .RC.

.RPS Translation Repository Desarrollo (ITE) No. Necesario para


(parte de Integrated adrninistracion de las
Translation Environment). traducciones.

.TLB Type Library (Biblioteca de Desarrollo Este es un archivo


tipos): un archivo creado que pueden necesitar
de forrna autornatica o por otros prograrnas OLE.
el Type Library Editor para
aplicaciones del servidor
OLE.

TODO Archivo de lista To-do en Desarrollo No. Este archivo


el que se guardan elernen- contiene notas para
tos relacionados con el 10s programadores.
proyecto entero.

.UDL Microsoft Data Link (Enlace Desarrollo Usado por ADO para
de datos Microsoft). referirse a un provee-
dor de datos. Similar
a un alias en el entor-
no BDE.

Ademas de 10s archivos creados durante el desarrollo de un proyecto en Delphi,


existen muchos otros creados y usados por el propio IDE. En la tabla 1.2, se

rn
presenta una breve lista de las extensiones que merece la pena conocer. La mayo-
ria de estos archivos estan en formatos propietarios no documentados, por lo que
poco se puede hacer con ellos.

Tabla 1.2. Extensiones de archivo d e personalizacion del IDE d e Delphi


seleccionadas.

.DC I Delphi Code Templates (Plantillas de codigo Delphi).


.DRO Delphi Object Repository (Object Repository d e Delphi) (De-
beria modificarse el Repository con la orden Tools>
Repository.)
.DMT Delphi Menu Templates (Plantillas d e menu d e Delphi).
.DBI Database Explorer Information (Informacion del explorador
d e bases d e datos).
.DEM Delphi Edit Mask (Mascara d e edicion d e Delphi) (Archivos
con formatos especificos seglin paises para mascaras d e
edicion).
.DCT Delphi Component Template (Plantillas d e componentes d e
Delphi).
.DST Desktop Settings File (Archivo d e configuracion del escrito-
rio): uno para cada configuracion d e escritorio definida.
I

Un vistazo a 10s archivos de codigo fuente


Los archivos basicos de Delphi son 10s archivos de codigo fuente en Pascal,
que son archivos de testo ASCII normales. El texto en negrita, cursiva y colorea-
do que se ve en el editor depende de la forma en que se resalte la sintaxis, per0 no
se guarda esa configuracion junto con el archivo. Merece la pena destacar que
hay un unico archivo para todo el codigo del formulario, no solo pequeiios frag-
mentos de codigo.
- ..- - -- - - - - . - - - - ------
TRUCO:En 10s listados de este libro, hemos asociado la fnrmade regaltar
la sintaxis en negrita del editor a las palabras clave y la cnrsira p lrts
cadenas y 10s comentarios.

En el caso de un formulario, el archivo Pascal contiene la declaracion de clase


del formulario y el codigo fuente de 10s controladores de eventos. Los valores de
las propiedades que se definen en el Object Inspector se almacenan en un archi-
vo a parte con la descripcion de formulario (con extension .DFM). La unica
excepcion es la propiedad Name,que se usa en la declaracion de formulario para
hacer referencia a 10s componentes del formulario.
El archivo DFM es de manera predeterminada una representacion textual del
formulario, pero se puede guardar en cl formato binario tradicional Resource de
Windows. Podemos establecer el formato predefinido que queremos usar para
proyectos nuevos en la ficha Preferences del cuadro de dialog0 Environment
Options y cambiar el formato de formularios individuales con la orden Tcxt
DFM del menu de metodo abreviado de un formulario. Un editor de texto normal
puede leer solo la version de texto. Sin embargo, se pueden cargar 10s archivos
DFM de ambos tipos en el editor Delphi, que 10s convertira primero, si cs neccsa-
rioj en una descripcion textual. La forma mas sencilla de abrir la descripcion
textual dc un formulario (sea en el formato que sea) es seleccionar la orden View
A s Text del menu de metodo abreviado del Form Designer. Esta orden cierra
el formulario, lo guarda si es necesario y abre el archivo DFM en el editor. Mas
tarde sc puede volver a1 formulario usando la orden View A s Form del menu dc
metodo abreviado de la ventana del editor.
En realidad, se puede editar la descripcion textual de un formulario, aunquc
esto deberia hacerse con extremo cuidado. Desde el momento en que se guarde el
archivo, se convertira de nuevo en un archivo binario. Si se han hecho cambios
incorrectos, se detendra la compilacion con un mensaje de error y habra que
corregir el contenido del archivo DFM antes de volver a abrir el formulario. Por
esa razonj no se deberia intentar cambiar la descripcion textual de un formulario
manualmentc hasta disponcr de un solido conocimiento de programacion en Delphi.
-

1
TRUCO:En el libro, aparecen normalmente extractos de archivos DFM.
En l a mayoridde estos extractos, aparecen unicamente 10s componentes o
propiedades m k relevantes, por lo general, he elirninado las propiedades de
posicion, 10s valores binarios y otra lineas que ofiecen poca informacion.

Ademas de 10s dos archivos que describen el formulario (PAS y DFM), hay un
tercer archivo que resulta vital para volver a construir la aplicacion. Este es el
archivo de proyecto de Delphi (DPR), otro archivo de codigo f~ienteen Pascal,
que se crea automaticamente y que rara vez es necesario modificar manualmente.
Puede verlo con la orden del menu View>Project Source.
Algunos de 10s demas archivos menos relevantes creados por el IDE usan la
estructura de archivos IN1 de Windows, en la que cada seccion se indica mediante
un nombre que va entre corchetes. Por ejemplo, este es un fragment0 de un archi-
vo de opciones (DOF).
[Compiler]
A= 1
B=O
[Linker]
MinStackSize=16384
MaxStackSize=1048576
ImageBase=4194304

[Parameters]
Runparams=
HostApplication=

Los archivos de escritorio (DSK) utilizan la misma estructura, que contiene el


estado del IDE de Delphi para un proyecto especifico, listando la posicion de cada
ventana.
[Mainwindow]
Create=l
Visible=l
State=O
Lef t = 2
Top=O
Width=800
Height=97

El Object Repository
Delphi tiene varias ordenes de menu que se pueden usar para crear un nuevo
formulario, una nueva aplicacion, un nuevo modulo de datos, un nuevo compo-
nente, etc. Dichas ordenes estan situadas en el menu File>New y en otros menus
desplegables. Si seleccionamos sencillamente File>New>Other, Delphi abre el
Object Repository, que se usa para crear nuevos elementos de cualquier tipo:
formularios, aplicaciones, modulos de datos, objetos thread, bibliotecas, compo-
nentes, objetos de automatizacion y muchos mas.
El nuevo cuadro de dialogo (que se ve en la figura 1.12) posee varias fichas,
contiene todos 10s elementos que puede crear, 10s formularios existentes y 10s
proyectos almacenados en el Repository, asistentes Delphi y 10s formularios del
proyecto actual.
Las fichas y las entradas de este cuadro de dialogo con solapas dependen de la
version especifica de Delphi, por lo que no las mencionaremos.

por techa o por descnpcl6n) y mostrat dttim%&s ViMw (idol3 giqhks,


iconos pequefios, listas y detalles). La vista Msib propo*ei& & hes-
cripcion, autor y fecha de la herramienta, una informacion que resulta so-
bre todo importante cuando se echa un vistazo a los asistentes, proyectos o
formularios que hemos aiiadido a1 Repository.

Console
Cwone* Applcat~on

Cunlrol Panel Control Panel Dda Module DCC W ~ z a d Fam


Appbcalmn Module

TI
Flame Package Ploiect GI- Resource DLL
Wnald
Service

1 - C

Figura 1.12. La primera pagina del cuadro de dialogo New Items, conocida
generalmente corno Object Repository.

El mod0 mas sencillo de personalizar el Object Repository es aiiadir nuevos


proyectos, formularios y modulos de datos como plantillas. Tambien podemos
aiiadir fichas nuevas y organizar 10s elementos de algunos de ellas (sin incluir las
fichas New ni la del proyecto en uso). Cuando se tiene una aplicacion en funcio-
namiento que se quiere emplear como punto de arranque para el desarrollo de
programas similares, se puede guardar el estado actual en una plantilla para
usarla mas tarde. Simplemente se usa la orden Project>Add To Repository y se
, cubre su cuadro de dialogo.
Tambien se pueden aiiadir nuwas plantillas de formulario. Sencillamente des-
plazamos el formulario que se quiere aiiadir y seleccionamos la orden ~ d Td o
R e p o s i t o r y del menu de metodo abreviado. A continuacion, indicamos el titu-
lo, la descripcion, el autor, la ficha y el icono en su cuadro de dialogo. Hay que
tener en cuenta que si se copia un proyecto o una plantilla de formulario a1
Repository y se vuelve a copiar a otro directorio, simplemente se realiza una
operacion de copiar y pegar; no es muy distinto de copiar 10s archivos manual-
mente.

I La plmWla de pmyecb en blanco I


Cuando se inicia un nuevo proyecto, autodticamente se abre tambib un
formulario en blgaco. Sin embargo, si queremos basar el nuevo proyecto en
urn, de 10s objetos de fbmularios o asistentes, habra que afiadir una planti-
hay que seguir para ello son:
1. Crear un nuevo proyecto como de costumbre.
2. Eliminar el unico formulario del proyecto.
3. Aiiadir este proyecto a las plantillas y denominado Empty Project.
Cuando seleccionamos este proyecto en el Object Repository, se obtienen
dos ventajas: tener su proyecto sin un formulario y poder escoger un direc-
torio en el que se copiaran 10s archivos de la plantilla de proyecto. Tambien
hay una desventaja, habra que recordar usar la orden FileSave Project
AS para dar un nuevo nombre a1 proyecto, porque si se guarda el proyecto
de otro modo se utiliza automaticamente el nombre predefinido de la plan-
tilla.

Para personalizar aun mas el Repository se puede usar la orden Tools>


Repository. Esta orden abre el cuadro de dialog0 del Object Repository, que se
puede emplear para mover elementos a distintas fichas, aiiadir elementos nuevos
o borrar 10s que ya existen. Incluso se pueden aiiadir fichas nuevas, darles un
nombre nuevo o eliminarlas y cambiar su orden. Un elemento importantc de la
instalacion del Object Repository es el uso de 10s valores predefinidos:
Usar la casilla de verificacion New Form bajo la lista dc objetos para
designar un formulario como el que se usara cuando se Cree un nuevo
formulario (File>New Form).
La casilla de verificacion del Main Form indica que tipo de formulario
emplear cuando se crea el formulario principal de una nueva aplicacion
(File>New Application), si no se selecciona un New Project especial.
La casilla de verificacion New Project, disponible cuando se selecciona
un proyecto, marca el proyecto por defecto que Delphi utilizara cuando se
de la orden File>New Application.
Solo un formulario y un proyecto del Object Repository pueden tener cada
una de estas tres configuraciones marcadas con un simbolo especial situado sobre
su icono. Si no se selecciona ningun proyecto como New Project, Delphi crea un
proyecto por defecto basado en el formulario marcado como Main Form. Si no
hay ningun formulario marcado como Main Form, Delphi crea un proyecto por
defecto con un formulario en blanco. Cuando trabajamos con el Object
Repository, trabajamos con formularios y modulos guardados en el subdirectorio
OBJREPOS del directorio principal de Delphi. En este momento, si usamos un
formulario u otro objeto directamente sin copiarlo, acabaremos teniendo algunos
archivos de nuestro proyecto en este directorio. Es importante darse cuenta de
como funciona cl Repository, porque si queremos modificar un proyecto o un
objeto guardados en 61; la mejor tecnica es trabajar con 10s archivos originalcs,
sin copiar 10s datos una y otra vez en el Repository.
.

lnstalar nuevos asistentes DLL


Tecnicamente, 10s nuevos asistentes poseen dos formas diferentes: pueden
formar parte de 10s componentes o de 10s paquetes o pueden distribuirse
como archivos DLL independientes. En el primer caso, se instalaria del
mismo mod0 que un componente o un paquete. Cuando se recibe una DLL
independiente, hay que aiiadir el nombre de la DLL a1 Registro de Windows
baioa la clave \Software\Borland\DelDhi\7.O\Ex~erts. . Sirn-.
plemente se aiiade una nueva clave de cadena bajo esta clave, se escoge un
nombre (no importa realmente cual) y se utiliza como texto la ruta y nom-
.2 . _
L - _ J - ---l-_.-
ore a e arctuvo ael3-1
asmenre nULL.
__:_*_-A_ m 0 . T
3 e pueaen ver- las
I - _ _..*---l-_ -..-_._
enrraaas que ya estan __LZ__

presentes bajo la clave Experts para averiguar el mod0 en que se debe


introducir la ruta.

Actualizaciones del depurador en Delphi 7


Cuando se ejecuta un programa en el IDE de Delphi, generalmente se arranca
en el depurador integrado. Se pueden fijar puntos de ruptura, ejecutar el codigo
linea a linea y explorar sus detalles internos, como el codigo ensamblador que se
ejecuta y el uso de 10s registros de la CPU en la vista CPU.
Para mencionar un par de las nuevas caracteristicas del depurador, en primer
lugar el cuadro de dialog0 Run Parameters en Delphi 7 permite establecer un
directorio de traba.jo para el programa que se va a depurar. Esto significa que el
directorio actual sera el que se indique, no aquel en el que se haya compilado del
programa. Otra modificacion importante tiene que ver con la Watch List. Ahora
dispone de multiples pestaiias que permiten mantener un conjunto distinto de
escuchas de variables activo para las distintas areas del programa que se esta
depurando, sin amontonarse en una unica ventana. Puede aiiadirse un grupo nue-
vo a la Watch List mediante su menu abreviado y tambien modificar la visibilidad
de las cabeceras de las columnas y habilitar escuchas individuales con sus corres-
pondientes casillas de activacion.

--

I
Wc_hName ' V W - - _ -_ . ..-
rnControls ' ' expected but end of lde found
rnTRad~oButton Symbol was el~mtnatedby l~nker
El lenguaje
de programaclon
Delphi

El entorno de desarrollo para Delphi se basa en una extension orientada a


objetos del lenguaje de programacion Pascal conocida como Object Pascal o Pascal
orientado a objetos. Recientemente, Borland declaro su intencion de referirse a1
lenguaje como "el lenguaje Delphi", probablemente porque la empresa deseaba
ser capaz de decir que Kylix usa el lenguaje Delphi y porque Borland ofrecera el
lenguaje Delphi sobre la plataforma .NET de Microsoft. Debido a la costumbre
de 10s aiios, es comun utilizar ambos nombres por igual.
La mayoria de 10s lenguajes de programacion modernos soportan programa-
cion orientada a objetos (OOP). Los lenguajes OOP se basan en tres conceptos
fundamentales: la encapsulacion (normalmente implementada mediante clases),
la herencia y el polimorfismo (o enlace tardio). Aunque se puede escribir codigo
Delphi sin comprender las caracteristicas principales del lenguaje, no es posible
dominar este entorno hasta que se comprende totalmente el lenguaje de programa-
cion. Este capitulo trata 10s siguientes temas:
Clases y objetos.
Encapsulacion: p r i v a t e y pub1 ic.
Uso de propiedades.
Constructores.
Objetos y memoria.
Herencia.
Metodos virtuales y polimorfismo.
Conversion de tipos segura (informacion de tip0 en tiempo de ejecucion).
Interfaces.
Trabajo con excepciones.
Referencias de clase.

Caracteristicas centrales del lenguaje


El lenguaje Delphi es una extension OOP del clasico lenguaje Pascal, que
Borland ha liderado durante muchos aiios con sus compiladores Turbo Pascal. La
sintaxis del lenguaje Pascal suele considerarse bastante explicita y mas legible
que, por ejemplo, el lenguaje C. Su extension orientada a objetos sigue el mismo
enfoque, ofreciendo la misma potencia de 10s recientes lenguajes OOP, desde Java
a C#.
Incluso el nucleo del lenguaje esta sujeto a cambios continuos, per0 algunos de
ellos afectaran a las necesidades diarias de programacion. En Delphi 6, por ejem-
plo, Borland aiiadio el soporte para varias caracteristicas mas o menos relaciona-
das con el desarrollo de Kylix, la version para Linux de Delphi:
Una directiva nueva para la compilacion condicional ($IF).
Un conjunto de directivas de sugerencia ( p l a t f o r m , d e p r e c a t e y
l i b r a r y , de las cuales solo se suele usar la primera) y la nueva directiva
$WARN que se utiliza para inhabilitarlas.
Una directiva $MESSAGEpara emitir informacion personalizada entre 10s
mensajes del compilador.
Delphi 7 aiiade tres advertencias del compilador adicionales: tipo inseguro,
codigo inseguro, y conversion insegura. Estas advertencias se emiten en caso de
operaciones que no se puedan utilizar para generar codigo "gestionado" seguro
sobre la plataforma Microsoft .NET.
Otra modification se encuentra relacionada con 10s nombres de unidad, que
ahora pueden formarse con multiples palabras separadas por puntos, como en la
unidad m a r c o . t e s t , almacenada en el archivo m a r c o . t e s t . p a s .
Esta caracteristica ayudara a ofrecer soporte para espacios de nombres y
para referencias de unidad mas flexibles en Delphi para .NET y las futuras ver-
siones del compilador Delphi para Windows, per0 en Delphi 7 tiene un uso limi-
tado.
Clases y objetos
Delphi se basa en 10s conceptos de la orientacion a objeto y, en particular, en
la definition de nuevos tipos de clase.
El uso de OOP esta forzado en parte por el entorno de desarrollo visual, ya que
para cada formulario nuevo definido en tiempo de disefio, Delphi define
automaticam'ente una clase nueva. Ademas, cada componente situado visualmente
en un formulario es un objeto de un tip0 de clase disponible en la biblioteca del
sistema o afiadido a ella.

NOTA: Los tkrminos clase y objeto se utdizan con mucha fiecuencia y a


rnenudo se confunden, ssi que asegurbmonos de estar de acuerdo sobre sus
definiciones. Una clase es un tip0 de dabs definido por el usuario, que
posee un estado (su representacibn o sus datos internos) y algunas opera-
ciones (su comportamiento o sus mbtodos). Un objeto es una instancia de
una clase o una variable del tipo de datos demdo por la clase. Los objetos
son entidades rcales. ~ u a n d ~programa
el se ejeiuta, los objetos ocipan
park de la memoria para su repreaentacilln intern. La relacih en- objeto
y clase es la misma que entre variable y tipo.

Como en la mayor parte del resto de 10s lenguajes orientados a objetos (como
Java y C#),en Delphi una variable de tipo clase no proporciona el almacenamien-
to para el objeto, sino solo un punter0 o referencia al objeto en la memoria. Antes
de utilizar el objeto, se debe reservar memoria para 61 mediante la creacion de una
nueva instancia o asignando una instancia ya existente a la variable:
var
Obj 1, Obj2 : TMyClass;
begin
// a s i g n a r un o b j e t o r e c i e n c r e a d o
Objl : = TMyClass.Create;
// a s i g n a r un o b j e t o e x i s t e n t e
Obi2 : = ExistingObject;

La llamada a create invoca un constructor predefinido disponible para cada


clase, a no ser que la clase lo vuelva a definir (como ya veremos). Para declarar
un nuevo tipo de datos de clase en Delphi, con algunos campos de datos locales y
algunos metodos, se puede utilizar la siguicnte sintaxis:
type
TDate = class
Month, Day, Year: Integer;
procedure SetValue (m, d, y: Integer);
function Leapyear: Boolean;
end;
qOTA: La convenci6n en Delphi es usar la letra T mmo prefijo para el
lombre de cada clase que se escribe y cualquier otro tipo (T significa Tipo).
h~GE
?
,
A
, ,,,'I, ., ,,--.,,, :*, I,,,, -1 ,,,:t,A,, 'Pa,,'I ,,.,, I1dC,L, 1 4 CiVUlV
JUlU U W W I l V G U b l U l l WWir GI W l l l ~ l i W l ,1 ES &UlW
,,
,,

cualquier otra), per0 es tan frecuente que respetarla harfr' que el d g o


resulte intis facil de entender.

Un metodo se define con la palabra clave f u n c t i o n o p r o c e d u r e , segun


si dispone de un valor de retorno o no. Dentro de la definicion de clase, solo se
pueden definir 10s metodos; despues deben definirse en la seccion de implernentacion
de la misma unidad. En este caso, se antepone a1 nombre de cada metodo el
nombre de la clase a la que pertenece, mediante una notacion de puntos:
procedure TDate.SetValue (m, d, y: Integer) ;
begin
M o n t h : = m;
Day : = d;
Y e a r : = y;
end;

function TDate.LeapYear: Boolean;


begin
// l l a m a I s L e a p Y e a r e n S y s U t i l s . p a s
Result := IsLeapYear (Year);
end;

TRUCO:Si se pulsa Controi-Maylis-C mientras que el cursor se m'cw-


tra sobre la definicion de clase, la ~aracteristieaClass Completion
del editor de Delphi generara el esqueleto de la deWci6n d&10s rn6bcbs
declarados en una clase.

Es asi como se puede usar un objeto de la clase definida anteriormente:


var
ADay: TDate;
begin
// c r e a un o b j e t o
A D a y : = TDate.Create;
tr~
// u s a e l o b j e t o
A D a y - S e t V a l u e ( 1 , 1 , 2000);
if A D a y - L e a p Y e a r then
ShowMessage ( ' A d o b i s i e s t o : ' + IntToStr ( A D a y - Y e a r ) ) ;
finally
// d e s t r u y e e l o b j e t o
ADay. Free;
end ;
end :
Fijese en que ADa y .L e a p Y e a r es una expresion similar a ADa y .Year, sin
embargo, la primera es una llamada a una funcion y la segunda es un acceso
direct0 a datos. Opcionalmente se pueden aiiadir parentesis tras la llamada a la
funcion sin parametros. Se pueden encontrar 10s fragmentos de codigo anteriores
en el codigo fuente del ejemplo Date 1, la unica diferencia es que el programa crea
una fecha basada en el aiio que se introduce en un cuadro de edicion.

Mas sobre metodos


Hay mucho mas que comentar sobre 10s metodos. Estas son algunas breves
notas sobre las caracteristicas disponibles en Delphi:
Delphi soporta la sobrecarga de metodos. Esto significa que se pueden
tener dos metodos con el mismo nombre, siempre que se marquen 10s meto-
dos con la palabra clave o v e r l o a d y que las listas de parametros de 10s
dos metodos Sean lo suficientemente diferentes. Mediante la comprobacion
de 10s parametros. el compilador puede determinar que version se desea
Ilamar.
Los metodos pueden tener uno o mas parametros con valores predefinidos.
Si estos parametros se omitiesen en la llamada a1 metodo, se asignaria el
valor predefinido.
Dentro de un metodo se puede usar la palabra clave self para acceder a1
objeto actual. Cuando se hace referencia a 10s datos locales del objeto, la
referencia a s e l f es implicita. Por ejemplo, en el metodo s e t v a l u e de
la clase TDa t e comentada anteriormente, se usa Month para hacer refe-
rencia a un campo del objeto y el compilador transforma Month en
S e l f .Month.
Se pueden definir metodos de clase, indicados por la palabra clave c l a s s .
Un metodo de clase no tiene una instancia de objeto sobre la que actuar, ya
que puede aplicarse a un objeto de la clase o a la clase en su totalidad.
Actualmente Delphi no tiene un mod0 de definir datos de clase, per0 puede
simularse esta prestacion aiiadiendo datos globales en la porcion de
implementacion de la unidad en que se defina a la clase.
De manera predeterminada, 10s metodos usan la convencion de llamada
r e g i s t e r : 10s parametros (simples) y 10s valores de retorno se pasan del
codigo de llamada a la funcion y de vuelta mediante registros de la CPU,
en lugar de en la pila. Este proceso hace que las llamadas a metodo resul-
ten mucho mas rapidas.

Creacion de componentes de forma dinamica


Para hacer hincapie en el hecho de que 10s componentes de Delphi no son muy
distintos de otros objetos (y para demostrar el uso de la palabra clave S e l f ) ,
existe el ejemplo CreateCompos. Este programa tiene un formulario sin compo-
nentes y un manejador para su evento OnMouseDown, escogido porque recibe
como uno de 10s parametros la posicion del clic de raton (no como el evento
Onclick).
Esta informacion es necesaria para crear un componente boton en esa posi-
cion. Veamos el codigo de este metodo:
procedure TForml.FormMouseDown (Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Btn: TButton;
begin
Btn : = TButton-Create (Self);
Btn-Parent : = Self;
Btn-Left : = X;
B t n - T o p : = Y;
Btn-Width := Btn.Width + 50;
Btn-Caption : = Format ( ' B o t d n e n %d, % d l , [ X , Y]);
end ;

Con este codigo, se crean botones en las posiciones en las que se haga clic con
el raton, como muestra la figura 2.1. En el codigo anterior, fijese en concreto en el
uso de la palabra clave S e 1f , tanto como parametro del metodo c r e a t e (para
especificar el dueiio del componente), como valor de la propiedad P a r e n t .

Figura 2.1. El resultado del ejernplo CreateCornps, que crea componentes boton en
tiernpo de ejecucion.
Cuando se escribe un procedimiento como el codigo que acabamos de ver,
podriamos sentirnos tentados a utilizar la variable F o r m l en lugar de S e l f . En
este ejemplo concreto, el cambio no tendria ninguna diferencia practica, per0 si
existen diversas instancias de un formulario, usar F o r m l seria un error. De
hecho, si la variable F o r m l se refiere a1 primer formulario de ese tipo que se ha
creado, a1 pinchar sobre otro formulario del mismo tipo, el nuevo boton siempre
aparecera en el primer formulario. Sus O w n e r y P a r e n t seran el F o r m l y no
el formulario que ha pinchado el usuario. Por lo general, no conviene referirse a
una instancia concreta de una clase cuando se necesita el objeto actual.

Encapsulado
Una clase puede tener cualquier cantidad de datos y cualquier numero de meto-
dos. Sin embargo, para conseguir una buena tecnica orientada a objetos, 10s datos
deberian estar ocultos o encapsulados dentro de la clase que 10s usa. Cuando se
accede a una fecha, por ejemplo, no tiene sentido cambiar solo el valor del dia
directamente. De hecho, si se cambia el valor del dia podria resultar una fecha no
valida, como el 30 de febrero, por ejemplo. Si se usan metodos para acceder a la
representacion interna de un objeto, se limita el riesgo de generar situaciones
erroneas, puesto que 10s metodos pueden verificar si la fecha es valida y negarse
a modificar el nuevo valor si no lo es. El encapsulado es importante porque permi-
te que la persona que escribe las clases modifique la representacion interna en una
version futura.
El concepto de encapsulado se describe normalmente como una "caja negra",
en la que no se conoce el interior: simplemente se sabe como interactuar con ella
o como usarla, sin tener en cuenta su estructura. La parte "modo de empleo",
denominada interfaz de clase, permite que otras partes de un programa tengan
acceso y utilicen 10s objetos de dicha clase. Sin embargo, cuando se emplean 10s
objetos, la mayor parte de su codigo esta oculto. Rara vez se conocen 10s datos
internos que contiene el objeto y normalmente no hay manera de acceder directa-
mente a 10s datos. Por supuesto, se supone que utilizamos 10s metodos para acce-
der a 10s datos, que estan protegidos contra accesos no autorizados. Esta es la
tecnica orientada a objetos del concepto de programacion clasico conocido como
ocultacion de informacion. Sin embargo, en Delphi existe un nivel adicional de
ocultacion, mediante propiedades.
Delphi implementa este encapsulado basado en clases per0 todavia soporta el
encapsulado clasico basado en modulos, que usa la estructura de unidades. Todo
identificador que se declare en la seccion de interfaz de una unidad resulta visible
a otras unidades del programa, siempre que se utilice una sentencia u s e s que se
refiere a la unidad que define el identificador. Por otro lado, 10s identificadores
declarados en la seccion de implernentacion de la unidad seran locales a esa uni-
dad.
Privado, protegido y public0
En el caso de un encapsulado basado en clases, el lenguaje Pascal orientado a
objetos tiene tres especificadores de acccso: p r i v a t e , p r o t e c t e d y p u b l i c .
Un cuarto; p u b l i s h e d , controla la RTTI (informacion de tipo en tiempo de
e.jecucion) y la informacion en tiempo de diseiio, proporcionando la misma dispo-
nibilidad de cara a la programacion que si fuera p u b l i c . A continuacion se
enumeran 10s tres especificadores de acceso clisi'cos:
La directiva private: Denota campos y metodos de clase no accesiblcs
fuera de la unidad (el archivo dc codigo fuente) que declara la clasc.
La directiva protected: Se utiliza para indicar metodos y campos con
visibilidad limitada. Solo la clase actual y sus clases heredadas pueden
acceder a 10s elementos protegidos. Para ser mas precisos, solo la clase.
las subclases y cualquier codigo presente en la misma unidad que la clase
pueden acceder a 10s miembros protegidos.
La directiva public: Denota campos y metodos a 10s que se puede acceder
libremente desdc cualquier otra parte de un programa asi como en la uni-
dad en la que se definen.
Por lo general, 10s campos dc una clase deberian ser privados. Los metodos
son normalmente publicos. Aunque esto no siempre es asi, 10s metodos pueden ser
privados o protegidos si son necesarios internamente solo para realizar parte de
un calculo. Los campos pueden ser protegidos para que se puedan manipular en
subclases, aunque no se considera una buena practica de la orientation a objetos.

ADVERTENCIA: Los especificadores de acceso solo restringen el acceso


por parte del codigo que esti fuera de la unidad a ciertos miembros de
clases declaradas en la parte de interfaz de la misma. Esto significa que si
dos clases estAn en la misma unidad, sus campos privados no e s t h protegi-
dos.

Como e.jemplo, consideremos esta nueva version de la clase TDate:


type
TDate = class
private
Month, Day, Year: Integer;
public
p r o c e d u r e SetValue (y, m, d: Integer) ; overload;
p r o c e d u r e SetValue (NewDate: TDateTime); overload;
f u n c t i o n Leapyear: Boolean;
f u n c t i o n GetText: string;
p r o c e d u r e Increase;
end;
Se podria pensar en aiiadir otras funciones, como G e t D a y , G e t M o n t h y
G e t Y ear, que simplemente devuelvan 10s datos privados correspondientes, per0
no siempre se necesitan funciones similares directas de acceso a datos. Si se
conceden funciones de acceso para cada uno de 10s campos, se podria reducir el
encapsulado y dificultar la modificacion de la implementacion interna de una
clase. Las funciones de acceso deberian de definirse unicamente si forman parte
de la interfaz logica de la clase que esta implementando.
Otro nuevo metodo es el procedimiento Increase, que suma un dia a la
fecha. Este calculo no es nada sencillo, porque hay que considerar las distintas
longitudes de 10s meses asi como 10s 6 0 s bisiestos o no bisiestos. Lo que hare-
mos, para que resulte mas sencillo escribir el codigo, sera cambiar la
implementacion interna de la clase a1 tipo T D a t e T i m e de Delphi para la
implernentacion interna. La definicion de clase cambiara a lo siguiente (el codigo
completo aparece en el proximo ejemplo, D a t e p r o p ) :
type
TDate = class
private
fDate: TDateTime;
public
procedure SetValue (y, m, d: Integer); overload;
procedure SetValue (NewDate: TDateTime); overload;
function Leapyear: Boolean;
function GetText: string;
procedure Increase;
end ;

Fijese en que debido a que la unica modificacion se realiza en la seccion priva-


daj no habra que modificar ninguno de 10s programas existents que usen la clase.
Esa es la ventaja del encapsulado.

NOTA: El tipo TDateTime es en realidad un numero de coma flotante.


La parte entera del numero indica la fecha desde el 3O/l2/1899, la misma
fecha basica usada por las aplicaciones OLE Automation y Win32. (Para
- .
exDresar 10s afios anteriores se usan valores ne~ativos.)La t>arte decimal
indica la hora en forma de fiacci6n. Por ejemplo, un valor de 3,75 correspon-
de a1 dos de enero de 1900, a las 6:00 de la tarde (tres cuartos de un dia).
_ -- _L_ - -- ____---
- restar el --- ue ----
n---
rara sumar o resrar r_
---L--
recnas, se pueue sumar
J-
o ---A_- -1
numero
--1_- 2-
mas, que
_I--

resulta d s sencillo que aiiadir &as con una representacih de dia/mes/aito.

Encapsulado con propiedades


Las propiedades son un mecanismo de orientacion a objetos muy sensato o una
aplicacion practica muy bien pensada de la idea de encapsulado. Basicamente, se
tiene un nombre que oculta por completo 10s datos de implernentacion. Esto per-
mite modificar la clase ampliamente sin que afecte a1 codigo que la utiliza. Una
buena definicion de propiedades es la de campos virtuales. Desde la perspectiva
del usuario de la clase que las define, las propiedades poseen m a apariencia
exactamente igual a la de 10s campos, ya que, por lo general se puede leer o
escribir su valor. Por ejemplo, se puede leer el valor de la propiedad C a p t i o n de
un boton y asignarla a la propiedad T e x t de un cuadro de edicion con el siguiente
codigo:

Parece que estuvidramos leyendo y escribiendo campos. Sin embargo, las pro-
piedades pueden proyectarse directamente a datos, asi como a metodos de acceso,
para leer y escribir el valor. Cuando las propiedades se proyectan a metodos, 10s
datos a 10s que acceden pueden formar parte del objeto o estar fuera de el y
pueden producir efectos secundarios, como volver a pintar un control tras haber
cambiado sus valores. Tecnicamente, una propiedad es un identificador que esta
proyectado a datos o metodos que usan una clausula r e a d y otra w r i t e . Por
ejemplo, aqui tenemos la definicion de una propiedad M o n t h para una clase de
fecha:
property Month: Integer read FMonth write SetMonth;

Para acceder a1 valor de la propiedad Month, el programa lee el valor del


campo privado FMonth, mientras que para cambiar el valor de la propiedad
llama a1 metodo S e t M o n t h (que ha de estar definido dentro de la clase, por
supuesto). Son posibles diversas combinaciones (por ejemplo, un metodo para
leer el valor o cambiar directamente un campo en la directiva w r i t e ) , per0 el
uso de un metodo para cambiar el valor de una propiedad es muy comun. Estas
son dos definiciones alternativas para la propiedad, proyectada sobre dos meto-
dos de acceso o directamente sobre 10s datos en ambas direcciones:
property Month: I n t e g e r read GetMonth write SetMonth;
property Month: I n t e g e r read m o n t h write m o n t h ;

Normalmente, 10s datos reales y 10s metodos de acceso son privados (o prote-
gidos) mientras que la propiedad es publica. Esto significa que hay que usar la
propiedad para tener acceso a aquellos metodos o datos, una tecnica que ofrece
tanto la version simplificada como la extendida del encapsulado. Se trata de un
encapsulado ampliado, porque no solo se puede cambiar la representacion de 10s
datos y sus funciones de acceso, sino tambien aiiadir o eliminar funciones de
acceso sin cambiar el codigo de llamada en absoluto. Un usuario solo necesita
volver a compilar el programa usando la propiedad.

TRUCO: Cuando se definen propiedades, se puede aprovechar la funcion


Class C o m p l e t i o n del editor de Delphi. que se activa con la combina- .
popiedad y el punto y coma, a1 pulsar Control-Mayus-C, Delphi propor-
cionara una definicion completa y el esqueleto del mttodo de escritura. Si
se escribe G e t delante del nombre del identificador despues de la pala-
bta clave read tambitn se conseguira un m e t o d ~de lectura sin apenas
escribir.

Propiedades de la clase TDate


Como ejemplo, hemos aiiadido propiedades para acceder al aiio, el mes y el dia
de un objeto de la clase T D a t e . Dichas propiedades no eskin proyectadas a
campos especificos, sino al campo unico fD a t e que almacena toda la informa-
cion de la fecha. Esta es la nueva definicion de la clase, con mejores metodos de
lectura y escritura:
type
TDate = c l a s s
public
p r o p e r t y Year: I n t e g e r r e a d GetYear w r i t e SetYear:
p r o p e r t y Month: I n t e g e r r e a d GetMonth w r i t e SetMonth;
p r o p e r t y Day: I n t e g e r r e a d GetDay w r i t e SetDay;

Cada uno de estos metodos se implementa facilmente utilizando funciones


disponibles en la nueva unidad DateUtils. Veamos el codigo de dos de ellos (10s
otros son muy similares):
f u n c t i o n TDate.GetYear: I n t e g e r ;
begin
R e s u l t := YearOf ( f D a t e ) ;
end;

p r o c e d u r e T D a t e . S e t Y e a r ( c o n s t Value: I n t e g e r ) ;
begin
f D a t e : = Recodeyear ( f D a t e , Value) ;
end ;

El codigo de esta clase esta disponible en el ejemplo Dateprop. El programa


utiliza una unidad secundaria para que la definicion de la clase T D a t e active el
encapsulado y Cree un objeto de fecha simple guardado en una variable de formu-
lario y almacenado en memoria durante toda la ejecucion del programa. Si se usa
una tecnica estandar, el objeto se crea en el controlador de eventos oncreate
del formulario y se destruye en el controlador de eventos O n D e s t r o y del formu-
lario.
El formulario del programa (vease figura 2.2) tiene tres cuadros de edicion y
botones para copiar 10s valores de estos cuadros de edicion en las propiedades del
objeto de fecha:
Figura 2.2. El formulario del ejemplo Dateprop.

ADVERTENCIA: Cuando se escribefi 10s v a l m ; d $pn>&'ama utiliza el


metodo setvalue en lugar & &£inkcada una de las pfopiedades. De
hecho, asignar e1 mes y el &a por separado puede cam& pLoblemati cuando
el mes no es valid0 para el dia en uso. Pongam~spor &&plo que la fecha
actual es el 3 1 de enero jr qgxemos twignark el 20 de fe'bkro. Si asignamos
primero el mes, esa pa& darsr error, puesto que el 3 1 d;e febrero no existe.
Si asignamos primero el &a, el problem slirgilpr al hacer 1a asignstci6n
inversa. Debido a las rm&m de validmikt para fixkttd, ss enejor.wigm.r
todo a1mismo ~~.

Caracteristicas avanzadas de las propiedades


Las propiedades tienen varias caracteristicas avanzadas. Este es un breve re-
sumen de ellas:
La directiva write de una propiedad se puede omitir, convirtiendola asi
en una propiedad de solo lectura. El compilador dara error si intentamos
cambiarla. Tambien se puede omitir la directiva read y definir una pro-
piedad de solo escritura, per0 ese enfoque no tiene mucho sentido y no se
suele emplear.
El IDE de Delphi da un trato especial a las propiedades en tiempo de
diseiio, que se declaran con el especificador de acceso pub1 ished y que
por lo general aparecen en el Object Inspector para el componente selec-
cionado.
Las otras propiedades, normalmente denominadas propiedades solo de tiem-
po de ejecucion, son las declaradas con el especificador de acceso public.
Dichas propiedades pueden usarse en el codigo del programa.
Se pueden definir propiedades basadas en matrices, que usan la notacion
tipica con corchetes para acceder a un elemento de la lista. Las propieda-
des basadas en la lista de cadenas, como Lines en un cuadro de lista, son
un ejemplo tipico de este grupo.
Las propiedades tienen directivas especiales, como stored y default,
que controlan el sistema de streaming de componentes.
incluso se pueden usar propiedades en expresiones, pero no siempre se
puede pasar una propiedad como parimetro a un procedimiento o metodo.
I
Esto se debe a que una propiedad no es una posicion de memoria, por lo que
no se puede utilizar como parimetro var u o u t : no se puede pasar por .'
referencia.

Encapsulado y forrnularios
Una de las ideas clave del encapsulado es reducir el numero de variables globales
cmpleadas por el programa. Se puede acceder a una variable global desde todas
las partes de un programa. Por esa razon, un cambio en la variable global afecta
al programa entero. Por otra parte, cuando se cambia la representacion de un
campo de clase, solo hay que cambiar el codigo de algunos metodos de dicha clase
y nada mas. Por lo tanto, podemos decir que la ocultacion de informacion se
refiere a 10s cambios de encapsulado.
Cuando tengamos un programa con diversos forrnularios, podemos hacer que
algunos datos estkn disponibles para todos 10s formularios, si 10s declaramos
como variable global en la parte de interfaz de la unidad de uno de 10s formula-
rios:
var
Form1: TForml;
nClicks: Integer;

Esto funciona pero tiene dos inconvenientes. En primer lugar, 10s datos no
estan conectados a un caso especifico del formulario, sino a1 programa entero. Si
creamos dos formularios del mismo tipo, compartiran 10s datos. Si queremos que
cada formulario del mismo tip0 tenga su propia copia de 10s datos, la unica
solucion es aiiadirlos a la clase de formulario:
type
T F o r m l = class ( T F o r m )
public
nClicks: Integer;
end;

Afiadir propiedades a formularios


La clase anterior utiliza datos publicos, asi que por el bien del encapsulado, se
la deberia modificar para que use datos privados y funciones de acceso a 10s
datos. Una solucion aun mejor es aiiadir una propiedad a1 formulario Cuando sea
necesario que alguna informacion del formulario este disponible en otros formu-
larios, se deberia utilizar una propiedad. Simplemente hay que cambiar la decla-
ration de campo del formulario, como se indica en el listado anterior, aiiadir la
palabra clave p r o p e r t y delante de ella y a continuacion, pulsar Control-Mayus-
C para activar la funcion C o d e Comple t ion.Delphi generara automaticamente
todo el codigo adicional necesario.
El codigo completo para esta clase de formulario esta disponible en el ejemplo
FormProp y la figura 2.3 muestra el resultado. El programa puede crear multiples
instancias del formulario (es decir, multiples objetos basados en la misma clase
de formulario), cada una con su propia cuenta de clic.

Figura 2.3. Dos forrnularios del ejemplo ForrnProp en tiernpo de ejecucion.

NOTA:~ i ~ ceg
s equo el a & d ~ p & i ~ d d a dpun f o n d ario, no 3. Mia& -~4'

a'la M a de ~ ~ del f a h~d ! del Qbject


e B p e cs t o r .

Conviene usar las propiedades tambien en las clases de formulario para


encapsular el acceso a 10s componentes de un formulario. Por ejemplo, si hay un
formulario principal con una barra de estado en la que se muestre cierta informa-
cion (y con la propiedad s i m p l e p a n e l definida como T r u e ) y hay que modi-
ficar el texto de un formulario secundario, podriamos sentir la tentacion de escribir:
Form1 .StatusBarl .SimpleText : = ' n u e v o texto' ;

Esta es una costumbre muy comun en Delphi, pero no es una buena costumbre,
porque no ofrece encapsulado de la estructura de formulario ni de sus componen-
tes. Si hay un codigo similar en una aplicacion y mas tarde se decide modificar la
interfaz de usuario del formulario (y reemplazar S t a t u s B a r por otro control o
activar diversos paneles), habra que adaptar el codigo en muchos sitios. La alter-
nativa es utilizar un metodo o, incluso mejor, una propiedad para ocultar un
control concreto. Esta propiedad puede definirse como:
property StatusText: string read GetText write SetText;

siendo G e t T e x t y S e t T e x t metodos que leen de y escriben en la propiedad


S i m p l e T e x t de la barra de estado (o la etiqueta de uno de sus paneles). En 10s
demas formularios del programa simplemente se puede hacer referencia a la pro-
piedad S t a t u s T e x t del formulario y si la interfaz de usuario cambia, solo se
veran afectados 10s metodos de lectura y escritura.

Constructores
Para asignar la memoria a1 objeto, podemos llamar a1 metodo C r e a t e . Este
es un constructor, un metodo especial que podemos aplicar a una clase para
asignar memoria a una instancia de dicha clase. El constructor devuelve la instan-
cia, que puede asignarse a una variable para almacenar el objeto y usarlo mas
tarde. El constructor por defecto TObj e c t .C r e a t e inicializa todos 10s datos
del nuevo caso a cero. Para que 10s datos de dicho caso comiencen con un valor
diferente a cero, hay que escribir un constructor personalizado.
El nuevo constructor se puede denominar c r e a t e o tener otro nombre, y hay
que usar la palabra clave c o n s t r u c t o r delante de el. Fijese en que no es
necesario llamar a TOb j e c t .C r e a t e : es Delphi el que asigna memoria para el
nuevo objeto, no el constructor de clase. Todo lo que hay que hacer es iniciar la
base de clase.
Aunque se puede usar cualquier nombre para el constructor, deberia ajustarse
a1 nombre estandar, c r e a t e . Si se usa otro nombre distinto de c r e a t e , el
constructor C r e a t e de la clase basica TOb j e c t aun estara disponible, per0 un
programador que llame a1 constructor por defecto podria pasar por alto el codigo
de inicializacion ofrecido porque no reconoce el nombre.
A1 definir un constructor c r e a t e con algunos p a r h e t r o s , reemplazamos la
definicion predeterminada por una nueva y hacemos que su uso resulte obligato-
rio. Por ejemplo, despues de haber definido:
type
TDate = class
public
constructor Create (y, m, d: I n t e g e r ) ;

solo podremos llamar a este constructor y no a1 c r e a t e estandar:


var
ADay: TDate;
begin
// Error, no c o n p i l a :
A D a y : = TDate.Create;
// OK:
A D a y : = TDate.Create (1, 1, 2000);

Las normas de escritura de constructores para componentes personalizados


son diferentes. L a razon es que en este caso hay que sobrescribir un constructor
virtual. La sobrecarga resulta particularmente importante para 10s constructores
ya que se pueden aiiadir multiples constructores a una clase y llamarlos a todos
ellos create.Este enfoque hace que 10s constructores resulten faciles de recor-
dar y sigan una via estandar proporcionada por otros lenguajes de orientacion a
objetos en 10s que 10s constructores deben de tener todos el mismo nombre. Como
ejemplo, podemos aiiadir a la clase dos constructores create distintos; uno sin
parametros, que oculta el constructor predeterminado; y otro con valores de
inicializacion. El constructor sin parametros usa el valor predefinido de la fecha
de hoy (como se puede ver el codigo completo del ejemplo Dataview):
'=we
TDate = c l a s s
public
c o n s t r u c t o r Create; overload;
c o n s t r u c t o r C r e a t e ( y , m, d : I n t e g e r ) ; o v e r l o a d ;

Destructores y el metodo Free


Del mismo mod0 que una clase puede tener un constructor personalizado,
tambien puede tener un destructor personalizado, un mCtodo declarado con la
palabra clave destructor y llamado Destroy.A1 igual que una llamada a1
constructor asigna memoria para el objeto, un destructor libera la memoria. Los
destructores son necesarios solo para objetos que adquieren recursos externos en
sus constructores o durante su vida util. Se puede escribir codigo personalizado
para un destructor, en general sobrescribiendo el destructor Destroy predeter-
minado, para permitir que un objeto ejecute algo de codigo de limpieza antes de su
destruccion. Destroy es un destructor virtual de la clase TObject. Jamas
deberia definirse un destructor distinto, ya que 10s objetos suelen destruirse me-
diante una llamada al metodo Free y este metodo llama al destructor virtual
Destroy de la clase especifica. Free es un metodo de la clase TOb ject,
heredado por todas las demas clases. El metodo Free verifica basicamente si el
objeto actual (Self) no es nil antes de llamar a1 destructor virtual Destroy.
Free no cambia el objeto a nil automaticamente, sino que es algo que se debe-
ria hacer personalmente. La razon es que el objeto no sabe que variables pueden
referirse a el, por lo que no hay mod0 de cambiarlas todas a nil.
Delphi 5 present6 un procedimiento FreeAndNil que se puede usar para
liberar un objeto y dar el valor nil a su referencia a1 mismo tiempo. Se puede
llamar a FreeAndNil (Obj 1 ) en lugar de escribir lo siguiente:
0bjl.Free;
Objl := n i l ;

El modelo de referencia a objetos de Delphi


En algunos lenguajes orientados a objetos, a1 declarar una variable de un tipo
de clase, se crea una instancia de dicha clase. Delphi, en cambio, se basa en un
modelo de referencia a objetos. La idea es que una variable de un tipo de clase,
como la variable TheDay en el ejemplo anterior ViewDate, no mantiene el
valor del objeto. En lugar de eso, contiene una referencia, o un puntero, para
indicar la posicion de mernoria en la que se ha almacenado el objeto. Se puede ver
la estructura en la figura 2.4.

TheDay objeto TDay

Figura 2.4. Una representacion de la estructura de un objeto en memoria, con una


variable que se refiere a el.

El unico problema de esta tecnica es que cuando se declara una variable, no se


crea un objeto en memoria (lo que es inconsistente con el resto de variables,
confundiendo a 10s nuevos usuarios de Delphi); solo se reserva la posicion de
memoria para una referencia al objeto. Las instancias de objetos h a b r h de crear-
se manualmente, a1 menos para 10s objetos de las clases que se definan. Las
instancias de 10s componentes que se coloquen en un formulario son creadas
automaticamente por la biblioteca de Delphi.
Hemos visto como crear una instancia de un objeto, aplicando un constructor a
su clase. Cuando hayamos creado un objeto y hayamos terminado de usarlo, es
necesario eliminarlo (para evitar llenar la rnemoria que ya no necesita, lo cual
origina lo que se conoce como "goteo de memoria"). Esto se puede hacer mediante
una llamada a1 metodo Free.Siernpre que se creen objetos cuando Sean necesa-
rios y se liberen cuando ya no lo Sean, el modelo de referencia a objetos funciona-
ra perfectamente. El modelo de referencia a objetos tiene una gran influencia en la
asignacion de objetos y en la administracion de memoria.

Asignacion de objetos
Podemos preguntarnos que ocurriria si una variable que mantiene un objeto
solo contiene una referencia a1 objeto en memoria y se copia el valor de dicha
variable. Supongamos que escribimos el metodo BtnToda yCli ck del ejemplo
ViewDa te del siguiente modo:
procedure TDateForm.BtnTodayClick(Sender: TObject);
var
NewDay: TDate;
begin
NewDay : = TDate-Create;
TheDay : = NewDay;
LabelDate.Caption : = TheDay-GetText;
end;
Este codigo copia la direccion de memoria del objeto NewDay a la variable
TheDay (corno muestra la figura 2.5); no copia 10s datos de un objeto en el otro.
En esta circunstancia concreta, esta tecnica no es muy adecuada, puesto que cada
vez que se pulsa el boton, se asigna memoria para un nuevo objeto y nunca se
libera la memoria del objeto a la que anteriormente apuntaba la variable TheDay.
NewDay objeto TDate

TheDay
Figura 2.5. Una representacion de la operacion de asignacion de una referencia de
objeto a otro objeto. Esto es distinto de copiar el contenido real de un objeto en otro.

Esta cuestion especifica puede resolverse liberando el objeto antiguo, como en


el siguiente codigo (que tambien esta simplificado, sin el uso de una variable
explicita para el objeto de nueva creacion):
procedure TDateForm.BtnTodayClick(Sender: TObject);
begin
TheDay-Free;
TheDay : = TDate.Create;

Lo importante es que cuando se asigna un objeto a otro objeto, Delphi copia la


referencia a1 objeto en memoria en la nueva referencia a objeto. No deberia consi-
derarse esto como algo negativo: en muchos casos, ser capaz de definir una varia-
ble que se refiera a un objeto ya existente puede ser una ventaja. Por ejemplo, se
puede almacenar el objeto devuelto a1 acceder a una propiedad y usarla en las
sentencias siguientes, como se indica en este fragment0 de codigo:
var
ADay: TDate;
begin
ADay: User1nformation.GetBirthDate;
/ / usar u n A D a y

Lo mismo ocurre si se pasa un objeto como parametro a una funcion: no se


crea un nuevo objeto, sino que se hace referencia a1 mismo en dos lugares diferen-
tes del codigo. Por ejemplo, a1 escribir este procedimiento y llamarlo como se
indica a continuacion, se modificara la propiedad C a p t i o n del objeto B u t t o n l ,
no de una copia de sus datos en memoria (algo que seria completamente inutil):
procedure Captionplus (Button: TButton);
begin
Button.Caption : = Button.Caption + ' + I ;

end;

/ / llamar.. .
CaptionPlus (Buttonl)

Esto significa que el objeto se pasa por referencia sin el uso de la palabra clave
var y sin ninguna otra indicacion obvia de la semantica de paso por referencia,
lo que confunde a 10s novatos. Cabria preguntarse lo que sucede si realmente se
quieren cambiar 10s datos de un objeto existente, para que se corresponda con 10s
datos de otro objeto. En este caso, hay que copiar cada campo del objeto, lo cual
es posible solo si son todos publicos, u ofrecer un metodo especifico para copiar
10s datos internos. Algunas clases de la VCL tienen un metodo Assign, que
realiza esta operacion de copia. Para ser mas precisos, la mayoria de las clases de
la VCL que heredan de TPers is tent, per0 no de TComponent, tienen el
metodo Ass ign. Otras clases derivadas de TComponent tienen este metodo
per0 lanzaran una excepcion cuando se llama.
En el ejemplo Da t eCopy, se ha aiiadido un metodo Assign a la clase TDa te
y se le ha llamado desde el boton Today, con el siguiente codigo:
p r o c e d u r e TDate .Assign (Source: TDate) ;
begin
fDate : = Source.fDate;
end ;
p r o c e d u r e TDateForm.BtnTodayClick(Sender: TObject);
var
NewDay: TDate;
begin
NewDay : = T D a t e - C r e a t e ;
TheDay .Assign (NewDay);
LabelDate.Caption : = TheDay.GetText;
NewDay.Free;
end ;

Objetos y memoria
La administracion de memoria en Delphi esta sujeta a tres normas, a1 menos si
se permite que el sistema trabaje en armonia sin violaciones de acceso y sin
consumir memoria innecesaria:
Todo objeto ha de ser creado antes de que pueda usarse
Todo objeto ha de ser destruido tras haberlo utilizado.
Todo objeto ha de ser destruido solo una vez.
El tener que realizar estas operaciones en el codigo o dejar que Delphi controle
la administracion de memoria, dependera del modelo que escojamos entre las
distintas tecnicas que ofrece Delphi.
Delphi soporta tres tipos de administration de memoria para elementos dinamicos:
Cada vez que creamos un objeto explicitamente en el codigo de una aplica-
cion, tambien debemos liberarlo (con la sola excepcion de un puiiado de
objetos del sistema y de objetos que se utilizan a traves de referencias de
interfaz). Si no se hace asi, la memoria utilizada por dicho objeto no se
libera hasta que finaliza el programa.
Cuando creamos un componente, podemos especificar un componente pro-
pietario, pasando el propietario a1 constructor del componente. El compo-
nente propietario (normalmente un formulario) se transforma en el
responsable de destruir todos 10s objetos que posee. Asi, si creamos un
componente y le damos un propietario, no es necesario que nos acordemos
dc destruirlo. Este es el comportamiento estandar de 10s componentes que
se crean en tiempo de diseiio a1 colocarlos sobre un formulario o modulo de
datos. Sin embargo, es imperativo que se escoja un propietario cuya des-
truccion quede garantizada; por ejemplo, 10s formularios suelen pertenecer
a 10s objetos globales A p p l i c a t i o n , que son destruidos por la bibliote-
ca cuando acaba el programa.
Cuando la RTL de Delphi reserva memoria para las cadenas y matrices
dinamicas, libera automaticamente la memoria cuando la referencia resul-
ta inalcanzable. No es necesario liberar una cadena: cuando resulta inacce-
sible, se libera su memoria.
Destruir objetos una sola vez
Otro problema es que si se llama a1 metodo F r e e (o a1 destructor D e s t r o y )
de un objeto dos veces, dara error. Sin embargo, si recordamos cambiar el objeto
a n i l , se puede llamar a F r e e dos veces sin ningun problema.
b -
NOTA: Podriamos preguntarnos por que se puede llamar a Free con total
seguridad si la referencia del objeto es n i l , pero no se pue& llamar a
D e s t r o y . La razon es que F r e e es un mbodo conocido en una posicibn
de memoria dada, rnientras que la funcion virtual Destroy se defrne en
tiempo de ejecucion a1 ver el tip0 de objeto, una operacibn muy peligrosa si
el objeto ya no existe.

Para resumir todo esto, hemos elaborado una lista de directrices:


Llamar siempre a F r e e para destruir objetos, en lugar de llamar a1 des-
tructor D e s t r o y .
Utilizar F r e e A n d N i 1 o cambiar las referencias de objeto a n i 1 despues
de haber llamado a F r e e , a no ser que la referencia quede inmediatamente
despues fuera de alcance.
En general, tambien se puede verificar si un objeto es nil usando la funcion
signed.Por lo que las dos sentencias siguientes son equivalentes, a1 menos
la mayor parte de 10s casos:
i f Assigned (ADate) then .. .
i f ADate <> nil then . . .

Fijese en que estas sentencias solo verifican si el puntero no es nil, no verifi-


can si se trata de un puntero valido. Si se escribe el siguiente codigo, se realizara
la verificacion, per0 se obtendra un error en la linea de llamada a1 metodo del
objeto:
ToDestroy.Free;
i f ToDestroy <> n i l then
ToDestroy.DoSomething;

Es importante darse cuenta de que llamar a F r e e no cambia el objeto a nil.

Herencia de 10s tipos existentes


Normalmente es necesario usar una version ligeramente diferente de una clase
existente. Por ejemplo, se podria necesitar aiiadir un metodo nuevo o modificar
ligeramente uno dado. Si se copia y se pega la clase original y se modifica (una
alternativa terrible, a no ser que exista una razon especifica para hacer esto), se
duplicara el codigo, 10s errores y 10s dolores de cabeza. En lugar de esto, se
deberia utilizar una caracteristica clave de la programacion orientada a objetos:
la herencia. Para heredar de una clase existente en Delphi, solo hay que indicar
esa clase a1 principio de la declaracion de la nueva clase. Por ejemplo, esto se
hace cada vez que se crea un formulario:
tYPe
TForml = c l a s s (TForm)
end;

Esta definicion indica que la clase T F o r m l hereda todos 10s metodos, cam-
pos, propiedades y eventos de la clase T F o r m . Se puede llamar a cualquier meto-
do public0 de la clase T F o r m para un objeto del tipo T F o r m l . T F o r m , a su vez,
hereda algunos de sus metodos de otra clase, y asi sucesivamente hasta la clase
basica TOb j e c t . Como ejemplo de herencia, podemos cambiar una nueva clase
a partir de T D a t e y modificar su funcion G e t T e x t . Se puede encontrar este
codigo en la unidad Date del ejemplo NewDate:
tYPe
TNewDate = c l a s s (TDate)
pub1i c
f u n c t i o n GetText: string;
end :
Para implementar la nueva version de la funcion GetText, utilizamos la
funcion Format DateTime, que emplea (entre otras caracteristicas) 10s nom-
bres de mes predefinidos disponibles en Windows, estos nombres dependen de la
configuracion regional del usuario y de la configuracion del lenguaje. Muchas de
estas configuraciones las copia Delphi en constantes definidas en la biblioteca,
como LongMonthNames, ShortMonthNames y muchas otras que puede
encontrar bajo el tema "Currencyand datehime formatting variables" (Variables
para formatear la moneda y la fecha/hora) en el archivo de ayuda de Delphi.
Veamos el metodo GetText,en el que 'dddddd' corresponde a1 formato de fecha
largo:
function TNewDate.GetText: string;
begin
GetText : = FormatDateTime ( ' d d d d d d ' , f D a t e ) ;
end :

TRUCO: Cuando usamos la information regional, el programa NewDate


se adapta automaticamente%las diferentes codi@r&i~nes & u~aricide
Windows. Si ejecuta este mismo programa en un ordenador con una confi-
guracion regi~nalen ,espaiiol, 10s nombres de 10s m e s a agsuecerh
automaticamenteen eqpat(o1

Cuando tengamos la definicion de la nueva clase, hay que usar este nuevo tipo
de datos en el codigo del formulario del ejemplo NewDate. Simplemente hay que
definir el objeto TheDay de tipo TNewDate y crear un objeto de la nueva clase
mediante en el metodo Formcreate.No es necesario modificar el codigo con
llamadas de metodo, ya que 10s metodos heredados seguiran funcionando del
mismo modo; sin embargo, se modifica su efecto, como muestra la nueva salida
(vease figura 2.6)

jueves, 25 de dlclembre de 2003

Figura 2.6. El resultado del programa NewDate, con el nombre del mes y del dia de
acuerdo con la configuracion regional de Windows.

CCI;I
Campos protegidos y encapsulado
El codigo del mktodo GetText de la clase TNewDate compila solo si esta
escrito en la misma unidad que la clase TDate. De hecho, accede a1 campo
privado f Date de la clase ascendiente. Si queremos colocar una clase descen-
diente en una unidad nueva, debemos declarar el campo fDate como protegido o
aiiadir un metodo de acceso protegido en la clase ascendiente para leer el valor del
campo privado.
Muchos desarrolladores creen que la primera solucion es siempre la mejor, ya
que declarar la mayor parte de 10s campos como protegidos permitira que una
clase resulte mas extensible y hara mas sencillo escribir clases heredadas. Sin
embargo, este enfoque se enfrenta con la idea del encapsulado. En una gran jerar-
quia de clases, modificar la definicion de algunos campos protegidos de las clases
base resulta tan dificil como modificar algunas estructuras globales de datos. Si
diez clases derivadas acceden a estos datos, modificar su definicion significa
modificar potencialmente el codigo de cada una de estas 10 clases.
La flexibilidad, extension y encapsulado normalmente son objetivos conflicti-
vos, por lo que deberiamos favorecer el encapsulado, sin sacrificar la flexibili-
dad. Normalmente eso se puede conseguir usando un metodo virtual. Si se decide
no utilizar el encapsulado para que la codification de las subclases sea mas rapi-
da, el disefio podria no ajustarse a 10s principios de la orientacion a objetos.

Acceder a datos protegidos de ottas clases


Hemos vim que en Delphi, 10s datos privados y p r i P t e ~ 1de3 ~una elase
son accesibles para cualquier funcib o rnM~ que aparezca ed fa rnismti
unidad qae l a dase. Por ejemplo. ~ ~ ~ i d e r e esta'clase
r n i j ~ f ~ &del ejem
plo Protec&~):
tYP9
TTest = ex-s
ptotec ted
~ r o t e k t e d ~ I k BInteger;
?
d;

Cued0 hayarnos'co~ocadoesta clase qn Ia unidad, no se podra accedet absu


park protegida directamente desde otras unidades. Segiin esto, si escribi-
mm el kipiente cbdigo.
p~ocecbre TForrnl. B u t C o n l C & i c R (Sender: T O b j e c t ) ;
VaE
Ob'j: TTest;
.begin
Obj : = TTest . C r e a t e :
.
O b ] ProtectedData := 20; / / no v a a compiler
'ProtectedDatan'(Identificador no declarado: Datos Protegidos). En este
momento, se podria pensar que no hay manera de acceder a 10s datos prote-
gidos de una clase defmida en una unidad diferente. Sin embargo, en cierto
mod0 si se puede. Tengarnos en cuenta lo que ocurre si se crea una clase
aparentemente derivada inutil, corno:
type
TTestHack = alaas (TTest);

Ahora, si realizamos una conversion directa del objeto a la nueva clase y


accedemos a 10s datos protegidos a traves de ella, el codigo sera:
var
Obj: TTest;
begin
Ob j := TTest. Create;
TTestHack (Obj).ProtectedData := 20; // ;conpila!

csre coaigo compua y runciona correctamenre, como se pueae ver si se


ejecuta el programa Protection. La raz6n es que la clase TTestHack
hereda autornstticamente 10s campos protegidos de la clase birsica TTest
y, como est4 en la misma unidad que el ckligo que intenta acceder a 10s
datos de 10s campos heredados, 10s datos protegidos resultan accesibles.
Como seria de esperar, si se mueve la declaracibn de la clase TTestHac k
a una unidad secundaria, el programa ya no cornpilad.
Ahora que ya hemos visto d m o se hace, hay que W e t en cuenta que viola
el mecanismo de proteccibn de c l a m de este m o b pbdrfir Brigbar errores
en el programa (a1 acceder a datos a 10s que no deberimw tener acceso) y
no respeta las tbcnicas de orientacih a objetos. Sin embargd, en muchas
ocasiones usar esta thnica es la mejor solucibn, como veremos a1 analizar
el codigo fuente de Ia VCL y el ddigo fuente de mucbos wmponentes
Delphi. Dos ejemplos de ello son el acceso a la propiedad Text de la clase
TControl y las posicio&s Row y G o 1 del control DBGrid. EQtas dos
ideas aparecen en-10s ejemplos Text Prop y DBGridCol, respectiva-
mente. (Son ejemplos bastante avanzados, asi que es mejor no enfientarse a
ellos hasta tener un buen conocimientode Delphi.) Aunque el primer0 es un
^:--I- --L1- 1 - 1 ---- A - 1- LA- -i -:---la
~jcruylo r ~
---a
~u cu~WQ~UG IU----.---:I-
A:---3-
e z;onvwsrun oe upus wwX;sr,
---A

cl qcmplu
DBGrid de Row y Col es en &dad un ejemplo de w o opuesto. que
ilustplos riwgm de a c d r a bits que la persona que escribib las clases
prefifio s o exgoner. La fib y colbima de una clase DB-id no significan
lo mismo gw en u& ~ r p w ~ r iQ duna StringGrid &q clpses bhi-
cas). En pjmer h p r , ll5Gxld ao cuenta las w b @as oomo cedas
rcakr ( & t i a d i s eelda? dehatos dc 10s c l ~ ~ d e ~ b r a t i l i q ] , lo
~P~r
que sys indices ,de fils. y aoluraaa.€idrib qua aj$#arse a los,efemento#
biar sin que nos demos cuenta). En segundo lugar, la DBGrid es una vista
virtual de 10s datos. Cuando nos desplazamos hacia arriba en una DBGrid,
10s d a b s pueden moverse bajo ella, bero la fila seleccionada en ese momen-
to podria no cambiar.

protegidos miembros de una clase) se describe normalmente como un hack


o apaKo y deberia evitarse siempre que sea posible. El problema no esth en
.,. - .
acceder a datos protegidos de una cIase en la misma unidad sino en declarar
.. . . .
una clase con el unico tin de acceder a datos protegldos de un ObJetO exls-
tente de una clase distinta. El peligro de esta tecnica esth en la conversion
de tipos codificada directamente de un objeto de una clase a otra diferente.

Herencia y compatibilidad de tipos


Pascal es un lenguaje con tipos estrictos. Esto significa que no se puede, por
ejemplo, asignar un valor entero a una variable booleana, a no ser que se aplique
una conversion de tipos explicita. La regla es que dos valores son compatibles en
tip0 so10 si son del mismo tipo de datos o (para ser mas precisos) si su tipo de dato
se rcfiere a una unica definicion de tipo. Para simplificarlo todo, Delphi hace que
algunas asignaciones de tipos predefinidas Sean compatibles: se puede asignar un
Extended a un Double y viceversa: con promocion o degradacion automatica
(y una potencial perdida de precision).
-- - - - - -
ADVERTENCIA: Si se redefine el mismo tip0 de datos en dos unidades
diferentes, no s e r h compatibles, incluso aunque sus nombres Sean identi-
cos. SerA muy dificil compilar y depurar un programa que use dos tipos con
el mismo nombre de dos unidades diferentes.

Existe una importante excepcion a esta norma en el caso de 10s tipos de clase.
Si se declara una clase, como TAnimaL, y se deriva de ella una nueva clase,
como por cjemplo TDog, se puede asignar un objeto de tipo TDog a una variable
de tipo TAnimal. Esto se debe a que un perro (dog) es un animal. Como regla
general, se puede usar un objeto de una clase descendente cada vez que se espere
un objeto de la clase ascendente. Sin embargo, lo opuesto no resulta legal; no se
puede usar un objeto de una clase antecesora cuando se espera un objeto de una
clase que desciende de la anterior. Para simplificar la esplicacion, veamos este
codigo:
var
MyAnimal : T A n i m a l ;
MyDog: T D o g ;
begin
MyAnimal : = MyDog; // E s t o es correcto
MyDog : = MyAnimal; // ; E s t o es u n error!

Enlace posterior y polimorfismo


Las funciones y procedimientos de Pascal se basan normalmente en el enlace
estatico o anterior. Esto significa que el compilador y el enlazador resuelven una
llamada a un metodo, que reemplazan la peticion por una llamada a la posicion de
memoria especifica en la que se encuentra la funcion o el procedimiento (esto se
conoce como la direccion de la funcion). Los lenguajes de orientacion a objetos
permiten el uso de otra forma de enlace, conocida como enlace dinamico o poste-
rior. En este caso, la direccion real del metodo a llamar se establece en tiempo de
ejecucion, segun el tipo de instancia utilizada para hacer la llamada.
Esta tecnica se conoce como polimorfismo (que en griego significa muchas
formas). Polimorfismo significa que se puede llamar a un metodo, aplicarlo a una
variable, per0 que el metodo a1 que realmente llama Delphi depende del tipo de
objeto con el que este relacionada la variable. Delphi no puede decidir la clase
real del objeto a1 que se refiere la variable hasta estar en tiempo de ejecucion,
debido a la norma de la compatibilidad de tipos. La ventaja del polimorfismo es
que permite escribir codigo mas simple, tratar tipos de objetos distintos como si
se tratara del mismo y conseguir el comportamiento correcto en tiempo de ejecu-
cion.
Por ejemplo, supongamos que una clase y una clase heredada (las clases
TAnimal y TDog) definen ambas un nuevo metodo y que este metodo tiene
enlace posterior o tardio. Se puede aplicar este metodo a una variable generica
como MyAnimal que en tiempo de ejecucion puede referirse a un objeto de la
clase TAnimal o a un objeto de la clase TDog. El metodo real a llamar se
determina en tiempo de ejecucion, segun la clase del objeto real.
El ejemplo PolyAnimals muestra esta tecnica. Las clases TAnimal y TDog
tienen un metodo Voice,que pretende reproducir el sonido que realiza el animal
seleccionado, como texto y como sonido (mediante una llamada a la funcion
Playsound de la API definida en la unidad MMSystem). Este metodo se define
como virtual en la clase TAnimal y mas tarde se sobrescribe cuando se define la
clase TDog,mediante el uso de las palabras clave virtual y override:
type
TAnimal = class
public
function Voice: string; virtual;

TDog = class (TAnimal)


public
function Voice: string; override;
El efecto de la llamada M yAnimal .Voice puede variar. Si la variable
MyAnimal se refiere en un momento dado a un objeto de la clase TAnimal,
llamara a1 metodo TAnimal . Voice.Si se refiere a un objeto de la clase TDog,
llamara en cambio a1 metodo TDog .Voice.Esto ocurre solo porque la funcion
es virtual (como veremos si se elimina esta palabra clave y se vuelve a compilar).
La llamada a MyAnima1. Voice funcionara en el caso de un objeto que sea
una instancia de cualquier descendiente de la clase TAnimal,aunque las clases
esten definidas en otras unidades, o aunque todavia no se hayan escrito. El
compilador no necesita conocer todos 10s descendientes para hacer que la llamada
sea compatible con ellos, solo se necesita la clase ascendiente. En otras palabras,
esta llamada a MyAnima 1 . Voice es compatible con todas las futuras clases
que hereden de TAnimal.
-- - - - .....---- -
. . --
.- . .- -. .- .

I NOTA: Esta es la raz6n clave por la que 10s lenguajes de programa&n I


orientada a obietos favorecen la reutilizacion. Se vuede escribir un cbdiqo
i-
[Y
GI prugriu~lajiuuavla sc yucuc iulrpllar, aunquc sc n a y u de
GSGIILU IIIIICS

lineas de c d i g o que la usan. Por supuesto, existe una condicion: las clases
ascendientes de la jerarquia ban de disekrse con mucho cuidado.

En la figura 2.7 se puede ver un ejemplo dc la salida del programa PolyAnimals.


A1 ejecutarlo, se oiran 10s sonidos correspondientes producidos por la llamada a
Playsound.

-
Figura 2.7. El resultado del ejemplo PolyAnimals.

Sobrescribir y redefinir metodos


Como acabamos de ver, para sobrescribir un metodo con enlace posterior en
una clase descendiente, hay que usar la palabra clave override.Fijese en que
esta solo puede utilizarse si se definio el metodo comovirtual (o dinamico) en
la clase ascendiente.
Las normas son sencillas: un metodo definido como estatico sigue siendo esta-
tico en todas sus subclases, a no ser que se oculte con un nuevo metodo virtual
que tenga el mismo nombre. Un metodo definido como virtual, sigue manteniendo
el enlace posterior de cada subclase (a menos que se oculte con un metodo estati-
co, que resulta algo bastante alocado). No hay ningun mod0 de cambiar este
comportamiento, debido a la forma en que el compilador genera un codigo dife-
rente para 10s metodos con enlace posterior.
Para redefinir un metodo estatico, hay que aiiadir un metodo a una subclase
que tenga 10s mismos parametros o parametros diferentes que el original, sin
ninguna otra especificacion.
Para sobrescribir un metodo virtual, habra que especificar 10s mismos
parametros y usar la palabra clave o v e r r i d e :

type
TMyClass = class
procedure One; virtual;
procedure Two; (metodo estdtico)
end;
TMyDerivedClass = class (TMyClass)
procedure One; override;
procedure Two;
end;

Hay dos formas muy comunes de sobrescribir un metodo. Una consiste en


reemplazar el metodo de la clase ascendiente por una nueva version. La otra, en
aiiadir mas codigo a1 metodo existente. Para ello se utiliza la palabra clave
i n h e r i t e d que llama a1 mismo metodo de la clase ascendiente. Por ejemplo, se
puede escribir:
procedure TMyDerivedClass.0ne;
begin
/ / codigo nuevo
...
/ / llamada a 1 p r o c e d i m i e n t o M y C l a s s . O n e
inherited One ;
end;

Cuando se sobrescribe un metodo virtual existente de una clase basica, hay


que usar 10s mismos parametros. Cuando se presenta una nueva version de un
metodo en una clase descendiente, se puede declarar con cualquier parametro. De
hecho, este sera un nuevo metodo independiente del metodo ascendiente del mis-
mo nombre, solo que tendra el mismo nombre. Veamos un ejemplo:

type
TMyClass = class
procedure One;
end;

TMyDerivedClass = class (TMyClass)


procedure One ( S : string) ;
end;
NOTA: Si se usan las definiciones de clase anteriores, cuando se crea un
objeto de la clase TMyDer ivedClass, se puede usar su m&odo One con
3 3 . . . . 4 _ _
1 L ! I , 1 I-
el parametro ae cauena, pero no la version sm parametros aeImaa en la
A .

clam bit8iaa. Qi se necesita esto, se puede marcar el metodo redeclarado (el


de h c h derivada) con la palabra clave overload. Si el &todo time
pariimetros diferentes a 10s de la versibn de la clase bbica, se cunvierte
cfedtivamente en un mktodo sobrecargado. Si no es asi, reemplaza a1 rnkto-
do ck la olase bbsica. Ffjese en que el m&odo no necesita estar marcado con
overload en la c h e bkica. Sin embargo, si el m h d o de la chse b h i c a
es virtual, el c o m p i h d ~ emite
r la advertencia "Method 'One'hfdes virtual
method of base type "~yClass"'(E1m&odo 'One'oculta el metodo virtual
del tip bbico "TMyClassW).Para evitar este mensaje e instruir a1compilador
de forma m h precisa sobre nuestras intenciones, se puede usar la directiva
reintroduce. El c6digo sobre este tema se puede encontrar en el ejem-
plo Reintr.

Metodos virtuales frente a metodos dinamicos


En Delphi, hay dos formas distintas de activar el enlace posterior. Se puede
declarar el metodo como virtual, como hemos visto antes, o como dinamico. La
sintaxis de estas dos palabras clave (virtual y dynamic) es exactamente la
misma y el resultado de su uso tambien. Lo que cambia es el mecanismo interno
usado por el compilador para implementar el enlace posterior.
Los metodos virtuales se basan en una tabla de metodos virtuales (VMT, tam-
bien conocida como vtable), que es una matriz de direcciones de metodo. Para una
llamada a un metodo virtual, el compilador genera codigo para saltar a una direc-
cion almacenada en la enesima ranura de la tabla de metodos virtuales del objeto.
Las tablas de metodo virtual permiten que las llamadas a metodo se ejecuten
rapidamente, pero se necesita una entrada para cada metodo virtual de cada clase
descendiente, aunque el metodo no se sobrescriba en la subclase.
Las llamadas a un metodo dinamico, por otra parte, se realizan usando un
numero unico que indica el metodo, el cual se guarda en una clase solo si la clase
lo define o sobrescribe. La busqueda de la funcion correspondiente es, por lo
general, mas lenta que la busqueda de 10s metodos virtuales en la tabla, que
consta de un solo paso. La ventaja es que las entradas del metodo dinamico solo
se propagan a descendientes cuando estos sobrescriben el metodo.

Manejadores de mensajes
Tambien se puede usar un metodo de enlace posterior para manejar un mensaje
de Windows, aunque la tecnica es algo distinta. Con este proposito, Delphi ofrece
otra directiva, message, para definir 10s metodos de control de 10s mensajes,
que habran de ser procedimientos con un unico parametro var. La directiva
message va seguida del numero del mensaje de Windows que el metodo quiere
controlar.

ADVERTENCIA;- La directin i e s sage tambien ,est&dispodble ,en


Kylix y el bguaja y lsr RTL la soportan por cornpleto. 8ir1embargo, par&
visual del mars de hbajo de la apiicacion-CLX nd WJQY m b t o b del
mensaje para enviar las natificacioneg a Basqontrolm. Por esq ra26a, impre
que sea posible, se deberia usar un mitach virtual: propomionado por la
biblioteca en lugar de manejar un m@hscLje de Winhws dlrectamente. Por
supuesto, esto importa s61o si queremoa qiie el &go se pueda tramportar.

Por ejemplo, la siguiente porcion de codigo permite manejar un mensaje defi-


nido por el usuario, con el valor numirico indicado por la constante vm-User de
Windows.
type
TForml = class (TForm)
...
procedure WMUser (var Msg: TMessage) ;
message vm-User;
end ;

El nombre del procedimiento y el tip0 de 10s parametros dependen del progra-


mador, aunque esisten varios tipos de registros predefinidos para 10s diversos
mensajes de Windows. Podria generarse mas adelante este mensaje, invocando a1
metodo correspondiente, como en:
PostMessage (Form1.Handle, vm-User, 0, 0) ;

Esta tecnica puede resultar extremadamente util para un programador vetera-


no de Windows, que lo sepa todo sobre 10s mensajes y las funciones de la API de
Windows. Tambien se puede enviar inmediatamente un mensaje mediante la lla-
mada a la API de SendMessage o a1 metodo Perform de la VCL.

Metodos abstractos
La palabra clave abstract se usa para declarar metodos que se van a defi-
nir solo en subclases de la clase actual. La directiva abstract define por com-
pleto el metodo, no es una declaracion que se completara mas adelante. Si se
intenta definir el metodo, el compilador protestara. En Delphi se pueden crear
instancias de clases que tengan metodos abstractos. Sin embargo, a1 intentarlo, el
compilador de 32 bits de Delphi emite un mensaje de advertencia "Constrtrcting
instance of <class name> containing abstract methods" (Creando caso de +om-
bre de clase> que contiene metodos abstractos). Si se llama a un metodo abstract0
en tiempo de ejecucion, Delphi creara una escepcion, como muestra el ejemplo
AbstractAnimals (una ampliacion del ejemplo PolyAnimals), que usa la siguiente
clase:
type
TAnimal = c l a s s
public
function Voice: s t r i n g ; v i r t u a l ; abstract;

NOTA: La rnayoria de 10s lenguajes orientados a objetos usan un enfo


m h estricto: generalmente no se pueden crear instancias de cIases que c
tengan m6todos abstractos.

Podriamos preguntarnos por la razon del uso de 10s metodos abstractos. Esta
razon es el polimorfismo. Si la clase TAnimal tiene el metodo virtual Voice,
toda clase heredada puede volver a definirlo. Si se trata de un metodo abstracto
Voice,cada clase heredada debe volver a definirlo.
En las primeras versiones de Delphi, si un metodo sobrescribia un metodo
abstracto llamado inherited,el resultado era una llamada a1 metodo abstrac-
to. A partir de Delphi 6; el compilador se ha mejorado para detectar la presencia
dcl metodo abstracto y evitar la llamada inherited.Esto significa que se
puede usar con seguridad inherited en todo metodo sobrescrito, a no ser que
se desee inhabilitar esplicitamente la ejecucion de parte del codigo de la clase
basica.

Conversion descendiente con seguridad


de tipos
La norma sobre compatibilidad de tipos de Delphi para las clases descendien-
tes permite usar una clase descendiente donde se espera una clase ascendiente. Sin
embargo, el caso contrario nunca es posible. Ahora supongarnos que la clase
TDog posee un metodo Eat,que no esta presente en la clase TAnimal.Si la
variable MyAnimal se refiere a un perro, se podra llamar a la funcion. Pero si lo
intenta y la variable se refiere a otra clase, el resultado le dara un error. A1
realizar una conversion de tipos explicita, podemos originar un fastidioso error en
tiempo de ejecucion (o peor, un problema de sobrescritura de la memoria subya-
cente), porque el compilador no puede establecer si el tip0 del objeto es correct0
ni si 10s metodos a 10s que se llama existen realmente. Para solucionar el proble-
ma, podemos usar tecnicas basadas en informacion de tip0 en tiempo de ejecucion
(abreviado RTTI). Basicamente, dado que cada objeto "conoce" su tip0 y su clase
padre y podemos pedir informacion con el operador is o utilizar el metodo
InheritsFrom de la clase TObject.
Los parametros del operador is son un objeto y un tipo de clase y el valor de
retorno es un booleano:
i f MyAnimal i s TDog t h e n ...
La expresion is evalua como True si se el objeto MyAnimal se refiere
realmente a un objeto de clase T D O o~ de un tipo descendiente de T D O ~ Esto
.
significa que si se comprueba si un objeto TDog es de tipo TAnimal,la compro-
bacion tendra exito. En otras palabras, esta sentencia evalua como True si se
puede asignar con seguridad el objeto (MyAnimal) a una variable del tipo de
datos (TDO~).
Ahora que sabemos con seguridad que el animal es un perro (dog), se puede
realizar una conversion de tipos segura. Se puede realizar dicha conversion direc-
ta escribiendo el siguiente codigo:
var
MyDog: TDog;
begin
i f MyAnimal i s TDog t h e n
begin
MyDog := TDog (MyAnimal);
Text : = MyDog.Eat;
end;

Esta misma operacion se puede realizar directarnente mediante el segundo ope-


rador RTTI, as, que convierte el objeto solo si la clase solicitada es compatible
con la real. Los parametros del operador as son un objeto y un tip0 de clase, y el
resultado es un objeto convertido a1 nuevo tipo de clase. Podemos escribir el
siguiente fragment0 de codigo:
MyDog : = MyAnimal a s TDog;
Text : = MyDog. Eat;

Si solo queremos llamar a la funcion E a t , tambien podemos usar una notacion


incluso mas corta:
(MyAnimal a s TDog) .Eat;

El resultado de esta expresion es un objeto del tip0 de datos de clase TDog,


por lo que se le puede aplicar cualquier metodo de dicha clase. La diferencia entre
la conversion tradicional y el uso de as es que el segundo enfoque crea una
excepcion si el tip0 del objeto es incompatible con el tipo a1 que estamos intentan-
do convertirlo. La excepcion creada es E I nva 1 idCa s t .
Para evitar esta excepcion, hay que usar el operador is y, si funciona, realizar
una conversion de tipos normal (en realidad, no hay ninguna razon para usar is
y as de manera secuencial y hacer la verificacion de tipos dos veces):
i f MyAnirnal i s TDog t h e n
.
TDog (MyAnimal) Eat ;
Ambos operadores RTTI resultan muy utiles en Delphi para escribir codigo
generico que se pueda usar con diversos componentes del mismo tipo o incluso de
distintos tipos. Cuando un componente se pasa como parametro a un metodo de
respuesta a un evento, se usa un tipo de datos generico (TOb j ect), por lo que
normalmente es necesario convertirlo de nuevo a1 tip0 de componente original:
procedure TForml.ButtonlClick(Sender: TObject);
begin
if Sender is TButton then
...
end;

Se trata de una tecnica habitual en Delphi. Los dos operadores RTTI, i s y


as, son realmente potentes y podriamos sentirnos tentados a considerarlos como
construcciones de programacion estandar. Sin embargo, probablemente se debe-
ria limitar su uso para casos especiales. Cuando sea necesario resolver un proble-
ma complejo relacionado con diversas clases, hay que intentar utilizar primer0 el
polimorfismo. Solo en casos especiales, en 10s que el polimorfismo solo no se
pueda aplicar, deberiamos intentar usar 10s operadores RTTI para complementar-
lo. No hay que usar RTTI en lugar del polimorfismo, puesto que daria lugar a
programas mas lentos. La RTTI, de hecho, tiene un impact0 negativo en el rendi-
miento, porque debe pasar por la jerarquia de clases para ver si la conversion dc
tipos es correcta. Como hemos visto, las llamadas de metodo virtual solo necesi-
tan una busqueda en memoria, lo cual es mucho mas rapido.
I

NOTA: En realidad hay m i s informaci6n de tipo en tiempo de ejecuei6o


(RTTI) que 10s operadores is y as.Se puede acceder a clases detalladas e
information de tipos en tiempo de ejecucion, sobre todo para propiedades
eventos y mCtodos pub1 i s hed.

Uso de interfaces
Cuando se define una clase abstracta para representar la clase basica de una
jerarquia, se puede llegar a un punto en el que la clase abstracta sea tan abstracta
que so10 liste una serie de funciones virtuales, sin proporcionar ningtin tip0 de
implernentacion real. Este tip0 de clase puramente abstracta puede definirse tam-
bien mediante una tecnica concreta, una interfaz. Por esta razon, nos referimos a
dichas clases como interfaces.
Tecnicamente, una interfaz no es una clase, aunque puede parecerlo, porque se
considera un elemento totalmente a parte con caracteristicas distintivas:
Los objetos de tipo interfaz dependen de un recuento de referencias y se
destruyen automaticamente cuando no hay mas referencias al objeto. Este
mecanismo es similar a la forma en que Delphi maneja cadenas largas y
administra la memoria casi de forma automatica.
Una clase puede heredar de una clase basica simple, per0 puede implementar
varias interfaces.
A1 igual que todas las clases descienden de T O b j ect, todas las interfaces
descienden de 1Interface y forman una jerarquia totalmente indepen-
diente.

m z E d i a s e r IUnknown has@ ~ e l ~5,hper0 i ~el~hi 7


6 le otorgo un nuevo nombre, I I n t e r face,para paarcar de un modo rnk
claro el hecho de que de esta f u n c i h del lenguaje es independiente del
COM de Microsoft (que usa IUnknown como su iaterfaz base). De hecho,
las interfaces Delphi tambikn e s t h disponibles en Kylix.

Es importante fijarse en que las interfaces soportan un modelo de programa-


cion orientada a objetos ligeramente distinto a1 que soportan las clases. Las
interfaces ofrecen una implernentacion del polimorfismo menos restringida. El
polimorfismo de las referencias de objetos se basa en una rama especifica de una
jerarquia. El polimorfismo de interfaces funciona en toda una jerarquia. Ademas,
el modelo basado en interfaces es bastante potente. Las interfaces favorecen el
encapsulado y proporcionan una conexion mas flexible entre las clases que la
herencia. Hay que resaltar que 10s lenguajes orientados a objetos mas recientes,
de Java a C#, poseen el concept0 de interfaces. Veamos la sintaxis de la declara-
cion de una interfaz (que, por convencion, comienza con el caracter I):
type
ICanFly = interface
['{EAD9C4B4-ElC5-4CF4-9FAO-3B812C880A21]']
function Fly: s t r i n g ;
end;

La interfaz anterior posee un GUID, un identificador numeric0 que sigue a su


declaracion y se basa en las convenciones Windows. Estos identificadores (llama-
dos generalmente GUID) se pueden generar pulsando la combinacion de teclas
Control-Mayus-G en el editor de Delphi.
- -- - -- - . - - - -- -
- - --- - -

NClTkt Awqne se $udden coMilar y usar interfaces sin:especificar un


4&RD para ellas For la general conviene generar uno, puesto que es nece-
sari0 t a r s realizar consbltas & interfaz o la conversibn dinamica de tipos
mediante as c y ese tipo de interfaz. Dado que todo el inter& de las interfaces
coslsiste'(nomalmentefin aprovechar la flexibilidad mejorada en tiernpo
I& ejecuci'6ir,'siTa compaqmos cob 10s tipos de clase, las interfaces sin 10s

GUIH no resultan muy utiles.


Cuando hayamos declarado una interfaz, se puede definir una clase que la
implemente, como en:
type
TAirplane = class (TInterfacedObject, ICanFly)
f u n c t i o n Fly: string;
end;

La RTL ya ofrece unas cuantas clases basicas para implementar el comporta-


miento fundamental que necesita la interfaz II n t e r f ace. Para 10s objetos in-
ternos, se usa la clase T I n t e r f acedOb j ect , utilizada en el codigo anterior.
Se pueden implementar mktodos de interfaz con metodos estiticos (como en el
codigo anterior) o con metodos virtuales. Se pueden sobrescribir mktodos virtuales
en subclases utilizando la directiva o v e r r i d e . Si no se usan metodos virtuales,
aun asi se puede ofrecer una nueva implementacion en la subclase, volviendo a
declarar el tipo de interfaz en la subclase y a enlazar 10s metodos de interfaz con
nuevas versiones de 10s metodos estaticos. A primera vista, el uso de metodos
virtuales para implementar interfaces parece permitir un codigo mas limpio en las
subclases, per0 ambos enfoques son igual de potentes y flexibles. Sin embargo, el
uso de metodos virtuales afecta a1 tamaiio del codigo y de la memoria necesaria.
I
NQTA: coolpiladm ha de garerar mtinas de d e w p ~ r qajustar lm
puntos b entrada & la llatnada dq infe&gaI r n b cmespgndiente de hi
olase de impletneq@ci&y adaptar el punter0 self ~ s tt-$o c de mi- &
m6todo de interfaz para m w a p ~ 4 t i c o ess muy sencillo: a j u m r ' s s i f y
p&r al o&# real de la clase. 'tas mtinas de mCtodo de interfaz para
m&'&s virtuales son mucho mas complejas y requieren unas cuatro veces
has' codigtj (20 a 30 bytes) en cada una que en el caso esthtico. Ademas,
aiiadir mas- metodos virtuales a la clase de implementacion contribuye a
inflat la tabla.de rn6todos virtuaIes (VMT) en la clase y en todas sus
subdases. Una interfaz ya dispone de su propia VMT y volver a declarar
una interfaz en las subclases para volver a enlazar la interfaz con 10s nue-
vos metodos supone tanto polimorfismo como usar metodos virtuales, pet0
requiere un codigo menor,

Ahora que hemos definido una implementacion de las interfaces, podemos es-
cribir algo de codigo para usar un objeto de esa clase, mediante una variable de
tipo interfaz:
var
Flyerl: ICanFly;
begin
Flyerl : = TAirplane.Create;
Flyerl.Fly;
end;
En el momento en que se asigna un objeto a una variable de tipo interfaz,
Delphi comprueba automaticamente si el objeto implementa esa interfaz, median-
te el operador as.Se puedc espresar csplicitamente esta operacion de este modo:
Flyerl := T A i r p l a n e - C r e a t e as ICanFly;

--
NOTA:El cornpilador genera diferente cbdigo para el operador as cuamlo
se usa con intefices que cuando se usa con clases. Con clases, introduce
verificaciones en tiempo de ejecuci6n para cornprobar que el objeto es efec-
tivamente "compatible en tipo" con la clase dada. Con las interfaces, com-
prueba en tiempo de compilaci6n que puede extraer la interfaz necesaria del
tipo de clase disponible y asi lo hace. Esta operacih es como un "as en
tiempo de compilation", no algo que exista en tiempo de ejecucibn.

Usemos la asignacion directa o bien la sentencia as,Delphi realiza una accion


extra: llama a1 metodo AddRef del objeto (definido por I Interface). La
implementacion estand& de este mktodo, como la que ofrece TInt er fa -
cedObject,es aumentar el recuento de referencias. Al mismo tiempo, desde el
momento en que la variable Flyerl esta fuera de alcance, Delphi llama al meto-
do Release (de nuevo parte de IInterface). La irnplementacion de
~1;ter fa c e d ~jbect de -Release decrementa el recuento de referencias,
verifica si este es cero y, si es necesario, destruye el objeto. Por esa razon en el
ejemplo anterior, no hay codigo para liberar el objeto que hemos creado.

ADVERTENCIA: Cuando se usan &j&~&b en intediuxs, por lo


general deberiamos acceder a ellos s6io w n las variables da objeto o sblo
con las variables de interfaz. Si se m&+zlsahs dw ~~, el s h a de
recuenta de referencias de Del* se interrump y pue&.ariginar errores de
memoria que sean extre-entc diffciles de 1-ar: J3i la piktim, si
hemos decidido usar interfa~eer.probabkmeot~deberiamosiusa r f i w e n -
te variables basadas en inte&e$.' $i &d asi debcanps m e z w las vdrh-
bles, lo msls aconsejable es inhev6ilitar d reopanto de-re-fer&as-esd&do
m a clase base propia en lugar de usar T 1 n ter fa c e d ~ jbd ct .

Trabajar con excepciones


Otra caracteristica clave de Delphi es el soporte de excepciones. Las excepcio-
nes hacen que 10s programas Sean mas robustos ya que proporcionan un mod0
estandar de notificar y gestionar errores y situaciones inesperadas. Las excepcio-
nes hacen que 10s programas Sean mas faciles de escribir, leer y depurar porque
permiten separar el codigo de gestion de errores de codigo normal, en lugar de
entremezclar ambos. A1 obligar a mantener una division logica entre el codigo y
la gestion de errores y a1 conmutar al manejador de errores automaticamente, se
consigue que la logica real resulte mas limpia y clara. Nos permiten escribir un
codigo mas compacto y menos inundado por 10s habituales metodos de manteni-
miento no relacionados con el objetivo real de programacion. En tiempo de ejecu-
cion, las bibliotecas de Delphi crean excepciones cuando algo va ma1 (en el codigo
de tiempo de ejecucion, en un componente, en el sistema operativo). Desde el
punto del codigo en el que se crea, la escepcion se pasa a su codigo de llamada, y
asi sucesivamente. Por ultimo, si ninguna parte del codigo controla la excepcion,
la VCL se encarga de ella, mostrando un mensaje estandar de error y tratando de
continuar el programa proporcionando el siguiente mensaje del sistema o peticion
a1 usuario. Todo este mecanismo se basa en cuatro palabras clave:
try: Delimita el comienzo de un bloque protegido de codigo.
except: Delimita el final de un bloque protegido de codigo e introduce las
sentencias de control de excepciones.
finally: Se usa para especificar bloques de codigo que han de ejecutarse
siempre, incluso cuando se dan excepciones. Este bloque se usa general-
mente para realizar operaciones de limpieza que siempre se deberian ejecu-
tar, como cerrar archivos o tablas de bases de datos, liberar objetos y
liberar memoria y otros recursos adquiridos en el mismo bloque de progra-
ma.
raise: Es la sentencia usada para generar la excepcion. La mayoria de las
excepciones que encontramos en programacion en Delphi las genera el
sistema, per0 tambien'se pueden crear excepciones propias en el codigo,
cuando se descubren datos no validos o incoherentes en tiempo de ejecu-
cion. La palabra clave r a i s e tambien puede usarse dentro de un contro-
lador para volver a crear una excepcion, es decir, para propagarla a1
siguiente controlador

TRUCO: La gestibn de excepciones no supone un reemplazo aI adecuado


control de flujo en un progami. Es rcmmendabfe mmtener el uso de sen-
rencias .
*._ _ - _ _ n _I
para co~nproopr
C d 3 L- ,I 1- >.:_
mcqmaa aei usuano y ouas posmres concucio-
. I

nes de error. S61o d&erf& asarse ex.cepcianes para eituaciones a n o d e s


o inesperadas.

Flujo de programa y el bloque finally


La potencia de las excepciones en Delphi tiene que ver con el hecho de que se
"pasan" de una rutina o metodo del llamador, hasta un manejador global (si el
programa ofrece uno, como suele suceder con las aplicaciones de Delphi), en
lugar de seguir la ruta estandar de ejecucion del programa. Asi que el autentico
problema no consiste en saber como detener una excepcion sin0 como ejecutar
codigo incluso aunque se lance una excepcion.
Consideremos esta seccion de codigo (parte del ejemplo TryFinally), que rea-
liza algunas operaciones para las que emplea bastante tiempo y usa el cursor en
forma de reloj de arena para mostrar a1 usuario que esta haciendo algo:
Screen.Cursor : = crHourglass;
// g r a n a l g o r i t m o . . .
Screen.Cursor : = crDefault;

En caso de que se produzca un error en el algoritmo (corno el que se ha inclui-


do a proposito en el ejemplo TryFinally), el programa se detendra, per0 no volve-
ra a establecer el cursor predefinido. Es para esto para lo que sirve un bloque
try/f inally:
Screen.Cursor : = crHourglass;
try
// g r a n a l g o r i tmo . . .
finally
Screen.Cursor : = crDefault;
end ;

Cuando el programa ejecuta esta funcion, siempre reinicia el cursor, haya una
excepcion (de cualquier tipo) o no. Este codigo no controla la excepcion, simple-
mente hace que el programa sea robusto en caso de que se Cree un una excepcion.
Un bloque t r y puede ir seguido de una sentencia e x c e p t o f i n a l l y , per0 no
por ambas a1 mismo tiempo. La solucion mas comun para controlar tambien la
excepcion consiste en usar dos bloques t r y anidados. En ese caso, hay que
asociar el interno con una sentencia f i n a 11y y el externo con una sentencia
e x c e p t o viceversa, segun lo requiera la situacion. Aqui tiene el esquema del
codigo para el tercer boton del ejemplo T r y F i n a l l y :
Screen.Cursor : = crHourglass;
try try
/ / g r a n a l g o r i tmo . . .
finally
Screen.Cursor : = crDefault;
end;
except
on E: EDivByZero do .. .
end;

Cada vez que haya algun codigo de finalizacion a1 concluir un metodo, hay que
situar dicho codigo en un bloque f i n a l l y . Siempre se deberia, invariablemente
y de forma continuada proteger el codigo con sentencias f i n a l l y , para evitar
problemas de recursos o de goteos de memoria en caso de que se Cree una excep-
cion.
r
- .- - - - - - - - - ---- - - -- - -

TRUCO:Controlar la excepcion es generalmente mucho menos importan-


te que utilizar 10s bloques f i n a l l y , puesto que Delphi puede sobrevivir a
la mayoria de ellas. Ademas, dernasiados bloques para controlar excepcio-
nes en el c6digo probablernente indicarh errores en el flujo del programa y
una mala comprension de la funcion de las excepciones en el lenguaje.
.-.. . . . . . ... . ..
.-. - -
m t r e 10s ejemplos ae este llbr0 ser veran mucaos bloques t r y / r l n a l l y ,
unas cuantas sentencias raise, y casi ningin bloque t r y / e x c e p t .

Clases de excepciones
En las sentencias de control de escepciones mostradas anteriormente, capta-
mos la excepcion EDivBy Zero, que define el RTL de Delphi. Otras excepcio-
nes como esta se refieren a problemas en tiempo de ejecucion (como una conversion
dinamica erronea), problemas de recursos de Windows (como 10s errores por falta
de memoria), o errores de componentes (como un indexado erroneo). Los progra-
madores pueden definir tambien sus propias excepciones. Se puede crear una
nueva subclase de escepciones predefinidas o de una de sus subclases:
type
EArrayFull = class (Exception) ;

Cuando se aiiade un nuevo elemento a una matriz que ya esta llena (probable-
mente por un error en la Iogica del programa), se puede establecer la excepcion
correspondiente, creando un objeto de esa clase:
if MyArray.Ful1 then
r a i s e EArrayFull .Create ( 'Ma t r i z l l e n a ') ;

Este metodo c r e a t e (heredado de la clase E x c e p t i o n ) tiene un parametro


de cadena para describir la excepcion a1 usuario. No es necesario preocuparse de
destruir el objeto creado para la excepcion, porque se borrara automaticamente
gracias a1 mecanismo de control de excepciones.
El codigo presentado en 10s extractos anteriores forma parte de un programa
ejemplo, denominado Exception 1. Algunas de las rutinas se han modificado lige-
ramente, como en la siguiente funcion D i v i d e T w i c e P l u s O n e :
function DivideTwicePlusOne (A, B: Integer) : Integer;
begin
try
// e r r o r s i B e s i g u a l a 0
Result := A d i v B;
// h a c e o t r a c o s a . ..
o b v i a r s i s e c r e a una e x c e p c i o n
Result : = Result d i v B;
Result : = Result + 1;
except
on EDivByZero do
begin
Result : = 0;
MessageDlg ('Dividir por cero corregido.', mtError,
[ m b O K l , 0);
end ;
on E : Exception do
begin
Result : = 0 ;
MessageDlg (E.Message, mtError, [mbOK] , 0 ) ;
end;
end; / / finaliza except
end;

En el codigo de Esceptionl hay dos excepciones diferentes despues del mismo


bloque t r y . Puede haber un numero cualquiera de controladores de este tipo,
evaluados consecutivamente.
Si se usa una jerarquia de excepciones, tambien se llama a un controlador para
las subclases del tip0 a las que se refiere, como haria cualquier procedimiento.
Por csta razon es necesario colocar 10s controladores de excepciones de mayor
ambito (10s de la clase E x c e p t i o n ) a1 final. Pero hay que tener presente que
utilizar un controlador para cada excepcion, como el anterior, no suele ser una
buena opcion. Es mejor dejar las excepciones desconocidas para Delphi. El con-
trolador de excepciones por defecto de la VCL muestra el mensaje de error de la
clase de escepcion en un cuadro de mensaje y, a continuacion, reanuda el funcio-
namiento normal del programa. En realidad se puede modificar el controlador de
escepciones normales con el evento A p p l i c a t i o n . O n E x c e p t i o n o el even-
to O n E x c e p t i o n del componente A p p l i c a t i o n E v e n t s , como se demues-
tra en el ejemplo ErrorLog posterior. Otro importante elemento mas del codigo
anterior es el uso del objeto de excepcion en el controlador (vease en E:
E x c e p t i o n do). La referencia E de la clase E x c e p t i o n se refiere a1 objeto
de excepcion pasado por la sentencia r a i s e . Cuando se trabaja con escepcio-
nes, hay que recordar esta norma: se lanza una excepcion mediante la creacion de
un objeto y se controla mediante la indicacion de su tipo. Esto nos ofrece una
importante ventaja, porque como hemos visto, cuando se controla un tipo de
excepcion, en realidad se controlan excepciones del tip0 que se especifica asi
como de cualquier otro tip0 descendiente.

A1 arrancar un programa en el entorno Delphi (por ejemplo, a1 pulsar la


tecla F9), por lo general se ejecuta en el depurador. Cuanda se encuentra
una excepcion, el depurador detendrb por defecto el programa. Asi, sabre-
mos donde tuvo lugar la excepcion y podremo~ver la llamada del controla-
dor paso a paso. Tambih se puede usar la cafacteristica Stack Tmce de
Delphi para ver la secuencia de la funci6n y las llamadas de d o d o que
dieron lugar a que el programa crease una ex&pci6n.
Sin X r G , e x caso del progrim & ejemp1o dxceptioni este cirn---~
portamiento confundira a un programador que no sepa bien c6mo funciona
el depurador de Delphi. Aunque se prepare el d d i g o para controlar de
f o m a adecuada la excepcih, el depurador detendni la ejecucib del pro-
grama en la linea de c6digo fuente m& cercana a1 lvgar m ~l qw se cfeb la
excepcion. Asi, a1 moverse paso a paso por el cMgo, puede verse se
controla. Si sirnplemente queremos dejar que el program se ejtcutt mamb
la excepci6n se controla correctamente, hay que ejecutar el pr0gms.mdes&
el Explorador de Windows o desactivar temporalme& la deteaqhh $top en
las opcioncs de Delphi Extxptions de la ficha ~&guage b c d p f h s
del cuadro de diirlogo Debugger Options (activada mediaate la wden
Tools> Debugger Options), que aparece en la ficha Language
Exceptions del cuadro de diilogo Debugger Options que se muestra a
continuation. Tambien se puede detener el depurador.

'VCLEAbort Exceptions
lndy EIDConnCbsedG~acelul?yEnceplm
' Mtc~osoRDAD Excepl~ons
' V~s~Broke~
lntelnal Except~ons
't CORBA Syslem Exceplans
CORBA User Excepl~onr

Registro de errores
La mayor parte del tiempo, no se sabe que operacion va a crear una excepcion
y no se puede (ni se debe) envolver cada una de las partes del codigo en un bloque
try/except.La tecnica general consiste en dejar que Delphi controle todas las
escepciones y finalmente pasarselas todas a1 usuario, mediante el control del
evento OnException del objeto global Application. Esto se puede hacer
de un mod0 mas sencillo con el componente ApplicationEvents. En el
ejemplo ErrorLog, se ha aiiadido a1 formulario principal una copia del componen-
te Appl icat ionEvent s y un controlador para su evento OnExcept ion:
procedure TForrnLog. LogException (Sender: TObj ect; E: Exception) ;
var
Filename: string;
LogFile : TextFile;
begin
// prepara un a r c h i v o de r e g i s t r o
Filename : = ChangeFileExt (Application.Exename, ' . l o g 1 ) ;
AssignFile (LogFile, Filename) ;
i f FileExists (FileName) then
Append (LogFile) // abre un a r c h i v o e x i s t e n t e
else
Rewrite (LogFile); // c r e a r uno nuevo
tr~
// e s c r i b e e n u n a r c h i v o y m o s t r a r e r r o r
Writeln (LogFile, DateTimeToStr (Now) + ' : ' + E-Message);
i f not CheckBoxSi1ent.Checked then
Application. ShowException (E);
finally
// cierra e l archivo
CloseFile (LogFile);
end:

-- -
- --- - - . - -.
. . .- .-
.- . - .- . -
. -- .- ..- -- - -
-- ~~ - - . -

NOTA: El ejemplo E r r o r h g usa el soporte de archivos de texto que pro-


porciona el tradicional t i p de datos Turbo Pascal TextFile. Se puede asig-
nar una variable de archivo de texto a un archivo real y despues leerlo o
escribirlo.

En el controlador de excepciones global, se puede escribir en el registro, por


ejemplo, la fecha y hora del evento y tambien decidir si mostrar la excepcion
como suele hacer Delphi (ejecutando el metodo ShowException de la clase
TApplicat ion). De hecho, Delphi ejecuta ShowExcept ion de manera pre-
determinada solo si no hay instalado un controlador OnException. La figura
2.8muestra el programa ErrorLog en ejecucion y una excepcion de muestra abierta
en ConTEXT (una practico editor para programadores incluido con Delphi y
disponible en w~vw.fixedsys.com/context).

Referencias de clase
La ultima caracteristica del lenguaje que trataremos en este capitulo son las
referencias de clase, lo cual implica la idea de manipular las propias clases dentro
del codigo. El primer punto que hemos de tener en cuenta es que la referencia de
clase no es un objeto; es sencillamente una referencia a un tipo de clase. Un tipo
de referencia de clase establece el tip0 de una variable de referencia de clase.
Aunque esto suene confuso, con unas cuantas lineas de codigo quedara un poco
mas claro.
.-.

1
- - .-

17/05/2003
..............
-

ll:37:48:Divisiun bv zero
119 - -
......................
I
1 17/05/2003 ll:37:53: raise button pressed I
7/05/2003 11:37:56:Divislon b y zero
7/05/2003 ll:37:58:raise button pressed
7/05/2003 ll:37:59:raise button pressed

Div by 0 1 7/05/2003 11:38:00:raise button pressed

........ .

Figura 2.8. El ejemplo ErrorLog y el registro que produce.

Supongamos que hemos definido la clase T M y C l a s s . Ahora, se puede definir


un nucvo tipo de referencia de clase relacionado con dicha clase:
type
TMyClassRef = class of TMyClass;

Ahora se pueden declarar variables de ambos tipos. La primera variable se


reficre a un objeto, la segunda a una clase:
var
AnOb j ect : TMyClass;
AClassRef: TMyClassRef;
begin
AnObject : = TMyClass.Create;
AClassRef : = TMyClass;

Podriamos preguntarnos para que se usan las referencias de clase. En general,


las referencias de clase permiten manipular un tipo de datos de clase en tiempo de
ejecucion. Se puede usar una referencia de clase en cualquier espresion en la que
sea valido el uso de un tipo de datos. En realidad, no hay muchas expresiones de
este tipo, per0 10s pocos casos que esisten son interesantes, como la creacion de
un objeto. Podemos rescribir las dos lineas anteriores del siguiente modo:
AnObject : = AC1assRef.Create;

Esta vez hemos aplicado el constructor create a la referencia de clase en


lugar de a una clase real. Hemos utilizado una referencia de clase para crear un
objeto de dicha clase.
Los tipos de referencia de clase no serian tan utiles si no soportasen la misma
norma de compatibilidad de tipos que se aplica a 10s tipos de clase. Cuando se
declara una variable de referencia de clase, como MyClas s Ref, se le puede
asignar esa clase especifica y cualquier subclase. Por lo tanto, si TMyNewClas s
es una subclase de nuestra clase, tambien se puede escribir
AClassRef : = TMyNewClass; "uno"

Delphi declara una larga lista de referencias de clase en la biblioteca de tiempo


de ejecucion y en la VCL, como por ejemplo las siguientes:
TClass = class of TObject;
TComponentClass = class of TComponent;
TFormClass = class of TForm;

En concreto, el tipo de referencia de clase TC la s s se puede usar para almace-


nar una referencia de cualquier clase que se escriba en Delphi, porque toda clase
se deriva en ultimo termino de TOb j ec t . La referencia T FormClas s, en cam-
bio, se usa en el codigo fuente de la mayoria de 10s proyectos Delphi. El metodo
Create Form del objeto Appl i cat ion, en realidad, requiere como parametro
la clase del formulario que va a crear:
Application. CreateForm(TForm1, Forml) ;

El primer parametro es una referencia de clase, el segundo es una variable que


almacena una referencia a la instancia de objeto creada.
Por ultimo, cuando se tiene una referencia de clase, se le pueden aplicar 10s
metodos de clase de la clase relacionada. Si tenemos en cuenta que cada clase
hereda de TOb j ec t, se pueden aplicar a cada referencia de clase algunos de 10s
metodos de TObject.

Crear componentes usando referencias de clase


El uso practico de las referencias de clase en Delphi consiste en poder manipu-
lar un tip0 de datos en tiempo de ejecucion, lo cual es un elemento fundamental
del entorno Delphi. Cuando se aiiade un componente nuevo a un formulario, se-
leccionandolo de la Component Palette, se selecciona un tip0 de datos y se crea un
objeto de dicho tipo de datos. (En realidad, eso es lo que Delphi hace sin que
podamos verlo.) En otras palabras, las referencias de clase aportan polimorfismo
para la construction de objetos. Para que pueda hacerse una idea de como funcio-
nan las referencias de clase, hemos creado un ejemplo llamado ClassRef. El for-
mulario que aparece en este ejemplo es bastante sencillo. Tiene tres botones de
radio, situados dentro de un panel en la parte superior del formulario. Cuando
seleccionamos uno de estos botones de radio y hacemos clic sobre el formulario,
podremos crear nuevos componentes de estos tres tipos indicados por las etique-
tas del boton: botones de radio, botones de pulsador y cuadros de edicion.
Para que este programa se ejecute correctamente, es necesario modificar 10s
nombres de 10s tres componentes. El formulario tambien tendra un campo de
referencia de clase, declarado como C l a s s R e f : T C o n t rolclass. Almace-
na un nuevo tipo de datos cada vez que el usuario hace clic sobre uno de 10s tres
botones de radio, con asignaciones como C l a s s R e f := T E d i t . La parte
interesante del codigo se ejecuta cuando el usuario hace clic sobre el formulario.
Hemos escogido de nuevo el evento O n M o u s e D o w n del formulario para tener
acceso a la posicion del cursor del raton:
procedure TForml.FormMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer) ;
var
NewCtrl: TControl;
MyName: String;
begin
// crea e l control
NewCtrl := ClassRef-Create (Self);
/ / l o o c u l t a t e m p o r a l m e n t e , para e v i t a r e l parpadeo
NewCtrl.Visible : = False;
// d e f i n e padre y p o s i c i d n
.
NewCtrl Parent := Self;
NewCtrl-Left := X;
NewCtrl.Top : = Y;
/ / c a l c u l a e l nombre u n i c o ( y e l t i t u l o )
Inc (Counter);
MyName : = ClassRef .ClassName + IntToStr (Counter);
Delete (MyName, 1, 1);
NewCtrl.Name : = MyName;
// l o rnuestra ahora
NewCtrl.Visible : = True;
end ;

La primera linea del codigo de este metodo es la clave. Crea un nuevo objeto
del tipo de datos de clase almacenados en el campo C l a s s R e f . Esto se consigue
simplemente aplicando el constructor c r e a t e a la referencia de clase. Ahora se
puede establecer el valor de la propiedad P a r e n t , fijar la posicion del nuevo
componente, darle un nombre (que se usa tambien automaticamente como
C a p t i o n o T e x t ) y hacerlo visible.

I NOTA: Para que fkncione la construccion polimorfica. el tipo de la clase


I basica de la referencia de clase habd dc tener un constructor virtual. Si re
m a w constructor virtual (corno en el ejemplo), la llamada dcl constructor
apljcgda la referencia de clase llamara at constructor del tip0 al que
realmente sc refiere la variable de referencia de clase. Pero sin un construc-
tax virtual, el cbrfigQ llamara a1 constructor del tipo dc clase fijo indicado
exi k~&clwa&:de Ia referencia de clase. Los constructores son nccesarios
para la construccion,~olimorf~ca del mismo modo que 10s mktodos virtuales
son nccesariba para ei poIimorfismo.
biblioteca
en tiempo

El lenguaje de programacion Delphi favorece un enfoque orientado a objetos,


junto con un estilo visual de desarrollo. Es aqui donde sobresale Delphi y tratare-
mos acerca del desarrollo visual y basado en componentes a lo largo de este libro;
sin embargo, deseo subrayar el hecho de que muchas de las caracteristicas listas
para ser utilizadas de Delphi proceden de su biblioteca en tiempo de ejecucion
(RTL).Se trata de un gran conjunto de funciones que puede utilizar para realizar
tareas sencillas, a1 igual que algunas complejas, dentro de su propio codigo Pascal.
(Utilizo aqui "Pascal" porque la biblioteca en tiempo de ejecucion contiene princi-
palmente procedimientos y funciones escritas con 10s mecanismos tradicionales
del lenguaje y no con las extensiones de orientacion a objetos aiiadidas al lenguaje
por Borland.)
Existe un segundo motivo para dedicar este capitulo del libro a la biblioteca en
tiempo de ejecucion: Delphi 6 supuso un gran numero de mejoras en este campo,
y Delphi 7 aporta algunas mejoras mas. Estan disponibles nuevos grupos de fun-
ciones, se han desplazado funciones a nuevas unidades y han cambiado otros
elementos, lo que crea unas pocas incompatibilidades con el codigo antiguo a
partir del cual podria adaptar sus proyectos. Por eso, incluso aunque haya utiliza-
do las versiones antiguas de Delphi y se sienta comodo con la RTL, aun asi
deberia leer a1 menos parte de este capitulo. Este capitulo comenta 10s siguientes
temas:
Nociones generales de la RTL.
Funciones de la RTL de Delphi.
El motor de conversion
Fechas, cadenas de caracteres y otras nuevas unidades de la RTL.

Informacion de clase en tiempo de ejecucion.

Las unidades de la RTL


En las versiones mas recientes de Delphi, la RTL (biblioteca en tiempo de
ejecucion) posee una nueva estructura y varias unidades nuevas. Borland aiiadio
nuevas unidades ya que tambien aiiadio numerosas funciones nuevas. En la mayo-
ria de 10s casos, las funciones existentes se encuentran en las unidades en las que
solian estar, pero las nuevas funciones aparecen ahora en unidades especificas.
Por ejemplo, las nuevas funciones relacionadas con fechas estan en la unidad
DatcUtils, per0 las funciones de fecha que ya existian no se han movido de SysUtils,
para evitar incompatibilidades con el codigo existente.
La excepcion a esta norma tiene que ver con algunas de las funciones de
soporte de variantes, que se han extraido de la unidad System para evitar enlaces
no deseados de bibliotecas especificas de Windows, incluso en programas que no
utilizaban dichas caracteristicas. Estas funciones variantes son ahora parte de la
nucva unidad Variants.

D
AD
Dephi 5 necesite ntilizar nueva unidad Variants para volver a compi-
esta
lar. Delphi es lo suficientemente listo como para darse cuenta de ello e
incluir automiticamente la unidad Variants en proyectos que usan el tipo
Variant ,emitiendo unicarnente UM advertencia.

TambiCn se han aplicado ciertos ajustes para reducir el tamaiio m i n i m ~de un


archivo ejecutable, a veces ampliado por inclusiones no deseadas de variables
globales o codigo de inicializacion.

I
El tamafio emutable bajo el microscopio

nucmn a e ~ramano mumno ae programa en unos cuantos h a parece algo


ncnte, pero suponc una gran ayuda para 10s
desarrolladoTes. En aQpgtbs casos. incfuso anos cuantos KB (rttuItiplicados
pur muchas apliCaciones) puedcn reducir cl tam-iio y, cn ultima instancia.
el tiempa de descarga.
Como pcqueiia prueba, h a s ercado el programa Minisiic, que n o e s a n

scr y muesua el resulraao en mymensajc. cr p g a m a nomme vauanas


de alto nivel. A b m i x se utilka la funcibn s k r ~ p a r acu&ertir un edero-
I

en una cadena y no incluir sysufjls, ,que definc toc4-f


mas complejas e implicaria un pequcfia numcntb I@
Si este prograrna se compila con Delphi 5. s e ~ wcf:i
. - - .--. - . . . - . i. . -
de 18.452 bytes. Uelphi b reduce dicho tamano a mh 1 5 . 3 6 ~ ~ b y t erecor-
.
tando unos 3 KB.Con Delphi 7. qFtama5o o~.solgIigeranl,en& mayor. cle
15.872 bytes. A1 reemplazar laiWena larg&.por m a caden? colta y mod I-
7---o-- ------ -- r--
Y

ta menos de 10 KB. Esto es debido a rque se acabath eliminando las rutinas


de
-- scmarte
- - r ---- deedenas
- ,tamhih el
v -- ,:stor
se de memoria, lo cud es hicamen-
te posible en programas que utilizan exclusivamente llamadas de bajo ni-
vel. Se pueden enwntrar ambas versiones en el c6digo fuente del archivo de
ejemplo.
Fijese, de todos modos, en que las decisiones de este tip^ siempre implican
una sene de concesiones. A1 eliminar el encabezamientode variantes de las
aplicaciones Delphi qure no las usan, por ejemplo, Borland ha aiiadido una
carga extra a ias aplicaciones que si lo h a m . La ventaja real de esta opera-
cibn, sin embargo, estA en el reducido tamafio en memoria que necesitan las
aplicaciones Delphi que no usan variantes, como consecuencia de no tener
que introducir varios megabytes debido a las bibliotecas de sistema Ole2.
Lo realmente importante, en mi opini&n,es el tamaiio de las grandes aplica-
ciones Delphi basadas en paquetes en tiempo de ejecuci6n. Una sencilla
prueba con un programa que no hace nada, el ejemplo Minipack, muestra
un ejecutable de 17.408 bytes.

En 10s siguientes apartados encontrara una lista de las unidades de la RTL en


Delphi, asi como de todas las unidades disponibles (con el codigo fuente comple-
to) que se encuentran en el subdirectorio Source\Rtl\Sys del directorio Delphi
y algunas de las disponibles en el subdirectorio Source\Rtl\Common. Este
segundo directorio contiene el codigo fuente de las unidades que conforman el
nuevo paquete de la RTL, que engloba tanto la biblioteca basada en funciones
como las clases centrales comentadas m h adelante.

Comentare de forma breve el papel de cada unidad y tambien 10s grupos de


funciones incluidas. Ademas dedicare mas espacio a las unidades mas nuevas. No
se trata de ofrecer una lista detallada de las funciones incluidas, ya que la ayuda
electronica incluye un material de referencia similar.
Sin embargo, la intencion es fijarse en algunas funciones interesantes o poco
conocidas.
Las unidades System y Syslnit
System es la unidad principal de la RTL y se incluye automaticamente en
cualquier compilacion (siempre que haya una sentencia uses automatica e im-
plicita que se refiera a ella). En realidad, si intentamos aiiadir la unidad a la
sentencia uses de un programa, obtendremos el siguiente error en tiempo de
compilacion:
[Error] Identifier redeclared: System

La unidad System se compone entre otras cosas de:


La clase TO^ j ect, que es la clase basica de toda clase definida en el
lenguaje Pascal orientado a objetos, como todas las clases de la VCL.
Las interfaces IInterface,IInvokable,IUnknown y IDispatch,
asi como la clase de implementation simple T Inter facedOb j ect.
I~nterfacese aiiadio en Delphi 6 para recalcar el hecho de que el tip0
de interfaz en la definicion del lenguaje Delphi, no depende en mod0 algu-
no del sistema operativo Windows. I~nvokablese aiiadio en Delphi 6
para soportar las llamadas basadas en SOAP.
Codigo de soporte de variantes, como las constantes de tip0 variante, el
tip0 de registro TVarData y el nuevo tipo TVariantManager,un
amplio numero de rutinas de conversion de variantes y tambien registros
variantes y soporte de matrices dinamicas. En este ambito ha habido un
monton de cambios en comparacion con Delphi 5.
Muchos tipos de datos basicos, como 10s tipos de punteros y de matrices y
el tipo TDateTime.
Rutinas de asignacion de memoria, como GetMem y FreeMem y el pro-
pio administrador de memoria, definido por el registro TMemoryManager
y a1 que se accede mediante las funciones GetMemoryManager y
SetMemoryManager. Para mas informacion, la funcion GetHeap-
Status devuelve una estructura de datos THeapStatus. Dos nuevas
variables globales (A11ocMemCount y A1 locMemSize) guardan el
numero y tamaiio total de 10s bloques de memoria asignados. En el capitulo
sobre la arquitectura de las aplicaciones Delphi encontrara mas informa-
cion sobre la memoria y estas funciones.
El codigo de soporte de modulos y paquetes, como el tip0 de punter0
PackageInfo,la funcion global GetPackageInfoTable y el pro-
cedimiento EnumModules.
Una lista bastante larga de las variables globales, como el caso de aplica-
cion Windows MainInstance; IsLibrary,que indica si el archivo
ejecutable es una biblioteca o un programa independiente; Isconsole,
que indica aplicaciones de consola; I s M u l t i T h r e a d , que indica si esis-
ten hilos de proceso secundarios; y la cadena de la linea de comandos
CmdLine. (La unidad incluye tambien P a r a m c o u n t y P a r a m S t r para
poder acceder mas facilmente a 10s parametros de la linea de comandos.)
Algunas de estas variables son especificas de la plataforma Windows, otras
estan tambien disponibles en Linux, mientras que otras son especificas de
Linux.
El codigo de soporte de hilos de proceso (threads), con las funciones
B e g i n T h r e a d y E n d T h r e a d ; registros de soporte de archivos y ruti-
nas relacionadas con archivos; rutinas de conversion de cadenas anchas y
cadenas OLE; asi como muchas otras rutinas de sistema y de bajo nivel
(junto con una serie de funciones de conversion automaticas).
La unidad que acompaiia a System, denominada SysInit, incluye el codigo de
inicializacion, con funciones que rara vez se utilizaran directamente. Esta es otra
unidad que siempre se incluye de forma implicita, puesto que la unidad System
hace uso de ella.
Cambios recientes en la unidad System
Ya se han mencionado algunas caracteristicas interesantes de la unidad System.
La mayoria de 10s cambios estan relacionados con el objetivo de conseguir que la
RTL de Delphi sea mas facil de transportar entre distintas plataformas, reempla-
zando caracteristicas especificas de Windows por implementaciones genericas
que ahora comparten Delphi y Kylix. De acuerdo con esta tendencia, existen
nombres nuevos para tipos de interfaz, soporte para variantes totalmente revisa-
do, nuevos tipos de punteros, soporte de matrices dinamicas y funciones para
personalizar la adrninistracion de 10s objetos de excepcion.

uso que se hace de la c&qdacibn condicio& con muchas referencias a


($IFDEF L M ) p ($WDEF MSTKMDOWS), que se usan para diferen-
ciar entre 10s dos sistemas o~erativos.Fiiese en me aara Windows. Borland
utiliza MSWINDOWS &a indicar 1; plataf&r& a1 complete, ya q e
WINDOWS se utilizaba en las versiones de 16 bits del sistema operativo
(en contraste con el simboIo WIN32).

Por ejemplo, otro aiiadido para la compatibilidad entre Linux y Windows esta
relacionado con 10s saltos de linea en 10s archivos de testo. La variable
DefaultTextLineBreakStyle, afecta a1 comportamiento de las rutinas
que leen y escriben en archivos, como la mayoria de las rutinas de flujos de texto.
Los valores posibles para esta variable global son t l b s L F (valor predetermina-
do en Kylix) y t l b s C R L F (valor predeterminado en Delphi). El estilo de salto de
linea tambien se puede configurar archivo por archivo mediante la funcion
S e t T e x t L i n e B r e a k S t y l e . Del mismo modo, la constante global de cadena
s L i n e B r e a k tiene el valor #13#10 en la version Windows del entorno de
desarrollo y el valor # 1 0 en la version para Linux. Otro cambio es que la unidad
System incluye ahora las estructuras T F i leRec y TTex t Rec, que en versiones
anteriores de Delphi estaban definidas dentro de la unidad S y s u t i l s .

Las unidades SysUtils y SysConst


La unidad SysConst define una serie de cadenas de constantes utilizadas por
otras unidades RTL para mostrar mensajes. Estas cadenas se declaran con la
palabra clave r e s o u r c e s t r i n g y se guardan en 10s recursos de programa. A1
igual que otros recursos, se pueden traducir mediante el Integrated Translation
Manager o el External Translation Manager.
La unidad SysUtils es un conjunto de utilidades del sistema de varios tipos. A
diferencia de otras unidades RTL, es en gran parte una unidad dependiente del
sistema operativo. La unidad SysUtils no posee un enfoque especifico, sino que
engloba una pequeiia parte de todo, desde la gestion de cadenas a1 soporte de
caracteres multibyte y locales, desde la clase E x c e p t i o n y muchas otras clases
de excepcion derivadas a una multitud de constantes y rutinas de formato de
cadena. Algunas de las caracteristicas de SysUtils las utilizan todos 10s progra-
madores a diario, como las funciones de formato de cadena I n t T o S t r o Format.
Otras caracteristicas son menos conocidas, como el caso de las variables globales
de informacion sobre la version de Windows. st as indican la plataforma Windows
(Window 9x o NT/2000/XP), la version del sistema operativo y el numero de
creacion, asi como el paquete de servicio instalado. Se pueden usar del mismo
mod0 que en el siguiente codigo, extraido del ejemplo Winversion:
case Win32Platform of
VER-PLATFORM-WIN32-WINDOWS: ShowMessage ('Windows 9x');
VER-PLATFORM-WIN32-NT: ShowMessage ( 'Windows NT ' ) ;
end:

ShowMessage ( 'Ejecutando en Windows: ' + IntToStr


(Win32MajorVersion) + ' . ' + IntToStr (Win32MinorVersion) + '
(Creaci6n ' + IntToStr (Win32BuildNumber) + ' ) ' + #10#13 +
' Actualizacidn: ' + Win32CSDVersion) ;

El segundo fragment0 de codigo crea un mensa-je como el que muestra en la


siguiente figura, dependiendo, claro esta, de la version del sistema operativo que
se hava instalado.
Otra caracteristica poco conocida de esta unidad es la clase T M u l t i R e a d -
ExclusiveWriteSynchronizer (probablemente la clase VCL de nombre
mas largo). Borland ha definido un alias para la clase, que es mucho mas corto:
TMREWSync (ambas clases son identicas). Esta clase soporta multithreading:
permite trabajar con recursos que pueden usar diversos threads a1 mismo tiempo
para leer (multilectura), pero que a1 escribir han de utilizar un unico thread (es-
critura exclusiva). Esto significa que no se puede comenzar a escribir hasta que
todos 10s threads de lectura hayan terminado su labor.
La implementation de la clase TMultiReadExclusiveWriteSyn-
c h r o n i z e r se ha actualizado en Delphi 7, pero mejoras similares estan dispo-
nibles en forma de un parche que aparecio tras la segunda actualizacion de Delphi
6. La nueva version de la clase esta mas optimizada y menos sujeta a bloqueos,
que suelen ser un problema habitual del codigo de sincronizacion.
- -
NOTA: El sincronizador multilectura es linico porque soporta bloqueos
recursivos y conversi6n de 10s bloqueos de lectura en bloqueos de escritura.
El objetivo principal de la clase es permitir un acceso rapid0 y facil a
diversos threads de lectura a1 recurso compartido, per0 a h asi pennitir
que un thread obtenga el control exclusive del recurs; para reali&r actua-
lizaciones relativamente poco frecuentes. Hay otras clases de sincronizacion
-.-. - . . .. . .. --. - - .
.a

en Delphi, declaradas en la unidad SyncObj s (disporuble baj0 Source /


R t 1 /Common) y con correspondencia directa con 10s objetos de
sincronizaci6n del sistema operativo'(como eventos y secciones criticas en
Windows).

Nuevas funciones de SysUtils


Durante las ultimas versiones, Delphi ha aiiadido algunas funciones nuevas
dentro de la unidad SysUtils. Uno de 10s nuevos campos esta relacionado con la
conversion de booleano a cadena. La funcion B o o l T o S t r por lo general devuel-
ve -1 y 0 como valores verdadero o falso. Si se especifica el segundo parametro
optional, la funcion devuelve la primera cadena de las matrices T r u e BoolS t rs
y F a l s e B o o l S t r s (por defecto T R U E y FALSE):
BoolToStr (True) / / devuelve '-1 '
BoolToStr (False, True) / / devuelve 'FALSE' p o r d e f e c t o

La funcion inversa es S t r T o B o o l , que puede convertir una cadena que con-


tenga uno de 10s valores de las dos matrices booleanas mencionadas anteriormen-
te o un valor numerico. En este ultimo caso, el resultado sera verdadero si el valor
numerico es distinto de cero. Se puede ver una sencilla demostracion del uso de
las funciones de conversion booleanas en el ejemplo StrDemo. Otras funciones
aiiadidas a SysUtils estan relacionadas con las conversiones de coma flotante en
tipos divisa y fecha-hora: FloatToCurr y FloatToDateTime se pueden
usar para evitar una conversion de tipos explicita. Las funciones TryStrTo-
Float y TryStrToCurr intentan convertir una cadena en un valor de coma
flotante o de divisa, y, en caso de error, devuelven el valor False en lugar de
generar una excepcion (corno hacen las clasicas funciones StrTo Float y
StrToCurr).
La Ans iDequotedStr,que elimina comillas de una cadena, se correspon-
de con la funcion Ans iQuotestr aiiadida en Delphi 5. Con respecto a las
cadenas, desde Delphi 6 existe un soporte muy mejorado de cadenas anchas, con
una serie de rutinas como W i d e u p p e r c a s e , W i d e L o w e r C a s e ,
WideCompareStr,WideSameStr,WideCompareText,WideSameText
y WideFormat. Todas estas funciones se utilizan como sus homologos
AnsiString.
Existen tres funciones ( T r y S t r T o D a t e , T r y E n c o d e D a t e y
TryEncodeTime) que intentan convertir una cadena en una fecha o codificar
una fecha u hora, sin crear una excepcion, de un mod0 similar a las funciones
Try antes mencionadas. Ademas, la funcion DecodeDate Fully devuelve in-
formation mas pormenorizada, como el dia de la semana y la funcion
CurrentY ear devuelve el aiio de la fecha actual.
Hay una version que se puede transportar, sobrecargada de la funcion
GetEnvironmentvar iab le. Esta nueva version usa parametros de cadena
en lugar de parametros PChar y es, en definitiva, m b facil de utilizar:
function GetEnvironmentVariable(Name: string): string;

Otras funciones nuevas estan relacionadas con el soporte de interfaz. Dos


nuevas versiones sobrecargadas de la poco conocida funcion support permiten
verificar si un objeto o una clase soporta una interfaz dada. La funcion se corres-
ponde con el comportamiento del operador is para clases y se proyecta a1 metodo
QueryInterf ace.Veamos un ejemplo:
var
W1: IWalker;
J1: IJumper;
begin
W1 : = TAthlete.Create;
// mds codigo. . .
i f Supports (wl, IJumper) then
begin
J1 : = W1 as IJumper;
Log (J1.Walk) ;
end;

SysUtils incluye tambien una funcion IsEqualGUID y dos funciones de


conversion de cadenas a GUID y viceversa. La funcion CreateGUID ha sido
desplazada a sysutils para que este disponible tambien en Linux (con una
implernentacion personalizada, por supuesto).
Por ultimo, en las ultimas versiones se han aiiadido algunas funciones mas de
soporte para varias plataformas. La funcion Ad j us tLineBrea ks puede reah-
zar ahora diferentes tipos de ajustes en las secuencias de retorno de carro y de
avance de linea, y se han introducido nuevas variables globales para archivos de
texto en la unidad System. La funcion Fi 1eCrea te tiene una version sobrecar-
gada en la que se pueden especificar derechos de acceso a archivos a la manera
Unix. La funcion ExpandFi 1eName puede localizar archivos (en sistemas de
archivos que distinguen entre mayusculas y minusculas), incluso cuando su tipo-
grafia no se corresponde exactamente. Las funciones relacionadas con 10s
delimitadores de ruta (barra inversa o barra oblicua) son ahora mas genericas que
en las versiones precedentes de Delphi, por lo que se les han asignado nombres
nuevos de acuerdo con ello. (Por ejemplo, la vieja funcion I ncludeTraling-
Backslash ahora es mas conocida como IncludingTrailingPathDe-
limiter).
Ya que hablamos de archivos, Delphi 7 aiiade a la unidad SysUtils la funcion
Get Fi levers ion,que lee el numero de version a partir de la informacion de
version que se aiiade opcionalmente a un archivo ejecutable de Windows (que es
por lo que esta funcion no funcionara sobre Linux).

Rutinas extendidas de formato de cadenas en Delphi 7


La mayor parte de las rutinas de formato de cadenas de Delphi utilizan varia-
bles globales para determinar 10s separadores de decimales y miles, 10s formatos
de fecha y hora, etc. Los valores de estas variables se leen en primer lugar desde
el sistema (la configuracion local de Windows) cuando arranca un programa, y se
puede sobreescribir cualquiera de ellas. Sin embargo, si el usuario modifica las
opciones regionales en el Panel de control mientras que el programa se esta ejecu-
tado, el programa respondera a1 mensaje radiado actualizando las variables, con
lo que probablemente se perderan 10s cambios introducidos directamente en el
codigo. Si necesita distintos formatos de salida en diferentes partes de un mismo
programa, puede aprovecharse del nuevo conjunto de rutinas sobrecargadas de
formato de cadenas; admiten un parametro adicional de tipo T FormatSettings,
que incluye todas las opciones relevantes. Por ejemplo, ahora hay dos versiones
de Format:
function Format (const.Format : string;
const Args: array of const) : string; overload;
function Format (const Format: string; const Args: array of
cons t ;
const FormatSettings: TFormatSettings) : string; overload;

Decenas de funciones disponen de este nuevo parametro adicional, que se usa


en lugar de las opciones globales. Sin embargo, puede inicializarlo con las opcio-
nes predeterminadas del ordenador en el que se ejecutar su programa mediante la
invocation de la nueva funcion GetLocale Format Settings (solo disponi-
ble en Windows, no en Linux).
La unidad Math
La unidad Math (matematica) csta compuesta por un conjunto de funciones
matcmaticas: unas cuarenta funciones trigonomdtricas, funciones logaritmicas y
esponenciales, funciones de redondeo, evaluaciones polinomicas; casi treinta fun-
ciones estadisticas y una doccna dc funciones economicas.
Describir todas estas funcioncs seria bastante aburrido, aunquc algunos lecto-
res probablementc se cncuentren muy intcresados en las capacidadcs matematicas
dc Delphi. Es por esto, que hemos decidido centrarnos en las funciones matcmati-
cas prescntadas en las illtimas vcrsiones de Delphi (en particular Delphi 6) y
tratar dcspues un tema espccifico que suele confundir a 10s programadorcs dc
Delphi, el redondeo.
Veamos algunas dc las funcioncs matematicas mas nuevas.

Nuevas funciones matematicas


Las vcrsioncs recicntes de Delphi ahaden a la unidad Math un numero considc-
rable dc caractcristicas nuevas. Esiste soporte para constantes infinitas
( In f i n i t y y Neg I n f i n i t y ) y funciones de comparacion relac~onadas
( I s I n f i n i t e y I s N a n ) . junto con las nuevas funciones trigonomdtricas para
cosecantcs y cotangcntes. y nuevas funciones dc conversion de angulos.
Una caracteristica nluy comoda es la disponibilidad de una funcion sobrecar-
gada I f T h e n . que devuelvc uno de dos valores posiblcs, con dependencia de una
expresion booleana. (Ahora tambien hay una funcion similar disponiblc para ca-
denas.) Puede usarse, por ejcmplo. para calcular el minimo dc dos valorcs:
nMin : = IfThen (nA < nB, na, nB) ;

NOTA: La hncion IfThen es similar a1 operador ? : del lenguaje C/


C++,que es muy util porque permite reemplazar una sentencia completa
i f /th e n / e l s e por una expresion mucho mas breve, escribiendo menos
codigo y declarando normalmente menos variables temporales.

RandomRange y RandomFrom se pueden usar en lugar dc la traditional


funcion Random para tener un mayor control de 10s valores aleatorios produci-
dos por la RTL.
La primera funcion devuelve un numero comprendido cntrc dos cstremos que
se especifican, mientras que el segundo escoge un valor aleatorio de una matriz de
numeros posiblcs quc sc pasa como un parametro.
La funcion booleana I n R a n g e se puede usar para comprobar si un numero sc
encuentra entrc otros dos valores. La funcion E n s u r e R a n g e , en cambio, obliga
a que el valor cstd dentro del rango especificado. El valor dc retorno es el propio
numero o el limite mas bajo o limite mas alto, en el caso de quc el numero sc
encuentrc fuera del rango. Veamos un cjcmplo:
// a c t u a s o l o s i e l v a l o r e s t d e n t r e e l m i n y e l max
if InRange (value, min, max) then

/ / s e a s e g u r a q u e e l v a l o r e s t d e n t r e min y m x
value : = EnsureRange (value, min, m a x ) ;

Otro grupo muy util de funciones esta relacionado con las comparaciones. Los
numeros de coma flotante son basicamente inexactos. Un numero de coma flotan-
tc cs una aproximacion de un valor real teorico. Cuando realizamos operaciones
matematicas con numeros de coma flotante, la inesactitud de 10s valores origina-
les se acumula en 10s resultados. Si multiplicamos y dividimos por el mismo
numero puede que no consigamos exactamente el numero original, sino uno muy
proximo a 121. La funcion samevalue permite verificar si dos valores se aproxi-
man lo suficiente como para ser considerados iguales. Se puede especificar el
grado de aproximacion que deberian tener dos numeros o dejar que Delphi calcule
un rango de error razonable para la representacion que estamos utilizando. (Por
csta razon se sobrecarga la funcion.)
Del mismo modo, la funcion Iszero compara un numero con cero, mediante
esta misma "logica borrosa".
La funcion C o m p a r e v a l u e usa la misma norma para 10s numeros de coma
flotante per0 esta disponible tambien para enteros. Devuelve una de las tres cons-
tantes LessThanValue, EqualsValue y GreaterThanValue (que se
corresponden con -1,O y 1). Del mismo modo, la nueva funcion sign devuelve
-1,0 y 1 para indicar un valor negativo, cero o un valor positivo.
La funcion D i v M o d es equivalente a las operaciones de division y resto, de-
volviendo el resultado de la division del entero y del resto al mismo tiempo La
funcion RoundTo nos permite especificar el digito de redondeo (permite, por
ejemplo, redondear hasta el millar mas proximo o hasta dos decimales):
RoundTo (123827, 3 ) ; // e l r e s u l t a d o e s 1 2 4 . 0 0 0
RoundTo (12.3827, -2); // e l r e s u l t a d o e s 1 2 , 3 8
. .
ADVERTENCIA: Fijese en que la funci6n RoundTo usa un nhnero po-
sitivo para indicar la potencia de diez h a s h la que hay que redondear (por
ejemplo, 2 para centenas) o un n6mero negativo para el numero de cifras
decimales. Esto es exactamente lo contrario de la funci6n Round utilizada
por hojas de calculo como Excel.

TambiCn ha habido algunos cambios en las operaciones de redondeo estandar


de la funcion Round: ahora, se puede controlar el mod0 en que la FPU (la Unidad
de Coma Flotante de la CPU) realiza el redondeo llamando a la funcion
SetRoundMode. Existen tambien funciones de control del mod0 de precision de
la FPU y sus excepciones.
Redondeo y dolores de cabeza
La clasica funcion Round de Delphi y las mas recientes funciones RoundTo
se proyectan sobre algoritmos de redondeo de la CPU y la FPU. De manera
predeterminada, las CPU de Intel utilizan el redondeo bancario, que es tambidn el
tipo de redondeo que se suele encontrar en aplicaciones de hojas de calculo.
El redondeo bancario se basa en la suposicion de que cuando se redondean
numeros que residen exactamente entre dos valores (10s numeros ,5), a1 redon-
dearlos arriba o abajo se aumenta o reduce estadisticarnente la cantidad total (en
general de capital). Por este motivo, la regla del redondeo bancario indica que 10s
numeros ,5 deberian redondearse arriba o abajo dependiendo de que el numero
(sin decimales) sea impar o par. De esta manera, el redondeo se equilibrara, a1
menos estadisticamente. La figura 3.1 muestra un ejemplo del resultado del re-
dondeo bancario. Se trata de un ejemplo diseAado para demostrar distintos tipos
de redondeo.

Figura 3.1. El ejernplo de redondeo, dernuestra el redondeo bancario y el aritmetico.

El programa tambidn utiliza otro tipo de redondeo proporcionado por la uni-


dad Math mediante la funcion SimpleRoundTo, que utiliza un redondeo arit-
mktico asimetrico.
En este caso, todos 10s numeros ,5 se redondean a1 valor superior. Sin embar-
go, tal y como se recalca en el ejemplo de redondeo, la funcion no actua como se
esperaria cuando se redondea hasta un digito decimal (es decir, cuando se pasa un
segundo parametro negativo). En este caso, debido a 10s errores de representacion
de 10s numeros de coma flotante, el redondeo recorta 10s valores; por ejemplo
convierte 1,15 en 1,l en lugar del esperado 1.2.
La solucion es multiplicar el valor por diez antes de redondear, redondearlos
hasta cero digitos decimales, y despues dividirlo, como se muestra a continua-
cion:
(SimpleRoundTo ( d *10 , 0 ) / 10 )
Las unidades ConvUtils y StdConvs
En la unidad ConvUtils se encuentra el nucleo del motor de conversion presen-
tad0 en Delphi 6. Utiliza las constantes de conversion definidas por una segunda
unidad, StdConvs.

I NOTA: DeJphi 7 supone solo una mejora en esta unidad de coaversi6n: 1


-te parastones (la unidad britiinica de medida que es equivalente
c
.. C,.. U"I"..:,,
a 14 lib&). LII , ," ..:c:,,* ,., ,;,l m l G j a I a.:a,.*U, u I u a u G a UG,a, ,
,
I U ~ U L G Ibaa", 3 1 LIGUG ~ U IG
, ..a..;I.
a
,
LIIGUIUU 6 1 1

su codigo, apreciara las caracteristicas disponibles en este motor.

La unidad DateUtils
La unidad DateUtils es una nueva coleccion de funciones relacionadas con la
fecha y la hora. Engloba nuevas funciones para seleccionar valores de una varia-
ble TDa t e T i m e o contar valores de un intervalo dado como:
// e s c o g e r v a l o r
function DayOf (const AValue : TDateTime) : Word;
function HourOf (const AValue : TDateTime) : Word;
/ / v a l o r en r a n g o
function WeekOf Year (const AValue : TDateTime) : Integer;
function HourOfWeek (const AValue: TDateTime) : Integer;
function SecondOfHour (const AValue: TDateTime) : Integer;

Algunas de estas funciones son bastante extraiias, c o m o M i l l i S e c o n d O f -


M o n t h o S e c o n d o f w e e k , pero 10s desarrolladores de Borland han decidido
suministrar un con.junto de funciones complete, sin importar lo poco practicas
que parezcan. (Realmente he utilizado algunas de estas funciones en mis e.jem-
plos.)
Existen funciones para calcular el valor final o inicial de un intervalo de tiem-
po dado (dia, semana, mes, aiio) como la fecha actual y para verificacion del
rango y consultas. Por e.jemplo:
function DaysBetween (const ANow, AThen: TDateTime) : Integer;
function WithinPastDays(const ANow, AThen: TDateTime;
const ADays: Integer) : Boolean;

Otras funciones abarcan el increment0 y decrement0 por parte de cada interva-


lo de tiempo posible, codificando y "recodificando" (reemplazando un elemento
del valor T D a t e T i m e , como el dia, por uno nuevo) y realizando comparaciones
"borrosas" (comparaciones aproximadas en las que una diferencia de una milesi-
ma de segundo haria que dos fechas fuesen iguales).
En general, DateUtils resulta bastante interesante y no es excesivamente dificil
de utilizar.
La unidad StrUtils
La unidad StrUtils es una nucva unidad presentada en Delphi 6 con algunas
nucvas funciones relacionadas con cadenas. Una de las caractcristicas clave dc
csta unidad es la existencia de muchas funcioncs de comparacion de cadenas. Hay
funciones basadas en un algoritmo "soundex" ( A n s i R e s e m b l e T e x t ) ; y algu-
nas que ofrecen la capacidad de realizar busquedas en matrices de cadenas
(Ans iMatc h T e x t y Ans i I n d e x T e x t ) , localizar y sustituir subcadenas (como

NOTA: Soundex es un algoritmo para comparar nombres basados en el


mod0 en que suenan y no en el modo en que se deletrean. El algoritmo
calcula un numero para cada sonido de la palabra, de modo que comparan-
do dos de esos numeros se puede decidir si dos nombres suenan igual. El
sistema lo aplic6 por pimerzi vez en 1880 la U.S.Bureau of the census (La
Oficina del Censo de EEUU); se patent6 en 1918 y en la actualidad es de
dominio publico. El codigo soundex es un sistema de indexado que traduce
- - - - - .... - : I : _ _
1 3- ---__
-..-L-_ _--_
L 3 -
L- l-L-_ A_-_
nomores a un coalgo ae cuatro caracteres Iormaao por una m r a y rres
..-A

numeros. Puede encontrar mas information a1 respecto en www.nara.gov/


genealogylcoding .html .

Mas a116 de las comparaciones, otras funciones proporcionan una prueba en


dos direcciones (la simpatica funcion I f T h e n , similar a la que ya hemos visto
para 10s numeros), duplican e invierten cadenas y sustituyen subcadenas. La ma-
yoria de estas funciones de cadena se aiiadieron por comodidad para 10s progra-
madores en Visual Basic que se pasaban a Delphi. Hemos utilizado algunas de
dichas funciones en el ejemplo StrDemo, que usa tambien algunas conversiones
de booleano a cadena definidas dentro de la unidad SysUtils. El programa en
realidad es algo mas que una prueba para unas cuantas funciones. Por ejemplo, se
usa la comparacion "soundex" entre las cadenas introducidas en dos cuadros de
edicion. convierte el booleano resultante en una cadena y lo muestra:
ShowMessage (BoolToStr (AnsiResemblesText
(EditResemblel-Text, EditResemble.2 .Text) , True) ) ;

El programa tambien utiliza las funciones An s i M a t c h T e x t y


Ans i I n d e x T e x t , tras haber rellenado una matriz dinamica de cadenas (deno-
minada s t r A r r a y ) con 10s valores de las cadenas del interior del cuadro de
lista. Se podria haber utilizado el metodo I n d e x o f de la clase T S t r i n g s , que
es mas sencillo, pero esto habria anulado el proposito del ejemplo. Las dos com-
paraciones de lista se realizan del siguiente modo:
procedure TForml.ButtonMatchesClick(Sender: TObject);
begin
ShowMessage (BoolToStr (AnsiMatchText(EditMatch.Text,
strArray) , True) ) ;
end;

procedure TForml.ButtonIndexClick(Sender: TObject);


var
m a t c h : Integer;
begin
m a t c h : = AnsiIndexText (EditMatch.Text, strArray) :
ShowMessage ( IfThen ( m a t c h >= 0, ' C o r r e s p o n d e a 1 n u r n e r o d e
c a d e n a ' + IntToStr ( m a t c h ), ' N o c o r r e s p o n d e ' ) ) ;
end;

Fijese en el uso de la funcion I f T h e n en las ultimas lineas de codigo; tiene


dos cadenas de salida alternativas, que dependen del resultado del test inicial
( n M a t c h >= 0).
Tres botones adicionales realizan llamadas sencillas a otras tres funciones
nuevas, con las siguientes lineas de codigo (una para cada una):
// r e p i t e ( 3 v e c e s ) u n a c a d e n a
ShowMessage (Dupestring (EditSample.Text, 3 ) ) ;
// i n v i e r t e l a c a d e n a
ShowMessage (Reversestring (EditSample.Text));
// e s c o g e u n a c a d e n a a l e a t o r i a
ShowMessage (RandomFrom ( s t r A r r a y ) ) ;

De Pos a PosEx
Delphi 7 aporta su granito de arena a la unidad StrUtils. La nueva funcion
PO sE X resultara muy practica para muchos desarrolladores y merece que hable-
mos de ella. Cuando se buscan multiples apariciones de una cadena dentro de
otra, una solucion clasica de Delphi era utilizar la funcion Pos y repetir la bus-
queda sobre la parte restante de la cadena. Por ejemplo, podria contar el numero
de apariciones de una cadena dentro de otra con un codigo como este:
f u n c t i o n CountSubstr (text, sub: string) : Integer;
var
nPos: Integer;
begin
Result : = 0;
nPos : = Pos (sub, t e x t ) ;
while nPos > 0 do
begin
Inc (Result) ;
text : = Copy (text, nPos + Length ( s u b ), MaxInt) ;
nPos : = Pos (sub, text) ;
end;
end;

La nueva funcion PoS E X permite especificar la posicion de comienzo de la


busqueda dentro de una cadena, de manera que no se necesita modificar la cadena
original (que supone una ligera perdida de tiempo). Por eso. el codigo anterior
puedc simplificarsc como:
function C o u n t S u b s t r ( t e x t , s u b : s t r i n g ) : I n t e g e r ;
var
nPos : I n t e g e r ;
begin
R e s u l t : = 0:
n p o s : = PosEx ( s u b , t e x t , 1 ) ; // predeterminado
while nPos > 0 do
begin
Inc ( R e s u l t );
n p o s := PosEx ( s u b , t e x t , nPos + L e n g t h ( s u b ) ) ;
end ;
end ;

Ambas porciones de codigo se utilizan de una manera trivial en el ejemplo


S t r Demo comentado anteriormente.

La unidad Types
La unidad Types (de tipos) almacena tipos de datos comunes a diversos siste-
mas operativos. En las anteriores versiones de Delphi, la unidad de Windows
definia 10s mismos tipos; ahora se han desplazado a esta unidad comun, compar-
tida por Delphi y Kylix. Los tipos definidos aqui son sencillos y engloban, entre
otros. las estructuras de registro TPoint,T R e c t y TSmallPoint mas sus
tipos de punter0 relacionados.

ERTENCIA:Fijese en que tendri que actualizar 10s programas Delphi


uos que hagan referencia a T R e c t o TPo i n t , dadiendo la unidad
s en la sentencia uses: de no ser asi- 10s Droeramas no se comoilarh.

La unidad Variants y VarUtils


Las unidades Variants (de variantes) y VarUtils son dos nuevas unidades pre-
sentadas en Delphi 6 que agrupan las partes de la biblioteca relacionadas con
variantes. La unidad Variants contiene codigo generico para variantes. Algunas
rutinas de esta unidad han sido desplazadas aqui desde la unidad System. Las
funciones abarcan soporte generico de variantes, matrices variantes, copiado de
variantes y convcrsiones de matriz dinarnica a matriz variante. Tambien esta la
clase T C u s t omVa r i an t T y p e , que define 10s tipos de datos variantes
personalizables.
La unidad Variants es totalmente independiente de la plataforma y utiliza la
unidad VarUtils, que contiene codigo dependiente del SO. En Delphi, esta unidad
usa las API dcl sistcma para manipular datos de variantcs: cn Kylis usa codigo
particularizado quc Ic proporciona la biblioteca RTL.

NOTA: En Dclphi 7, estas unidades se han arnpliado y se han solucionado


algunos problcmas. La impiementacion de variantes ha sido remodelada a
conciencia para mejorar la velocidad de esta tecnologia y reducir la ocupa-
cion en memoria de su codigo.

Un arca cspccifica quc ha visto una me-jora significativa en Dclphi 7 es la


capacidad de controlar cl comportamicnto de las implemcntacioncs dc variantes,
en particular las rcglas dc comparacion. Delphi 6 supuso un cambio en el codigo
dc variantes de mancra quc 10s valores null no podian compararsc con otros
valores. Estc comportamiento es correct0 desde un punto de vista formal, de
manera cspccifica para 10s campos de un conjunto dc datos (un area en que se
usan mucho variantcs). pcro cste cambio tuvo cl cfccto colateral de romper el
codigo csistcntc. Ahora sc puede controlar cstc comportamiento mediante el uso
dc las variablcs globales Nu1 lEqual it yRule y Nu1 lMagnitudeRule;
cada una dc las cualcs toma uno de 10s siguicntcs valorcs:
n c r E r r o r : Cualquicr tipo de comparacion provoca quc sc levante una es-
cepcionj \,a quc no pucdc compararse un valor indcfinido: cste cra cl com-
portamicnto prcdctcrminado (nuevo) en Dclphi 6 .
ncrstrict: Cualquier tipo de comparacion falla siempre (devuelvc False),
sin importar 10s valores.
ncrLoose: Las comprobaciones de igualdad solo tienen cxito cntrc valores
nulos (un valor nulo es distinto de cualquicr otro valor). En las compara-
cioncsi 10s valorcs nulos se consideran como valorcs vacios o cero.
Otras opcioncs con10 NullStrictConvert y NullAsStringValue
controlan cl mod0 cn quc sc rcalizan las comparaciones en caso dc valorcs nulos.
Un buen consejo cs cspcrimcntar con el e.jcmplo VariantComp quc se cncuentra
disponiblc mas adelantc. Como mucstra la figura 3.2. este programa dispone de
un formulario con un RadioGroup que se puede utilizar para modificar 10s \ d o -
res de las variablcs globales NullEqualityRule y NullMagnitudeRule
y unos cuantos botoncs para rcalizar diversas comparacioncs.

Variantes personalizadas y numeros complejos


La posibilidad dc ampliar el sistema dc tipos con variantes personalizadas es
una extension rcciente del concept0 dc variantcs. Nos permite definir un nuevo
tipo de datos quc, cn oposicion a la clase; sobrccarga 10s operadores aritmiticos
estandar. Una variantc es un tipo que manticnc tanto la especificacion de tipo
como el valor real. Una variante puedc contener una cadcna. otra pucde contener
un numero. El sistema define conversiones automaticas entre tipos dc variantes
(como variantes personalizadas), lo que le permite mezclarlas en las opcraciones.
Esta flexibilidad tiene un coste muy alto: las operaciones con variantes son mu-
cho mas lentas que las rcalizadas con tipos originales y las variantcs utilizan
memoria adicional.

Figura 3.2. El forrnulario del ejernplo VariantCornp en tiempo de diseiio.

Como ejemplo dc un tip0 de variante personalizada, Delphi aporta una intere-


sante definicion para 10s numeros complejos en la unidad VarCmplx (disponi-
bles en formato de codigo fuente en el directorio Rtl\Common). Se pueden crear
variantes complejas utilizando una de las funciones sobrecargadas Varcomplex-
Create y usarlas en cualquier espresion, como se demuestra en el siguiente
fragment0 de codigo:
var
vl, v2: Variant;
begin
vl : = VarComplexCreate (10, 1 2 ) ;
v 2 : = VarComplexCreate (10, 1) ;
ShowMessage (vl + v2 + 5) ;

Los numeros complejos se definen en realidad utilizando clases, per0 la super-


ficie quc adoptan es la de variantes, mediante la herencia de una nueva clase de la
clase TCus tomVar iantT ype (definida en la unidad Variants), sobrescritura
de una serie de funciones abstractas virtuales y creacion de un objeto global que
se encarga del registro dentro del sistema.
Ademas de estas definiciones internas, la unidad incluye una larga lista de
rutinas para operar con variantes, como las operaciones matematicas y
trigonometricas.

ADVERTENCIA: Construir una variante personalizada no es una tarea


nada sencilla y apenas se pueden encontrar razones para usarlas en lugar de
. . ae usar la soorecarga ae operaaores en las esrrucruras
raja I I . I I I .
objetos y clases. De hecho, con una variante personalizada se tiene la ven-
. l r
ae aaros, per0 se
pierde la verification en tiempo de cornpilacion, se hace que el codigo sea
z l e n t o v i s t i c a s be orlentaci6n a 04et.o~y se
ha de escribir un codigo bastante mas complejo.

Las unidades DelphiMM y ShareMem


Las unidades DelphiMM y ShareMem estan relacionadas con la gestion de
memoria. El administrador de memoria estandar de Delphi se declara en la unidad
S y s tern.
La unidad DelphiMM define una bibliotcca de administrador de mcmoria al-
tcrnativa para utilizarla a1 pasar cadenas de un ejecutable a una DLL (una biblio-
tcca de enlace dinamico de Windows), ambas construidas en Delphi. Esta biblioteca
dc administrador de memoria se encuentra compilada de manera predeterminada
cn el archivo de biblioteca Borlndmrn. dl1 que habra que distribuirjunto con el
programa.
La interfaz de este administrador de memoria se define en la unidad ShareMem.
Esta es la unidad que se habra de incluir (es obligatoria como primera unidad) en
10s proyectos del Gecutablc y de la biblioteca (bbibliotecas).
_LC--- -

NOTA: Al contrario que Delphi, Kylix no dispone de unidades DelphiMM


y ShareMem, ya que la gestion de memoria se proporciona en las bibliote-
cas nativas de Linux (en particular, Kylix utiliza malloc de glibc) y por
eso se comparte efectivamente entre distintos modulos. Sin embargo, en
Kylix, las aplicaciones con multiples modulos deben utilizar la unidad
ShareExcept, que permite que las excepciones lanzadas en un modulo se
reflejen en otro.

Unidades relacionadas con COM


ComConst, ComObj y ComServ proporcionan soporte COM a bajo nivel. Es-
tas unidades no son realmente parte de la RTL, desde un cierto punto de vista, asi
que no se comentaran aqui. Mas adelante encontrara informacion detallada, per0
baste aiiadir que estas unidades no han cambiado mucho en las mas recientes
versiones de Delphi.

Convertir datos
Delphi incluye un nuevo motor de conversion, definido en la unidad ConvUtils.
El motor por si mismo no incluye definicion alguna de las unidades de medida
reales; en cambio, posee una serie de funciones principales para 10s usuarios
finales. La funcion clave es la llamada de conversion, la funcion Convert.
Sencillamente, nosotros proporcionamos la cantidad, las unidades en las que se
expresa y las unidades a las que queremos que se conviertan.
Lo siguiente convertiria una temperatura de 3 1 grados centigrados a Fahren-
heit:
Convert (31, tucelsius, tuFahrenheit)

Una version sobrecargada de la funcion convert permite convertir valores


que poseen dos unidades, como la velocidad (que tiene una unidad de longitud y
una unidad de tiempo). Por ejemplo, se pueden convertir kilometros por hora en
metros por segundo con esta llamada:
Convert (20, duKilometre, tuHours, duMeters, tuseconds)

Otras funciones de la unidad permiten convertir el resultado de una suma o una


resta, verificar si las conversiones se pueden aplicar e incluso listar las familias y
unidades de conversion disponibles.
En la unidad StdConvs, se proporciona un conjunto predefinido de unidades de
medida. Esta unidad tiene familias de conversion y un impresionante numero de
valores, como en el siguiente extracto:
// U n i d a d e s d e c o n v e r s i o n d e d i s t a n c i a s
// l a u n i d a d b d s i c a d e m e d i d a e s e l m e t r o
cbDistanke: TConvFamily;

duAngstroms : TConvType;
dulrlicrons: TConvType;
dulrlillimeters: TConvType;
duMeters : TConvType;
duKilometers: TConvType;
duInches: TConvType;
duMiles: TConvType;
duLightYears: TConvType;
duFurlongs: TConvType;
duHands : TConvType ;
duPicas: TConvType;

Esta familia y las diversas unidades se registran en el motor de conversion en


la parte de inicializacion de la unidad y proporcionan ratios de conversion (guar-
dados como una serie de constantes, como MetersPerInch en el siguiente
codigo):
cbDistance : = RegisterConversionFamily('Distancia');
duAngstroms : = RegisterConversionType(cbDistance, ' A n g s t r o m s ' ,
1E-10) ;
d m i l l i m e t e r s : = RegisterConversionType(cbDistance, ' M i l i m e t r o s ' ,
0.001) ;
duInches : = RegisterConversionType(cbDistance, ' P u l g a d a s ' ,
MetersPerInch) ;
Para probar el motor de conversion, creamos un e.jemplo generic0 (ConvDemo)
quc permite traba.jar con todo el conjunto de conversiones disponibles. El progra-
ma rellena un cuadro combinado con las familias de conversion disponibles y un
cuadro de lista con las unidades disponibles de la familia activa. Este es el codigo:
procedure TForml. Formcreate (Sender: TObject) ;
var
i: Integer;
begin
GetConvFamilies (aFamilies);
for i : = Low (aFamilies) to High (aFamilies) do
ComboFamilies.1tems.Add (ConvFamilyToDescription
(aFamilies [i]) ) ;
// obtiene el primer0 y lanza el evento
ComboFamilies.Item1ndex : = 0;
ChangeFamily (self);
end ;

procedure TForml.ChangeFamily(Sender: TObject);


var
aTypes : TConvTypeArray;
1: Integer;
begin
ListTypes .Clear;
CurrFamily : = aFamilies [ComboFamilies.ItemIndex];
GetConvTypes (CurrFamily, aTypes) ;
for i : = Low(aTypes) to High(aTypes) do
ListTypes.Items.Add (ConvTypeToDescription (aTypes[i]));
end;

Las variables aFamilies y CurrFamily se declaran en la parte privada


del formulario dcl siguiente modo:
aFamilies: TConvFamilyArray;
CurrFamily: TConvFamily;

En este punto, un usuario puede introducir dos unidades de medida y una


cantidad en 10s cuadros de edicion correspondientes del formulario, como se pue-
de ver en la figura 3 3.Para que la operacion sea mas rapida, es posible seleccio-
nar un valor de la lista y arrastrarlo hasta uno de 10s dos cuadros de edicion de
tipo.

pulsado el boton lzquierdo del raton mientras se arrastra el elemento sobre


una de las cajas de edicion que se encuentran en el centro del formulario.
--
- p a r a c o i k g z c & p a z , hay q z - ~ propiedad a
DragMode de la caja de lista (el componente fuente) con el valor
dmAutomatic e implementar 10s eventos OnDragOver y OnDragDrop
de las cajas de edicion objetivo (las dos cajas de edicion se encuentran
conectadas a 10s mismos manejadores de eventos, compartiendo el mismo
codigo). En el primer mktodo, el programa indica que las cajas de edicion
siempre aceptan la operaci6n de arrastre, sin importar la fuente. En el se-
gundo mktodo, el programa copia el texto seleccionado en la caja de lista
(el control source para la operation de arrastre) a la caja de edicion que
haya disparado el evento (el objeto Sender). Este es el c d i g o para 10s
dos metodos:
procedure TForml.EditTypeDragOver(Sender, Source: TObject;
X I Y: Integer; State: TDragState; var Accept: Boolean);
begin
Accept := True;
end;

procedure TForml.EditTypeDragDrop(Sender, Source: TObject;


X I Y: Integer) ;
begin ;
(Sender ar TEdit) .Text := (Source as TListBox) .Items
.
[ (Source as TListBox) ItemIndexl;
end ;

Eamiks
D~stance Snnple Ted I Inslruciimr hap types
lrorn lM to dlboxes.
enlm am*
lvper Base lype: &&:
LghlYeats Cmlirnetus 1100
Parsecs

Fathom
Furbngs Qerlinalica Type Cmvwted Am&
Hands
Paces

Cham

I 1

Figura 3.3. El ejemplo ConvDemo en tiempo de ejecucion.

Las unidades habran de corresponderse con aquellas disponibles en la familia


activa. En caso de error, el texto de 10s cuadros de edicion de tipo aparecc en rojo.
Este es el efecto de la primera parte del metodo D o C o n v e r t del forinulario, que
se activa desde el momento en que el valor de uno de 10s cuadros de edicion para
las unidades o la cantidad cambian. Despues de verificar 10s tipos de 10s cuadros
de edicion, el metodo D o C o n v e r t realiza la conversion real y muestra el resul-
tad0 en el cuarto cuadro de edicion, que esta en gris. En caso de errores, aparece-
ra el mensaje correspondiente en el mismo cuadro. Veamos el codigo:
procedure TForml.DoConvert(Sender: TObject);
var
BaseType, DestType: TConvType;
begin
// o b t i e n e y v e r i f i c a e l t i p o b d s i c o
i f not DescriptionToConvType(CurrFamily, E d i t T y p e - T e x t ,
BaseType) then
EditType.Font.Color : = clRed
else
EditType.Font.Color : = clBlack;

// o b t i e n e y v e r i f i c a e l t i p o d e d e s t i n o
i f not DescriptionToConvType (CurrFamily,
EditDestination-Text,
DestType) then
EditDestination.Font.Color : = c l R e d
else
EditDestination.Font.Co1or : = clBlack;

if (DestType = 0 ) or (BaseType = 0 ) then


EditConverted.Text : = ' T i p o n o v d l i d o '
else
EditConverted.Text : = FloatToStr (Convert (
StrToFloat ( E d i t A m o u n t - T e x t ) , BaseType, DestType));
end;

Si todo esto no resulta interesante, hay que tener en cuenta que todos 10s tipos
de conversion proporcionados en el ejemplo son solo una muestra: se puede perso-
nalizar completamente el motor para que proporcione las unidades de medida en
que se este interesado, como se comentara a continuacion.

iConversiones de divisas?
La conversion de divisas no es exactamente lo mismo que la conversion de
unidades de medida, ya que 10s valores de las divisas cambian constantemente. En
teoria, se puede registrar un valor de cambio en el motor de conversion de Delphi.
De vez en cuando, se comprobara el nuevo indice de cambio, se desregistrara la
conversion ya existente y se registrara la nueva. Sin embargo, mantener la tasa de
cambio real implica modificar la conversion tan a menudo que la operacion po-
dria no tener mucho sentido. Ademas, habra que triangular conversiones: hay que
definir una unidad base (probablemente el euro, si vive en Europa) y convertir a/
y desde esta divisa incluso si la conversion se realiza entre dos divisas distintas.
Por ejemplo, antes de la adopcion del euro como divisa de la Union Europea, lo
mejor era utilizar esta divisa como base para las conversiones entre las divisas de
10s estados miembros, por dos motivos. En primer lugar, 10s tipos de cambio eran
fijos. En segundo lugar, la conversion entre las divisas euro se rcalizaban legal-
mente convirtiendo una cantidad a euros y convirtiendo dcspues esa cantidad en
euros a la otra divisa. el comportamiento exacto del motor de conversion de Delphi.
Existe un pequeiio problema: debcria aplicarse un algoritmo de redondco en cada
paso de la conversion.
Considerarcmos este problema mas adelante, tras ofrccer el codigo base para
integrar las divisas euro con cl motor de conversion de Delphi.

NOTA: El ejemplo Conver I t disponible entre 10s ejemplos Delphi ofre-


ce soporte para las conversiones a1 euro, utilizando un enfoque de redondeo
ligeramente distintos, aunque no tan precis0 como el requerido por las re-
glas de conversion de divisas europeas. Aun asi, resulta aconsejable mante-
ner este ejemplo porque es bastante instructive en relacion con la creaci6n
de un nuevo sistema de medida.

El ejemplo, llamado EuroConv,muestra como registrar cualquier nueva uni-


dad de medida con el motor. Siguiendo la plantilla que proporciona la unidad
S tdConvs creamos una nueva unidad (llamada EuroConvCons t). En la sec-
cion de la interfaz, declaramos las variables para la familia y las unidades especi-
ficas:
interface

var
// U n i d a d e s d e C o n v e r s i o n d e D i v i s a s E u r o p e a s
c b E u r o C u r r e n c y : TConvFamily;

cuEUR: TConvType;
cuDEM: TConvType; // A l e m a n i a
cuESP: TConvType; // E s p a f i a
cuFRF: TConvType; // P r a n c i a
// y e l r e s t o . . .
La seccion de implementation de la unidad define constantes para diversas
tasas de conversion oficiales:
implementation

cons t
DEMPerEuros = 1 , 9 5 5 8 3 ;
ESPPerEuros = 166,386;
FRFPerEuros = 6,55957;
// y e l r e s t o . . .
Finalmente, el codigo de inicializacion de la unidad registra la familia y las
diversas divisas, cada una con su propio tip0 de cambio y un nombre legible:
initialization
/ / T i p o de l a familia de divisas europeas
cbEuroCurrency : = RegisterConversionFamily ( ' D i v i s d s E u r o ) ;

c u E U R : = RegisterConversionType(
cbEuroCurrency, 'EUR', 1) ;
c u D E M : = RegisterConversionType(
cbEuroCurrency, IDEM', 1 / DEMPerEuros) ;
c u E S P : = RegisterConversionType(
cbEuroCurrency, 'ESP', 1 / E S P P e r E u r o s ) ;
c u F R F : = RegisterConversionType(
cbEuroCurrency, ' F R F ', I / FRFPerEuros) ;

NOTA: El motor utiliza como factor de conversion la cantidad de la unidad


base necesaria para obtener las unidades secundarias, con una constante
como Meters P e r I n c h , por ejemplo. El tipo e s t h d a r de las divisas euro
se define al rev&. Por este motivo, se han mantenido las constantes de
conversion con 10s valores oficiales (como DEMPerEuros) y se han pasa-
do a1 motor como fracciones ( I / DEMPerEuros).

Tras registrar esta unidad, se pueden convertir 120 marcos alemanes en liras
italianas de esta manera:
Convert ( 1 2 0 , cuDEM, cuITL)

El programa de ejemplo hace algo mas: ofrece dos cajas de lista con las divisas
disponibles, extraidas como en el ejemplo anterior, y cajas de edicion para el
valor de entrada y el resultado final. La figura 3.4 muestra el formulario.

Ilabn L#e[ITLJ '


Belg~anFrancs [BEF) 1 Cmml BelgianFrancs [BEF)
Dutch Gulders [NLG)
Dutch Gu~lders[NLG]
Austrian Sch~ll~ngi
[ATS] Auslnan Sch~nlngs(ATS]
Poituguese Escudos [PTE) Porluwese Escudos lPTEl
F~nn~shMarks [FIM] ~ m n &~ a r k o[FIM] '
G~eekDrachms [GRD] Greek Drachmas(GRDI
LuxembourgFrancs [LUF)
Luembou'g F'-f [LUFI

Figura 3.4. La salida del ejemplo EuroConv, que muestra el uso del motor de
conversion de Delphi con una unidad de medida personalizada.

El programa funciona bien per0 no perfectamente, ya que no se aplica el re-


dondeo correcto; deberia redondearsc no solo el resultado final de la conversion
sin0 tambien el valor intermedio. Mediante el motor de conversion no se puede
realizar este redondeo directamente de manera sencilla. El motor permite ofrecer
una funcion de conversion o una tasa de conversion particularizadas. Pero escri-
bir funciones de conversion identicas para todas las divisas parece una mala idea,
asi que hemos escogido un camino diferente. (Puede ver ejemplos de funciones de
conversion personalizadas en la unidad StdCo nvs, en la seccion relacionada
con las temperaturas.)
En el ejemplo EuroConv,aiiadimos a la unidad con las tasas de conversion
una funcion EuroConv personalizada que realiza la conversion correcta. Lla-
mando simplemente esta funcion en lugar de la funcion Convert estandar con-
seguiremos el efecto deseado (y no parece existir ningun inconveniente, ya que en
este tip0 de programas es extraiio mezclar divisas con distancias o temperaturas).
De manera alternativa, podriamos haber heredado una nueva clase a partir de
TCo nvT ype Fact o r, proporcionando una nueva version de 10s metodos
FromCommon y Tocommon; o haber utilizado la version sobrecargada de
Regi sterconversionType que acepta estas dos funciones como parametros.
Sin embargo, ninguna de estas tecnicas habria permitido enfrentarse a casos espe-
ciales, como la conversion de una divisa a si misma.
Este es el codigo de la funcion EuroConv, que utiliza la funcion inter-
na EuroRound para redondear a1 numero de digitos especificado en el pa-
rametro Decimals (que debe estar entre 3 y 6, de acuerdo con las reglas oficia-
les):
type
TEuroDecimals = 3. . 6 ;

function EuroConvert (const AValue: Double;


const AFrom, ATo: TConvType;
const Decimals: TEuroDecimals = 3): Double;

function EuroRound (const AValue: Double): Double;


begin
Result :=AValue * Power (10, Decimals) ;
Result : = Round (Result);
Result : = Result / Power (10, Decimals) ;
end ;

begin
// comprobacion d e l c a s o e s p e c i a l : s i n c o n v e r s i o n
if AFrom = ATo then
Result : = AValue;
else
begin
/ / conversion a1 euro y redondeo
Result : = ConvertFrom (AFrom, AValue) ;
Result : = EuroRound (Result);
/ / conversion a la divisa y nuevo redondeo
Result : = ConvertTo (Result, ATo) ;
Result : = EuroRound (Result);
end ;
end ;
Por supuesto, podria desearse ampliar el ejemplo para ofrecer conversion a
otras divisas no europeas, tal vez tomando 10s valores automaticamente desde un
sitio Web.

Gestion de archivos con SysUtils


Para acceder a archivos y a la informacion de archivos, generalmente puede
confiarse en las funciones estandar disponibles en la unidad SysUtils. Confiar en
estas tradicionales bibliotecas de Pascal hace que el codigo sea mas transportable
entre diferentes sistemas operativos (aunque deberian considerarse con mucho
cuidado las diferencias en las arquitecturas del sistema de archivos, en particular
la cuestion de las mayusculas y las minusculas en la plataforma Linux).
Por ejemplo, el ejemplo FilesList utiliza la combinacion F i n d F i r s t ,
F i n d N e x t y F i n d C l o s e para obtener a partir de una carpeta una lista de
archivos que se corresponden con un filtro, con el mismo codigo que se podria
utilizar en Kylis y Linux. La figura 3.5 muestra el aspect0 de este ejemplo.

ID \md7code\O2\ClassRel\ClassRel dpr
D \md7cude\02\C1ealeComps\CreateComps
dp~

~:\rnd7code\02\~ale~rdp\~ale~rd~d~
D:W7code\U2\Dalesl\Datesl dpr
D-\md7code\OZ\DBGridCol\DBG11dCol,dpr
D \rnd7coJe\02\Erro1Log\ErrorLog dpf
D \md7code\02\Excepbun1\Excepbornl.dpr
D.\md7wde\O2\F~mPfopU01mProp.dp
D:\md7code\02\1IDrecl1veUID~ecl1ve.dp1
D:Lnd7code\02\lnllDerno\lnllDemo dpr
D hd7code\02\NewDale\NewDale d p ~
D \md7c~de\02\Polu4n1mals\Polu9nmals.&r

Figura 3.5. Un ejernplo de la salida de la aplicacion FilesList.

El codigo siguiente aiiade 10s nombres de archivo a la caja de lista llamada


1bFiles:

procedure TForml.AddFilesToList(Filter, Folder: string;


Recurse : Boolean) ;
var
sr: TSearchRec;
begin
if FindFirst (Folder + Filter, faAnyFile, sr) = 0 then
repeat
1bFiles. Items .Add (Folder + sr .Name) ;
until FindNext (sr) <> 0;
FindClose (sr);
Si el parametro Re c u r s e se activa, el procedimiento A d d F i l e s T o L i s t
obtiene una lista de subcarpetas inspeccionando 10s archivos locales de nuevo y
autoinvocandose para cada una de las subcarpetas. La lista de carpetas se coloca
en un objeto de lista de cadenas, con el codigo siguiente:
procedure GetSubDirs (Folder: string; sList: TStringList);
var
s r : TSearchRec;
begin
i f FindFirst (Folder + ' * . * I , faDirectory, s r ) = 0 then
try
repeat
i f ( sr.Attr and faDirectory) = faDirectory then
sList .Add (sr.Name) ;
u n t i l FindNext (sr) <> 0;
finally
Findclose ( s r );
end ;
end ;

Finalmente, el programa utiliza una interesante tecnica para solicitar a1 usua-


rio que seleccione el directorio inicial para la busqueda de archivos, mediante una
llamada a1 procedimiento S e l e c t D i r e c t o r y . (Vease la figura 3.6.)
i f SelectDirectory ('Seleccione una carpeta' , I , , CurrentDir)
then . . .

Figura 3.6. El cuadro de dialog0 del procedimiento s e l e c t ~ i r e c t o r y utilizado


, por
la aplicacion FilesList.

La clase TObject
La definicion de la clase T O b j e c t es un elemento clave de la unidad System,
"la madre de todas las clases Delphi". Cada clase del sistema es una subclase de la
clase TObj e c t , directa (si se especifica TOb j e c t como la clase base), impli-
citamente (a1 no indicarse la clase base), o indirectamente (cuando se especifica
otra clase como antecesor). Toda la jerarquia de las clases de un programa en
Pascal orientado a objetos posee una raiz unica. Esta permite usar el tipo de datos
TOb j e c t como substituto del tipo de datos de cualquier tipo de clase del siste-
ma.
Por ejemplo, 10s controladores de eventos de componentes normalmente tienen
un parametro Sender de tipo TObj e c t . Esto significa sencillamente que el
objeto Sender puede pertenecer a cualquier clase, puesto que cada clase se
deriva en ultima instancia de Tob j e c t . El inconveniente mas habitual de esta
tecnica es que para trabajar sobre el objeto, es necesario conocer su tipo de datos.
De hecho, cuando se tiene una variable o un parametro del tipo TObj e c t , se le
pueden aplicar solo 10s metodos y propiedades definidas por la propia clase
TOb j e c t . Si esta variable o parametro se refiere por casualidad a un objeto del
tipo TButton, por ejemplo, no se puede acceder directamente a su propiedad
C a p t i o n . La solucion a este problema recae en el uso de 10s operadores de
conversion segura de tipos siguientes o en 10s operadores de informacion de tip0
en tiempo de ejecucion (RTTI) (10s operadores i s y as).
Existe otra tecnica. Para cualquier objeto, se puede llamar a 10s metodos defi-
nidos en la misma clase TObj e c t . Por ejemplo, el metodo ClassName devuel-
ve una cadena con el nombre de la clase. Debido a que es un metodo de clase, se
puede aplicar tanto a un objeto como a una clase. Supongamos que hemos defini-
do una clase TButton y un objeto B u t t o n 1 de dicha clase. En ese caso, las
siguientes sentencias tendran el mismo efecto:
Text := Button1.ClassName;
Text := TButton.ClassName;

Hay ocasiones en las que es necesario usar el nombre de una clase, per0 tam-
bien puede ser util recuperar una referencia de clase a la propia clase o a su clase
basica. La referencia de clase, de hecho, permite trabajar en la clase en tiempo de
ejecucion, mientras quc cl nombre de clase es simplemente una cadena. Podemos
obtener estas referencias de clase con 10s metodos C l a s sType y C l a s s Parent.
El primer0 devuelve una referencia de clase a la clase del objeto, el segundo a su
clase basica. Cuando tengamos una referencia de clase, podemos aplicarle cual-
quier metodo de clase TOb j e c t , por ejemplo, para llamar a1 metodo ClassName.
Otro metodo que podria resultar util es I n s t a n c e s i z e , que devuelve el
tamaiio en tiempo de ejecucion de un objeto. Aunque se podria pensar que la
funcion global S i z e o f ofrece dicha informacion, esa funcion en realidad de-
vuelve el tamaiio de una referencia al objeto (un punter0 que siempre tiene cuatro
bytes), en lugar del tamaiio del objeto en si.
En el listado 3.1, se puede encontrar la definicion completa de la clase
TOb j e c t , extraida de la unidad System. Ademas de 10s metodos mencionados,
fijese en que I n h e r i t s From proporciona una comprobacion muy similar a la
del operador is, pero que se puede aplicar tambien a clases y referencias de clase
(mientras el primer argument0 dc i s habra dc ser un objeto).

Listado 3.1. La definicion de la clase TObject (en la unidad System de la RTL).

type
TObject = class
constructor Create;
procedure Free;
class function Init Instance (Instance: Pointer) : TObj ect;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(
const Name: string): Boolean;
class function Classparent: TClass;
class function ClassInfo: Pointer;
class function Instancesize: Longint;
class function InheritsFrom(AC1ass: TClass) : Boolean;
class function MethodAddress (const Name : ShortString) :
Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress (const Name: ShortString) : Pointer;
function GetInterface (const IID: TGU1D;out Obj) : Boolean;
class function GetInterfaceEntry(
const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer) : HResult; virtual;
procedure Afterconstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;

I tipo en tiempo de ejecucib (RTTI) dc h clase

Estos metodos de TObj ect estan disponibles para 10s objetos de cada clase,
puesto que TObj ect es el antcccdente comun de cada clase. Veamos como pode-
mos usar estos metodos para acceder a informacion de clase:
procedure TSenderForm.ShowSender(Sender: TObject);
begin
Memo1 .Lines.Add ( 'Nombre de clase:' ' + Sender .ClassName);

if Sender.ClassParent <> nil then


Memol. Lines .Add ( ' Clase p a d r e : ' +
Sender.ClassParent.ClassName);

Memol .Lines .Add ( ' T a m d o de la instancia : ' + IntToStr


(Sender-Instancesize));
end;

El codigo verifica si la Classparent es nil, en caso de que se este utili-


zando realmente una instancia del tipo TOb ject, que no tiene tipo basico.
Este metodo Showsender es parte del ejemplo I f Sender del CD.El me-
todo esta conectado con el evento OnClic k de diversos controles: tres botones,
una casilla de verificacion y un cuadro de edicion. Cuando hacemos clic sobre
cada control, se recurre a1 metodo Showsender con el control correspondiente
como remitente. Uno de 10s botones es en realidad un boton Bitmap, un objeto de
una subclase TButton. Se puede ver un ejemplo de este programa en tiempo de
ejecucion en la figura 3.7

Class Name TButton


Parent Chss: TBultonConlrd
Instance Sin: 536
TButton ClassType
Sender inherits Lom TButton
Sender is a TButlm

Class Name. TBitBtn


Palent Class TBulton
Instance Size: 560
Sender &its from TButton
Sendm is a TBulton
Class Name TCheckBox
Parent Class TCustornCheckBon
Indance Size. 536

Class Nam: TEB


Paent Class: TCustomEdit
lnstace Size: 544

Figura 3.7. El resultado del ejemplo Ifsender

Se pueden usar otros metodos para realizar pruebas. Por ejemplo, se puede
verificar si el objeto Sender es de un tipo especifico con el siguiente codigo:
if Sender-ClassType = TButton then .. .
Tambien se puede verificar si el parametro Sender se corresponde a un obje-
to dado, con este test:
if Sender = Button1 then.. .

En lugar de verificar una clase o objeto concreto, sera necesario, por lo gene-
ral, comprobar la compatibilidad de tip0 de un objeto con una clase dada, es
decir, sera necesario verificar si la clase del objeto es una clase determinada o una
clase de sus subclases. Esto permite saber mejor si se puede trabajar sobre el
objeto con 10s metodos definidos para dicha clase. Esta comprobacion se puede
realizar utilizando el metodo InheritsFrom,a1 que tambien se llama cuando
se usa el operador is.Las siguientes pruebas son equivalentes:
if Sender. InheritsFrom (TButton) then ...
if Sender i s TButton then . . .

Mostrar informacion de clase


Hemos ampliado el ejemplo If Sender para mostrar una lista completa de
clases basicas de un objeto o clase dados. De hecho, cuando tengamos una refe-
rencia de clase, se pueden aiiadir todas sus clases basicas a1 cuadro de lista
List Parent mediante el codigo siguiente:
w i t h ListParent.Items d o
begin
Clear;
w h i l e MyClass.ClassParent <> n i l d o
begin
MyClass := MyClass.ClassParent;
Add (MyClass.ClassName) ;
end;
end;

Se usa una referencia de clase en la parte principal del bucle while, que
comprueba la ausencia de una clase padre (de mod0 que la clase actual es
TOb j ect). Como alternativa, podiamos haber escrito la sentencia while de
una de las siguientes formas:
w h i l e not MyClass.ClassNameIs ('TObject') d o ...
w h i l e MyClass <> TObject do...

El codigo de la sentencia with que se refiere a la lista Listparent es parte


del ejemplo class Info, que muestra la lista de clases padres y alguna otra
informacion sobre una serie de componentes de la VCL (basicamente aquellos de
la pagina Standard de la Component Palette). Dichos componentes se aiiaden de
forma manual a la matriz dinamica que mantiene las clases y que se declara como:
private
ClassArray: array o f TClass;

Cuando se inicia el programa, se usa la matriz para mostrar todos 10s nombres
de clase en un cuadro de lista. A1 seleccionar un elemento del cuadro de lista se
desencadena la presentacion visual de sus datos y sus clases basicas.
-

i
7

N6%?& Comq extension adicional a este ejemplo. es posible crear un arb01


con todas las tlases basicas de diversos componentes en una jerarquia.
La biblioteca
de clases
principales

Ya vimos que Delphi incluye una gran cantidad de funciones y procedimien-


tos, per0 la autentica potencia de la programacion visual en Delphi reside en la
gigantesca biblioteca de clases que proporciona.
La biblioteca de clases estandar de Delphi contiene cientos de clases, con miles
de metodos, y es tan inmensa que no se puede proporcionar una referencia detalla-
da en este libro. En su lugar, exploraremos diversas areas de esta biblioteca a
partir de este punto.
Este capitulo esta dedicado a las clases principales de la biblioteca a1 igual que
a algunas tecnicas estandar de programacion, como la definicion de eventos. Ex-
ploraremos las clases mas habitualmente utilizadas, como las listas, listas de
cadenas, colecciones y streams o flujos. La mayor parte del tiempo exploraremos
10s contenidos de la unidad c1a s se s, per0 tambien examinaremos otras unida-
des principales de la biblioteca.
Las clases de Delphi pueden utilizarse completamente desde el codigo o desde
el diseiiador visual de formularios. Algunas de ellas son clases componentes, que
apareceran en la paleta de componentes, y otras son de proposito mas general.
Los terminos clase y componente puede usarse casi como sinonimos en Delphi.
Los componentes son 10s elementos centrales de las aplicaciones Delphi. Cuando
se escribe un programa, basicamente se escoge un cierto numero de componentes
y se definen sus interacciones, y ya esta.
Antes de comenzar con este capitulo, seria necesario disponer de una buena
compresion del lenguaje, en temas como la herencia, las propiedades, 10s metodos
virtuales, las referencias de clases y demas. Este capitulo trata 10s siguientes
temas:
El paquete RTL, CLX y VCL.
TPersistent y published.
La clase basica TComponent y sus propiedades
Componentes y propiedad
Eventos.
Listas, clases contenedoras y colecciones.
Streaming.
Las unidades del paquete RTL.

El paquete RTL, VCL y CLX


Hasta la version 5, la biblioteca de clases de Delphi era conocida como VCL,
que significa Biblioteca de Componentes Visuales (Visual Components Library).
Se trata de una biblioteca de componentes que se proyecta sobre la API de Windows.
Kylix, la version Delphi para Linux, introdujo una nueva biblioteca de compo-
nentes, denominada CLX, pronunciado "clics", y que significa Biblioteca de Com-
ponentes para Plataforma X o Multiplataforma (Component LibraryforX-Platform
or Cross Platform). Delphi 6 fue la primera version en incluir ambas bibliotecas,
la VCL y la CLX. Para 10s componentes visuales, las dos bibliotecas resultan
alternativas. Sin embargo, las clases principales y las partes de la base de datos e
Internet de las dos bibliotecas son basicamente compartidas.
La VCL estaba considerada como una gran biblioteca unica, aunque 10s pro-
gramadores solian referirse a diferentes partes de ella (componentes, controles,
componentes no visuales, conjuntos de datos, controles data-aware, componentes
de Internet, etc). CLX presenta una division en cuatro partes: BaseCLX,
VisualCLX, DataCLX y NetCLX. La biblioteca utiliza un enfoque totalmente
diferente entre Windows y Linux solo en VisualCLX, puesto que el resto del
codigo puede transportarse de forma inherente a Linux.
En las versiones mas recientes de Delphi, esta distincion se ve resaltada por el
hecho de que 10s componentes y las clases centrales no visuales de la biblioteca
forman parte del nuevo paquete RTL, que utilizan tanto la VCL como la CLX.
Aun mas, utilizar este paquete en aplicaciones no visuales (por ejemplo, en pro-
gramas de servidor Web) permite reducir considerablemente el tamaiio de 10s
archivos que se van a desplegar y cargar en memoria.
Partes tradicionales de la VCL
Los programadores de Delphi se solian referir a distintas partes de la VCL con
10s nombres que Borland sugirio originalmente en su documentacion y que se
hicieron comunes posteriormente para diferentes grupos de componentes. Tecni-
camente, 10s componentes son subclases de la clase TComponent,que es una de
las clases raiz de la jerarquia, como muestra la figura 4.1. En realidad, la clase
TComponent hereda de la clase TPersistent.

ventana
(subclases de
Controles TWinControl)
(oomponentes visuales)

Controles no
de ventana

(TComponent) (subclases de
TGraphicControl)
Componentes no visuales I
I

(otras subclases de
TComponent)

Figura 4.1. Una representacion grafica de 10s principales grupos de componentes


de la VCL.

Ademas de 10s componentes, la biblioteca incluye clases que heredan directa-


mente de TOb j ect j1 de TPers istent. Estas clases se conocen de mod0
colectivo como Objects en parte de la documentacion, un nombre bastante confu-
so. Estas clases no componentes se utilizan normalmente para valores de propie-
dades o como clases de utilidad empleadas en el c6digo; a1 no heredar de
TComponent , no se pueden utilizar directamente en programacion visual.
-- -- -

NOTA: Para ser m b precisos, las clases no componentes no pueden estar


disponibles en la Component Palette ni se pueden dejar eaer ditectamente
en un formulario, pero se pueden,administtar visu.almeste con el Object
Inspector, como subpropiedades de otras propiedadis o elemmtos de varios
tipos. Por lo que, incluso las clases no componentes son n o ~ a h m t faci-
e
les de usar, gracias a la interfaz con el Form Designer. .

Las clases componentes pueden dividirse ademas en dos grupos principales:


controles y componentes no visuales.
Controles: Todas las clases que descienden de TControl.Tienen una
posicion y tamafio en pantalla y aparecen en el formulario en tiempo de
diseiio en la misma posicion que tendrian en tiempo de ejecucion. Los
controles tienen dos subespecificaciones diferentes, basados en ventanas o
graficos.
Componentes n o visuales: Son todos 10s componentes que no son contro-
les, todas las clases que descienden de T C o m p o n e n t pero no de
T C o n t r o l . En tiempo de diseiio, un componente no visual aparece en el
formulario o modulo de datos como un icono (con un titulo debajo opcional
en 10s formularios). En tiempo de ejecucion, algunos de estos componentes
pueden resultar visibles (por ejemplo, 10s cuadros de dialogo estandar) y
otros estan visibles siempre (por ejemplo, el componente de tabla de la
base de datos).

01 o componente en
el Form Designer, s e puede ver una sugerencia sobre herramientas con su
nombre y tip0 de clase (y alguna information ampliada). Se puede utilizar
tambien una opcion del entorno, show Component Captions, parai
vet el nombre del componente no visual bajo su icono.

Esta es la subdivision tradicional de VCL, muy comun para 10s programadores


Delphi. A pesar de la introduccion de CLX y de algunas estructuras de denomina-
cion nuevas, 10s nombres tradicionales sobreviviran probablemente y sc mezcla-
ran en la jerga de 10s programadores en Delphi.

La estructura de CLX
Borland se refiere ahora a distintas secciones de la biblioteca CLX empleando
una terminologia para Linux y una estructura de nombrado ligeramente distinta
(y menos clara) en Delphi.
Esta nueva subdivision de la biblioteca multiplataforma representa areas mas
logicas que la estructura de la jerarquia de clases:
BaseCLX: Forma el nucleo principal de la biblioteca de clases: las clases
mas altas (corno T C o m p o n e n t ) y diversas clases de utilidades generales
(corno listas, contenedores, colecciones y streams). En comparacion con
las clases correspondientes de la VCL, BaseCLX ha cambiado poco y
resulta muy facil de transportar entre las plataformas Windows y Linux.
Este capitulo se dedica en gran medida a explorar BaseCLS y las clases
principales comunes de VCL.
VisualCLX: Es la coleccion de componentes visuales, por lo general lla-
mados controles. Esta es la parte de la biblioteca que esta relacionada mas
estrechamente con el sistema operativo: VisualCLX se implementa en la
parte superior de la biblioteca Qt, disponible tanto en Windows como en
Linux. Utilizar VisualCLX permite una capacidad de transporte total de la
parte visual de una aplicacion entre Delphi en Windows y Kylix en Linux.
Sin embargo, la mayoria dc 10s componentes VisualCLX poscen sus co-
rrespondientes controles VCL, por lo que podemos adaptar facilmente el
codigo de una biblioteca a otra.
DataCLX: Engloba todos 10s componentes relacionados con bases de da-
tos de la biblioteca. En realidad, DataCLX es la fachada del nuevo motor
de base de datos dbExpress incluido tanto en Delphi como Kylix. Delphi
incluye tambien la tradicional interfaz BDE, dbGo e InterBase Express
(IBX). Si consideramos todos estos componentes como parte de DataCLX,
solo la interfaz dbExpress e IBX resultan transportables entre Windows y
Linux. DataCLX incluye tambien el componente C l i e n t Data S e t, aho-
ra llamado MyBa s e y otras clases relacionadas.
NetCLX: Incluye 10s componentes relacionados con Internet, desde el marco
de trabajo WebBroker, a 10s componentes del productor HTML, desde
Indy (Internet Direct) a Internet Express, de WebSnap a1 soporte XML.
Esta parte de la biblioteca es, una vez mas, muy facil de transportar entre
Windows y Linux.

Partes especificas de VCL de la biblioteca


Las anteriores partes de la biblioteca estan disponibles, con las diferencias
mencionadas, tanto en Delphi como en Kylix. Sin embargo, en Delphi existen
otras secciones de la VCL, que por una razon u otra son solo especificas para
Windows:
El marco de trabajo Delphi ActiveX (DAX) proporciona soporte para COM,
Automatizacion OLE, ActiveX y otras tecnologias relacionadas con COM.
Los componentes Decision Cube ofrecen soporte OLAP, per0 tienen lazos
con el BDE y no se han actualizado recientemente. No comentaremos estos
componentes en el libro.
Por ultimo, la instalacion predefinida de Delphi incluye algunos componentes
creados por terceros, como el TeeChart para graficos empresariales, RAVE para
generacion de informes e impresion e IntraWeb para desarrollo para Internet.
Algunos de estos componentes se comentaran en el libro, per0 no forman estricta-
mente parte de la VCL. RAVE e IntraWeb tambien se encuentran disponibles
para Kylix.

La clase TPersistent
La primera clase principal de la biblioteca de Delphi que veremos es su clase
T Pe r s is t e n t , que es bastante atipica: tiene poco codigo y casi no tiene uso
directo, per0 ofrece la base para la idea global de la programacion visual. Se
puede ver la definicion de la clase en el listado 4.1.
Listado 4.1. La definicion de la clase TPersistent, desde la unidad Classes.

ISM+)
TPersistent = class (TObject)
private
procedure AssignError(Source: TPersistent);
protected
procedure AssignTo (Dest: TPersistent) ; virtual;
procedure Defineproperties (Filer: TFiler) ; virtual;
function Getowner: TPersistent; dynamic;
public
destructor Destroy; override;
procedure Assign (Source: TPersistent) ; virtual;
function GetNamePath: string; dynamic;
end;

Como su nombre indica, esta clase controla la permanencia (es decir, el hecho
de guardar el valor de un objeto en un archivo para usarlo mas tarde y volver a
crear el objeto en el mismo estado y con 10s mismos datos). La permanencia es un
elemento clave de la programacion visual. De hecho, en tiempo de diseiio en
Delphi manipulamos objetos reales, que se guardan en archivos DFM y se vuel-
ven a crear en tiempo de ejecucion a1 mismo tiempo que el contenedor especifico
(formulario o modulo de datos) del componente.

- - - - ---- - . .-
NOTA: ~ ~ 1 d o re aplica tarnbien a
10s archivos XF M, el ~ o m r ae o arcolvo que u u ~ ras m t@xwiones CLX.
El formato es idCntico. La diferencia en la extens&n ts 2@Mante porque
Delphi la utiliza para detenninar si el formularib se h a en CLS/Qt o en
. ..
Vf'T
v u r
En U x r l i v tnrln Fnrmn.lnAm m m
r r r u w r r u . uu x r j u n , wuv r w l u r u x u l v v u ~n f~rmulfwripCLXIQt, sin
AUinrlnrtre

importar la extension que se emplee; por eso, la extensi~nXF'IWDFM no


tiene importancia en Kylix.

Sin embargo, el soporte de streaming no esta incluido en la clase


TPersistent, aunque nos lo ofrecen otra clases, que tienen como objetivo
T P e r s i s t ent y sus descendientes. En otras palabras, con el streaming
predefinido de Delphi se puede hacer que "permanezcan" objetos solo de clases
que hereden de T P e r s i s tent. Una de las razones de este comportamiento
recae en el hecho de que la clase se compila con una opcion especial, { $M+).
Este atributo activa la generacion de informacion ampliada RTTI para la parte
publicada de la clase. El sistema de streaming de Delphi, de hecho, no intenta
guardar datos en memoria de un objeto, algo que seria complejo debido a 10s
muchos punteros y a otras posiciones de memoria. En su lugar, Delphi guarda
objetos listando el valor de todas las propiedades de una seccion marcada con una
palabra clave especial, published. Cuando una propiedad se refiere a otro
objeto, Delphi guarda el nombre del objeto o el objeto entero (con el .mismo
mecanismo), dependiendo de su tip0 y relacion con el objeto principal. De 10s
metodos de la clase TPersis tent,el iinico que se utilizara por lo gcneral es el
procedimiento Assign,que puede utilizarse para copiar el valor real de un obje-
to. En la biblioteca, este metodo esta implementado por varias clases no compo-
nentes, pero por muy pocos componentes. En realidad, la mayoria de las subclases
vuelven a implementar el metodo virtual protegido AssignTo, al que llama la
implementation predefinida de Assign.
Otros metodos son, por ejemplo, Def ineproperties,utilizado para per-
sonalizar el sistema de streaming y aiiadir informacion adicional (pseudopropie-
dades); y Getowner o GetNamePath, utilizados por colecciones y otras clases
especiales para identificarse ante el Object Inspector
- .

Streaming de objetos frente a generaci6n de c6digos


El enfoque usado por Delphi (y Kylix) difiere del enfoque que utilizan otras
herramientas y lenguajes de desarrollo visual. Por ejemplo, en Java, el efec-
to de la definicion de un formulario dentro de un IDE es la generacion del
codigo fbente Java usado para crear 10s componentes y fijar sus propieda-
des. Configurar propiedades en un inspector afecta a1 codigo fbente. Algo
similar sucede en C#, aunque las propiedades en este lenguaje son mas
cercanas a1 concepto de propiedades
- - en Delphi. Ya ha visto esto en Delphi;
se puede escribir c6digo para generar 10s componentes en lugar de confiar
en el streaming, pero ya que no existe un soporte especifico en el IDE
..PC..I+C.-A n~,.a~.m..;r. aC...-;h;.. A.LXA;~A- A . . . . ~ I Y ~ ~ + ~
1 G J U l U l I a UGkGJCIl 1 U G a b 1 I U l l G J G b V U I ~ V
lIlLUIU~lIIIGllCG.

Cada enfoque tiene sus ventajas e inconvenientes. Cuando se genera codigo


fbente, se tiene mas control sobre lo que sucede y la secuencia exacta de
creacion e inicializacion. Delphi vuelve a cargar 10s objetos y sus propieda-
des pero retarda algunas asignaciones hasta una fase posterior de retoque,
....
mlclanzaao. .
aste proceso es mas complejo,
T . .

- -
.
para evitar 10s problemas con las referencias a objetos que aun no se han
. ..
. pero queaa tan men ocu~toque . ..
resulta mis simple para el p r o g r ~ b r .
El lenguaje Java permite que una herramienta como JBuilder vuelva a com-
.- - .
pilar ma clase formulano y cargarla en m programa en ejecuci6n para
cada cambio. En un sistema cornpilado como Delphi, ese enfoque seria
mucho 11lPls complejo (entiempo de diseiio Delphi utiliza una versibn falsa,
tecnicahmte Jlamada proxy, del formulario, no el formulario real).
Una ventaia del enfoque utilizado por Delphi es que 10s archivos DFM
pueden traducirse a distintos lenguajes sin afectar a1 c M g o fbente; es por
este motivo que Java ofrece la permanencia XML de formularies. Otra
diferen& es c p e Delphi incrusta el grirfico del compomte en el archivo
DFM,en & ~~~
referemias a archivw e?rternoa.'3hxr esto sim-
plifka el desamHq fpprque to& acab farmanda'parte dd,a~lrkvoe j ~ t a -
bIe] pero tambicn puede imficiwque el e j w t a b l e sea mucho mayor,
, <-
La palabra clave published
Junto con las directivas de acceso public, protected y private se
puede usar una cuarta. dcnominada published.Para cualquier campo. propie-
dad o metodo pub 1ished,el compilador genera inforrnacion ampliada RTTI,
de mod0 que el entorno en tiempo de ejecucion de Delphi o un programa pueden
preguntar a una clase sobre su interfaz publicada. Por ejemplo, cada componente
de Delphi tiene una intcrfaz publicada que es usada por el IDE, en particular por
el Object Inspector. Un uso correct0 de 10s elementos publicados cs importante
cuando se escriben componentes. Normalmente, la partc publicada de un compo-
nentc no contiene ni campos ni metodos sino propiedades y eventos.
Cuando Delphi genera un formulario o modulo de datos, coloca las dcfinicio-
nes de sus componentes y metodos (10s controladorcs de eventos) en la primera
parte de su definition, antes de las palabras clave public y private. Estos
campos y metodos de la parte inicial de la clase son publicados. Cuando no se
aiiade ninguna palabra clave especial antes de un elemento de una clase de com-
ponente, la predefinida es published.
Para ser mas precisos, published es la palabra clave predefinida solo si la
clase se compilo con la directiva de compilador $M+ o desciende de una clase
compilada con $M+.Dado que esta directiva se usa en la clase TPersistent,
la mayoria de las clases de la VCL y todas Ias clases de componentes se predefinen
como published. Sin embargo, las clases no componentes en Delphi (corno
TStream y TList) se compilan con $M-y se predefinen como de visibilidad
publica.
Los metodos asignados a cualquier evento en el IDE (y en 10s archivos DFM)
deberian ser publicados y 10s campos correspondientes a nuestros componentes
en el formulario tambien, para que se conecten automaticamente con 10s objetos
descritos en el archivo DFM y sk creen junto con el formulario.

Acceso a campos y mbtodos publicados


Como he mencionado, solo tres tipos de declaraciones tienen sentido en la
s e c c i h published de una clase: campos, mdtodos y propiedades. En el
chdigo, generalmente se hard referencia a 10s elementos publicados del mis-
mo mod0 que a 10s elementos publicos, es decir, empleando 10s
identificadores correspondientes en el c6digo. Sin embargo, en algunos ca-
sos especiales, es posibIe acceder a elementos publicados en tiempo de
ejecucibn por su nombre. La clase TOb ject tiene tres interesantes mdto-
dos para interactuar en tiempo de ejecucion con campos y metodos:
MethodAddress,MethodName y FieldAddress.
La primera funcih, M e thodAddres s,devuelve la direccion de memoria
del c6digo compilado (una especie de punter0 a fuacibn) del m6todo que se
pasa como p a r h e t r o en una cadena. A1 asignar esta direccibn de mktodo a1
~ n d e ~ ~ e~ t h &ignar
o d~ ~un objeto
& a1 & n p o
D a t a , se puede obtener un puntero a metodo completo. En este punto, para
llamar a1 metodo se debe convertir a tip0 de puntero a metodo correcto.
Este es un fragment0 de cbdigo que recalca 10spuntos clave de esta tecnica:
var
Method: m e t h o d ;
Evt: TNotifyEvent;
begin
Method.Code : = MethodAddress ('ButtonlClickl);
Method-Data := Self;
Evt := TNotif yEvent (Method);
Evt (Sender); // llamada a1 d t o d o

Delphi utiliza un c6digo similar para asignar un manejador de eventos cuando


carga un archivo DFM, ya que estos archivos almacenan el nombre de 10s
metodos utiiizados para manejar 10s eventos, mientras que 10s componentes
guardan el puntero a1 metodo. El segundo metodo, M e t h o d N a m e , realiza
la transformation contraria, devolviendo el nombre del metodo para una
direccion de memoria dada. Este metodo puede utilizarse para conseguir el
nombre de un manejador de evento, dado su valor, algo que Delphi hace
cuando envia mediante streaming un componente a un archivo DFM.
Finalmente, el metodo F i e l d A d d r e s s de TOb ject dewelve la posi-
cion de memoria de un campo publicado, dado su nombre. Delphi usa este
metodo para conectar componentes credos a partir de archivos DFM con
10s campos de su propietario (por ejemplo, un formulario) que tienen el
mismo nombre.
Fijese en que estos tres metodos rara vez se utilizan en programas "nonna-
les", per0 juegan un papel central en el funcionamiento de Delphi. E s t h
estrictamente relacionados con el sistema de streaming. Solo necesitara
utilizar estos mCtodos cuando escriba programas extremadamente dinami-
cos, asistentes de proposito especial u otras extensiones de Delphi.

Acceso a las propiedades por su nombre


El Object Inspector muestra una lista de las propiedades publicadas de un
objeto, incluso para 10s componentes que escribimos. Para ello. confia en la infor-
macion RTTI generada para las propiedades publicas. Cuando se usan algunas
tecnicas avanzadas, una aplicacion puede recuperar una lista de las propiedades
publicadas de un objeto y usarlas.
Aunque esta capacidad no es muy conocida, en Delphi es posible acceder a las
propiedades por nombre, utilizando simplemente la cadena con el nombre de la
propiedad y, a continuacion, recuperando su valor. Acceder a la informacion
RTTI de propiedades es algo posible gracias a un grupo de subrutinas no docu-
mentadas, parte de la unidad TypInfo

ADVERTENCIA: Estas subrutinas siempre han estado no documentadas


en Ias versiones anteriores de Delphi, asi que Borland ha seguido siendo
libre de modificarlas. Sin embargo. desde Delphi 1 a Delphi 7,los cambios
han sido muy limitados y solo han estado relacionados con el soporte de
nuevas caracteristicas, con un alto nivel de compatibilidad hacia atras. En
Delphi 5, Borland aiiadio muchas mas caracteristicas y unas pocas rutinas
"auxiliares" que se promocionan oficialmente (aunque no queden completa-
mente documentadas en el archivo de ayuda sino que se explican ~610con
mmentnrinc e n In ~ ~ n i d a d b

Antes de Delphi 5, era necesario utilizar la funcion GetPropInfo para


recuperar un punter0 a alguna informacion interna sobre la propiedad y aplicar a
continuation una de las funciones de acceso como Get St rProp,a dicho punte-
ro. Tambicn hay que verificar la existencia y el tipo de la propiedad.
Ahora se puede utilizar el nuevo conjunto de rutinas de TypInfo, entre las que
se incluye la practica GetPropValue; que devuelve una variante con el valor
de la propiedad y levanta una escepcion si la propiedad no esiste. Para evitar la
escepcion, sc puede llamar en primer lugar a la funcion IsPublishedProp.A
estas funciones simplcmente se pasa el objeto y una cadena con el nombre de
propiedad. Un parametro opcional mas de GetPropValue permite escoger el
formato para el retorno de 10s valores de las propiedades de cualquier tip0 de
conjunto (ya sea una cadena o el valor numeric0 para el conjunto). Por ejemplo,
se puede utilizar:
ShowMessage (GetPropValue (Buttonl, ' ' C a p t i o n ' )) ;

Esta llamada tiene el mismo efecto que una llamada a ShowMessage en la


que se utilice como parametro Buttonl .Caption. La unica diferencia real es
que esta version del codigo es mucho mas lenta, ya que el compilador generalmen-
te resuelve el acceso normal a propiedades de una manera mas eficiente. La ven-
taja del acceso cn ticmpo de ejecucion es que puede hacer que resulte muy flexible,
como en el ejemplo RunProp. Este programa muestra en un cuadro de lista el
valor de una propiedad de un tip0 cualquiera para cada componente de un formu-
lario. El nombre de la propiedad que buscamos aparece en un cuadro de edicion.
Esto hace que el programa resulte muy flexible. Ademas del cuadro de edicion y
del cuadro de lista, el formulario tiene un boton para crear la salida y algunos
otros componentes solo para verificar sus propiedades. Cuando hacemos clic en
el boton, se ejecuta el siguiente codigo:
uses
TypInfo;
procedure TForml.ButtonlClick(Sender: TObject);
var
I: Integer;
Value: Variant;
begin
ListBoxl.Clear;
for I : = 0 to Componentcount -1 do
begin
if IsPublishedProp (Components[I], Edit1 .Text) then
begin
Value : = Getpropvalue (Components[I], Editl.Text) ;
ListBoxl.Items .Add (Components[I] .Name + ' . ' +
Editl.Text + ' = ' + string (Value))
end
else
ListBoxl.1tems.Add ('No ' + Components[I] .Name + ' ' + .
Editl.Text) ;
end;
end;

Se puede ver el efecto que se obtiene a1 pulsar el boton Fill List mientras se
usa el valor predefinido C a p t i o n del cuadro de edicion en la figura 4.2. Se
puede probar con cualquier otro nombre de propiedad. Los numeros se converti-
ran en cadenas mediante una conversion de variante. Los ob-ietos (como el valor
de la propiedad Font) apareceran como direcciones de memoria

!?!~ow~Y I ~ a b e lCapl~on
l = &Pmpe~ty
Captron
INO
Ed61 Caption
- eFn Lnl

Figura 4.2. El resultado del ejemplo RunProp, que accede a las propiedades por
nombre en tiempo de ejecucion.
- - - - -
ADVERTENCIA: No conviene usar con frecuencia la unidad Typ In f o
en lugar del polimorfismo y de otras thcnicas de acceso a propiedades. Hay
que usar primer0 el acceso a propiedades de clase basica o la conversion de
tipos segura (as) cuando sea necesario, y reservar el acceso RTTI para
propiedades como ultimisimo recurso. Usat tknicas T y p I n f o ralentiza el
codigo, lo hace d s complejo y mris proclive a1 error humano; de hecho,
o& la verificacibn de tipos en tiernpo de compilacibn.
La clase TComponent
Si la clase T P e r s i s t e n t es realmente mas importante de lo que parece a
primera vista, la clase clave en el corazon de la biblioteca de clases basada en
componentes de Delphi es TComponent, que hereda de T P e r s i s t e n t (y de
T O b je c t ) . La clase TComponent define muchos elementos principales de com-
ponentes, per0 no es tan compleja como se puede pensar, puesto que las clases
basicas y el lenguaje ya ofrecen la mayoria de 10s elementos realmente necesarios.

Posesion
Una de las caracteristicas principales de la clase TComponent es la defini-
cion de posesion. Cuando se crea un componente, se le puede asignar un compo-
nente propietario, que sera responsable de destruirlo. Asi, cada componente puede
tener un propietario y puede ser tambien el propietario de otros componentes.
En realidad, existen varios metodos y propiedades publicos de la clase que se
dedican a controlar las dos partes de la posesion. Veamos una lista, extraida de la
declaracion de clase (en la unidad Classes de la VCL):
type
TComponent = class(TPersistent, IInterface,
IInterfaceComponentReference)
public
constructor Create(A0wner: TComponent); virtual;
procedure DestroyComponents;
function FindComponent(const AName: string): TComponent;
procedure InsertComponent(AComponent: TComponent);
procedure RemoveComponent(AComponent: TComponent);
property Components [Index: Integer] : TComponent read
Getcomponent;
property Componentcount: Integer read Getcomponentcount;
property ComponentIndex: Integer
read GetComponentIndex write SetComponentIndex;
property Owner : TComponent read FOwner;

Si se crea un componente y se le asigna un propietario, este se aiiadira a la lista


de componentes ( I n s e r t c o m p o n e n t ) , a la que se accede utilizando la propie-
dad de la matriz C o m p o n e n t s . El componente especifico tiene un o w n e r (pro-
pietario) y conoce su posicion en la lista de componentes propietarios, gracias a la
propiedad C o m p o n e n t I n d e x . Por ultimo, el destructor del propietario se en-
cargara de la destruccion del objeto que posee, llamando a D e s t r o y -
Component s.Hay unos pocos metodos protegidos mas envueltos en este proceso,
per0 esto servira como vision global.
Es importante enfatizar que la posesion de componentes puede resolver una gran
parte de 10s problemas de administracion de memoria de las aplicaciones, si se usa
de forma apropiada. Cuando se usa el Form D e s i g n e r o el Data M o d u l e
D e s i g n e r del IDE, ese formulario o modulo de datos poseera a cualquier com-
ponente que se deje caer sobre el. Si siempre se crean componentes con un propie-
tario (la operacion predefinida a1 usar el diseiiador visual del IDE), solo sera
necesario recordar destruir estos contenedores de componentes cuando ya no 10s
necesitemos y podemos olvidarnos de 10s componentes que contengan. Por ejem-
plo, se elimina un formulario para destruir de una sola vez todos 10s componentes
que contenga, lo que supone una gran simplification en comparacion con tener
que acordarse de liberar todos y cada uno de 10s objetos individualmente. En una
escala mayor, 10s formularios y modulos de datos generalmente perteneceran a1
objeto A p p l i c a t i o n , que es destruido por el codigo de cierre de la VCL que
libera todos 10s contenedores de componentes, con lo que se liberaran 10s compo-
ncntes que contenga.
La matriz Components
La propiedad c o m p o n e n t s se puede usar tambicn para acceder a un compo-
nente que tiene un propietario, digamos, por ejemplo, un formulario. Esta propie-
dad puede resultar muy util (comparada con el uso direct0 de un componente
especifico) para escribir codigo generico, que actue en todos o en muchos compo-
nentes a la vez. Por ejemplo, se puede usar el siguiente codigo para aiiadir a un
cuadro de lista 10s nombres de todos 10s componentes de un formulario:
procedure TForml.ButtonlClick(Sender: TObject);
var
I: Integer;
begin
ListBoxl.Items.Clear;
£0; I : = 0 to Componentcount - 1 do
ListBoxl.1tems.Add (Components [I].Name);
end;

Este codigo usa la propiedad C o m p o n e n t c o u n t . que mantiene el numero


total de componentes que posce el formulario activo y la propiedad c o m p o n e n t s ,
que en realidad es la lista de 10s componentes poseidos. Cuando accedemos a un
valor desde esta lista, se obtiene un valor del tipo TComponent. Por esa razon,
se pueden usar directamente solo las propiedades comunes a todos 10s componen-
tes, como la propiedad N a m e . Para usar propiedades especificas de componentes
concretos, hay que w a r la comprobacion de tipos correcta ( a s ) .

componentes Form. Cuando se emplean estos controles, se pueden aiiadir


otros componentes dentro de ellos. En este caso, el contenedor es el padre
de 10s componentes (como indica la propiedad Parent), mientras que el
formulario es su propietario (como indica la propiedad Owner). Se puede
J
ULYi Y p ~ ~de un formulario
~ o cuadro
~ de grupo
t pard f ~ ~
ma~ersep&doRcontroleS.biiop se puede usar Ia propicdad components
ikl form&.&.r para 'haw& pot d o s lo$ componentes. sea cual sea su
PW. ,

A1 utilizar la propiedad Components.siempre podemos acceder a cada com-


ponente de un formulario. Sin embargo, si hay que acceder a un componente
especifico, en lugar de comparar cada nombre con el nombre del componente que
buscamos, podemos dejar que lo haga Delphi, utilizando el metodo FindCompo-
nent del formulario. Este metodo simplemente recorre la matriz Components
en busca del nombre correspondiente.

Cambio de propietario
Hemos visto que casi todos 10s componentes tienen un propietario. Cuando se
crea un componente en tiempo de diseiio (o desde el archivo D F M resultante); su
propietario sera siempre su formulario. Cuando se crea un componente en tiempo
de ejecucion, el propietario se pasa como parametro a1 constructor create.
owner es una propiedad de solo lectura, por lo que no se puede cambiar. El
propietario s e establece en el momento de la creacion y por lo general no deberia
cambiar durante la vida util de un componente. N o se deberia cambiar el propie-
tario de un componente en tiempo de diseiio ni tampoco su nombre libremente,
porque para hacerlo, se puede llamar a 10s metodos Insertcomponent y
RemoveComponent del propietario, que pasan el componente actual como
parametro. Sin embargo, no se pueden aplicar directamente a ningun controlador
de eventos de un formulario, como se pretende aqui:
procedure TForml.ButtonlClick(Sender: TObject);
begin
RemoveComponent (Buttonl);
Form2.InsertComponent (Buttonl);
end;

Este codigo produce una violacion del acceso a la memoria, porque cuando se
llama a R e m o v e C o m p o n e n t , Delphi desconecta el componente del campo del
formulario (Buttonl), definiendolo como nil.La solucion es escribir un pro-
cedimiento como el siguiente:
procedure ChangeOwner (Component, Newowner: TComponent);
begin
Component.Owner.RemoveComponent (Component);
NewOwner.1nsertComponent (Component);
end;

Este metodo (extraido del ejemplo ChangeOwner) cambia el propietario del


componente. Se le llama junto con el codigo mas simple utilizado para cambiar el
componente padre. Las dos ordenes combinadas desplazan el boton por completo
a otro formulario, cambiando su propietario:
procedure TForml.ButtonChangeClick(Sender: TObject);
begin
if Assigned (Buttonl) then
begin
// c a m b i a e l p a d r e
Buttonl.Parent : = Form2;
// c a m b i a el p r o p l e t a r i o
Changeowner (Buttonl, Form2) ;
end;
end;

El metodo verifica si el campo B u t t o n l se refiere aun a1 control, porque


mientras mueve el componente, Delphi define B u t t o n l como n i l . Podemos
ver el efecto de este codigo en la figura 4.3.

1
Figura 4.3. En el ejemplo Changeowner, al hacer clic sobre el boton Change se
mueve el componente Buttonl a1 segundo formulario.

Para demostrar que el propietario del componente Buttonl cambia realmen-


te, hemos decidido aiiadir otra funcion a ambos formularies. El boton List rellena
el cuadro de lista con 10s nombres de 10s componentes que posee cada formulario,
usando el procedimiento que aparece en el apartado anterior. Al hacer clic sobre
10s dos botones List antes y despues de mover el componente, veremos lo que
Delphi hace internamente. Como caracteristica final, el componente Butt on1
tiene un sencillo controlador para su evento Onclick,para mostrar el titulo del
formulario propietario:
procedure TForml.ButtonlClick(Sender: TObject);
begin
ShowMessage ('Mi p r o p i e t a r i o es ' + ( (Sender as
TButton) .Owner as TForm) .Caption);
end ;
La propiedad Name
Cada componente en Delphi deberia tener un nombre. El nombre ha de ser
unico dentro del componente propietario, que por lo general. es el formulario en el
que se coloca el componente. Esto significa que una aplicacion puede tener dos
formularios diferentes, cada uno con un componente con el mismo nombre. Por lo
general, para que no haya confusion, es mejor mantener nombres de componente
unicos a lo largo de una aplicacion.
Es muy importante establecer un valor adecuado para la propiedad Name: si
es demasiado largo, sera necesario teclear un monton de codigo para usar el
objeto y si es demasiado corto, se pueden confundir diferentes objetos. Normal-
mente, el nombre de un componente tiene un prefijo con el tip0 de componente.
Esto hace que el codigo resulte mas facil de leery permite que Delphi agrupe 10s
componentes en el cuadro combinado o b j e c t I n s p e c t o r , donde se clasifi-
can por nombre.
Existen tres elementos importantes relacionados con la propiedad Name de 10s
componentes:
Primero, en tiempo de diseiio, el valor de la propiedad Name se usa para
definir el nombre del campo de formulario en la declaracion de la clase del
formulario. Este es el nombre que normalmente se va a usar en el codigo
para referirse a1 objeto. Por esa razon, el valor de la propiedad Name ha de
ser un identificador de lenguaje Delphi legal (sin espacios y que empiece
con una letra, no con un numero).
Segundo, si se establece la propiedad Name de un control antes de modifi-
car su propiedad C a p t i o n o T e x t , el nuevo nombre se copia normal-
mente en el titulo. Es decir, si el nombre y el titulo son identicos, entonces
a1 cambiar el nombre tambien cambiara el titulo.
Tercero, Delphi usa el nombre del componente para crear el nombre
predefinido de 10s metodos relacionados con estos eventos. Si tenemos un
componente B u t t o n l , el controlador predefinido del evento OnCl i c k
se llamara B u t t o n l C l i c k , a no ser que se especifique un nombre dife-
rente. Si mas tarde se cambia el nombre del componente, Delphi modifica-
ra 10s nombres de 10s metodos relacionados en fincion de ello. Por ejemplo,
si se cambia el nombre del boton aMyButton, el metodo B u t t o n l C l i c k
se transforma automaticamente en MyBut t onC 1i c k.
Como antes mencionamos, si tenemos una cadena con el nombre de un compo-
nente, se puede obtener su instancia llamando a1 metodo F i n d c o m p o n e n t de
su propietario, que devuelve n i l en caso de no encontrar el componente. Por
ejemplo, se puede escribir:
var
Comp: TComponent;
begin
Comp : = Findcomponent ( ' B u t t o n 1 ' ) ;
if Assigned (Comp) then
with Comp as TButton do
/ / algo d e c o d i g o . . .

NOTA: Delphi incluye t a m b i h una funcion Fi ndGloba lCompone n t,


que encuentra un componente de alto nivel, basicamente un fonnulario o un
modulo de datos, que tenga un nombre dado. Para ser precisos, la funcion
FindGlobalComponent llama a una o mas hnciones instaladas, por
lo que en teoria se puede modificar el resultado de la funcion. Sin embargo,
cuando el sistema de streaming usa FindGlobalComponent, es muy
recomendable no instalar sus propias funciones de sustitucion. Si queremos
buscar componentes en otros contenedores de forma personalizada, simple-
mente hay que escribir una nueva funcion con un nombre personalizado.

Eliminacion de campos del formulario


Cada vcz quc aiiadimos un componentc a un formulario, Dclphi aiiadc una
cntrada para el mismo, junto con algunas dc sus propiedadcs, a1 archivo DFM.
Para cl archivo Pascal, Delphi aiiade cl campo correspondiente en la declaracion
de clase dcl formulario. Este campo del formulario cs una referencia a1 objeto
correspondiente, como succde con cualquicr variable dc tipo de clase en Delphi.
Cuando se crea cl formulario, Dclphi carga el archivo DFM y lo usa para volver
a crear todos 10s componentes y volver a establecer sus propiedadcs de nuevo a
10s valores en ticmpo de diseiio. Entonces, engancha el nuevo objeto a1 campo de
formulario que corresponde a su propiedad N a m e . Es por eso por lo que en el
codigo se puedc usar el campo de formulario para trabajar con el componente
correspondiente.
Por esta razon, es posible tener un componente sin un nombrc. Si una aplica-
cion no manipula el componente o lo modifica cn tiempo dc ejecucion, se puede
eliminar el nombrc del componente del Object Inspcctor. Como ejemplos, cstan
una etiqueta estatica con un testo fijo o un elemento del menu o, incluso mas
obvio, 10s separadores de elementos del menu. Si borramos el nombre, eliminare-
mos el elemento correspondiente de la declaracion de clase de formulario. Esto
reduce el tamaiio del ob-jeto del formulario (a solo cuatro bytes, el tamaiio de la
referencia del objeto) y reduce el archivo DFM, a1 no incluir una cadena ini~til(el
nombre del componente). Reducir el DFM implica tambien reducir el tamaiio del
archivo ejecutable final, aunque solo sea ligeramente.

I
ADVERTENCIA: Si se borran 10s nombres de componente hay que ase- ,
gurarse de que se deja a1 menos un componente con nombre e cada clase 1
- Y el sktema
utilizada en el formulario, de modo aue el'enlazador intelinente
de streaming enlacen el codigo necesario para la clase y lo reconozcan en el
archivo DFM.Si, como ejemplo, se eliminan todos 10s campos de un for-
,..I,,:, ,..-U -
I I I U I ~ I I W~
-,c,,,,
,
G IGLIGIGU
SG
,,
a ,---- ,-a,
w.mlpunGuiGs
ms - L - 1 -..---I,
I Ldual, wauuu GI
, -1
slaicura ,
, -&, : , ,.,
r ; i r ~ g u ~

el fonnulario en tiempo de ejecucion, no sera capaz de crear un objeto de


una clase desconocida y emitira un error indicando que la clase no estir
disponible. Se pueden emplear las rutinas Reg i s t e r C 1 a s s o
Registerclasses para evitar este error.

Tambien se puede mantener el nombre del componente y eliminar manualmen-


te el campo correspondiente de la clase de formulario. Aunque el componente no
tenga un campo de formulario correspondiente, se creara de todos modos, per0
sera un poco mas dificil usarlo (a traves del metodo Findcomponent, por
ejemplo).

Ocultar campos del formulario


Muchos puristas de la orientacion a objetos se quejan de que Delphi no sigue
realmente las reglas de la encapsulation, ya que 10s componentes de un formula-
rio se proyectan sobre campos publicos y puede accederse a ellos desde otros
formularios y unidades. Los campos de 10s componentes se listan en la primera
parte sin nombre de la declaracion de clase, que tiene una visibilidad publicada de
manera predefinida. Sin embargo, Delphi esta predefinido asi para ayudar a 10s
principiantes a aprender a usar el entorno de desarrollo visual de Delphi rapida-
mente. Un programador puede seguir una tkcnica diferente y usar propiedades y
metodos para trabajar en formularios. Sin embargo, existe el riesgo de que otro
programador del mismo equipo pueda pasar por alto esta tCcnica sin advertirlo y
acceda directamente a 10s componentes si se dejan en la parte publicada. La
solution, que muchos programadores desconocen, consiste en mover 10s compo-
nentes a la parte privada de la declaracion de clase. Como ejemplo, hemos tomado
un sencillo formulario con un cuadro de edicion, un boton y un cuadro de lista.
Cuando el cuadro de edicion contiene texto y el usuario pulsa el boton, el texto se
aiiade a1 cuadro de lista. Cuando el cuadro de edicion esta en blanco, el boton se
desactiva. Este es el codigo del ejemplo HideComp:
procedure TForml.ButtonlClick(Sender: TObject);
begin
ListBoxl. Items .Add (Editl-Text);
end;

procedure TForml.EditlChange(Sender: TObject);


begin
Buttonl.Enabled : = Length (Edit1 .Text) <> 0;
end;
Hemos listado estos metodos solo para mostrar que en el codigo de un formu-
lario normalmente nos referimos a 10s componentes disponibles, definiendo sus
interacciones. Por esa razon, parece imposible librarse de 10s campos que corres-
ponden a1 componente. Sin embargo, lo que se puede hacer es ocultarlos y mover-
10s de la parte publicada predefinida a la parte privada de la declaracion de clase
del formulario:
TForml = class (TForm)
procedure ButtonlClick (Sender: TObject) ;
procedure EditlChange(Sender: TObject);
procedure FormCreate (Sender: TObj ect) ;
private
Buttonl: TButton;
Editl : TEdit ;
ListBoxl: TListBox;
end;

Si ejecutamos el programa ahora, tendremos problemas: el formulario se car-


gara bien, per0 debido a que no se inicializan 10s campos privados, 10s eventos
anteriores utilizaran referencias de objeto n i l . Delphi inicia normalmente 10s
campos publicados del formulario utilizando 10s componentes creados desde el
archivo DFM. Podemos preguntarnos que ocurre, si lo hacemos nosotros mismos,
con el siguiente codigo:
procedure TForml .FormCreate (Sender: TOb j ect) ;
begin .
Buttonl := FindComponent ( ' B u tton1 ' ) as TButton;
Editl : = FindComponent ( 'Editl I ) a s TEdit;
ListBoxl := FindComponent ( ' ListBoxl ' ) a s TListBox;
end;

Casi funciona, per0 genera un error de sistema similar a1 comentado en el


apartado anterior. Esta vez, las declaraciones privadas daran lugar a que el
enlazador relacione las implementaciones de aquellas clases, per0 el sistema de
streaming necesita conocer 10s nombres de las clases para localizar la referencia
de clase necesaria y construir 10s componentes mientras carga el archivo DFM.
El toque final que necesitamos es algun codigo de registro para avisar a Delphi
en tiempo de ejecucion de la existencia de las clases de componentes que quere-
mos usar. Deberiamos hacerlo antes de crear el formulario, asi normalmente con-
viene colocar este codigo en la seccion de inicializacion de la unidad:
initialization
Registerclasses ([TButton, TEdit, TListBox]);

La pregunta es si merece el esfuerzo. Se puede obtener un mayor grado de


encapsulado y proteger 10s componentes de un formulario de otros formularios (y
de otros programadores que 10s escriban). Repetir estos pasos para cada formula-
rio puede ser tedioso, asi que lo ideal es utilizar un asistente para generar el
codigo sobre la marcha, ya que se trata de una tecnica muy aconsejable para un
proyecto grande que deba desarrollarse de acuerdo con 10s principios de la pro-
gramacion orientada a objetos.

La propiedad personalizada Tag


La propiedad Tag es una propiedad atipica, porque no tiene ningun efecto. Es
meramente una posicion de memoria adicional, presente en cada clase de compo-
nente, en la que se pueden almacenar valores personalizados. El tip0 de informa-
cion almacenada y la forma en que se usa dependen completamente de nosotros.
Normalmente, es util disponer de una posicion de memoria para adjuntar in-
formation a un componente, sin la necesidad de tener que definirla en su clase.
Tecnicamente, la propiedad Tag almacena un entero largo para poder, por ejem-
plo, almacenar el numero de entrada de una matriz o lista que corresponda a un
objeto. A1 usar la conversion de tipos se puede almacenar en la propiedad Tag,
un puntero, una referencia a objeto o cualquier otra cosa que ocupe cuatro bytes.
Esto permite que un programador asocie virtualmente cualquier cosa con un com-
ponente usando su identificador (tag).

Eventos
En realidad, 10s componentes de Delphi, se programan usando PME: propieda-
des, metodos y eventos. Aunque a estas alturas, 10s metodos y propiedades debe-
rian estar claros, 10s eventos todavia no se han comentado. La razon es que 10s
eventos no implican una nueva funcion del lenguaje, sin0 que son una tecnica
estandar de programacion. Un evento, de hecho, es tecnicamente una propiedad,
con la unica diferencia de que se refiere a un metodo (un tip0 de puntero a metodo,
para ser precisos) en lugar de a otros tipos de datos.

Eventos en Delphi
Cuando un usuario hace algo con un componente, como hacer clic sobre el, el
componente genera un evento. Otros eventos 10s produce el sistema, en respuesta
a una llamada de metodo o un cambio de una de las propiedades del componente
(o incluso de un componente diferente). Por ejemplo, si ponemos el foco en un
componente, el componente que tenga el foco en ese momento lo pierde y se
desencadena el evento correspondiente. Tecnicamente, la mayoria de 10s eventos
Delphi se desencadenan a1 recibir el mensaje correspondiente del sistema operati-
vo, aunque no existe un mensaje individual para cada evento. Los eventos de
Delphi suelen ser de mayor nivel que 10s mensajes del sistema operativo y Delphi
ofrece una serie de mensajes adicionales entre componentes.
Desde un punto de vista teorico, un evento es el resultado de una peticion
enviada a un componente o control, que puede responder a1 mensaje. Siguiendo
ese enfoque, para controlar el evento clic de un boton, seria necesario crear una
subclase para la clase TButton y aiiadir el nuevo codigo del controlador de
eventos dentro de esa nueva clase.
En la practica, crear una nueva clase para cada componente que queramos
usar es demasiado complicado como para resultar razonable. En Delphi, el con-
trolador de eventos de un componente es normalmente un metodo del formulario
que contiene el componente, no del propio componente. En otras palabras, el
componente confia en su propietario para controlar eventos. Esta ttcnica se deno-
mina delegation y resulta basica para el modelo basado en componentes de Delphi.
De este modo, no hay que modificar la clase T B u t t o n , a no ser que queramos
definir un nuevo tip0 de componente, sino sencillamente personalizar su propieta-
rio para modificar el comportamiento del boton.

NOTA: Como se vera a continuacibn, 10s eventos en Delphi se basan en


punteros a m&odos. E s b es bastante distinto de Java, que emplea clases de
escucha (1i s t ene r) con m M o s para una Earnilia de eventos.Estos mC-
todos de escucha llaman a 10s controladores & e m t o s . C# y .NET utilizan
una idea similar a las clases delegadas. El t&nmiao "delegadon es el mismo
que el usado tradicionalmente en la bibliograa sobre Delphi para explicar
la idea de 10s controladores de eventos.

Punteros a metodo
Los eventos dependen de una caracteristica especifica del lenguaje Delphi: 10s
punteros a metodo. Un tipo de puntero a metodo es como un tip0 de procedimien-
to, pero uno que se refiere a un metodo. Tecnicamente, un tip0 de puntero a
metodo es un tip0 de procedimiento que tiene un parametro Self implicito. En
otras palabras, una variable de un tip0 de procedimiento almacena la direccion de
una funcion a la que Ilamar, dado que tenga un conjunto de parametros. Una
variable puntero a metodo almacena dos direcciones: la direccion del codigo del
metodo y la direccion de un caso del objeto (datos). La direccion de la instancia
del objeto aparecera como self dentro de la estructura del metodo, cuando se
llame al codigo del metodo utilizando este puntero a metodo.
- -

NOTA: Esto explica la definicih del tipo genCrico de Delphi TMethod,


un registro con un campo Code y un campo Data.

La declaracion de un tip0 de puntero a metodo es similar a la de un tipo de


procedimiento, except0 por el hecho de que tiene las palabras clave of ob j e c t
a1 final de la declaracion:
t m e
IntProceduralType = procedure (Num: Integer);
IntMethodPointerType = procedure (Num: Integer) o f object;

Cuando hemos declarado un puntero a metodo, como el anterior, se puede


declarar una variable de ese tipo y asignarle un metodo compatible (un metodo
que tenga 10s mismos parametros, tipo de retorno, convencion de llamada) de otro
objeto. Cuando se aiiade un controlador de eventos OnClick para un boton,
Delphi hace exactamente eso. El boton tiene una propiedad de tipo de puntero a
metodo, llamada OnClick y se le puede asignar directa o indirectamente un
metodo de otro objeto, como un formulario. Cuando un usuario hace clic sobre el
boton, se ejecuta este metodo, aunque lo hayamos definido dentro de otra clase.
Lo que sigue es un fragmento del codigo que en realidad usa Delphi para
definir el controlador de eventos de un componente boton y del metodo relaciona-
do de un formulario:
t m e
TNotif yEvent = procedure (Sender: TObj ect) o f object;

MyButton = class
OnClick: TNotifyEvent;
end;

TForml = class (TForm)


procedure ButtonlClick (Sender: TObject) ;
Buttonl: MyButton;
end;

var
Form1 : TForml ;

Ahora dentro de un procedimiento, se puede escribir:

La unica diferencia real entre este fragmento de codigo y el codigo de la VCL


es que OnC 1 i c k es un nombre de propiedad y 10s datos reales a 10s que se refiere
se llaman FOnClic k.Un evento que aparece en la ficha Events del Object Ins-
pector, de hecho, no es nada m h que una propiedad de un tipo de puntero a
metodo. Esto significa, por ejemplo, que se puede modificar de forma dinamica el
controlador de eventos asociado a un componente en tiempo de diseiio o incluso
construir un nuevo componente en tiempo de ejecucion y asignarle un controlador
de eventos.

Los eventos son propiedades


Otro concept0 importante que ya hemos mencionado es que 10s eventos son
propiedades. Esto significa que para controlar un evento de un componente, se
asigna un metodo a la propiedad de evento correspondiente. Cuando hacemos
doble clic sobre un valor de evento en el Object Inspector, se aiiade un nuevo
metodo a1 formulario propietario y se asigna a la propiedad de evento correcta del
componente.
Esta es la razon por la cual es posible compartir el mismo controlador de
eventos para diversos eventos o cambiar un controlador de eventos en tiempo de
ejecucion. Para utilizar esta caracteristica no se necesita mucho conocimiento
sobre el lenguaje. De hecho, cuando se selecciona un evento en el Object Inspec-
tor, se puede pulsar el boton de flecha situado a la derecha del nombre del evento
para ver una lista desplegable de metodos compatibles (una lista de metodos que
tienen la misma definicion que el tipo de puntero a metodo). Al usar el Object
Inspector, es facil seleccionar el mismo metodo para el mismo evento de diferen-
tes componentes o para diferentes eventos compatibles del mismo componente.
Aiiadiremos un evento muy sencillo. Se denominara O n C h a n g e y se puede
usar para advertir a1 usuario del componente de que el valor de la fecha ha cam-
biado. Para definir un evento, simplemente definimos una propiedad correspon-
diente a1 mismo y aiiadimos algunos datos para almacenar el puntero a metodo
real a1 que se refiere el evento. Estas son las nuevas definiciones aiiadidas a la
clase, disponibles en el ejemplo DateEvt:
type
TDate = c l a s s
private
FOnChange: T N o t i f y E v e n t ;
...
protected
p r o c e d u r e Dochange; dynamic;
...
public
p r o p e r t y OnChange : T N o t i f y E v e n t
read FonChange w r i t e FOnChange;
.-.
end;

La definicion de propiedad es sencilla. Un usuario de esta clase puede asignar


un nuevo valor a la propiedad y, por lo tanto, a1 campo privado F O n C h a n g e . La
clase no asigna un valor a1 campo F O n C h a n g e ; es el usuario del componente el
que lo hace. La clase T D a t e simplemente llama a1 metodo almacenado en el
campo F O n C h a n g e cuando cambia la fecha. Por supuesto, la llamada se realiza
solo si se ha asignado el evento correctamente. El metodo D o C h a n g e (declarado
como metodo dinamico, como es tradicional en el caso de 10s metodos de lanza-
miento de eventos) realiza la comprobacion y la llamada a1 metodo:
p r o c e d u r e TDate.DoChange;
begin
i f A s s i g n e d (FOnChange) t h e n
FOnChange ( S e l f ) ;
end;
Asi, el metodo D o C h a n g e se llama cada vez que uno de 10s valores cambia,
como en el siguiente metodo:
.
p r o c e d u r e TDate SetValue (y, m, d: Integer) ;
begin
fDate : = EncodeDate (y, m, d);
// a c t i v a e l e v e n t o
DoChange ;

Si prestamos atencion a1 programa que utiliza esta clase, podemos simplificar


en gran medida su codigo. En primer lugar, aiiadimos un metodo personalizado a
la clase del formulario:
type
TDateForm = class (TForm)
...
p r o c e d u r e DateChange (Sender: TObj ect) ;

El codigo del metodo simplemente actualiza la etiqueta con el valor actual de


la propiedad T e x t del objeto T D a t e :
p r o c e d u r e TDateForm.DateChange;
begin
LabelDate.Caption : = TheDay.Text;
end :

Este controlador de evento se instala despues en el metodo F o r m C r e a t e


p r o c e d u r e TDateForm. FormCreate (Sender: TObject) ;
begin
TheDay : = TDate.Init (2003, 7 ,4) ;
LabelDate.Caption : = TheDay.Text;
/ / a s i g n a r e l c o n t r o l a d o r d e e v e n t o para f u t u r o s cambios
TheDay.OnChange : = DateChange;
end :

Parece mucho trabajo, per0 es cierto que el controlador de eventos ahorra


bastante programacion. Despues de haber aiiadido algo de codigo, nos podemos
olvidar de actualizar la etiqueta cuando se cambien 10s datos de algun objeto.
Veamos como ejemplo el controlador del evento O n C l i c k de uno de 10s botones:
p r o c e d u r e TDateForm.BtnIncreaseClick(Sender: TObject);
begin
TheDay.Increase;
end;

El mismo codigo simplificado esta presente en muchos otros controladores de


eventos. Cuando hayamos instalado el controlador de eventos, no tendremos que
recordar actualizar la etiqueta continuamente. Eso elimina una potencial e impor-
tante fuente de errores en el programa. Ademas, fijese en que tenemos que escribir
algun codigo a1 principio, porque esto no es un componente instalado en Delphi,
sino sencillamente una clase. Con un componente, simplemente seleccionamos el
controlador de eventos en el Object Inspector y escribimos una unica linea de
codigo para actualizar la etiqueta, eso es todo.

Listas y clases contenedores


Suele ser importante controlar grupos de componentes u objetos. Ademas de
usar matrices estandar y matrices dinamicas, existen una cuantas clases de la
VCL que representan listas de otros objetos. Estas clases pueden estar dividas en
tres grupos: listas simples, colecciones y contenedores.

Listas y listas de cadena


Las listas se representan mediante la lista generica de objetos, TList,y me-
diante las dos listas de cadenas, TStrings y TStringList:
TList: Define una lista de punteros, que se pueden usar para almacenar
objetos de cualquier clase. Una TList es mas flexible que una matriz
dinamica, porque se amplia automaticamente mediante la adicion de nue-
vos elementos. La ventaja de las matrices dinamicas sobre una TLi st,en
cambio, esta en que las matrices dinamicas permiten indicar un tip0 espe-
cifico para 10s objetos contenidos y realizar la verificacion de tipos apro-
piada en tiempo de compilacion.
TStrings: Es una clase abstracta para representar todas las formas de las
listas de cadena, tengan la implernentacion de almacenamiento que tengan.
Esta clase define una lista abstracta de cadenas. Por esa razon, 10s objetos
T string s se usan solo como propiedades de componentes capaces de
almacenar las propias cadenas, como en un cuadro de lista.
TStringList: Es una subclase de TStr ing s,define una lista de cadenas
con su propio almacenamiento. Se puede usar esta clase para definir una
lista de cadenas en un programa.
Los objetos TStringList y TStrings poseen ambos una lista de cadenas
y una lista de objetos asociados con las mismas. Esto hace posible una serie de
usos diferentes para dichas clases. Por ejemplo, se pueden usar para diccionarios
de objetos asociados o para almacenar mapas de bits u otros elementos que se
usaran en un cuadro de lista.
Las dos clases de listas de cadenas tambien tienen metodos preparados para
almacenar o cargar sus contenidos de un archivo de texto, SaveToFile y
LoadFromFi le.Para movernos a traves de una lista, se puede usar una senci-
lla sentencia basada en su indice, como si la lista fuera una matriz. Todas estas
listas tienen una serie de metodos y propiedades. Se puede trabajar con listas
usando la notacion de matriz ([ y I), tanto para leer como para cambiar elementos.
Existe una propiedad Count, asi como metodos de acceso comunes, como Add,
Insert, Delete, Remove y metodos de busqueda (por ejemplo, Indexof).
La clase TLis t posee un metodo Assign que, ademas de copiar 10s datos
fiente, puede realizar operaciones fijas en las dos listas, como and, or y xor.
Para rellenar una lista de cadenas con elementos y, mas tarde, verificar si uno
de ellos esta presente, se puede escribir un codigo como el siguiente:
var
sl: TStringList;
idx: Integer;
begin
s l : = TStringList.Create;
try
sl.Add ('uno') ;
sl.Add ('dos');
sl.Add ('tres') ;
/ / despues
idx := sl. IndexOf ( ' dos ' ) ;
i f idx >= 0 then
ShowMessage ( 'Encontrada cadena ' ) ;
finally
sl.Free;
end ;
end;

Pares nombre-valor (y extensiones de Delphi 7)


La clase T s t r ingL i s t siempre ha dispuesto de otra prestacion muy como-
da: el soporte de pares nombre-valor. Si se aiiade a una lista una cadena como
'nombre=sonsoles', puede buscarse la existencia del par empleando la funcion
I ndexof Name o la propiedad de matriz Values. Por ejemplo, puede obtenerse
el valor 'sonsoles' mediante una llamada a Values [ ' nombre ' ] .
Puede utilizarse esta caracteristica para crear estructuras de datos mucho mas
complejas, como diccionarios, y beneficiarse aun asi de la posibilidad de adjuntar
un objeto a la cadena.
Esta estructura de datos se proyecta directamente sobre 10s archivos de
inicializacion y otros formatos habituales.
Delphi 7 amplia aun mas las posibilidades del soporte de pares nombre-valor
permitiendo la particularizacion del separador, para que no sea solo el signo
igual, mediante la propiedad NameValueSepara t o r.Ademas, la nueva pro-
piedad ValueFromIndex ofrece acceso direct0 a la parte del valor de una
cadena que se encuentre en una posicion dada; ya no es necesario extraer el valor
del nombre manualmente a partir de una cadena completa mediante alguna sen-
tencia retorcida (y extremadamente lenta):
str : = MyStringList.Values [MyStringList.Names [I]]; / / antes
str : = MyStringList .ValueFromIndex [I]; / / ahora
Usar listas de objetos
Podemos escribir un ejemplo que se centre en el uso de la clase generica TList.
Cuando se necesita una lista de cualquier tipo de datos, por lo general se puede
declarar un objeto TList, rellenarlo con datos y luego acceder a esos datos
mientras se convierten al tip0 adecuado. El ejemplo ListDemo demuestra precisa-
mente ese caso y tambien muestra las desventajas de esta tecnica. El formulario
ticnc una variable privada. que contiene una lista de fechas:
private
ListDate: TList;

El objeto lista se crea cuando se crea el propio formulario:


procedure TForml.FormCreate(Sender: TObject);
begin
Randomize;
ListDate : = TList.Create;
end;

Un boton del formulario aiiade una fecha aleatoria a lista:


p r o c e d u r e TForml.ButtonAddClick(Sender: TObject);
begin
ListDate.Add (TDate.Create (1900 + Random (ZOO), 1 + Random
(12), 1 + Random (30)));
end;

Cuando se extraen 10s elementos de la lista, hay que convertirlos de nuevo a1


tip0 apropiado, como en el siguiente metodo, que esta conectado a1 boton List (se
pueden ver sus efectos en la figura 4.4):
p r o c e d u r e TForml.ButtonListDateClick(Sender: TObject);
var
I: Integer;
begin
ListBoxl.Clear;
f o r I : = 0 to ListDate.Count - 1 d o
.
Listboxl Items .Add ( (TObject (ListDate [I]) a s TDate) .Text) ;
end;

A1 final del codigo anterior, antes de que podamos hacer una conversion de
tipos siguientes con as, es necesario convertir manualmente el puntero que ha
devuelto TList en una referencia TOb j ect. Este tipo de expresion puede cau-
sar una excepcion de conversion de tipos no valida o generar un error de memoria,
si el puntero no es una referencia a un objeto.

n ) hcraposible la exidencia de nada sin6 o m 1


a enen
la h t a , la extracci'h mediante una conversion esthtica en lugar de median- .
te la conversiiin 4s rqultaria mas eficaz. Sin embargo, cuando existe una
Wsibilidad siquie& rsrmotir de tener un objeto errbneo, es mcjor utilizar as.
Figura 4.4. La lista de fechas que muestra el ejemplo ListDemo.

Para demostrar que las cosas pueden salir realmente mal, hemos aiiadido un
boton m b , que aiiade un objcto T B u t t o n a la lista mediante una llamada a
L i s t D a t e . A d d ( S e n d e r ) . Si se hace clic sobrc este boton y despues se ac-
tualiza una de las listas, resultara en un error. Por ultimo, hay que recordar que
cuando sc destruye una lista de objetos, hay que destruir primer0 todos 10s objetos
de la lista. El programa L i s t Demo usa para esto el metodo F o r m D e s t r o y del
formulario:
procedure TForml.FormDestroy(Sender: TObject);
var
I: Integer;
begin
for I : = 0 to ListDate.Count - 1 do
TObject (ListDate [I]) .Free;
ListDate.Free;
end;

Colecciones
El segundo grupo, las colecciones, contiene so10 dos clases, T C o l l e c t i o n
y T C o l l e c t i o n I t e m . T C o l l e c t i o n define una lista homogenea de obje-
tos, poseidos por la clase coleccion. Los objetos dc la coleccion han de descender
de la clase T C o l l e c t i o n I t e m . Si se necesita una coleccion que almacene
objetos especificos, hay que crear una subclase de T C o l l e c t i o n y una subclase
correspondiente de T C o l l e c t i o n 1 t e r n . Las colecciones se usan para especifi-
car valores de propiedades de componentes; resulta muy poco frecuente trabajar
con colecciones para almacenar objetos propios.

Clases de contenedores
Las versiones recientes de Delphi incluyen una serie de clases de contenedores,
definida en la unidad C o n t n r s . Las clases contenedores amplian las clases T L i s t
aiiadiendo el concept0 de posesion y definiendo normas de estraccion especificas
(que imitan pilas y colas) o capacidades de ordenacion.
La diferencia basica entre T L i s t y la nueva clase TOb je c t L i s t, por ejem-
plo, es que la ultima se define como una lista de objetos TOb j ec t , no como una
lista de punteros. Sin embargo, es incluso mas importante el hccho de que si la
lista de objetos tiene la propiedad O w n s O b j e c t s definida como True,
automaticamente se elimina un objeto a1 reemplazarlo por otro y se elimina cada
objeto cuando se destruye la propia lista. Veamos una lista de todas las clases de
contenedores:
L a clase TObjectList: Representa una lista de objetos, que en ultimo
termino son poseidos por la propia lista.
La clase heredada TComponentList: Representa una lista de componen-
tes, con total soporte para la notification de la destruccion (una importante
caracteristica de seguridad cuando 10s componentes estan conectados me-
diante sus propiedades, es decir, cuando un componente es el valor de una
propiedad de otro).
L a clase TClassList: Es una lista de referencias de clase. Hereda de T L i s t
y no necesita destruirse de manera especifica, ya que en Delphi no es
necesario destruir las referencias dc clase.
Las clases TStack y TObjectStack: Representan listas de punteros y ob-
jetos, a partir de 10s cuales se pueden extraer unicamente elementos co-
menzando desde el ultimo insertado. Una pila sigue cl orden LIFO (Last
In, F ~ r s Out;
l ultimo en entrar, primero en salir). Los metodos mas comu-
nes de una pila son p u s h para la insercion, Pop para la extraccion y
Peek para obtener una vista previa del primer elemento sin eliminarlo de
la pila. Aun se pueden usar todos 10s metodos de la clase basica, TList .
Las clases TQueue y TObjectQueue: Representan listas de punteros y
objetos, de 10s que siempre se puede eliminar el primer elemento insertado
(FIFO: First In, First Ottt, primero en entrar, primero en salir). Los meto-
dos de estas clases son 10s mismos que 10s de las clases de pila per0 se
comportan de un modo distinto.

ADVERTENCIA: A diferencia de TOb je c t L i s t , las clases TOb j e c t -


Stack y T O b jectQueueno poseen 10s objetos insertados y no destrui-
...,..'.""
qbjetos que queden en la estructura de datos cuando se
r i n an1lpll-a
'UY U

destruyan. Simplernente se pueden &ar todos esrtos elementos, destruidos


cuando se hayam terminado de usar y despues destruir el contenedor.

Para demostrar el uso de estas clases, hemos modificado el anterior ejemplo


ListDate para formar uno nuevo, Contain. Primero, cambiamos el tipo de varia-
ble ListDate a TOb jectList.En el metodo Formcreate,hemos modifi-
cad0 la creacion de la lista con el siguiente codigo, que activa la posesion de lista:
ListDate := TObjectList .Create (True);

En este punto, podemos simplificar el codigo de destruccion, puesto que a1


aplicar Free a la lista, se liberaran automaticamente las fechas que mantiene.
Tambien hemos aiiadido a1 programa un objeto pila y una cola, rellenando
cada uno de ellos con numeros. Uno de 10s dos botones del formulario muestra
una lista de numeros existentes en cada contenedor y el otro elimina el ultimo
elemento (que aparece en un cuadro de mensaje):
procedure TForml.btnQueueClick(Sender: TObject);
var
I: Integer;
begin
ListBoxl.Clear;
for I : = 0 to Stack-Count - 1 do begin
ListBoxl. Items .Add (IntToStr (Integer (Queue.Peek) ) ) ;
Queue. Push (Queue.Pop) ;
end;
ShowMessage ( ' E l i m i n a d o : ' + IntToStr (Integer
(Stack.Pop) ) ) ;
end;

A1 pulsar 10s dos botones, se puede ver que cuando se llama a pop para cada
contenedor, devuelve el ultimo elemento. La diferencia es que la clase TQueue
inserta 10s elementos a1 principio y la clase T Sta c k 10s inserta a1 final.
Listas asociativas de verificacion
Desde Delphi 6, el conjunto de las clases contenedores predefinidas incluye
TBucketList y TOb jectBuc ketList.Estas dos listas son asociativas, lo
que significa que tienen una clave y una entrada real. La clave se usa para identi-
ficar 10s elementos y buscarlos. Para aiiadir un elemento, se puede llamar a1
metodo ~ d con d dos parametros: la clave y 10s datos reales. Cuando se usa el
metodo Find,hay que pasar la clave y recuperar 10s datos. Se consigue el mismo
efecto utilizando la matriz Data de forma adecuada, pasando la clave como
parametro.
Estas listas tambien se basan en un sistema de verificacion. La lista crea una
matriz interna de elementos, denominados "cubos", cada uno de 10s cuales tiene
una sublista de 10s elementos reales de la lista. Cuando se aiiade un elemento, su
valor clave se usa para calcular el valor de verificacion (o hash), que determina el
cub0 a1 que se aiiadira el elemento. A1 buscar un elemento, se vuelve a calcular el
valor de verificacion y la lista atrapa inmediatamente la sublista que contiene el
elemento y lo busca en ella. Con esto se consigue que la insercion y las busquedas
Sean muy rapidas, per0 solo si el algoritmo de verificacion distribuye 10s elemen-
tos de manera uniforme entre 10s diversos cubos y si hay suficientes entradas
diferentes en la matriz. De hecho, cuando muchos elementos pueden estar en el
mismo cubo, la busqueda se ralentiza. Por esa razon, cuando se crea
Tob j ectBuc ketlist , se puede especificar el numero de entradas de la lista,
usando el parametro del constructor, eligiendo un valor entre 2 y 256. El valor del
cubo se fija tomando el primer byte del puntero (o numero) pasado como clave y
haciendo una operacion a n d con un numero que corresponda a las entradas.
. - - - - -- - - -

NOTA: Este algoritmo no resulta muy convincente como sistema de verifi-


cation, pero sustituirlo por uno propio implica sobrescribir la funcion vir-
- -

t y, en-ultimo caso, cambiar el ntimero de entradas dle la


Itual ~ u c k e For

I
..*-S l . - ?~ 3 - 1
marnz, esrameclenao un valor alrerenre para la propleaaa ~ u c ~ e i x o u n t .
~--A.~' 3.4- r - - 1 3 . a -.I

Otra caracteristica interesante. no disponible en el caso de las listas es el


metodo ForEach, que permite ejecutar una funcion dada en cada elemento que
contenga la lista. Se pasa a1 mdtodo ForEach un puntero a datos propios y un
procedimiento, que recibe cuatro parametros: el puntero personalizado, cada cla-
ve y ob-ieto de la lista y un parametro boolcano que se puede establecer como
False para detener la ejecucion. En otras palabras, estas son las dos definicio-
nes:
type
TBucketProc = procedure (AInfo, AItem, AData: Pointer;
out AContinue: Boolean) ;

function TCustomBucketList.ForEach(AProc: TBucketProc;


AInfo: Pointer) : Boolean;

NOTA: Ademas de estos contenedores, Delphi incluye tambien una clase


THashedStringList, que hereda de TStringList. Esta clase no
tiene una relacion directa con las listas de verificacion e incluso esth defmi-
da en una unidad diferente, I n i F i l e s . La lista de cadena verificada tiene
dos tablas asociadas de verificacion (de tipo T S t r i ng Ha sh), que se re-
- . - . . ..
na. Asi, esta clase solo resulta litil para leer un gran grupo de cadenas fijas,
no para manejar una lista de cadenas que cambien con frecuencia. Por otra
--.A- 1" -I""- -4- ..---.A- mmL-: L ----A- L--*--*- -<*:I -- ---a-

generafes, y dispone de un buen algoritmo para calcular el valor de verifi-


cation de una cadena.

Contenedores y listas con seguridad de tipos


Los contenedores y las listas tienen un problema: carecen de seguridad de
tipos, como hemos visto en varios ejemplos al aiiadir un objeto boton a una lista
de fechas. Para garantizar que 10s datos de una lista Sean homogtneos, se puede
verificar el tipo de 10s datos estraidos antes de insertarlos. Pero como medida de
seguridad adicional, tal vez queramos verificar el tip0 de datos durante la extrac-
cion. Sin embargo, afiadir verificaciones en tiempo de ejecucion ralentiza un pro-
grama y es arriesgado (un programador podria no verificar el tip0 en algunos
casos).
Para resolver ambos problemas, se pueden crear clases de lista especificas
para unos tipos de datos determinados y adaptar el codigo de las clases T L i s t o
TObje c t L i s t existentes (o de otra clase de contenedor). Existen dos tecnicas
para realizar esto:
Derivar una nueva clase de la clase de lista y personalizar el metodo A d d
y 10s metodos de acceso, relacionados con la propiedad I terns. Esta tec-
nica es la utilizada por Borland para las clases de contenedores, que se
derivan todas de T L i s t .
r -

NOTA: Las ~ l a s e de
s contenedores de Delphi utilizan sobrescritura esthti-
ca para realizar las adaptaciones simples de tip0 (sesultados de parhetros
y funciones del tip0 deseado). La sobrescritura estiitica no es lo mismo que
el polimorfismo; alguien que utilice una clase de contenedor mediante una
variable TLis t no Uamara a las funciones espe~iaha&s del contenedor.
La sobrescritura esthtica es una tecnica eficaz y sencilla, pero tiene una
importante restriccion: 10s metodos del descendiente no deberian hacer nada
rnlic nJlli AP rrnn e n n v ~ r c i A nA- t i n n c c p n e i l l a nnrnrre nn hrrv uarantirrc Ae
que se llame a 10s mktodos descendientes. Se podria acceder a la lista y
manipularla utilizando tanto 10s metodos ascendientes como 10s descen-
dientes, por lo que sus operaciones reales han de ser identicas. La 6nica
airerencra es er upo- --*:a:--
J : f ----- :--- -1 A:-
urruzaao 2 - -- a _ - --L&- J - - 2 ----- 3:--*--
en 10s mwwos -..-
aeswnolenres, que permlre ---:A-

evitar una conversion de tipos adicional.

Crear una clase con un nombre nuevo que contenga un objeto T L i s t y


proyectar 10s metodos de la nueva clase a la lista interna utilizando la
verificacion de tipos adecuada. Esta tecnica define una clase envoltorio,
una clase que "envuelve" a otra ya existente para ofrecer un acceso dife-
rente o limitado a sus metodos (en este caso, para realizar una conversion
de tipo).
Hemos implementado ambas soluciones en el ejemplo D a t e L i s t, que define
listas de objetos T D a t e . En el codigo siguiente, veremos la declaracion de dos
clases, la clase T D a t e L i s t I basada en la herencia y la clase envoltorio
TDateListW.

type
// b a s a d o e n h e r e n c i a
T D a t e L i s t I = class (TObj e c t L i s t )
protected
p r o c e d u r e S e t O b j e c t ( I n d e x : I n t e g e r ; Item: TDate) ;
f u n c t i o n GetObj e c t ( I n d e x : I n t e g e r ) : TDate;
public
f u n c t i o n Add ( O b j : T D a t e ) : I n t e g e r ;
p r o c e d u r e I n s e r t ( I n d e x : I n t e g e r ; Obj : T D a t e ) ;
p r o p e r t y O b j e c t s [ ~ n d e x : I n t e g e r ] : TDate
r e a d GetObject w r i t e SetObject; d e f a u l t ;
end;

// b a s a d o e n e n v o l t o r i o
TDateListW = class ( T O b j e c t )
private
FList: TObjectList;
f u n c t i o n G e t O b j e c t ( I n d e x : I n t e g e r ) : TDate;
p r o c e d u r e S e t o b j e c t ( I n d e x : I n t e g e r ; Obj : T D a t e ) ;
f u n c t i o n GetCount: I n t e g e r ;
public
constructor Create;
d e s t r u c t o r Destroy; o v e r r i d e ;
f u n c t i o n Add ( O b j : T D a t e ) : I n t e g e r ;
f u n c t i o n Remove ( O b j : T D a t e ) : I n t e g e r ;
f u n c t i o n IndexOf ( o b j : T D a t e ) : I n t e g e r ;
p r o p e r t y Count: I n t e g e r r e a d GetCount;
p r o p e r t y O b j e c t s [ I n d e x : I n t e g e r ] : TDate
r e a d GetObject w r i t e SetObject; d e f a u l t ;
end;

Obviamente, la primera es mas sencilla de escribir (tiene menos metodos y


simplemente llaman a 10s heredados). Lo bueno es que un objeto T D a t e L i s t I
se puede pasar a parametros que esperan una T L i s t . El problema es que el
codigo que manipula una instancia de esta lista mediante una variable T L i s t
generica no llama a 10s metodos especializados, puesto que no son virtuales y
podria acabar aiiadiendo a la lista objetos de otros tipos de datos.
En cambio, si decidimos no usar la herencia, escribimos una gran cantidad de
codigo, porque hay que reproducir cada uno de 10s metodos originales de T L i s t ,
llamando sencillamente a1 objeto interno F L i s t . El inconveniente es que la clase
T Da t e L is t W no es un tipo compatible con T 1i s t y esto restringe su utilidad.
No se puede pasar como parametro a metodos que esperen una TL i s t .
Ambos enfoques ofrecen una buena verification de tipos. Tras haber creado
una instancia de una de estas clases de lista, solo se pueden aiiadir objetos del tipo
apropiado y 10s objetos extraidos seran naturalmente del tip0 correcto. Esto se
demuestra con el ejemplo DateList. Este programa tiene unos pocos botones, un
cuadro combinado que permite escoger a1 usuario cual de las listas mostrar y un
cuadro de lista para mostrar 10s valores reales de la lista. El programa trata de
aiiadir un boton a la lista de objetos T D a t e . Para aiiadir un objeto de un tipo
diferente a la lista T D a t e L i s t I , podemos simplemente convertir la lista a su
clase basica, T L i s t . Esto podria ocurrir de forma accidental si pasamos la lista
como parametro a un metodo que espera un objeto de clase basica. En cambio,
para que la lista TDateLi stW falle hemos de convertir explicitamente el objeto
a TDate antes de insertarlo, algo que un programador nunca deberia hacer:
p r o c e d u r e TForml.ButtonAddButtonClick(Sender: TObject);
begin
ListW.Add (TDate (TButton.Create (nil)) ) ;
TList (Listl) .Add (TButton.Create (nil)) ;
UpdateList;
end;

La llamada a Update List lanza una excepcion, que se muestra directamen-


te en el cuadro de lista, porque hemos utilizado una conversion de tipos as en las
clases de lista personalizadas. Un programador inteligente jamas escribiria el
codigo anterior. Para resumir, escribir una lista personalizada para un tip0 espe-
cifico hace que un programa resulte mucho mas robusto. Escribir una lista envol-
torio en lugar de una basada en herencia suele ser algo mas seguro, aunque requiere
mucho mas codigo.

Streaming
Otro ambito principal de la biblioteca de clases de Delphi es el soporte de
streaming, que incluye adrninistracion de archivos, memoria, sockets y otras fuentes
de informacion organizadas de forma secuencial. La idea del streaming consiste
en moverse a traves de 10s datos mientras 10s leemos, de un mod0 muy parecido a
las tradicionales funciones Read y Write utilizadas por el lenguaje Pascal.

La clase TStream
La VCL define la clase abstracta Tst ream y diversas subclases. La clase
padre, TStream,posee solo unas cuantas propiedades y jamas se creara una
instancia de la misma, per0 posee una interesante lista de metodos que, por lo
general, se utilizara cuando se trabaje con clases stream derivadas.
La clase TStream define dos propiedades, size y Position.Todos 10s
objetos stream tienen un tamaiio especifico (que generalmente aumenta si escribi-
mos algo despues del final del stream) y habra que especificar una posicion dentro
del stream en la que se quiere leer o escribir la informacion.
La lectura y escritura de bytes depende de la clase de stream utilizada, per0 en
ambos casos no es necesario saber mucho mas que el tamaiio del stream y la
posicion relativa dentro del stream para leer o escribir datos. De hecho, esta es
una de las ventajas de usar streams. La interfaz basica sigue siendo la misma si
manipulamos un archivo del disco, un campo de objeto binario ancho (BLOB) o
una secuencia larga de bytes en memoria. Ademas de las propiedades size y
Position,la clase Tstream define tambien varios metodos importantes, la ,
mayoria de 10s cuales son virtuales y abstractos. (En otras palabras, la clase
TSt ream no define lo que hacen estos metodos, por lo tanto, las clases derivadas
son responsables de su implementacion.) Algunos de estos metodos solo son im-
portantes en el context0 de lectura y escritura de componentes dentro de un stream
(por ejemplo, Readcomponent y Writecomponent), per0 algunas son uti-
les tambien en otros contextos. En el listado 4.2, se puede encontrar la declara-
cion de la clase TSt ream,extraida de la unidad Classes.

Listado 4.2. La seccion publica de la definicion de la clase TStream.

TStream = class (TObject)


public
// lee y escribe un buffer
function Read (var Buffer; Count: Longint) : Longint; virtual;
abstract;
function Write (const Buffer; Count : Longint) : Longint;
virtual; abstract;
procedure ReadBuf fer (var Buffer; Count: Longint) ;
procedure WriteBuffer(const Buffer; Count: Longint);

// m u e v e a una p o s i c i d n especifica
function Seek (Offset: Longint; Origin: Word) : Longint;
overload; virtual ;
function Seek (const Offset: Int64; Origin: TSeekOrigin) :
Int64;
overload; virtual ;

// copia el s t r e a m
function CopyFrom(Source: TStream; Count: Int64) : Int64;

// l e e o e s c r i b e un c o m p o n e n t e
function ReadComponent(1nstance: TComponent): TComponent;
function ReadComponentRes(1nstance: TComponent) : TComponent;
procedure WriteComponent(1nstance: TComponent);
procedure WriteComponentRes(const ResName: string; Instance:
TComponent) ;
procedure WriteDescendent(Instance, Ancestor: TComponent);
procedure WriteDescendentRes(
const ResName: string; Instance, Ancestor: TComponent) ;
procedure WriteResourceHeader(const ResName: string; out
FixupInf o: Integer) ;
procedure FixupResourceHeader (FixupInfo: Integer) ;
procedure ReadResHeader;

// p r o p i e d a d e s
property Position: Int64 read GetPosition write SetPosition;
property Size: Int64 read GetSize write SetSize64;
end ;

El uso basico de un stream implica llamadas a 10s metodos ReadBuf fer y


WriteBuf fer,que son muy potentes per0 no muy faciles de usar. El primer
parametro, de hecho, es un buffer sin tipo en el que se puede pasar la variable
para guardar o cargar en el. Por ejemplo, se puede guardar en un archivo un
numero (en formato binario) y una cadena mediante este codigo:
var
stream: TStream;
n: integer;
str: string;
begin
n : = 10;
str : = ' c a d e n a de prueba' ;
stream : = TFileStream.Create ( 'c:\ t m p \ t e s t l , fmcreate) ;
stream.WriteBuffer (n, sizeof (integer)) ;
stream-WriteBuffer (str[1], Length (str)) ;
stream.Free;

Una tecnica totalmente alternativa consiste en permitir que componentes espe-


cificos guarden o carguen datos en 10s streams. Muchas clases de la VCL definen
un metodo LoadFromStream o SaveToStream,como por ejemploTStrings,
TStringList, TBlobField, TMemoField, TIcon y TBitmap.

Clases especificas de streams


No tiene sentido crear una instancia de TStream,porque esta clase es abs-
tracts y no ofrece soporte direct0 para guardar datos. En su lugar, se puede
utilizar una de las clases derivadas para cargar datos desde ella o almacenarlos en
un archivo real, un campo BLOB, un socket o un bloque de memoria. Se puede
usar T F i1e Stream para trabajar con un archivo, pasando el nombre del archi-
vo y algunas opciones de acceso del archivo a1 metodo Create.Para manipular
un stream en memoria (y no un archivo real) hay que usar TMemoryStream.
Existen diversas unidades que definen las clases derivadas de Tstream.En
la unidad c1as ses se encuentran las siguientes clases:
THandleStream: Define un stream que manipula un archivo de disco re-
presentado por un manejador de archivo.
TFileStream: Define un stream que manipula un archivo de disco (un
archivo que existe en un disco local o de red) representado por un nombre
de archivo. Hereda de THand leStream.
TCustomMemoryStream: Es la clase basica para 10s streams almacena-
dos en memoria per0 no se utiliza directamente.
TMemoryStream: Define un stream que manipula una secuencia de bytes
en memoria. Hereda de TCustomMemorySt ream.
TStringStream: Ofrece una forma sencilla de asociar un stream a una
cadena en memoria, para poder acceder a la cadena mediante la interfaz
TStream y tambien copiar la cadena en o desde otro stream.
TResourceStream: Define un stream que manipula una secuencia de bytes
en memoria y ofrece acceso de solo lectura a datos de recurso enlazados
con el archivo ejecutable de una aplicacion (un ejemplo de dichos datos de
recursos son 10s archivos DFM). Hcrcda de TCust omMemorySt ream.
Las clases de stream definidas en otras unidades son entre otras:
TBlobStream: Define un stream que ofrece acceso simple a campos BLOB
de bases dc datos. Esisten strcams BLOB similares para otras tecnologias
de acccso a bases de datos a partc de BDE, como TSQLBlobStream y
TClientBlobStream. Fijcsc en que cada tipo de conjunto de datos
utiliza una clase de stream cspccifica para 10s campos BLOB). Todas cstas
clases hcrcdan de TMemorySt ream.
TOleStream: Dcfinc un stream para lecr y cscribir informacion sobrc la
intcrfaz para el strcaming proporcionada por un objeto OLE.
TWinSocketStream: Ofrecc soportc de streaming para una conesion
socket.

Uso de streams de archivo


Crcar y usar un strcam de archivo pucdc rcsultar tan sencillo como crear una
variable de un tipo quc descienda de TStream y llamar a 10s mktodos de 10s
componcntes para cargar cl contenido desdc cl archivo:
var
S: TFileStream;
begin
if 0penDialogl.Execute then
begin
S : = TFileStream.Create (OpenDialogl.FileName,
fmOpenRead) ;
try
Memol. Lines. LoadFromStream (S) ;
finally
S . Free;
end;
end ;
end;

Como se puede ver en el codigo, el metodo create para streams de archivo


tiene dos parametros: el nombre del archivo y un atributo que indica el mod0 de
acceso solicitado. En este caso, queremos leer el archivo, por eso utilizamos el
indicador fmOpenRead (en la ayuda de Delphi esiste documentacion sobre otros
indicadores muy utiles).

NOTA: De 10s diferentes modos, 10s mas importantes son fmshare-


Denywrite, que se usa cuando simplemente se leen datos de un archivo
. -
un aiobirvd -&mpartido. Existe un ter& par&m,e&o en ~ ~ i i e s t r e a r n .
C r e a t e . IM$!do Rights. Este p a r h e t r o se utiliza &a mai'
pkrmjsos
dei a c e s o a archiws a1 sistema de archivos de Linux c w d o el modo de
&cesocs fnicreate(es decir, s61o cuando se crea un arChiVUpzle~~). En
Windows se ignora este paritmetro.

Una gran ventaja de 10s streams sobre otras tecnicas de acceso a archivos es
que son intercambiables, por lo que podemos trabajar con streams de memoria y
guardarlos despues en un archivo o realizar las operaciones opuestas. Esta podria
ser una forma dc mejorar la velocidad de un programa que haga un uso intensivo
de archivos. Veamos un fragment0 de codigo, una funcion que copia un archivo,
para que hacernos una idea de como se pueden usar 10s streams:
procedure CopyFile (SourceName, TargetName : String) ;
var
Streaml, Stream2: TFileStream;
begin
Streaml : = T F i l e S t r e a m - C r e a t e (SourceName, fmOpenRead) ;
try
Stream2 : = T F i l e S t r e a m - C r e a t e (TargetName, fmOpenWrite or
fmcreate) ;
try
Stream2 .CopyFrom (Streaml, Streaml.Size) ;
finally
Stream2. Free;
end
finally
Streaml. Free;
end
end ;

Otro uso importante de 10s streams es controlar 10s campos BLOB de bases de
datos u otros campos grandes directamente. Se pueden exportar esos datos a un
stream o leerlos desdc uno, llamando simplemente a 10s metodos SaveToStream
y LoadFromStream de la clase TBlobField.
- , ---- - -

Gbrk de streaming de Delphi7 a& tma ni: e e~ l n c d e


e s ~ e p:ci&a," e ' h $ s ~ t r e a m ~ r r o rSu
. constructor to& L . & ~ oguu-hwro
un nor..,.- ,- ,..,, ,, ,
, ,, ,.,.,,
, , ,,ase estandariza y
,
,
simp lifica en gran mcdida el sistmm de notificacibe ds errores r e ~ ~ W o s
can a d v o s en streams.

Las clases TReader y TWriter


Por si mismas, las clases de stream de la VCL no ofrecen mucho soporte de
lectura ni escritura de datos. De hecho, las clases de stream no implementan nada
m b a116 de la escritura y lectura de bloques de datos. Si queremos cargar o
guardar tipos de datos especificos en un stream (sin realizar una conversion de
tipos muy exhaustiva), se pueden usar las clases T R e a d e r y TWr i t e r , que
derivan de la clase generica T F i l e r .
Basicamente, las clases T R e a d e r y TW r i t e r existen para simplificar las
tareas de cargar y guardar datos de stream segun su tipo y no solo como secuencia
de bytes. Para ello, T W r i t e r incluye marcas especiales (signatures) en el stream,
que especifican el tip0 de cada uno de 10s datos del objeto. A su vez, la clase
T R e a d e r lee estas marcas del stream, crea 10s objetos adecuados e inicia des-
pues esos objetos utilizando 10s datos que se encuentran a continuacion en el
stream.
Por ejemplo, se podria haber escrito un numero y una cadena en un stream del
siguiente modo:
var
stream: TStream;
n: integer;
str: string;
w: TWriter;
begin
n : = 10;
str : = 'cadena de prueba';
stream : = TFileStream.Create ( 'c: \trrp\test.txt' , fmcreate) ;
w : = TWriter .Create (stream, 1024) ;
w-WriteInteger (n);
w-WriteString (str);
w. Free;
stream.Free;

Esta vez, el archivo real tambien incluira 10s caracteres de marca adicionales,
para que se pueda leer de nuevo este archivo utilizando unicamente un objeto
T R e a d e r . Por esta razon, el uso de T R e a d e r y TWr i t e r se reduce general-
mente a1 streaming de componentes y rara vez se aplica en la administracion de
archivos general.

Streams y permanencia
En Delphi, 10s streams tienen una hncion bastante importante para la perma-
nencia. Por ese motivo, muchos metodos de T S t r e a m e s t h relacionados con las
acciones de guardar y cargar un componente y sus subcomponentes. Por ejemplo,
se puede almacenar un formulario en un stream a1 escribir:

Si examinamos la estructura de un archivo DFM de Delphi, descubriremos


que, en realidad, se trata simplemente de un archivo de recursos que contiene un
recurso de formato personalizado. Dentro de dicho recurso, se encontrara la in-
formacion sobre el componente para el formulario o modulo de datos y para cada
uno de 10s componentes que contiene. Como seria de esperar, las clases stream
ofrecen dos metodos para leer y escribir estos datos de recurso personalizado para
componentes: WriteComponentRes para almacenar 10s datos y ReadCompo-
nent Re s para cargarlos.
Sin embargo, para experimentar en memoria, (no con archivos DFM reales),
normalmente es mejor usar Writecomponent. Despues de crear un stream de
memoria y guardar el formulario actual en el, el problema esta en como mostrar-
lo. Esto se puede conseguir transformando la representacion binaria del formula-
rio en una representacion textual. Aunque el IDE de Delphi, desde la version 5,
puede guardar archivos DFM en formato de texto, la representacion utilizada
internamente para el codigo compilado es siempre un formato binario.
La conversion del formulario se puede realizar mediante el IDE, normalmente
con l a o r d e n v i e w as Text del Form Designer, ymedianteotros metodos.
Tambien hay una herramienta de linea de comandos, CONVERT. EXE, que se
encuentra en el directorio de Delphi, Bin. En nuestro codigo, la forma estandar
para obtener una conversion es llamar a 10s metodos especificos de la VCL.
Existen cuatro funciones para convertir a1 formato de objeto interno obtenido con
el metodo Writecomponent y d l otro formato a este:
p r o c e d u r e ObjectBinaryToText(Input, Output: TStream); overload;
p r o c e d u r e ObjectBinaryToText(Input, Output: TStream;
v a r OriginalFormat: TStreamOriginalFormat); overload;
p r o c e d u r e ObjectTextToBinary(Input, Output: TStream); overload;
p r o c e d u r e ObjectTextToBinary(Input, Output: TStream;
v a r OriginalFormat: TStreamOriginalFormat); overload;

Cuatro funciones distintas, con 10s mismos parametros y nombres que contie-
nen el nombre Resource en lugar de Binary (como en Obj ect Resource-
ToText), convierten el formato del recurso obtenido por WriteComponentRes.
Un ultimo metodo, TestStreamFormat, indica si un DFM contiene una re-
presentation binaria o textual.
En el programa FormToText, se ha utilizado el metodo O b jectBi-
naryToText para copiar la definicion binaria de un formulario en otro stream
y, a continuacion, se ha mostrado el stream resultante en un campo de texto, como
muestra la figura 4.5. Este es el codigo de 10s dos metodos utilizados:
p r o c e d u r e TformText.btnCurrentClick(Sender: TObject);
var
MemStr: TStream;
begin
MemStr : = TMemoryStream.Create;
try
.
MemStr Writecomponent (Self);
ConvertAndShow (MemStr) ;
finally
MemStr.Free
end;
end;

procedure TformText.ConvertAndShow (aStream: TStream);


var
ConvStream: TStream;
begin
aStream.Position : = 0;
ConvStream : = TMemoryStream.Create;
try
Obj ectBinaryToText (astream, ConvStream) ;
ConvStream.Position : = 0;
Memo0ut.Lines.LoadFromStream (ConvStream);
finally
ConvStream-Free
end ;
end :

PadO W Fmm in Ex

TOP- 113
W d h - 545

- -
H c , ~ .374
A c t w ~ o n l l o l blnCtwrenl
Caphon Fmm To Te4

-
C d n = clBlnFace
F d Charel DEFAULT-CWSET
F a d r h -&hdowTexl

-
F a n l H c M - 11
F m Nme M S Sans Sell?
Fwd St* = [I

-
O W C ~ r a l d r d n= T~ue
-
Vabk T w
P m l s P r d n d 96
TeulHmit4 = 13

-
&led m r d u l TMemo
Ldl 0
Top-41
-
Wdlh 537

&n -
Hrn.306
Kl~enl
Scret3rus = r N c l m a l
TabClldcr = 0

Figura 4.5. Descripcion textual de un cornponente forrnulario, rnostrada dentro de si


mismo por el ejernplo ForrnToText.

Fijese en que si hacemos clic varias veces sobre el boton Current Form Object,
obtendremos mas y mas texto y el texto del campo memo se incluira en el stream.
Despues de unas cuantas lineas, toda la operacion resultara extremadamente len-
ta, hasta que el programa parezca haberse colgado. En este codigo, empezamos a
ver parte de la flexibilidad de utilizar streams (podemos escribir un procedimiento
generic0 y utilizarlo para convertir cualquier stream).
NOTA: Es importante enfatizar que despuds de haber escrito dabs en un
stream, debemos de nuevo volver explicitamente a1 principio (o definir la
propiedad Posit ion como 0) antes de seguir usando el stream, a no ser
que queramos adjuntarle datos, por supuesto.

Otro boton, denominado Panel Object, muestra la representacion textual de


un componente especifico. cl panel, pasando el componente a1 mktodo
Writecomponent.El tercer boton, Form in Executable File, realiza una ope-
ration distinta.
En lugar de realizar un streaming de un objeto esistente en memoria, carga en
un objeto TResourceStream la representacion en tiempo de diseiio dcl formu-
lario (es decir, su archivo DFM) del corrcspondiente recurso inscrtado en el ar-
chive ejecutable:
p r o c e d u r e TformText.btnResourceClick(Sender: TObject);
var
ResStr: TResourceStream;
begin
ResStr : = TResourceStream.Create (hInstance, ' TFORMTEXT' ,
RT-RCDATA) ;
try
ConvertAndShow (ResStr);
finally
ResStr. Free
end ;
end;

A1 hacer clic sobre 10s botones de manera consecutiva (o modificar el formula-


rio del programa), se puede comparar el formulario guardado en el archivo DFM
con el objeto real en tiempo de ejecucion.
- .-- -- - . - - --.
Escribir una clase stream personalizada
Adembs de usar las clases de stream que ya existen, 10s programadores de
Delphi pueden escribir sus propias clases stream y usarlas en lugar de las
que ya existen. Para ello, solo es necesario especificar como se guarda y se
carga un bloque gentrico de datos en bruto y la VCL sera capazde usaresa
..
nueva.
clase slernpre que la Ilamemos. m
1 . I 11 i . - crear una
la1 vez no sea necesario ~ - - -~

clase stream con un nuevo nombre para trabajar con un nuevo tipo de
medio, sino solo personalizar un stream existente. En ese caso, todo lo que
hay que hacer es escribir 10s metodos de lectura y escritura apropiados.
Como ejemplo, hemos creado una clase para codificar y decodificar un
stream de archivo generico. Aunque este ejemplo esti limitado por el uso de
.
un mecanismo de Eodificaci6n titia1menti bobo, se integra cokpletamente
7 C . correcramente. La
con la VLL y runciona * I 1
nueva clase sueam aeclara senci-
L -
llamente 10s dos mdtodos de lectura y escritura y tiene m a propiedad que
almacena un clave:
type
TEncodedStream = class (TFileStream)
private
FKey: Char;
public
constructor Create (conat FileName: string; Mode: Word) ;
function Read (var Buffer; Count : Longint) : Longint ;
override:
function Write(con6t Buffer; Count: Longint): Longint;
override;
property Key: Char read FKey write FKey;
end ;

El valor de la clave se suma sencillamente a cada uno de 10s bytes guarda-


dos en un archivo y se resta cuando se leen 10s datos. Veamos el codigo
completo de 10s metodos Write y Read, que utiliza punteros con bastante
frecuencia:
constructor TEncodedStream.Create( const FileName: string;
Mode: Word) ;
begin
inherited Create (FileName, Mode) ;
FKey := 'A'; / / p r e d e f i n i d o
end ;

f u n ~ t i o nTEncodedStream.Write(const Buffer; Count: Longint):


~ongint;
var
pBuf, pEnc: PChar;
I, EncVal: Integer;
begin
// a s i g n a memoria para e l b u f f e r c o d i f i c a d o
GetMem (pEnc, Count) ;
try
// usa e l b u f f e r como una m t r i z d e c a r a c t e r e s
pBuf : = PChar (@Buffer) ;
// para cada c a r a c t e r d e l b u f f e r
for I := 0 to Count - 1 d o
begin
// c o d i f i c a e l v a l o r y l o a l m c e n a
EncVal := ( Ord (pBuf[I]) + Ord(Key) ) mod 256;
pEnc [I] := Chr (EncVal):
end;
// e s c r i b e e l b u f f e r c o d i f i c a d o para e l a r c h i v o
~ e s u l t := inherited Write (pEncA, Count) ;
finally
FreeMem (pEnc, Count) ;
end;
Load PI&.
-
-bow&
-.

a n ~ ~ e ~ o - n & i i en i i ell primer campo de


memo, el segundo boton guarda el texto de este primer campo de memo en
un archivo codificado y el ultimo b o t h carga de nuevo el archivo codifica-
do en el segundo campo de memo, tras decodificar el archivo. En este ejem-
plo, tras haber codificado el archivo, lo hemos vuelto a cargar en el primer
carnpo de memo como un archivo de texto normal a la izquierda, que por
supuesto resulta ilegible.
Como disponemos de la clase del stream codificado, el codigo de este pro-
grama es muy similar a1 de cualquier otro programa que use streams. Por
ejemplo, veamos el metodo usado para guardar el archivo codificado (se
puede comparar con el codigo de ejemplos anteriores basados en streams):
procedure TFormEncode.BtnSaveEncodedClick(Sender: TObject);
var
EncStr: TEncodedStream:
begin
if SaveDialog1.Execute then
begin
EncStr := TEncodedStream.Create(SaveDialogl.Filename,
fmcreate) ;
try
Memol.Lines.SaveToStream (EncStr);
finally
EncStr.Free;
end;
end ;
end ;

Compresion de streams con ZLib


Una nucva caracteristica de Delphi 7 cs el soportc oficial de la bibliotcca de
compresion ZLib (disponible y comentada cn cv\\lw.gzip.org/zlib). Durante mu-
cho tiempo ha estado disponible cn el CD dc Delphi una unidad que se comunica-
ba con ZLib, pero ahora queda incluida dentro de la distribucion principal y
forma p a r k del codigo fuente VCL (las unidades ZLib y ZLibConst). Ademas de
proporcionar una interfaz con la biblioteca (que es una bibliotcca escrita cn C que
se puede incrustar directamente cn el programa Delphi, sin necesidad de distribuir
una DLL). Delphi 7 dcfine un par de clases de stream auxiliares: TCompress-
S t r e a m y TDecompressStream.
Como cjemplo dcl uso de estas clases, esta un pequeiio programa llamado
ZCompress que comprime y descomprime archivos. El programa tiene dos cua-
dros de edicion en 10s que se puede escribir el nombre del archivo a comprimir y
el nombre del archivo resultante, que se crea en caso de que no exista previamen-
te. Cuando se hace clic sobre el boton Compress, el archivo original se utiliza
para crear el archivo destino; a1 hacer clic sobre el boton Decompress, se lleva el
archivo comprimido de vuclta a un stream de memoria. En ambos casos, el resul-
tado de la compresion o descompresion se muestra en un campo de mcmo. La
figura 4.6 mucstra el resultado para el archivo comprimido (que resulta ser cl
codigo fuentc del formulario del programa actual).

Orginal file ID\md7code\04VCnmpressVCompressForm pas

Compressed lik D.\rnd7code\04VComp1essVComp1essForm.zlib

Decompress
1

Figura 4.6. El ejemplo ZCompresss puede comprimir un archivo mediante la


biblioteca ZLib.

Para conseguir que el codigo de cste programa resulte mas reutilizable, pode-
mos escribir dos funciones para comprimir o descomprimir un stream en otro
stream. Este es el codigo:
procedure C o m p r e s s S t r e a m (aSource, a T a r g e t : T S t r e a m ) ;
var
comprStream: TCompresssionStream;
begin
c o m p r S t r e a m : = TCompressionStream.Create
clFastest, aTarget) ;
t rY
comprStream.CopyFrom(aSource, aSource
comprStream.CompressionRate;
finally
comprStream.Free;
end ;
end;

procedure D e c o m p r e s s S t r e a m (aSource, a T a r g e t : T S t r e a m ) ;
var
decompstream: TDecompressionStream;
n R e a d : Integer;
B u f f e r : array [O. .I0231 of C h a r ;
begin
decompstream : = TDecompressionStream.Create(aSource);
try
/ / a S t r e a m D e s t . C o p y F r o m (decompstream, size) no funciona
/ / c o r r e c t a m e n t e ya q u e s e d e s c o n o c e el tamado a priori,
/ / a s i q u e utilizamos ~ 7 nc o d l g o s i m z l a r "manual"
repeat
n R e a d : = decompstream. Read (Buffer, 102 4 ) ;
a T a r g e t .Write (Buffer, n R e a d ) ;
until nRead = 0;
finally
decompStream.Free;
end ;
end ;

Como se puede ver en 10s comentarios del codigo, la operacion de descompresion


resulta ligeramente mas compleja ya que no se puede utilizar el metodo CopyFrom:
se desconoce el tamaiio del stream resultante. Si se pasa 0 a este metodo, intenta-
ra obtener el tamaiio del stream de entrada, que es un TDecompressionstream.
Sin embargo, esta operacion causa una excepcion, ya que 10s streams de compre-
sion y descompresion solo pueden leerse desde el principio hasta el final y no
permiten buscar el final del archivo.

Resumen sobre las unidades principales


de la VCL y la unidad BaseCLX
Hasta ahora hemos hablado basicamente de una sola unidad de la biblioteca:
c1asses. De hecho, esta unidad contiene la mayoria de las clases realmente
importantes de la biblioteca. En este apartado haremos un resumen de lo disponi-
ble en la unidad c1asses y en algunas otras unidades.

La unidad Classes
Esta unidad es el corazon de las bibliotecas VCL y CLX y aunque ha sufrido
muchos cambios internos desde la ultima version de Delphi, para el usuario medio
las novedades son pocas. (La mayoria de 10s cambios estan relacionados con una
integracion modificada con el IDE y estan dirigidos a 10s escritores de componen-
tes expertos.)
Veamos una lista de lo que se puede encontrar en la unidad classes,una
unidad a la que todo programador deberia dedicar algun tiempo:

Diversos tipos enumerados, 10s punteros a metodo estandar (como


TNot i fyEvent) y muchas clases de excepcion.
Clases principales de la biblioteca, como TPersistent y TComponent,
per0 tambien muchas otras que rara vez se usaran directamente.
Clases de listas, como TList,TThreadList (una version con seguri-
dad de threads de la lista), TInterfaceList (una lista de interfaces,
usada internamente), T C o l l e c t i o n , T C o l l e c t i o n I t e m ,
TOwnedColle ction (que sencillamente es una coleccion con un pro-
pietario), TStrings y TStringList.
Todas las clases de streams mencionadas en la seccion anterior. Tambien
estan las clases TFiler,TReader y TWriter y una clase TParser
utilizada internamente para el analisis sintactico DFM.
Clases de utilidades, como TBits para la manipulacion binaria y unas
cuantas rutinas de utilidad (por ejemplo, constructores de punto y rectan-
gulo y rutinas de manipulacion de listas de cadenas como Linest art y
Extract Str ings). Tambien hay c l a m de registro para notificar a1
sistema la esistencia de componentes, clases, funciones de utilidad espe-
cial que se pueden sustituir y muchas mas.
La clase TDataModule, una sencilla alternativa a un formulario como
contenedor de objetos. Los modulos de datos solo pueden contener compo-
nentes no visuales y; por lo general, se usan en bases de datos y en aplica-
ciones Web.

nes anteriores de Delphi, la clase TDa taModule se


Forms; desde Delphi 6 se ha desplazado a la unidad
.,,,,,., -. VV J .e este cambio era eliminar el encabezamiento de cbdi-
go de las clases GUI de las aplicaciones no visuales (por ejemplo, 10s mo-
dulos de servidor Web) y para separar mejor el cirdigo de Windows no
transportable de las clases independientes del SO, como TDataModule.
Otros cambios esthn relacionados con 10s mbdulos de datos, por ejemplo,
para permitir la creacion de aplicaciones Web con diversos m6dulos de
datos .

Nuevas clases relacionadas con la interfaz, como TInter faced Per -


sistent, cuyo objetivo es ofrecer un mayor soporte para interfaces.
Esta clase concreta permite que el codigo Delphi mantenga una referencia
a un objeto TPersistent o a cualquier descendiente que implemente
interfaces y es un elemento principal del nuevo soporte para objetos con
interfaces del Object Inspector.
La nueva clase TRecall,usada para mantener una copia temporal de un
objeto, sobre todo para recursos basados en graficos.
La nueva clase TClassFinder, usada para encontrar una clase regis-
trada en lugar del metodo Findclass.
La clase TThread, que ofrece el nucleo del soporte independiente del
sistema operativo para aplicaciones multithreaded.

Novedades en la unidad Classes


En Delphi 7, la unidad Classes s610 ha sufrido unos aiiadidos minimos.
Ademas de 10s cambios ya mencionados en este capitulo, como el soporte amplia-
do para pares nombre-valor en la clase TStr ingList, existen unas cuantas
funciones globales nuevas, AncestorIsValid e
IsDefaultPropertyValue.
Ambas funciones se introdujeron para soportar el subrayado de propiedades
no predefinidas en el Object Inspector. Sirven para poco mas, y lo mas normal
sera no beneficiarse de su uso a no ser que se este interesado en guardar el estado
de un componente y un formulario, y escribir un mecanismo de streaming perso-
nalizado.

Otras unidades principales


Los programadores en Delphi no usan directamente las otras unidades que
forman parte del paquete RTL con tanta frecuencia como Classes. Veamos una
lista de estas otras unidades:
La unidad TypInfo incluye soporte para acceder a informacion RTTI para
propiedades publicadas.
La unidad SyncObjs contiene unas cuantas clases genericas para
sincronizacion de threads.
La unidad ZLib incluye streams de compresion y descompresion.
La unidad ObjAuto contiene codigo para invocar 10s metodos publicados
de un objeto por su nombre, pasando 10s parametros en un array de varian-
tes. Esta unidad forma parte del soporte ampliado para la invocacion dina-
mica de mttodos promovida por SOAP y otras nuevas tecnologias Delphi.
Por supuesto, el paquete RTL incluye tambien otras unidades con funciones y
procedimientos mencionadas anteriormente como Ma t h , S y s U t i 1 s ,
Variants, VarUtils, StrUtils, DateUtils, etc.
Controles
visuales

Ahora que hemos presentado el entorno de Delphi y hemos visto de manera


global el lenguaje Delphi y 10s elementos basicos de la biblioteca de componentes,
ya estamos preparados para profundizar en el uso de 10s componentes y el desa-
rrollo de las interfaces de usuario de aplicaciones. Esto es realmente de lo que
trata Delphi. La programacion visual mediante componentes es una caracteristica
clave de este entorno de desarrollo. Delphi incluye una gran cantidad de compo-
nentes listos para usar. No vamos a describir cada componente en detalle, con sus
propiedades y metodos; si se necesita esta informacion se puede encontrar en el
sistema de ayuda. La intencion de este capitulo y 10s siguientes es mostrar como
usar algunas de las caracteristicas avanzadas que ofrecen 10s componentes
predefinidos de Delphi para construir aplicaciones y comentar tecnicas especifi-
cas de programacion.
Para empezar compararemos la biblioteca VCL y la VisualCLX y analizare-
mos las clases basicas (en particular TControl). Despues examinaremos 10s
diversos componentes visuales, ya que escoger 10s controles basicos correctos
ayxdara a realizar el proyecto mas rapidamente. Este capitulo trata 10s siguientes
temas :
VCL frente a VisualCLX.
Vision global de 10s componentes estandar
Construccion basica y avanzada de menus.
Modificacion del menu del sistema.
Graficos en menus y cuadros de lista.
Estilos y dibujos por el propietario.

VCL frente a VisualCLX


Como vimos en el ultimo capitulo, Delphi dispone de dos bibliotecas de clases
visuales: la biblioteca multiplatafonna CLX y la tradicional biblioteca de Windows
VCL. Existen muchas diferencias, incluso en el uso de la RTL y de las clases de
la biblioteca de codigo, entre desarrollar programas para Windows o con un enfo-
que multiplataforma y estas diferencias resultan mas notables en la interfaz del
usuario.
La parte visual de la VCL es un envoltorio de la API de Windows. Contiene
envoltorios de controles originarios de Windows (como 10s botones y 10s cuadros
de edicion), de controles comunes (como vistas en arb01 y vistas en lista), ademas
de una serie de controles originarios de Delphi ligados a1 concept0 Windows de
ventana. Tambien hay una clase TCanvas que envuelve las llamadas graficas
basicas, de tal mod0 que se puede pintar facilmente sobre la superficie de una
ventana.
VisualCLX, la parte visual de CLX, es un envoltorio de la biblioteca Qt (pro-
nunciado "kiut"). Contiene envoltorios de 10s widgets nativos Qt, que van de
controles basicos a controles avanzados, muy similares a 10s propios controles
estandar de Windows. Tambien contiene soporte de dibujo utilizando otra clase
similar, TCanvas.Qt es una biblioteca de clases C++, desarrollada por Trolltech
(www.trolltech.com), una empresa noruega que mantiene una solida relacion con
Borland.
En Linux, Qt es una de las bibliotecas de interfaz de usuario estandar de facto
y es la base del entorno de escritorio KDE. En Windows, Qt ofrece una alternati-
va a1 uso de las API originarias. De hecho, a diferencia de la VCL, que ofrece un
envoltorio para 10s controles originales, Qt ofrece una implernentacion alternativa
de dichos controles. Incluso aunque ambos se basen en la ventana de Windows,
un boton no es un control Windows, un boton no es un control de clase BUTTON
de Windows (se puede ver si se ejecuta WinSight32). Esto permite que el progra-
ma resulte verdaderamente transportable, puesto que no hay diferencias ocultas
creadas por el sistema operativo (o introducidas por el distribuidor del sistema
operativo de forma oculta). Tambien nos pennite evitar una capa adicional. CLX
sobre Qt, sobre 10s controles originarios de Windows, sugiere tres capas, per0 en
realidad hay dos capas en cada solucion (controles CLX sobre Qt y controles
VCL sobre Windows).
NOTA: Distribuir aplicaciones Qt en Windows irnplica la distribution de
la propia biblioteca Qt. En la plataforma Linux, generalmente, se puede dar
por garantizada la presencia de la biblioteca Qt,pero aun hay que desple-
gar la biblioteca de la interfaz. Ademis, la CLX de Borland para Linux estA
enlazada a una versi6n especifica de Qt (que en Kylix 3 ha modificado
Borland especificamente), asi que probablemente sea necesario distribuir-
la. Distribuir las bibliotecas Qt - con una aplicaciones profesional Ial con-
trario que en un proyecto de cMigo abierto) generalmente implica pagar ~
una licencia a Trolltech. Sin embargo, si usa Delphi o Kylix para construir
aplicaciones Qt, Borland ya ha pagado la licencia a Trolltech en su lugar.
Se debe usar a1 menos una clase CLX que envuelva a Qt:si se utilizan

Tecnicamente, existen grandes diferencias en el ambito interno entre una apli-


cacion originaria de Windows creada con la VCL y un programa transportable Qt
desarrollado con la VisualCLX. Basta decir que a1 nivel mas bajo, Windows usa
las llamadas de funcion de la API y 10s mensajes para comunicarse con controles,
mientras Qt usa metodos de clase y callbacks (rctrollamadas) de metodo direct0 y
no tiene mensajes internos. Tecnicamente, las clases Qt ofrecen una arquitectura
orientada a objetos de alto nivel, mientras que la API de Windows esta todavia
ligada a su legado de C y a un sistema de mensajes que data de 1985 (aAo en el que
se sac6 a la venta Windows). VCL ofrece una abstraccion orientada a objetos en
la parte superior de una API de bajo nivel, mientras que VisualCLX proyecta una
interfaz ya de alto nivel en una biblioteca de clases mas familiar.

NOTA: Microsoft ha llegado a1 punto de comenzar a abandonar la tradi-


cional API de bajo nivel de Windows por ma biblioteca nativa de clases de
alto nivel, park de la arquitectura de .NET.

Si las arquitecturas subyacentes de la API de Windows y de Qt son relevantes,


las dos bibliotecas de clases de Borland (VCL y CLX) igualan la mayoria de las
diferencias, haciendo que el codigo de las aplicaciones Delphi y Kylix sea extre-
madamente similar. Tener una familiar biblioteca de clase sobre una plataforma
totalmente iiueva es la ventaja que adquieren 10s programadores de Delphi a1 usar
VisualCLX en Linux. Desde fuera, un boton es un objeto de la clase T B u t t o n
para ambas bibliotecas y tiene mas o menos el mismo conjunto de metodos, pro-
piedades y eventos. En muchas ocasiones, se pueden volver a compilar 10s pro-
gramas existentes para la nueva biblioteca de clase en cuestion de minutos, si no
se usan llamadas a funciones API de bajo nivel, caracteristicas dependientes de la
plataforma (como ADO o COM) o caracteristicas heredadas (como BDE).
Qt es una biblioteca de clase de C++ que incluye un completo conjunto de

lista). Ya que Qt es una biblioteca de C++, no se puede invocar directamen-


te desde el cMigo en Delphi. La API de Qt es accesible a traves de una capa
de enlace, definida en la unidad Qt.pas.
Esta capa de enlace consiste en una larga lista de envoltorios para casi cada
clase Qt con el sufijo finalde H. Asi, por ejemplo, las clase de Qt Q P a i n t e r
se convierte en el tipo Q P a i n t e r H en la capa de enlace. La unidad @pas
tambitn incluye una larga lista con todos ios r n h d o s pfiblicos de k&
clases, transformados en funciones esthdar (no en m h d o s de clase) que

cion importante son 10s constructores de clase, que se transforman en fi


ciones que devuelven la nueva instancia de la clase.
2 ---- 3- ---- - - - L l : - - ~ - 2 - - 1 ----
1T--.
nay que uarse cuenra ae que e s oollgaiorw el 2 - -1
uso oe 3-
a1 menos una ae
---a
las
clases de la capa de proyeccion para la licencia Qt que se incluye con
Delphi (y Kylix). Qt es gratuita para el us0 no comercial bajo X Window
(se llama Qt Free Edition), pero se debe pagar una licencia a Trolltech para
desarrollar aplicaciones comerciales. Cuando se compra Delphi, la licencia
Qt ya la ha pagado Borland, pero se debe usar Qt bisicamente a traves de la
biblioteca CLX (aunque se permitan llamadas a Qt de bajo nivel dentro de
una aplicacion CLX). No se puede usar directamente la unidad Qt.pas y
evitar la inclusion de la unidad QForms (que es obligatoria). Borland obli-
ga a esta limitation a1 omitir de la interfaz Qt 10s constructores QFormH y
QApplicationH.
En la mavor
- -, ~- r -a-r
~ -.
- e- - estos m
~t de -r
o m-~m a s en
-0 -
Debhi
- sblo
- -- - - usaremos
- ..-- . - - -obietos
~ -- v d

mttodos CLX. Es importante saber que si se necesita se pueden utilizar


directamente algunas caracteristicas adicionales de Qt; o p&de ser necesa-
rio realizar llamadas de bajo nivel para solucionar fallos de CLX.

Soporte dual de bibliotecas en Delphi


Delphi posee soporte total para ambas bibliotecas en tiempo de diseiio y en
tiempo de ejecucion. Cuando se comienza a desarrollar una nueva aplicacion, se
puede u tilizar la orden File>New Application para crear un nuevo programa
basado en VCL y File>New CLX Application para un nuevo programa basado
en CLX. Tras haber dado una de estas ordenes, el IDE de Delphi creara un
formulario VCL o CLX en tiempo de diseiio y actualizara la Component Palette
de forma que aparezcan solo 10s componentes visuales compatibles con el tip0 de
aplicacion seleccionada (vease la figura 5.1 para obtcner una comparacion). No
se pucde colocar un boton VCL en un formulario CLX; ni se pueden mezclar
formularios de bibliotecas en un mismo archivo ejecutablc. En otras palabras. la
interfaz de usuario de una aplicacion debe crearse de manera exclusiva con una de
las dos bibliotecas, lo que tiene mucho scntido.

I I I I
Standard Addtond Wh32 S~tmDaa Access Data Corirds &€mess I I DdaSnao I BDE I ADO I InloBsrt I Weffiavices I lnteld&
a d,a~FcT~& ~ ~ + ~ i ~ ~ b ~ ~ ~ ~ ~

I 1 1 1
~ l s d a r d A M m d Win32 ~v:tem 1 D a t a A m s 1 D d a Conl~ds dbExrverr I @ & ~ n r n 1 BDE 1 AD0 I lnldaoe 1 WFbSuwcer I l n l e ! n @ ~

Figura 5.1. Una comparacion de las tres primeras fichas de la Component Palette
para una aplicacion CLX o una VCL.

Es aconsejable experimcntar con la creacion de una aplicacion CLX. Se en-


contraran pocas difcrencias en cl uso dc 10s componentcs y probablementc sc
aprccie mas esta biblioteca.

Clases iguales, unidades diferentes


Una de las piedras angularcs de la compatibilidad del codigo fuentc entrc cl
codigo CLX y VCL es cl hecho de que las clascs similarcs dc las dos bibliotecas
poseen cxactamentc cl mismo nombrc de clase. Por ejcmplo. cada bibliotcca po-
scc una clasc denominada T B u t t o n quc representa un boton pulsador con mcto-
dos y propicdadcs muy similares. El siguientc codigo funcionara cn ambas
bibliotecas:
with TButton.Create (Self) do
begin
SetBounds ( 2 0 , 2 0 , 8 0 , 3 5 ) ;
Caption : = ' N u e v o ' ;
Parent : = Self;
end;

Las dos clases T B u t t o n tienen el mismo nombre y esto es posible debido a


que se guardan en dos unidades diferentes, denominadas s t d ~ rt1 s y
Q S t d C t r l s . Por supuesto, no podemos tener 10s dos componentes disponibles
en tiempo de diseiio en la paleta, porque el IDE de Delphi solo puede registrar
componentes con nombres unicos. Toda la biblioteca VisualCLX esta definida
mediante unidades que se corresponden a las unidades de la VCL, pero con la
letra Q como prefijo (de ahi que esista una unidad QForms, una unidad QDialogs,
una unidad QGraphics, etc.). Algunas unidades particulares como QStyle no tie-
ncn una unidad correspondiente en la VCL porque se proyectan sobre caracteris-
ticas de Qt que no tienen que ver con la API de Windows.
Fijese en que no hay configuraciones del compilador ni otras tecnicas ocultas
para distinguir entre las dos bibliotecas. Los que importa es el con.junto de unida-
des a las que sc hace refcrencia en el codigo. Recuerde que estas referencias
habran de resultar coherentes, puesto que no se pueden mezclar controles visuales
de las dos bibliotecas en un unico formulario ni tampoco en un unico programa.
DFM y XFM
Cuando creamos un formulario cn tiempo de diseiio, este se guarda en un
archivo de definicion de formulario. Las aplicaciones tradicionales VCL usan la
extension DFM (Delphi Form Module, Modulo de formulario Delphi). Las apli-
caciones CLX usan la extension XFM (Cross-platform (X) jbrm modules, Modu-
10s dc formulario multiplataforma o plataforma X). Un modulo de formulario es
el resultado del streaming del formulario y de sus componentes, y ambas bibliote-
cas comparten el mismo codigo streaming, por lo que producen un efecto bastante
similar. El formato de 10s archivos DFM y XFM, que puede basarse en una
rcpresentacion textual o binaria, es iddnlico.
Por eso, el motivo de usar dos estensiones diferentes es una simple indicacion
para programadores y para el IDE de 10s tipos de componentc que se deben espe-
rar en esa definicion; no sc trata de trucos internos del compilador o de formatos
incompatibles.
Si quercmos convertir un archivo DFM en un archivo XFM, sencillamente
podemos dark a1 archivo otro nombre. Sin embargo, cabe esperar ciertas diferen-
cias cn las propicdades. eventos y componcntes disponibles, de tal mod0 quc a1
abrir de nuevo la definicion de formulario para una biblioteca diferente, se oca-
sionarin probablemente algunas advertencias.

TRUCO:Aparentemente el IDE de Delphi escoge la biblioteca activa ob-


servando la extension del m6dulo de formulario, ignorando las referencias
de las sentencias uses. Por esa razon, hay que modificar la extension si
planearnos utilizar CLX. En Kylix, una extensibn diferente es totalmente
inutil, porque cualquier formulario se abre en el IDE como un formulario
CLX, sea cual sea su extension. En Linux, solo existe la biblioteca CLX
basada en Qt, que es la biblioteca de multiplataforma y la originaria.

Como ejemplo, hemos creado dos sencillas aplicaciones identicas, LibComp y


QLibComp, que so10 tienen algunos componentes y un controlador de eventos. El
listado 5.1 presenta las definiciones de formulario textuales de las dos aplicacio-
nes, que han sido construidas en el IDE de Delphi siguiendo 10s mismos pasos,
tras haber escogido una aplicacion CLX o VCL. Hemos marcado las diferencias
en negrita, como se podra ver, hay unas cuantas, la mayoria relacionadas con el
formulario y su fuente. La propiedad O l d C r e a t e O r d e r es una propiedad de
legado, utilizada para que sea compatible con Delphi 3 y con un codigo mas
antiguo, 10s colores estandar tienen nombres diferentes y CLX guarda 10s rangos
de las barras de desplazamiento.

Listado 5.1. Un archivo XFM (izquierda) y un archivo equivalente DFM (derecha).

object Forml : TForml object Forml: TForml


Left = 192 Left = 192
Top = 107 Top = 107
Width = 350 Width = 350
Height = 210 Height = 210
Caption = ' Q L i b C o r n p l Caption = ' L i b C o n p '
Color = clBackground Color = clBtnFace
VertScrollBar .Range = 161 Font.Charset = DEFAULT-CHARSET
HorzScrollBar.Range = 297 Font-Color = clWindowText
Font.Height = -11
Font.Name = 'MS S a n s S e r i f '
Font.Style = [ I
TextHeight = 13 TextHeight = 13
Textwidth = 6 Oldcreateorder = False
PixelsPerInch = 96 PixelsPerInch = 96
object Buttonl: TButton object Buttonl: TButton
Left = 56 Left = 56
Top = 64 Top = 64
Width = 75 Width = 75
Height = 25 Height = 25
Caption = ' A d d ' Caption = ' A d d '
TabOrder = 0 TabOrder = 0
OnClick = ButtonlClick OnClick = ButtonlClick
end end
object Editl: TEdit object Editl: TEdit
Left = 40 Left = 40
Top = 32 Top = 32
Width = 105 Width = 105
Height = 21 Height = 21
TabOrder = 1 TabOrder = 1
Text = ' m y name' Text = ' m y name'
end end
object ListBoxl: TListBox object ListBoxl: TListBox
Left = 176 Left = 176
Top = 32 Top = 32
Width = 121 Width = 121
Height = 129 Height = 129
Rows = 3 ItemHeight = 13
1tems.Strings = ( 1tems.Strings = (
'marc0 ' ' m r c o'
'john' 'john'
'helen' ) 'helen' )
TabOrder = 2 TabOrder = 2
end end
end end

Sentencias uses
Las unicas diferencias entre ambos ejemplos estan relacionadas con las senten-
cias u s e s . El formulario de la aplicacion CLX tiene el siguiente codigo inicial:
u n i t QLibCompForm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms,
QDialogs, QStdCtrls;

El formulario del programa VCL posee la tradicional sentencia u s e s :


u n i t LibCompForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;

El codigo de la clase y del unico controlador de eventos es absolutamente


identico. Por supuesto, la clasica directiva del compilador { S R * . dfm) se
sustituye por { $ R * .xfm) en la version CLX del programa.
lnhabilitar el soporte de ayuda a la biblioteca dual
Cuando se pulsa la tecla F1 en el editor para solicitar ayuda sobre una rutina,
clase o metodo de la biblioteca de Delphi, normalmente se podra escoger entre las
declaraciones VCL y CLX de cada tema. Sera necesario escoger antes de acceder
a la pagina indicada, lo que puede resultar bastante pesado despues de un tiempo
(ademas, muchas veces las paginas resulta identicas). Si no importa la CLX y
solo se tiene pensado usar la VCL (o a1 contrario), se puede inhabilitar esta alter-
nativa mediante la orden Help>Customize, con lo que se elimina todo lo que
tenga CLX en su nombre de entre 10s contenidos, el indice y 10s enlaces, y despues
guardando el proyecto. Despues se reinicia el IDE de Delphi y el motor de ayuda
no volvera a preguntar sobre CLX nunca mas. Por supuesto, no hay que olvidarse
de aiiadir esos archivos de ayuda de nuevo si se decide comenzar a utilizar CLX.
Del mismo modo, se puede reducir la ocupacion de memoria y el tiempo de carga
del IDE de Delphi a1 desinstalar 10s paquetes relacionados con CLX.

Eleccion de una biblioteca visual


A1 tener dos bibliotecas de interfaz de usuario diferentes que podemos usar en
Delphi, tendremos que seleccionar una para cada aplicacion visual.
Para efectuar la seleccion, debemos evaluar ciertos criterios. El primer criterio
es la capacidad de transporte. Si nos preocupa el hecho de ejecutar un programa
en Windows y en Linux con la misma interfaz de usuario, usando CLX probable-
mente podremos conseguir que las cosas resulten mas sencillas y permitira mante-
ner un archivo de codigo fuente unica con muy pocas I FDEF.Se puede aplicar lo
mismo, en el caso de considerar que Linux es (o posiblemente se transformara) en
nuestra plataforma clave. En cambio, si la mayoria de 10s usuarios que utilizan
nuestro programa son usuarios de Windows y simplemente queremos ampliar la
oferta con una version para Linux, se puede mantener el sistema dual VCLJCLX.
Esto implica probablemente dos conjuntos diferentes de archivos de codigo fuente
o demasiadas IFDEF. Otro criterio es el de la apariencia originaria. A1 utilizar
CLX en Windows, algunos de 10s controles se comportaran de un mod0 ligera-
mente diferente a1 que 10s usuarios esperarian (a1 menos 10s usuarios expertos).
En el caso de una interfaz de usuario simple (ediciones, botones, cuadriculas), eso
no importara demasiado, per0 si hay muchos controles de vista en arb01 y vista en
lista, las diferencias se haran patentes. Por otra parte, con CLX se puede dejar
que 10s usuarios seleccionen una apariencia a su gusto, diferente de la apariencia
basica de Windows y que la utilicen de manera consistente en las plataformas.
Esto significa que un aficionado a Motif sera capaz de escoger este estilo cuando
se le obligue a usar la platafonna Windows. Mientras que esta flexibilidad resulta
habitual en Linux, es extrafio usar una apariencia no nativa en Windows.
Usar controles originarios implica ademas que desde el momento en que se
consigue una nueva version del sistema operativo Windows, la aplicacion se adap-
tara (probablemente) a ella.Esto resulta muy ventajoso para el usuario, per0 po-
dria originar un monton de quebraderos de cabeza en caso de incompatibilidades.
Las diferencias en la biblioteca de controles comunes de Microsoft durante 10s
ultimos aiios ha sido una gran fuente de frustracion para 10s programadores de
Windows en general, incluidos 10s de Delphi.
Otro criterio es el despliegue: si se utiliza CLX, habra que incluir en el progra-
ma de Windows y Linux las bibliotecas Qt. Segun diversas pruebas, la velocidad
de las aplicaciones VCL y CLX es similar. Para comprobarlo, se pueden usar las
aplicaciones de ejemplo L i b s p e e d y Q L i b S p e e d , en las que se crean 1000
componentes y se muestran en pantalla. Otro criterio importante para decidir usar
CLX en lugar de VCL es la necesidad del soporte de Unicode. CLX tiene soporte
Unicode en sus controles de manera predefinida (incluso en plataformas Win9x
en que no lo soporta Microsoft). Sin embargo, la VCL tiene muy poco soporte de
Unicode incluso en las versiones de Windows que lo ofrecen, lo que hace dificil
construir aplicaciones VCL para paises en que el conjunto local de caracteres se
gestiona mejor cuando se basa en Unicode.

Ejecucion en Linux
La cuestion real sobre la eleccion de bibliotecas se reduce a la importancia que
tenga Linux o Unicode para nosotros y para 10s usuarios. Es muy importante
destacar que si se crea una aplicacion CLX, se podra compilar de nuevo inalterada
(con el codigo fuente esacto) en Kylis que crea una aplicacion originaria de
Linux, a no ser que se haya hecho algo de programacion con la API de Windows,
en cuyo caso la compilacion condicional resulta esencial.
Como ejemplo, se ha vuelto a compilar el ejemplo QLibComp y se puede ver
en ejecucion en la figura 5 . 2 , donde tambien aparece el IDE de Kylix en accion en
un sistema KDE.

Figura 5.2. Una aplicacion escrita con CLX puede volver a compilarse directamente
bajo Linux con Kylix.

Compilacion condicional de las bibliotecas


Si se quiere mantener un unico archivo de codigo fuente, pero compilar con
VCL en Windows y con CXL en Linux, se pueden utilizar 10s simbolos especifi-
cos de plataforma (como $ 1 FDE F LINUX) para distinguir las dos situaciones en
caso de compilacion condicional. Pero si queremos poder compilar una parte del
codigo en ambas bibliotecas en Windows, se puede definir un simbolo propio y
bien utilizar la compilacion condicional o (a veces) verificar la presencia de
identificadores que existan solo en VCL o solo en CLX, como a continuacion:
( $ I F Declared (QForms))
. . .CLX-specific c o d e
($IFEND)
Conversion de aplicaciones existentes
Ademas de comenzar nuevas aplicaciones CLX, tal vez queramos convertir
algunas de las aplicaciones VCL ya esistentes a la nueva biblioteca de clases.
Existen una serie de operaciones que habra que realizar, para las que el IDE de
Delphi no proporciona ayuda alguna:
Habra que cambiar el nombre del archivo DFM por XFM y actualizar
todas ]as sentencias { $ R * . D FM } como sentencias { $ R * .X FM } .
Habri que actualizar todas las sentencias u s e s del programa (en las uni-
dades y en 10s archivos dc proyecto) para referirse a las unidades CLX, en
lugar de a las unidades VCL. Fijese en que si faltan algunas, habra proble-
mas a1 e-iecutar la aplicacion.
- -
- - -- -

TRUCO:Para evitar que una aplicacibn CLX compile si contiene referen-


cias a unidades VCL, se pueden desplazar las unidades VCL a un directorio
rl;4Lran+a hain
u u w l w u r u
.r n&+sr ;nel..;r m n t n f i a m a t r
1 l4 u
vw,v r hwvl- -wIUIL
)I M L ~ en la rub de busqueda. De
U ~

este modo. ias referkcias a unidades VCIL W e s que queden causariin an


error "Unitnot found"' (Unidad no encontrada) .

La tabla 5.1 es una comparacion de 10s nombres de las unidades visuales VCL
y CLX, esceptuando la parte de la base de datos y algunas unidades a las que
raramente se hace referencia:

Tabla 5.1. Nombres de unidades equivalentes VCL y CLX.

ActnList QActn List


Buttons QButtons
Clipbrd QClipbrd
ComCtrls QComCtrls
Consts QConsts
Controls QControls
Dialogs QDialogs
ExtCtrls QExtCtrls
Forms QForms
Graphics QGraphics
Grids QGrids
ImgList QlmgList
Menus QMenus
Printers QPrinters
Search QSearch
StdCtrls QStdCtrls

Tambien podriamos convertir referencias a Windows y Messages en referen-


cias a la unidad Qt. Algunas estructuras de datos de Windows estan ahora dispo-
nibles en la unidad Types, por lo que tal vez queramos aiiadirla a nuestros
programas CLX. Sin embargo, hay quc tener en cuenta que la unidad QTypes no
esta en la version CLX de la unidad Types de VCL; estas dos unidades no estan
relacionadas en absoluto.

ADVERTENCIA: iPreste atenci6n a las sentencias uses! Si por casuali-


dad compilamos un proyecto que incluya un formulario CLX, per0 no ac-
tualizamos el codigo fuente del proyecto y dejarnos en el la referencia a la
unidad Forms de la VCL, el programa se ejecutara pero se detendra inme-
diatamente. La raz6n es que no se creo ningrin formulario VCL, por lo que
el programa finaliza directamente. En otros casos, intentar crear un formu-
lario CLX dentro de una aplicacion VCL originaria errores en tiempo de
ejecucion. Por ultimo, el IDE de Delphi podria aiiadir incorrectamente refe-
rencias a las sentencias uses de la biblioteca erronea Y asi acabariarnos
teniendo una unica sentencia uses, que se referiria a la misma unidad,
para ambas bibliotecas, pero solo la segunda de ellas seria efectiva. Esto en

Las clases TControl y derivadas


Una de las subclases mas importantes de TComponent es TControl, que
corresponde a 10s componentes visuales. Esta clase basica esta disponible tanto
en la CLX como en la VCL y define conceptos generales, como la posicion y
tamaiio del control, el control padre que lo contiene y muchos mas.
Sin embargo, para la implementacion real, tenemos que referirnos a sus dos
subclases. En VCL, estas son Twincontrol y TGraphicControl,en CLX
son TWidgetControl y TGraphicControl.Veamos sus caracteristicas
clave:
Los controles basados en ventanas: Son componentes visuales basados
en una ventana del sistema operativo. Un TWinCont r o 1 en una VCL
tiene un manejador de ventana, un numero que se refiere a una estructura
interna de Windows. Un T W i d g e t C o n t r o 1 en CLX tiene un manejador
Qt, una referencia a1 objeto interno Qt. Desde el punto de vista del usuario,
10s controles basados en una ventana pueden recibir el foco de entrada y
algunos pueden contener otros controles. Este es el mayor grupo de com-
ponentes de la biblioteca de Delphi. Podemos dividir 10s controles basados
en una ventana en dos grupos mas: envoltorios de controles originarios de
Windows o Qt y controles personalizados, que normalmente heredan de
TCustomControl.
Los controles graficos: Son componentes visuales que no se basan en una
ventana del sistema operativo. Por lo tanto, no tienen manejador, no pue-
den recibir el foco y no pueden contener otros controles. Estos controles
heredan de T G r a p h i c C o n t r o 1y 10s pinta su formulario padre, que les
envia eventos relacionados con el raton y de otros tipos. Como ejemplos de
controles no basados en ventanas estan L a b e l y S p e e d B u t t o n . Existe
una serie de controles en este grupo, que resultaban decisivos para minimi-
zar el uso de 10s recursos del sistema en las primeras versiones de Delphi
(en Windows de 16 bits). Usar controles graficos para ahorrar recursos de
Windows sigue siendo util en Win9x/Me, que ha elevado aun mas 10s limi-
tes del sistema per0 no ha conseguido librarse aun de ellos (no como
Windows NTl2000).

Parent y Controls
La propiedad P a r e n t de un control indica que otro control es responsable de
mostrarlo. Cuando dejamos un componente en un formulario en el Form Designer,
el formulario se transformara tanto en padre como en propietario del nuevo con-
trol.
Pero si dejamos el componente en un Panel, ScrollBox u otro componente
contenedor, este se convertira en su padre, mientras que el formulario seguira
siendo el propietario del control.
Cuando creamos el control en tiempo de ejecucion, sera necesario establecer el
propietario (usando el parametro del constructor C r e a t e ) , per0 habra que esta-
blecer tambien la propiedad P a r e n t o el control no sera visible.
A1 igual que la propiedad Owner, la propiedad P a r e n t posee su inverso. La
matriz C o n t r o l s , de hecho, lista todos 10s controles hijos del actual, enumera-
dos de 0 a C o n t r o l s c o u n t - 1.
Se puede analizar esta propiedad para trabajar con todos 10s controles que
aloje otro control, utilizando en ultimo termino un metodo recursivo que opere
sobre 10s controles hijos de cada subcontrol.
Propiedades relacionadas con el tamaho
y la posicion del control
Algunas de las propiedades introducidas por T C o n t r o l y comunes a todos
10s controles son aquellas relacionadas con el tamaiio y la posicion. La posicion
de un control la fijan sus propiedades L e f t y Top y su tamaiio las propiedades
H e i g h t y W i d t h . Tecnicamente, todos 10s componentes tienen una posicion,
porque cuando abrimos de nuevo un formulario existente en tiempo de diseiio,
queremos que se puedan ver 10s iconos de 10s componentes no visuales en la
posicion exacta en la que 10s situamos. Esta posicion es visible en el archivo de
formulario.

I Una caracteristica importante de la posicion de un componente es que, como


cualquier otra coordenada, siempre se relaciona con la zona de cliente de su
componente padre (indicada por su propiedad P a r e n t ) . En el caso de un formu-
lario, la zona del cliente es la superficie incluida dentro de sus bordes y la etiqueta
(exceptuando 10s propios bordes). Hubiera sido un poco confuso trabajar con las
coordenadas de la pantalla, aunque existen algunos metodos preparados para su
uso que convierten las coordenadas entre el formulario y la pantalla, y viceversa.
Sin embargo, fijese en que las coordenadas de un control siempre son relativas
a1 control padre, como un formulario u otro componente contenedor. Si se coloca
un panel en un formulario y un boton en un panel, las coordenadas del boton son
relativas a1 panel y no a1 formulario que contiene el panel. En este caso, el compo-
nente padre del boton es el panel.

Propiedades de activacion y visibilidad


Se pueden usar dos propiedades basicas para dejar que el usuario active u
oculte el componente. La mas sencilla es la propiedad E n a b l e d . Cuando se
desactiva un componente (cuando E n a b l e d se define como F a l s e ) , normal-
mente hay alguna pista visual que se lo indica a1 usuario. En tiempo de diseiio, la
propiedad desactivada no siempre provoca un efecto, per0 en tiempo de ejecu-
cion, 10s componentes estan, por lo general, en gris.
Para ver una tecnica mas drastica, se puede ocultar completamente un compo-
nente, ya sea utilizando el correspondiente metodo H i d e o definiendo su propie-
dad V i s i b l e como F a l s e . Sin embargo, hay que tener en cuenta que leer el
estado de la propiedad V i s i b l e no indica si el control es realmente visible. En
realidad, si el contenedor de un control esta oculto, incluso aunque el control este
configurado como V i s ib l e , no se puede ver. Por esta razon, existe otra propie-
dad, Showing, que es una propiedad solo de lectura en tiempo para determinar
si el control es realmente visible para el usuario; es decir, si es visible, su control
padre tambien lo es, el control padre del control padre tambien lo es, y asi sucesi-
vamente.

Fuentes
Normalmente se usan dos propiedades para personalizar la interfaz de usuario
de un componente, Co l o r y F o n t . Hay diversas propiedades relacionadas con
el color. La propiedad c o l o r se refiere normalmente a1 color de fondo del com-
ponente. Ademas, existe una propiedad C o l o r para las fuentes y muchos otros
elementos graficos. Muchos componentes tienen tambien las propiedades
P a r e n t c o l o r y P a r e n t F o n t , que indican si el control deberia utilizar la
misma fuente y color que su componente padre, que suele ser el formulario. Se
pueden usar estas propiedades para cambiar la fuente de cada control en un for-
mulario configurando sencillamente la propiedad F o n t de este ultimo.
Cuando se configura una fuente, introduciendo valores para 10s atributos de la
propiedad en el o b j e c t I n s p e c t o r o utilizando el cuadro de dialog0 estandar
de seleccion de fuente, se puede escoger una de las fuentes instaladas en el siste-
ma. El hecho de que Delphi permita usar todas las fuentes instaladas en el sistema
tiene tanto ventajas como inconvenientes. La principal ventaja es que si se tiene
instalado un cierto numero de agradables fuentes, el programa podra utilizarlas.
El inconveniente es que si se distribuye la aplicacion, estas fuentes puede que no
se encuentren disponibles en 10s ordenadores de 10s usuarios.
Si el programa utiliza una fuente que el usuario no tiene, Windows elegira
alguna otra fuente para reemplazarla. Un resultado cuidadosamente formateado
del programa puede verse arruinado por la sustitucion de fuentes. Por esta razon,
probablemente deberiamos confiar solo en las fuentes estandar de Windows (corno
MS Sans Serif, System, Arial, Times New Roman, etc.).

Colores
Existen diversas formas de fijar el valor de un color. El tip0 de esta propiedad
es T C o l o r , que no es un tipo de clase sino simplemente un tipo entero. Para
propiedades de este tipo, se puede escoger un valor de una serie de constantes de
nombre predefinidas o introducir directamente un valor. Las constantes para 10s
colores son entre otras c l B l u e , c l s i l v e r , c l W h i t e , c l G r e e n , c l R e d y
muchas mas (incluidas las aiiadidas con Delphi 6: clMone yGreen, c l SkyBlue,
c l C r e a m y clMedGray). Como una alternativa mejor, se puede utilizar uno de
10s colores usados por el sistema para indicar el estado de algunos elementos.
Este conjunto de colores es diferente en VCL y CLX.
VCL incluye colores predefinidos de Windows como el fondo de una ventana
( c l w i n d o w ) , el color del texto de un menu resaltado ( c l H i g h t l i g h t T e x t ) ,
el titulo activo ( c l A c t i v e c a p t i o n ) y el color de la cara ubicua del boton
( c l B t n F a c e ) . CLX contiene un conjunto diferente e incompatible de colores
del sistema, como c l B a c k g r o u n d , que es el color estandar de un formulario,
c l B a s e , utilizado por 10s cuadros de edicion y otros controles visuales,
c l A c t i v e F o r e g r o u n d , el color de primer plano para 10s controles activos y
c l D i s a b l e d B a s e , el control de fondo para 10s controles de texto desactivados.
Todas las constantes mencionadas aqui estan listadas en 10s archivos de ayuda de
la VCL y CLX bajo el titulo "TColor type" (Tipo TColor).
Otra opcion consiste en especificar un T C o l o r como un numero (un valor
hexadecimal de 4 bytes) en lugar de utilizar un valor predefinido. Si se utiliza esta
tecnica, se deberia saber que 10s tres bytes menores de dicho numero representan
las intensidades RGB de color del azul, verde y rojo respectivamente. Por ejem-
plo, el valor SO 0 FFO 0 0 0 se corresponde a un color azul puro, el valor
$ 0 0 0 0 ~ ~ 0a10verde, el valor SOOOOOOFF a1 rojo, el valor $ 0 0 0 0 0 0 0 0 a1
negro y el valor $0 0 FFFFFF a1 blanco. A1 especificar valores intermedios, se
puede obtener cualquiera de 10s 16 millones de colores posibles.
En lugar de especificar directamente estos valores hexadecimales, deberiamos
utilizar la funcion RGB de Windows, que tiene tres parametros, todos entre el 0 y
el 255. El primer0 indica la cantidad de rojo, el segundo la cantidad de verde y el
ultimo la cantidad de azul. Utilizar la funcion RGB hace que 10s programas Sean
por lo general m b faciles de leer que si usamos una constante hexadecimal sola.
En realidad, RGB es casi una funcion de la API de Windows. Esta definida por las
unidades relacionadas con Windows y no por las unidades de Delphi, per0 no
existe una funcion similar en la API de Windows. En C, existe una macro que
tiene el mismo nombre y efecto. RGB no esta disponible en CLX, por lo que hemos
escrito una version propia del siguiente modo:
function RGB ( r e d , g r e e n , b l u e : B y t e ) : C a r d i n a l ;
begin
R e s u l t : = b l u e + g r e e n * 256 + r e d * 256 * 256;
end:

El byte m b significative del tipo T C o l o r se utiliza para indicar en que paleta


deberia buscarse el color correspondiente mas proximo, per0 no hablaremos aqui
sobre las paletas. (Los sofisticados programas de tratamiento de imagenes usan
tambien este byte para llevar la informacion sobre transparencia de cada elemento
que aparece en pantalla.)
En cuanto a las paletas y la correspondencia de color, fijese en que Windows
sustituye a veces un color arbitrario por el color solido mas proximo, a1 menos en
10s modos de video que usan una paleta. Esto siempre ocurre en el caso de fuen-
tes, lineas, etc., En otras ocasiones, Windows usa una tecnica de punteado para
imitar el color solicitado a1 dibujar un ajustado modelo de pixeles con 10s colores
disponibles. En adaptadores de 16 colores (VGA) y a gran resolution, con fre-
cuencia acaban por verse extraiios modelos de pixeles de diferentes colores y no
del color que se tenia en mente.

La clase TWinControl (VCL)


En Windows, la mayoria de 10s elementos de la interfaz de usuario son venta-
nas. Desde el punto de vista de un usuario, una ventana es una parte de la pantalla
rodeada por un borde, que tiene un titulo y normalmente un menu de sistema. Pero
tecnicamente hablando, una ventana es una entrada en una tabla interna del siste-
ma, que se corresponde normalmente con un elemento visible en pantalla con un
codigo asociado. La mayoria de estas ventanas tienen la funcion de controles,
otras las crea temporalmente el sistema (por ejemplo, para mostrar un menu des-
plegable). Tambien hay otras ventanas creadas por la aplicacion per0 que perma-
necen ocultas a1 usuario y se usan solo como un medio para recibir un mensaje
(por ejemplo, 10s sockets sin bloqueo utilizan ventanas para comunicarse con el
sistema).
El comun denominador de todas las ventanas es que el sistema Windows las
conoce y que para mostrar su comportamiento se refieren a una funcion. Cada vez
que pasa algo en el sistema, se envia un mensaje de notificacion a la ventana
adecuada, que responde ejecutando algo de codigo. Cada ventana del sistema
tiene una funcion asociada (denominada por lo general su procedimiento de venta-
na), que gestiona 10s diversos mensajes de interes para la misma.
En Delphi, cualquier clase TW inCo nt ro 1 puede sobrescribir el metodo
WndProc o definir un nuevo valor para la propiedad WindowProc.Sin embar-
go, 10s mensajes interesantes de Windows pueden seguirse mejor mediante
controladores de mensajes especificos. Aun mejor, la VCL convierte estos mensa-
jes de bajo nivel en eventos.
En resumen, Delphi nos permite trabajar a un alto nivel, simplificando el desa-
rrollo de la aplicacion, per0 permitihdonos aun asi acudir a un nivel mas bajo
cuando sea necesario.
Fijese tambien en que a1 crear una instancia de una clase basada en
TWinControl no se crea automaticamente su correspondiente manejador de
ventana. En realidad, Delphi usa una tecnica de inicializacion perezosa, por lo
que el control de bajo nivel se crea solo cuando es necesario, normalmente desde
el momento en que un metodo accede a la propiedad Handle.El metodo get de
esta propiedad llama la primera vez a HandleNeeded, que a su vez llama a
C r e a t e H a n d l e ... y asi sucesivamente, hasta llegar a CreateWnd,
Createparams y CreateWindowHandle (la secuencia es bastante com-
pleja). Por el contrario, se puede conservar un control existente en memoria (tal
vez invisible) per0 destruir su manejador de ventana y ahorrar recursos del siste-
ma.
La clase TWidgetControl (CLX)
En CLX, todo control TWidgetControl tiene un objeto interno Qt, a1 que
se hacc rcferencia utilizando la propiedad Handle.Esta propiedad posee el mis-
mo nombre que la propiedad correspondiente de Windows, per0 es totalmente
difercnte en su ambito interno.
El objeto Qt/C++ lo posee normalmente el correspondiente objeto TWidget-
Control.La clase utiliza la construccion retardada (el objeto interno no se crea
hasta que se necesite uno de sus metodos), como se puede ver implementada en el
metodo Initwidget y en otros metodos. La clase CLX tambien libera el objeto
interno cuando se destruye. Sin cmbargo, tambien es posible crear un widget
alrededor de un objeto existente Qt: en este caso, el widget no poseera a1 objeto Qt
ni lo dcstruira. Este comportamiento se indica en la propiedad OwnHandle.
En realidad, cada componente VisualCLX tiene dos objetos C++ asociados, el
Qt Handle y el Qt Hook,que es el objeto que recibe 10s eventos del sistema.
Con el diseiio Q t actual, ~t Hook tiene que ser un objeto C++, que actua como
intermediario de 10s controladores de eventos del control de Delphi. El metodo
Hoo kEvent s asocia el objeto gancho a1 control CLX.
A diferencia de Windows, Qt define dos tipos diferentes de eventos:
Events: Son la traduccion de una entrada de usuario o eventos dc sistcma
(como la pulsacion de una tecla, un movimiento de raton o un dibujo).
Signals: Son eventos de componentes internos (que se corresponden con
las operaciones internas o abstractas VCL, como OnClic k y OnChange)
Sin embargo, 10s evcntos de un componente C L X fundcn eventos y seiiales.
Los eventos de controlcs CLX genericos dc Delphi incluyen OnMouseDown,
OnMouseMove,OnKeyDown, OnChange,OnPaint y muchos mas, exacta-
mentc como la VCL (que lanza la mayoria de 10s eventos cn respuesta a mensajes
dc Windows).

1 NOTA:
mit&
pKogrr;maddreesmpertos pug& no* q4e en CLX hqy un
utibadi3 can *a hecdrencia, EventHandler, que se cmres-

Abrir la caja de herramientas


de componentes
Si s e quiere escribir una aplicacion Delphi, hay que abrir un nuevo proyecto
Delphi y nos encontraremos ante un gran numero de componentes. El problema es
que para cada operacion, existen diversas alternativas. Por ejemplo, se puede
mostrar una lista de valores utilizando un cuadro de lista, un cuadro combinado,
un grupo de botones de radio, una malla de cadena (srring grid), una vista en lista
o incluso una lista en arb01 si existe un orden jerarquico. Para seleccionar una de
ellas; debemos considerar cual sera la tarea de la aplicacion. Hemos elaborado un
resumen bastante conciso de las opciones alternativas para realizar algunas ta-
reas muy comunes.

NOTA: Para algunos de 10s cantroles descritos en las siguientessecciones,


Delphi incluye tambikn una versibn &fa-aware, in&& nomlalmente por
el prefijo DB. C ~ m ose verh, la versibn DB de un control sueh prestar una
funcion similar a la de su equivalente "esthdar",per0 las propiedades y las
formas en que se usa son bastante diferentes. Por ejemplo, en un control
E d i t se usa la propiedad'~ex t, mientras que en un componente DBEdi t
se accede a1 campo Value del objeto relacionado.

Los componentes de entrada de texto


Aunque un formulario o componente puede controlar directamente la entrada
del teclado, utilizando un evento OnKeyPress, no se trata de una operacion
muy comun. Windows ofrece controles preparados para su uso para obtener en-
tradas de cadena e incluso construir un sencillo editor de textos. Delphi dispone
de varios componentes ligeramente diferentes en este campo.
El componente Edit
El componente Edit permite a1 usuario introducir una unica linea de texto.
TambiCn se puede mostrar una linea de texto con un control Label o un control
StaticText, pero estos componentes se utilizan, por lo general, solo para texto fijo
o para salidas generadas por el programa, no para entradas. En CLX, tambien
hay un control originario de digito LCD que se puede usar para mostrar numeros.
El componente Edit usa la propiedad Text, mientras que muchos otros con-
troles usan la propiedad Caption para referirse al texto que muestran. La unica
condicion que se puede imponer al usuario es el numero de caracteres aceptados.
Si queremos que se acepten solo unos caracteres especificos, se puede controlar el
evento OnKeyPress del cuadro de edicion. Por ejemplo, se puede escribir un
metodo que compruebe si el caracter es un numero o la tech Retroceso (quc tiene
un valor numeric0 de 8). Si no es asi. cambiamos el valor de la t e c h a1 caracter
cero (#0)>de mod0 que el control de edicion no lo procese y se produzca un sonido
de advertencia:
procedure TForml.EditlKeyPress(
Sender: TObject; var Key: Char) ;
begin
/ / v e r i f i c a s i l a t e c h es u n numero o r e t r o c e s o
i f not (Key i n [ ' 0 ' . . ' 9 ' , # 8 ] ) then
begin
Key : = #O;
Beep;
end ;
end ;

NOTA: Una pequefia diferencia de CLX es que el control Edit no tiene un


mecanismo Undo incorporado. Otra qs que la propiedad Pass~wordChar
se sustituye por la propiedad EchoMode. Nosotros no podemt1s establecer
el carhcter que aparece, sin0 si visualizar el texto enteroo mvJuar
lugar un asterisco.

El control LabeledEdit
Delphi 6 aiiadio un control llamado LabeledEdit, que es un control Edit
con una etiqueta adjunta. La etiqueta aparece como propiedad del control com-
puesto, que hereda de TCustomEdit.
Este componente es muy comodo, porque nos permite reducir el numero de
componentes de nuestros formularios, moverlos mas facilmente y tener una orga-
nizacion mas consistente para las etiquetas de todo un formulario o una aplica-
cion. La propiedad EditLabel esta conectada con el subcomponente, que tiene
las propiedades y eventos normales. Dos propiedades mas, LabelPosit ion y
Labelspacing, nos permiten configurar las posiciones relativas de 10s dos
controles.

NOTA: Este componente se ha airadid0 a la unidad EWCtrls para mostrar


el'u$o de subcomponentes en el Obj ect ~ n s ~ e c t o r .

El componente MaskEdit
Para personalizar aun mas la entrada de un cuadro de edicion, se puede utilizar
el componente Mas kEdit.Tiene una propiedad EditMask que es una cadena
que indica para cada caracter si deberia ser una mayhcula, minuscula o un nu-
mero y otras condiciones similares.
El editor Input Mask permite introducir una mascara, per0 tambien nos pide
que indiquemos un caracter que reserve el sitio para la entrada y decidir si se
guarda el material presente en la mascara junto con la cadena final. Por ejemplo,
se puede escoger mostrar el prefijo de zona del numero de telefono entre parente-
sis solo como una entrada de sugerencia o guardar 10s parentesis con la cadena
que almacena el numero resultante. Estas dos entradas en el editor'lnput Mask
corresponden a 10s dos ultimos campos de la mascara (separados por puntos y
coma). Se puede ver el editor de la propiedad EditMask a continuacion:

-
Date 06/27/94
Long Tme 09 05 15PM
Shod T~me
ILL.-
kid*... I I T ( I Hdp I

I TRUCO:Si hacemob did. sobre el&th MEW cfBt Inpd Mask ~ditnr. 1
se pueden eswger & c m de entrada predefbidas p&3 difkrentes paises.

Los componentes Memo y RichEdit


Los controles comentados hasta ahora solo permiten usar una linea de entrada.
El componente Memo puede contener varias lineas de testo, per0 (en las platafor-
mas Win9.5198) todavia mantiene el limite de texto (32 KB) de Windows de 16
bits y solo permite una unica fuente para todo el texto. Se puede trabajar con el
texto del campo memo linea por linea (utilizando la lista de cadena Lines) o
acceder a todo el texto de una sola vez (usando la propiedad Text).
Si queremos alojar una gran cantidad de testo o cambiar las fientes y las
alineaciones de parrafo, en la VCL deberiamos utilizar el control RichEdit, un
control comun de Win32 basado en el formato de documento RTF. Se puede encon-
trar un ejemplo de un completo editor basado en un componente RichEdit entre
10s programas de ejemplo que incluye Delphi. (El nombre del ejemplo tarnbien es
RichEdit). El componente RichEdit tiene una propiedad DefAttributes
que indica 10s estilos predefinidos y una propiedad SelAtt ributes que indica
el estilo de la selection actual. Estas dos propiedades no son del tipo TFont,pero
son compatibles con las fuentes, por lo que podemos usar el metodo AS s ign para
copiar el valor, como en el siguiente fragment0 de codigo:
procedure TForml.ButtonlClick(Sender: TObject);
begin
.
if RichEdit1 SelLength > 0 then
begin
FontDialogl.Font.Assign (RichEdit1.DefAttributes);
if FontDialog1.Execute then
RichEditl.SelAttributes.Assign (FontDialogl.Font);
end;
end;
El control CLX TextViewer
Entre todos 10s controles comunes, CLX y Qt no tienen un control RichEdit.
Sin embargo, ofrecen un completo visor HTML, que es muy potente para mostrar
texto formateado per0 no para escribirlo. Este visor HTML esta insertado en dos
controles diferentes, el control TextViewer de una unica pagina o el control
TextBrowser con enlaces activos. Como simple demostracion, hemos aiiadido un
campo de memo y un visor de texto a un formulario CLX y 10s hemos conectado
de forma que todo lo que se teclee en el campo de memo aparezca inmediatamente
en el visor. Hemos llamado a1 ejemplo HtmIEdit, no porque sea un autentico
editor HTML, sin0 porque este es el mod0 mas sencillo de crear una vista previa
de HTML dentro de un programa. El formulario del programa se puede ver en
tiempo de ejecucion en la figura 5.3.

Test Html
Test text with bold

and linally a "deaf' hyped&, marcocantu.com

4
Figura 5.3. El ejemplo HtmlEdit en tiempo de ejecucion: cuando se aiiade nuevo
texto HTML al campo de memo, se puede previsualizar inmediatamente.

Linux. Para daptar este ejcrnplo g Windows y Delphi, sblo es necesarid .


copiar Iw archivos y v o h r a compilar.

Selection de opciones
Existen dos controles estandar Windows que permiten a1 usuario escoger dife-
rentes opciones, asi como otros dos controles para agrupar conjuntos de opciones.
Los componentes CheckBox y RadioButton
El primer control estandar de seleccion de opciones es la casilla de verificacion
(o check box), que corresponde a una opcion que se puede seleccionar sea cual sea
el estado de otras casillas de verificacion. Configurar la propiedad AllowGrayed
de la casilla de verificacion nos permite mostrar tres estados diferentes (seleccio-
nado, no seleccionado y en gris), que se alternan a medida que el usuario hace clic
sobre la casilla de verificacion.
El segundo tip0 de control es el boton de radio, que corresponde a una selec-
cion exclusiva. Dos botones de radio del mismo formulario o dentro del mismo
contenedor de grupo de radio no se pueden seleccionar a1 mismo tiempo y uno de
ellos deberia estar siempre seleccionado (como programador, se tiene la respon-
sabilidad de escoger uno de 10s botones de radio en tiempo de diseiio).
Los componentes GroupBox
Para alojar varios grupos de botones de radio, se puede usar un control
GroupBox para mantenerlos juntos, tanto funcional como visualmente. Para cons-
truir un cuadro de grupo con botones de radio, sencillamente hay que colocar el
componente GroupBox sobre un formulario y, a continuation, aiiadir 10s boto-
nes de radio a1 cuadro de grupo, como en el siguiente ejemplo:

Se pueden controlar 10s botones de radio de forma individual, pero es mas


sencillo desplazarse a traves de la matriz de controles que posee el cuadro de
grupo. Aqui tenemos un pequeiio extract0 de codigo para obtener el texto del
boton de radio seleccionado de un grupo:
var
I: Integer;
Text: string;
begin
for I : = 0 to GroupBoxl.ControlCount - 1 do
if (GroupBoxl.Controls [I] as TRadioButton) .Checked then
Text : = (GroupBoxl.Contro1s[I] as TRadioButton) .Caption;

El componente RadioGroup
Delphi posee un componente similar que se puede utilizar de forma especifica
para botones de radio: el componente RadioGroup. Un RadioGroup es un cua-
dro de grupo con algunos clones de botones de radio en su interior. La diferencia
es que estos botones de radio internos se gestionan automaticamente desde el
control contenedor. Utilizar un grupo de radio es, por lo general, mas sencillo que
utilizar el cuadro de grupo, puesto que 10s diversos elementos forman parte de una
lista, como en un cuadro de lista. Asi es como se puede obtener el texto del
elemento seleccionado:
Text : = RadioGroupl.Items [RadioGroupl.ItemIndex];

Otra ventaja es que un componente R a d i o G r o u p puede alinear


automaticamente 10s botones de radio en una o mas columnas (corno indica la
propiedad columns) y se pueden aiiadir facilmente nuevas opciones en tiempo
de ejecucion, aiiadiendo cadenas a la lista de cadenas 1tems. Sin embargo, aiia-
dir nuevos botones de radio a un cuadro de grupo resulta bastante complejo.

Cuando hay muchas selecciones, 10s botones de radio no resultan apropiados.


El numero de botones de radio mas habitual es inferior a cinco o seis, para no
abarrotar la interfaz de usuario. Cuando tenemos mas opciones, podemos usar un
cuadro de lista o uno de 10s otros controles que muestran listas de elementos y
permiten la seleccion de uno de ellos.

El componente ListBox
La seleccion de un elemento en un cuadro de lista usa las propiedades ~t ems
e ItemIndex como en el codigo anterior para el control RadioGroup. Si hay
que acceder con frecuencia a1 texto de 10s elementos del cuadro de lista seleccio-
nado, se puede escribir una funcion envoltorio como esta:
f u n c t i o n SelText (List: TListBox) : string;
var
nItem: Integer;
begin
n I t e m : = List.ItemIndex;
i f n I t e m >= 0 then
Result : = List. Items [nItem]
else
Result := ' ' ;
end;

Otra caracteristica importante es que a1 utilizar el componente ListBox, se


puede escoger entre permitir solo una seleccion, como en un grupo de botones de
radio, y permitir selecciones multiples, como en un grupo de casillas de verifica-
cion. Esta eleccion la hacemos especificando el valor de la propiedad
~ u l iselect.
t Existen dos tipos de selecciones multiples en cuadros de lista en
Windows y en Delphi: seleccion multiple y seleccion ampliada. En el primer caso,
un usuario selecciona diversos elementos haciendo clic sobre ellos, mientras que
en el segundo caso, el usuario puede usar las teclas Mayus y Control para selec-
cionar diversos elementos consecutivos o no consecutivos, respectivamente. Esta
segunda opcion la determina el estado de la propiedad ExtendedSele ct .
En el caso de un cuadro de lista de seleccion multiple, un programa puede
recuperar informacion sobre un numero de elementos seleccionados utilizando la
propiedad selcount y se puede establecer que elementos estan seleccionados
examinando la matriz Sele cted. Dicha matriz de valores booleanos tiene el
mismo numero de entradas que un cuadro de lista. Por ejemplo, para concatenar
todos 10s elementos seleccionados en una cadena, se puede buscar en la matriz
Sele cted del siguiente modo:
var
SelItems: string;
nItem: Integer;
begin
SelItems : = ' I ;

f o r nItem : = 0 t o ListBoxl.Items.Count - 1 do
i f ListBoxl. Selected [nItem] then
SelItems : = SelItems + ListBoxl.Items [nItem] + ' ' ;
En CLX (no como en la VCL), se puede configurar una ListBox para que
utilice un numero fijo de columnas y filas, utilizando las propiedades columns,
Row, ColumnLayout y RowLayout.De ellas, la ListBox de la VCL tiene
solo la propiedad columns.

El componente ComboBox
Los cuadros de lista acaparan mucho espacio en pantalla y ofrecen unas opcio-
nes fijas (es decir, un usuario puede escoger solo entre 10s elementos de la lista y
no puede introducir ninguna opcion que el programador no haya tenido explicita-
mente en cuenta). Se pueden solucionar ambos problemas utilizando un control
ComboBox,que combina un cuadro de edicion y una lista desplegable. El com-
portamiento de un componente ComboBox cambia mucho dependiendo del valor
de su propiedad sty1e :
El estilo csDropDown: Define un cuadro combinado tipico, que permite
editar directamente y mostrar un cuadro de lista mediante solicitud.
El estilo csDropDownList: Define un cuadro combinado que no permite
editar (pero en el que se pueden pulsar ciertas teclas para seleccionar un
elemento).
El estilo cssimple: Define un cuadro combinado que siempre muestra el
cuadro de lista bajo el.
Fijese en que acceder a1 texto del valor seleccionado de un cuadro combinado
es mas sencillo que hacer la misma operacion en el caso de un cuadro de lista,
pucsto que podemos sencillamente usar la propiedad Text.Un truco util y habi-
tual para 10s cuadros combinados consiste en aiiadir un nuevo elemento a la lista
cuando un usuario introduce texto y pulsa la tech Intro. El siguiente metodo
comprueba primer0 si el usuario ha pulsado esa tecla, analizando el caracter con
el valor numeric0 (ASCII) de 13. A continuacion, verifica que el texto del cuadro
combinado no este vacio y que no esta ya en la lista (si su posicion en la lista es
menor que cero). Veamos el codigo:
procedure TForml.ComboBoxlKeyPress(
Sender: TObject; var Key: C h a r ) ;
begin
/ / s i el usuario pulsa l a tecla Intro
i f Key = Chr ( 1 3 ) then
with C o m b o B o x 3 do
i f (Text <> " I and (1tems.IndexOf (Text) < 0 ) then
1tems.Add (Text);
end :

TRUCO:En CLX, el cuadro combinado puede aiiadir automaticamente el


texto escrito en el cuadro de edicion a la lista desplegable, cuando el usua-
ria pulsa la tecla Intro. Ademas, algunos eventos ocurren en diferentes
ocasiones en la VCL.

Dcsdc Delphi 6, se incluyen dos nuevos eventos para el cuadro combinado. El


cvcnto onC lo s eUp corresponde a1 cicrre de la lista desplegable y complemcnta
a1 cvento OnDropDown que existia previamente. El evento onseiect solo se
lanza cuando el usuario hace una seleccion en la lista desplegable, en lugar de
escribir cn la parte de edicion.
Otro mcjora es la propiedad AutoComplete. Cuando se fija, el componente
ComboBox (y tambicn el componente List Box) busca automaticamente la ca-
dena mas parecida a aquella que el usuario esta escribiendo, sugiriendo la parte
final del testo.
La parte principal de esta caracteristica, tambidn disponible en CLX, se
implements en el mctodo TCustomList Box. KeyPres s.

El componente CheckListBox
Otra ampliacion del control de cuadro de lista la representa el componente
CheckListBox, un cuadro de lista con cada uno de 10s elementos precedidos
por una casilla de verificacion.
Un usuario puede seleccionar un unico elemento de la lista, pero tambien hacer
clic sobre las casillas de verificacion para alternar su estado. Esto hace que
CheckListBox sea un componente realmente adecuado para las selecciones
multiples o para resaltar el estado de una serie de elementos independientes (en
forma de una serie de casillas de verificacion).
10s grupos de colores que queremos ver en la lista (colores estandar, colores
ampliados, colores de sistema. etc.).
Los componentes ListView y TreeView
Si queremos una lista mas sofisticada, se puede utilizar el habitual control
ListView, que hara que la interfaz de usuario de la aplicacion parezca muy mo-
derna. Este componente es ligeramente mas complejo de usar, como ya se vera.
Otras alternativas para listar valores son el control comun TreeView, que mues-
tra elemcntos en una disposicion jerarquica y el control StringGrid, que muestra
divcrsos elementos para cada linea.
Si utilizamos 10s controles comunes en nuestras aplicaciones, 10s usuarios ya
sabran como interactuar con ellos y consideraran la interfaz de usuario del pro-
grama como actualizada. TrecVicw y ListView son dos componentes clave del
Esplorador de Windows y se puede suponer que muchos usuarios estaran familia-
rizados con ellos: incluso mas que con 10s controles tradicionales de Windows.
CLX tambien aiiade un control IconView, que es similar en parte de las caracte-
risticas a la ListView VCL.

ADVERTENCIA: El control ListView en la CLX no dispone de 10s estilos


de icono pequeiiolgrande de su contraparticla en Windows, pero un control
similar, IconView, proporciona esta capacidad.

El componente ValueListEditor
Las aplicaciones de Delphi utilizan normalmente la estructura nombrelvalor
que ofrecen en principio las listas de cadena. Delphi 6 introdujo una version del
componente StringGrid (tecnicamente una clase descendiente de TCustomDraw-
s t r i n g ) que se ha hecho concordar especificamente con este tip0 de listas de
cadena. El ValueListEditor tiene dos columnas en las que puede mostrar y dejar
que el usuario edite 10s contenidos de una lista de cadena con parejas nombrel
valor: como muestra la figura 5.4. Esta lista de cadena la indica la propiedad
S t r i n g s del control.
La potencia de este control se basa en que se pueden personalizar las opciones
de edicion para cada posicion de la cuadricula (grid)o para cada valor clave,
usando la propiedad solo en tiempo de ejecucion de matriz I t e m P r o p s . Para
cada elemento, se puede indicar:

Si es solo de lectura.
El numero masimo de caracteres de la cadena.
Una mascara de edicion (solicitada en ultimo termino en el evento
OnGetEditMask).
Plain Mema
rone=~
rwo=2
three-3

Figura 5.4. El ejemplo NameValues usa el componente ValueListEditor, que muestra 10s
pares nombrelvalor o clave/valor de una lista de cadena, tambien visible en un simple
campo de memo.

Los elementos de una lista de selection desplegable (solicitada en ultimo


termino en el cvento OnGet Pic kList).
La aparicion de un boton que muestre un dialogo de edicion (en el evento
OnEditButtonClick).
No es necesario decir que este comportamiento se parece al que esta general-
mente disponible para las cuadriculas de cadena (string grids) y el control DBGrid,
y tambien para el comportamiento del Ob j ect Inspector.
La propiedad Itemprops habra de establecerse en tiempo de ejecucion, a1
crear un objeto de la clase T I ternprop y asignarlo a un indice o a una clave de
la lista de cadena. Para tener un editor predefinido para cada linea, se puede
asignar el mismo objeto de propiedad de elemento varias veces. En el ejemplo,
este editor compartido configura una mascara de edicion de hasta tres numeros:
procedure TForml.FormCreate(Sender: TObject);
var
I: Integer;
begin
SharedItemProp : = TItemProp.Create (ValueListEditorl);
Shared1temProp.EditMask : = ' 999;O; ' ;
SharedItemProp.EditStyle : = esEllipsis;

FirstItemProp : = TItemProp-Create (ValueListEditorl);


for I : = 0 to 10 do
FirstItemProp.PickListAdd(1ntToStr (I));

Memol.Lines : = ValueListEditor1.Strings;
ValueListEditorl.ItemProps [0] : = FirstItemProp;
f o r I : = 0 t o ValueListEditor1.Strings.Count - 1 do
ValueListEditorl.1temProps [I] : = SharedItemProp;
end ;

Se debe repetir un codigo similar en caso de que cambie el numero de lineas,


por ejemplo a1 aiiadir nuevos elementos en el campo de memo y copiarlos a la lista
de valores.
procedure TForml.ValueListEditorlStringsChange(Sender: TObject);
var
I: Integer;
begin
Memol.Lines : = ValueListEditorl.Strings;
ValueListEditorl.1temProps [0] : = FirstItemProp;
f o r I : = 0 t o ValueListEditorl.Strings .Count - 1 do
i f n o t Assigned (ValueListEditorl. Itemprops [I]) then
ValueListEditorl.1temProps [I] : = SharedItemProp;
end ;

I asi que s61o asignmos el editor a las llneas qua w tienen &a h . I
Otra propiedad, K e y O p t i o n s , permite a1 usuario editar tambiin las claves
(10s nombres), aiiadir nuevas entradas, eliminar las existentes y contar con nom-
bres duplicados en la primera parte de la cadena. Es raro que no se puedan aiiadir
nuevas claves a no ser que se activen tambien las opciones de edicion, lo que
dificulta permitir que el usuario aiiada entradas adicionales mientras que se man-
tienen 10s nombres de las entradas basicas.

Rangos
Por ultimo, existen unos cuantos componentes que podemos usar para selec-
cionar valores dentro de un rango. Los rangos se pueden usar para entradas nu-
mericas y para seleccionar un elemento de una lista.
El componente ScrollBar
El control independiente ScrollBar es el componente original de este grupo,
per0 rara vez se utiliza por si solo. Las barras de desplazamiento (scroll bars) se
asocian normalmente con otros componentes, como cuadros de lista y campos de
memo o se asocian directamente con formularios. En todos estos casos, la barra
de desplazamiento se puede considerar parte de la superficie de otros componen-
tes. Por ejemplo, un formulario con una barra de desplazamiento es en realidad un
formulario que tiene una zona que parece una barra de desplazamiento pintada en
su borde, una caracteristica regida por un estilo especifico de Windows de la
ventana formulario. Con parecer, nos referimos a que no es tecnicamente una
ventana separada del tipo de componente ScrollBar. Estas barras de desplaza-
miento "falsas" se controlan normalmente en Delphi usando propiedades especifi-
cas del formulario y 10s otros componentes que las alojan: V e r t S c r o l l B a r y
HorzScrollBar.

Los componentes TrackBar y ProgressBar


El uso direct0 del componente S c r o l l B a r es bastante extraiio, sobre todo
con el componente T r a c k B a r introducido con Windows 95, que se usa para
dejar que el usuario seleccione un valor en un rango. Entre 10s controles comunes
de Win32, se encuentra el control acompaiiante de ProgressBar, que permite que
el programa muestre un valor en un rango, mostrando el progreso de una opera-
cion larga. Estos dos componentes se pueden ver aqui:

El componente UpDown
Otro control relacionado es el componente U p D o w n , que suele estar conectado
a un cuadro de edicion de forma que el usuario pueda teclear un numero en el o
aumentar y disminuir el numero utilizando dos pequeiios botones de flecha. Para
conectar 10s dos controles, se define la propiedad As s o c i a t e del componente
UpDown. Podemos utilizar el componente U p D o w n como un control indepen-
diente, que muestre el valor actual en una etiqueta, o de cualquier otro modo.
I

ua;Edit con el ~ p ~ o en
w un
n "nico control.
.
NOTA: En CLX no existe el control UpDown, sino un SpinEdit
-
que as&ia.
. . . , a. .. .

El componente PageScroller
El control PageScroller de Win32 es un contenedor que permite desplazar el
control interno. Por ejemplo, si se coloca una barra de herramientas en la barra de
desplazamiento de la pagina y la barra de herramientas es mas grande que el
espacio disponible, el PageScroller mostrara dos pequeiias flechas en el lateral. Si
hacemos clic sobre dichas flechas se desplazara la zona interna. Este componente
se puede usar como una barra de desplazamiento, per0 tambien sustituye en parte
a1 control ScrollBox.

El componente ScrollBox
El control ScrollBox representa una zona de un formulario que se puede des-
plazar independientemente del resto de la superficie. Por esta razon, el ScrollBox
tiene dos barras de desplazamiento utilizadas para mover 10s componentes inser-
tados. Podemos colocar facilmente otros componentes dentro de un ScrollBox,
como en el caso de un panel. De hecho, un ScrollBox es basicamente un panel con
barras de desplazamiento para mover su superficie interna, un elemento de la
interfaz usado en muchas aplicaciones de Windows. Cuando tenemos un formula-
rio con muchos controles y una barra de herramientas o barra de estado, podria-
mos usar un ScrollBox para cubrir la zona central del formulario, dejando sus
barras de desplazamiento y barras de estado fuera de la zona de desplazarniento.
A1 confiar en las barras de desplazamiento del formulario, se podria permitir que
el usuario moviera la barra de herramientas y la barra de estado fuera de vista
(una situation muy extrafia).

Comandos
La categoria final de componentes no es tan clara como en 10s casos anterio-
res, y tiene que ver con 10s comandos. El componente basico de este grupo es el
T B u t t o n (o boton pulsador, en la jerga de Windows). Mas que botones indepen-
dientes, 10s programadores de Delphi utilizan botones (u objetos T T o o l B u t t o n )
dentro de barras de herramientas (en las primeras fases de Delphi, se usaban
botones de atajo dentro de paneles). Ademas de botones y controles similares, la
otra tecnica clave para invocar comandos es el uso de 10s elementos de menu,
parte de 10s menus desplegables enlazados con 10s menus principales de 10s for-
mularios o 10s menus desplegables locales que se activan mediante el boton dere-
cho del raton.
Los comandos relacionados con el menu o la barra de herramientas entran en
distintas categorias dependiendo de su proposito y de la retroalimentacion que
ofrece su interfaz a 10s usuarios:
Comandos: Elementos del menu utilizados para ejecutar una accion.
Definidores d e estado (state-setters): Elementos del menu utilizados para
activar o desactivar una opcion o para cambiar el estado de un elemento
concreto. Los elementos de estado de estas ordenes normalmente tienen
una marca de verificacion a su izquierda para indicar que estan activos (se
puede conseguir automaticamente este comportamiento usando la propie-
dad A u t o c h e c k ) . Los botones generalmente se pintan en un estado pre-
sionado para indicar el mismo estado (el control ToolButton tiene una
propiedad Down).
Elementos de radio: Elementos del menu que posecn una marca circular y
estan agrupados para representar las selecciones alternativas, como 10s
botones de radio. Para obtener 10s elementos de radio del menu, hay que
configurar sencillamente la propiedad RadioItem como True y esta-
blecer la propiedad GroupIndex para 10s elementos alternatives del menu
con el mismo valor. De un mod0 similar, se pueden agrupar botones de la
barra de herramicntas que Sean mutuamente exclusives.
Enlaces d e didogo: Elementos que hacen que aparezca un cuadro de dia-
logo y normalmente estan indicados por tres puntos (...)despues del texto.

Comandos y acciones
Como se vera, las aplicaciones modernas de Delphi tienden a usar el compo-
nente Act ionlist o su extension ActionManager para gestionar comandos del
mcnu o dc la barra de herramientas. En pocas palabras, se define una serie dc
objetos dc accion y se asocia cada uno de ellos con un boton de la barra de
hcrramicntas ylo un elemento del menu. Se puede definir la ejccucion del coman-
do en un unico lugar pero actualizar tanibien la interfaz de usuario conectandola
simplemente con la accion: el control visual relacionado reflejara automaticamente
cl estado del objeto de accion.
Menu Designer
Si simplemente se necesita inostrar un menu sencillo en la aplicacion, se puede
colocar un componentc MainMenu o PopupMenu en un formulario y hacer
doble clic sobre dl para lanzar el Menu Designer, que muestra la figura 5.5. Se
pueden aiiadir nuevos clementos de menu y proporcionarles una propiedad
Caption,usando un guion (-) para separar las etiquetas de 10s elementos del
menu.

Figura 5.5. El Menu Designer de Delphi en funcionarniento.

Delphi crea un nuevo componente para cada elemento de menu que se aiiada.
Para dar nombre a cada componente, Delphi usa el titulo que introducimos y
adjunta un numero (de tal mod0 que Open se convierta en Openl). Debido a que
Delphi elimina espacios y otros caracteres especiales del titulo cuando crea el
nombre, si no queda nada, Delphi aiiade la IetraNal nombrc. Finalmente adjunta
el numcro, asi que 10s elementos de separation del menu se denominaran N1,N2
y asi sucesivamente. A1 saber lo que suele hacer Delphi de manera predefinida, se
deberia pensar cn editar el nombre en primer lugar. lo que es necesario si se quiere
acabar con un esquema dc nombrado de componentes sensato.
-

ADVERTENCIA: No se debe usar la propiedad Break, que se emplea


para incorporar un menu desplegable en diversas columnas. El valor
mbMenuBarBreak indica que este elernento apareceri en una segunda
linea o en las siguientes. El valor mbMenuBrea k indica que este elemento
se aiiadira a una segunda columna o a la siguiente del menu desplegable.

Para conseguir un menu de aspect0 mas modcrno, se puede a5adir un control


dc lista dc imagenes al programa, que contenga una scrie de mapas de bits, y
conectar la lista dc imagenes con el menu mediante su propiedad Images. Se
puedc dcfinir una imagen para cada elemento de menu fijando el valor correct0
para su propicdad I m a g e Index . La definicion de imagenes para mcnus es bas-
tante flexiblc (pucdc asociarse una lista de imagenes con cualquier menu desple-
gable especifico, e incluso con un elemento de menu dado, mediante la propiedad
SubMenuImages). A1 disponer de una lista de imagenes mas pequeiia especifi-
ca para cada menu dcsplcgablc en lugar de una gran lista de imagenes para todo el
menu se permitc una mayor particularization de una aplicacion en tiempo de
ejecucion.

TRUCO: Crear elementos de menu en tiempo de ejecucion es algo tan


habitual que Delphi ofrece algunas funciones listas para w a r en la unidad
Menus. Los nombres de estas funciones globales son autoexpiicativos:
NewMenu, NewPopupMenu, NewSubMenu, Newltem y NewLine.

Menus contextuales y el evento OncontextPopup


El componente PopupMenu aparece normalmente cuando el usuario hace
clic con el boton derecho del raton sobre un componente que usa el menu contextual
dado como el valor de su propiedad PopupMenu. Sin embargo, ademas de co-
nectar el menu contextual a un componente con la propiedad correspondiente,
podemos llamar a su metodo Popup,que necesita la posicion del menu contextual
en las coordenadas de la pantalla. Pueden obtenerse 10s valores adecuados a1
convertir un punto local en un punto de pantalla con el metodo ClientToScreen
del componente local, que en este fragment0 de codigo es una etiqueta:
procedure TForml.Label3MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
ScreenPoint: TPoint;
begin
// si se c u m p l e la c o n d i c i d n . . .
if Button = mbRight then
begin
ScreenPoint : = Label3. ClientToScreen (Point (X, Y) ) ;
PopupMenul.Popup (ScreenPoint.X, ScreenP0int.Y)
end;
end ;

Una tecnica alternativa es el uso del evento OnContextMenu.Este, introdu-


cido en Delphi 5, ocurre cuando un usuario hace clic con el boton derecho del
raton sobre un componente (exactamente lo que hemos rastreado anteriormente
con la comprobacion if But ton = mbRight). La ventaja esta en que el mismo
evento ocurre tambien en respuesta a la combinacion de teclas Mayus-F10, asi
como mediante las teclas del menu de metodo abreviado de algunos teclados.
Podemos utilizar este evento para mostrar un menu contextual con el siguiente
codigo:
procedure TFormPopup.LabellContextPopup(Sender: TObject;
MousePos: TPoint; var Handled: Boolean);
var
ScreenPoint: TPoint;
begin
// a d a d e e l e m e n t o s d i n d m i c o s
PopupMenu2. Items .Add (NewLine);
PopupMenu2. Items .Add (NewItem (TimeToStr (Now), 0, False,
True, nil, 0 , ' I ) ) ;
/ / muestra el menu c o n t e x t u a l
ScreenPoint : = ClientToScreen (MousePos);
PopupMenu2.Popup (ScreenPoint.X, ScreenP0int.Y);
Handled : = True;
// e l i m i n a 10s e l e m e n t o s dindmicos
PopupMenu2. Items [4]. Free;
PopupMenu2. Items [ 3 ] .Free;
end;

Este ejemplo aiiade algo de comportamiento dinamico a1 menu de atajo, aiia-


diendo un elemento temporal que indica cuando se muestra el menu contextual.
Este resultado no es particularmente util, per0 demuestra que si se necesita mos-
trar un simple menu contextual, se puede usar sin problemas la propiedad
PopupMenu del control en cuestion o de uno de sus controles padre. Gestionar el
evento OnContextMenu tiene sentido solo cuando se desea aiiadir algo de pro-
cesamiento adicional.
El parametro Handled se preinicia como False, de mod0 que si no hace-
mos nada en el controlador de eventos, el menu contextual se procesara con nor-
malidad. Si hacemos algo en el controlador de eventos para sustituir el
procesamiento normal del menu contextual (corno contextualizar un dialogo o un
menu personalizado, como en este caso), se deberia definir Handled como T r u e
y el sistema dejara de procesar el mensaje. Deberiamos establecer H a n d l e d
como T r u e en contadas ocasiones, dado que, por lo general, controlamos el
evento O n c o n t e x t Popup para crear de forma dinamica o personalizar el menu
contextual, per0 a continuacion podemos dejar que el controlador predefinido
muestre realmente el menu.
El controlador de un evento O n c o n t e x t P o p u p no se limita a mostrar un
menu contextual, sino que puede realizar cualquier otra operacion, como mostrar
directamente un cuadro de dialogo. Este es un ejemplo de una operacion de hacer
clic con el boton derecho utilizada para modificar el color del control.
procedure TFormPopup.Label2ContextPopup(Sender: TObject;
MousePos: TPoint; var Handled: Boolean) ;
begin
ColorDialogl.Color : = Label2.Color;
if ColorDialogl.Execute then
Label2.Color : = ColorDialogl.Color;
Handled : = True;
end :

Todos 10s fragmentos de codigo de esta seccion estan disponibles en el ejemplo


CustPop para la VCL y QCustPop para la CLX.

Tecnicas relacionadas con 10s controles


Despues de esta vision global de 10s controles de Delphi de uso mas habitual,
vamos a dedicarnos a comentar tecnicas genericas importantes no relacionadas
con un componente especifico. Hablaremos sobre el foco de entrada, 10s anclajes
de control, el uso del componente separador y de la visualizacion de sugerencias
flotantes. Por supuesto, en estos temas no se tratara todo lo que se puede hacer
con controles visuales, per0 proporcionaran un buen punto de arranque para co-
menzar a explorar algunas de estas tecnicas habituales.

Gestion del foco de entrada


Usando las propiedades T a b s t o p y T a b O r d e r disponibles en la mayoria de
10s controles, se puede especificar el orden en que 10s controles reciben el foco de
entrada cuando el usuario pulsa la tecla Tab. En lugar de configurar la propiedad
de orden de tabulacion de cada componente de un formulario manualmente, se
puede usar el menu de metodo abreviado del Form Designer para activar el cua-
dro de dialogo Edit Tab Order, como muestra la figura 5.6.
Ademas de la configuration basica, es importante saber que cada vez que un
componente recibe o pierde el foco de entrada, recibe el correspondiente evento
O n E n t e r u O n E x i t . Esto permite definir y personalizar el orden de las opera-
ciones de usuario. Algunas de estas tecnicas se demuestran en el ejemplo InFocus,
que crea una ventana bastante comun de contrasefia y nombre de usuario. El
formulario tiene tres cuadros de edicion con etiquetas que indican su significado,
como muestra la figura 5.7. En la p a r k inferior de la ventana esta la zona de
estado con mensajes de peticion que guian a1 usuario. Cada elemento habra de
introducirse de forma consecutiva.

wo
k bled ntab order
I

Figura 5.6. El cuadro de dialogo Edit Tab Order.

Figura 5.7. El ejernplo InFocus en tiernpo de ejecucion.

Para la salida de informacion sobre el estado, hemos utilizado el componente


S t a t u s B a r , con una unica zona de salida (obtenida a1 configurar su propiedad
S i m p l e P a n e l como T r u e ) . Veamos un resumen de las propiedades para este
ejemplo. Fijese en el caracter 8 de las etiquetas, que indica una tecla de metodo
abreviado y en la conesion de dichas etiquetas con 10s cuadros de edicion corres-
pondientes (usando la propiedad F o c u s C o n t r o 1):
o b j e c t FocusForm: TFocusForm
Activecontrol = EditFirstName
Caption = ' I n P o c u s '
o b j e c t Labell: TLabel
Caption = ' & F i r s t name'
FocusControl = EditFirstName
end
o b j e c t EditFirstName: TEdit
OnEnter = GlobalEnter
OnExit = EditFirstNameExit
end
o b j e c t Label2 : TLabel
Caption = ' & L a s t n a m e '
FocusControl = EditLastName
end
o b j e c t EditLastName: TEdit
OnEnter = GlobalEnter
end
o b j e c t Label3 : TLabel
Caption = ' & P a s s w o r d 1
FocusControl = Editpassword
end
o b j e c t Edit Password: TEdit
Passwordchar = ' * '
OnEnter = GlobalEnter
end
o b j e c t StatusBarl: TStatusBar
Simplepanel = True
end
end

El programa es muy sencillo y solo realiza dos operaciones. La primera con-


siste en identificar, en la barra de estado, el control de edicion que tiene el foco.
Esto lo consigue manejando el evento O n E n t e r de 10s controles, utilizando un
controlador de eventos generic0 para no repetir codigo.
En el ejemplo, en lugar de almacenar informacion adicional para cada cuadro
de edicion, hemos verificado cada control del formulario para determinar que
etiqueta esta conectada a1 cuadro de edicion actual (que indica el parametro
Sender):
procedure TFocusForm. GlobalEnter (Sender: TObject) ;
var
I: Integer;
begin
f o r I : = 0 t o Controlcount - 1 do
// s i e l c o n t r o l e s u n a e t i q u e t a
if (Controls [ I ] i s TLabel) and
// y l a e t i q u e t a e s t d c o n e c t a d a a 1 c u a d r o d e e d i c i o n a c t u a l
(TLabel (Controls [I]) .FocusControl = Sender) then
// c o p i a e l t e x t o , s i n e l c a r d c t e r i n i c i a l &
StatusBarl.Simp1eText : = ' E n t e r ' + Copy
(TLabel (Controls [I]) .Caption, 2, 1000) ;
end ;

El segundo controlador de eventos del formulario se refiere a1 evento OnExi t


del primer cuadro de dialogo. Si el control se deja en blanco, se niega a liberar el
foco y lo vuelve a recuperar despues de mostrar un mensaje a1 usuario. Los
metodos buscan tambien un valor de entrada dado, rellenan automaticamente el
segundo cuadro de edicion y desplazan el foco directamente a1 tercero:
procedure TFocusForm. EditFirstNameExit (Sender: TObj ect) ;
begin
i f EditFirstNarne.Text = " t h e n
begin
// n o d e j a s a l i r a 1 u s u a r i o
EditFirstName. SetFocus;
MessageDlg ( 'Es n e c e s a r i o e l n o m b r e ' , mtError, [mbOK] , 0) ;
end
else i f EditFirstName.Text = 'Adrnin' then
begin
// c u b r e e l s e g u n d o c u a d r o d e e d i c i 6 n y s a l t a a 1 t e r c e r o
EditLastNarne.Text : = ' A d r n i n ' ;
EditPassword.SetFocus;
end ;
end ;

- --
TRUCO: La versi6n CLX de este ejemplo tiene el m i s m &hgo y estA
disponible como programa QInFocus.

Anclajes de control
Para permitir la creacion de una interfaz de usuario agradable y flexible, con
controlcs que se adapten a1 tamaiio real del formulario, Delphi permite determinar
la posicion relativa de un control con la propiedad Anchors. Antes de que se
introdujera esta caracteristica en Delphi 4, todo control situado en un formulario
tenia unas coordenadas relativas a 10s bordes superior e izquierda, a no ser que se
encontrara alineado con el borde inferior o de la derecha. La alineacion es aconse-
jable para algunos controles, per0 no para todos ellos, en particular para 10s
botones.
A1 usar anclajes. podemos hacer que la posicion de un control sea relativa a
cualquiera de 10s lados del formulario. Por ejemplo, para tener un boton anclado
en la esquina inferior derecha del formulario, colocamos el boton en la posicion
deseada y configuramos su propiedad Anchors como [akRight , a kBottom] .
A1 cambiar el tamaiio del formulario, la distancia a1 boton desde 10s laterales a 10s
que se ancla se mantiene fija, el boton permanecera en su esquina.
Por otra parte, si colocamos un componente grande como un Memo o un ListBox
en medio de un formulario, podemos configurar su propiedad Anchors para
incluir 10s cuatro lados. De este modo, el control se comportara como un control
alineado y aumentara o disminuira segun el tamaiio del formulario, per0 habra
cierto margen entre el y 10s lados del formulario.
- .. --

TRUCO: Los anclajes, a1 igual que las restricciones, funcionan tanto en


tiempo de diseiio como en tiempo dt:ejecucion, por lo que deberiamos defi-
m a n t e s posi6fe-mos 'deesta ca-&
i mi&trT
I disefiamos el formulario y tambib en tiempo de ejecuei6n.
I
Como ejemplo de ambas tecnicas, podemos probar la aplicacion Anchors. que
tiene dos botones en la esquina inferior derecha y un cuadro de lista en el centro.
Como muestra la figura 5.8,los controles se mueven automaticamente y disminu-
yen de tamafio a1 mismo tiempo que cambia el tamaiio del formulario. Para que el
formulario funcione de forma correcta, debemos definir tambien su propiedad
constraints. si no, a medida que el formulario reduzca su tamaiio 10s contro-
les pueden solaparse o dcsaparecer.

Figura 5.8. Los controles del ejemplo Anchors se mueven y cambian de tamaiio
automaticamente con el formulario. No se necesita ninghn codigo adicional, solo usar
correctamente la propiedad Anchors.

Si climinamos todos 10s anclajes o dos opuestos (por ejemplo, izquierdo y


derecho). las operaciones de rnodificacion del tamaiio haran que el control flote en
el formulario. El control mantienc su tamaiio actual y el sistema afiade o elimina
el mismo numero de pixeles a cada uno de sus lados. Esto puede definirse como
anclaje centrado, porque si el componente csta en un principio en el medio del
formulario, mantcndra esa posicion. En cualquier caso, si deseamos disponer de
un control centrado. deberiamos usar por lo general ambos anclajes, de tal mod0
que si el usuario aumenta el tamaiio del formulario, el tamaiio del control aumenta
a su vez. en el caso comentado, aumentar el tamafio del formulario hace que el
pequeiio control permanezca en el medio.

Uso del componente Splitter


Esisten muchas maneras de implementar tecnicas de division de formularios
en Delphi, per0 la mas sencilla consiste en emplear el componente Splitter, que se
encuentra en la ficha Additional de la Component Palette. Para que resulte
mas eficaz, el divisor se puede usar combinado con la propiedad constraints
de 10s controles a 10s que haga referencia. Como veremos en el ejemplo Splitl,
csto permite definir las posiciones maxima y minima del divisor y del formulario.
Para crear este ejemplo, hay que colocar sencillamente un componente ListBos en
un formulario y, a continuacion, aiiadir un componente Splitter, una segunda
ListBox, otro Splitter y por ultimo un tercer componente ListBox. El formulario
tiene tambien una barra de herramientas simple basada en un panel.
Simplemente a1 colocar estos dos componentes divisores, se proporciona a1
formulario la funcionalidad complcta de mover y modificar el tamaiio dc 10s con-
troles que contiene en tiempo de ejecucion. Las propiedadcs W i d t h , Beveled y
Color de 10s componentes divisores definen su apariencia, y en el ejemplo Splitl
podemos usar 10s controles de la barra de herramientas para cambiarlos. Otra
propiedad relcvante es MinSi ze,que determina el tamaiio minimo de 10s com-
ponentes del formulario. Durante la operacion de division (vease la figura 5.9).
una linea marca la posicion final del divisor, per0 no podemos arrastrar esta linea
mas alla de un cierto limite. El comportamiento del programa Splitl consiste en
no permitir que 10s controles sc hagan demasiado pequeiios. Una tecnica alterna-
tiva es definir la nueva propiedad Autosnap del divisor como T r u e . Esta pro-
piedad hara que el divisor oculte el control cuando su tamaiio vaya mas alla del

zard

Wh -..-
Dog
cat
11
hr~~np
ug
ee

Rhi Lzf
She,Sheep
Hare

para cada control del formulario, incluso para aquellos no contiguos al divisor.

Es aconsejable probar el ejemplo Split 1, para que se comprenda completamen-


te como afecta el divisor a sus controles configuos y al resto de componentes del
formulario. Incluso aunque se f?ie su propiedad MinSize, un usuario puede
rcducir el tamaiio de todo el formulario a su minima espresion, ocultando algunos
de 10s cuadros dc lista. Si se prueba la version Split2 dcl ejemplo, se comprendera
me.jor. En Split2 se nianipula la propiedad Constraints de 10s controles
ListBox.
object ListBoxl: TListBox
Constraints.MaxHeight = 400
Constraints,MinHeight = 200
Constraints.MinWidth = 1 5 0

Las restricciones dc tamaiio sc aplican solo cuando se modifica el tamaiio dc


10s controles, por eso, para que este programa funcionc de manera satisfactoria,
se debe dar a la propiedad Resizestyle de 10s dos divisores el valor rsupdate.
Estc valor indica quc la posicion del control se actualizara con cada movimiento
del divisor. no solo a1 final de la operacion. Si en su lugar se escoge el valor
rsLine o el nucvo valor rspattern, el divisor simplemente dibujara una
linca en la posicion solicitada. comprobando la propiedad MinSize per0 no las
rcstricciones dc 10s controles.
-

TRUCO:Cuando configuramos la propiedad Autosnap del componente


Splitter como T r u e , el divisor ocultara por cornpleto el control contiguo
cuando el tamaiio de dicho control sea inferior a1 rninimo establecido para
el componente Splitter.

Division en sentido horizontal


Tambien se puede usar el componente splitter para realizar una division
cn sentido horizontal, en lugar de la division predefinida en sentido vertical. Basi-
camente, podemos colocar un componente sobre un formulario, alinearlo con la
parte superior del formulario y, a continuacion, colocar el divisor en el formula-
rio. Por defecto, el divisor se alineara a la izquierda. Hay que escoger el valor
alTop para la propiedad Align y ya esta. Se puede ver un formulario con un
divisor horizontal en el ejemplo SplitH. Este programa posee dos componentes de
memo en 10s que podemos abrir un archivo, y tiene un divisor definido como:
object Splitterl: TSplitter
Cursor = crVSplit
Align = alTop
OnMoved = SplitterlMoved
end

El programa dispone de una barra de estado, que registra la altura actual de


10s dos componentes de memo. Controlamos el evento OnMoved del divisor (el
unico evento de este componente), para actualizar el texto de la barra de estado.
Este mismo codigo se ejecuta siempre que se adapte el tamaiio del formulario:
procedure TForml.SplitterlMoved(Sender: TObject);
begin
StatusBarl.Panels [0].Text : = Format ( ' U p p e r memo: %d - Lower
memo: % d ' ,
[Memoup-Height, MemoDown.Height1);
end;

Teclas aceleradoras
Desde Delphi 5, no se necesita aiiadir el caracter & a la propiedad Caption
de un elemento de menu, que proporciona una tecla aceleradora automatica si se
omite una. El sistema automatico de teclas aceleradoras de Delphi tambien puede
averiguar si hemos insertado teclas aceleradoras que resulten conflictivas y ajus-
tarlas sobre la marcha. Esto no significa que debamos dejar de aiiadir teclas
aceleradoras personalizadas con el caracter &, porque el sistema automatico uti-
liza sencillamente la primera letra disponible y no sigue 10s esthdares predefinidos.
Podriamos encontrar claves mnemotecnicas mejores que las elegidas por el sis-
tema.
Esta caracteristica es controlada por la propiedad Auto Hotkeys,disponi-
ble en el componente menu principal y en cada uno de 10s menus desplegables y
elementos del menu. En el menu principal, esta propiedad tiene de manera
predefinida el valor maAutomat i c , mientras que en 10s menus desplegables y
en 10s elementos del menu es maparent,de manera que el valor fijado para el
componente menu principal lo utilizaran de forma automatica todos 10s
subelementos, a no ser que tengan un valor especifico maAutomat i c o
maManua1.
El motor que se esconde tras este sistema es el metodo Re thin kHotkeys de
la clase TMenuItem y su compaiiero InternalRethinkHotkeys.Existe
tambien un metodo llamado RethinkLine s,que verifica si un menu desplega-
ble posee dos separadores consecutivos o comienza o termina con un separador.
En todos esos casos, se elimina automaticamente el separador.
Una de las razones por las que Delphi incluye esta caracteristica es el soporte
para traducciones. Cuando se necesita traducir el menu de una aplicacion, resulta
comodo no tener que trabajar con teclas aceleradoras o a1 menos no tener que
preocuparse de 10s posibles problemas entre dos elementos de un mismo menu. A1
tener un sistema que pueda resolver automaticamente problemas similares, conta-
mos en definitiva con una gran ventaja. Otro motivo era el propio IDE de Delphi.
Con todos 10s paquetes que se pueden cargar de forma dinamica, que instalan
elementos del menu en el menu principal del IDE o en 10s menus contextuales, y
con diferentes paquetes cargados en distintas versiones del producto, resulta casi
imposible conseguir teclas aceleradores no conflictivas en cada menu. Por esa
razon, este mecanismo no es un asistente que realiza un analisis estatico de 10s
menus en tiempo de diseiio, sino que se creo para resolver el problema real de la
administracion de 10s menus creados de forma dinamica en tiempo de ejecucion.
ADVERTENCIA: Esta caracteristica es realmente 6til, pero al estar acti-
vada por defecto, puede estropear el c6digo existente. Un problema puede
ser que si se usa el titulo en el cbdigo, 10s caracteres & adicionales pueden
romperlo. Aun asi, el cambio es bastante simple: todo lo que es necesario
hacer es establecer la propiedad AutoHot keys del componente menu
principal como maManual.

Sugerencias flotantes
Otro elemento habitual en las barras de herramienta es la sugerencia de la
barra, tambien llamada sugerencia flotante, un texto que describe brevemente el
boton que se encuentra en ese momento bajo el cursor. Este testo suele mostrarse
en un cuadro amarillo junto al cursor del raton que haya permanecido parado
durante un boton durante una cantidad de tiempo dada.
Para aiiadir sugerencias a la barra de herramientas de una aplicacion, sencilla-
mente hay que definir su propiedad ShowHints como T r u e y escribir texto
para la propiedad Hint de cada boton. Podria desearse habilitar las sugerencias
para todos 10s componentes de un formulario o para todos 10s botones de una
barra de herramientas o panel.
Si queremos tener mayor control sobre como aparecen las sugerencias, pode-
mos usar algunas de las propiedades y eventos del objeto Application. Este
objeto global tiene, entre otras; las siguientes propiedades:

El color de fondo de la ventana de sugerencia.


El tiempo que habra de mantenerse el cursor sobre
un componente antes de que aparezcan las suge-
rencias.
I HintHidePause EI tiempo durante el que se muestra la sugerencia.
Hintshortpause El tiempo que habra de esperar el sistema para
mostrar una sugerencia, si acaba de mostrar otra
distinta.

Un programa, por ejemplo, podria permitir que un usuario personalizase el


color de fondo de la sugerencia, seleccionando uno especifico mediante el siguien-
te codigo:
ColorDialog.Color : = Application.HintColor;
if ColorDialog.Execute then
Application.HintColor : = ColorDialog.Color;
Como alternativa, podemos cambiar el color de la sugerencia, controlando la
propiedad OnShowH i n t del objeto Appl i c a t i o n . Este controlador puede
cambiar el color de la sugerencia solo para controles especificos. El evento
OnShowHint se usa en el ejemplo CustHint.

Personalization de las sugerencias


A1 igual que se pueden aiiadir sugerencias a la barra de herramientas de una
aplicacion, se pueden aiiadir sugerencias a formularios o a 10s componentes de un
formulario. Para un control grande, la sugerencia aparecera cerca del cursor del
raton. En algunos casos, es importante saber que un programa puede personalizar el
mod0 en que se muestran las sugerencias. Una cosa que se puede hacer es modificar
el valor de las propiedades del objeto A p p l i c a t i o n . Para conseguir mayor con-
trol sobre las sugerencias, podemos personalizarlas mas asignando un metodo a1
evento OnShow H i n t de la aplicacion. Podemos engancharlas de forma manual o
(mejor) aiiadir el componente App 1i c a t i o nEve n t s a1 formulario y controlar
su evento OnShowHint. El metodo del gestor de eventos tiene algunos parametros
interesantes, como una cadena con el texto de la sugerencia, un indicador booleano
para su activation y una estructura T H i n t I n f o con mas informacion, como el
control, la posicion de la sugerencia y su color. Cada parametro se pasa mediante
referencia, de mod0 que tengamos la oportunidad de cambiarlos y tambien de modi-
ficar 10s valores de la estructura T H i n t I n f o . Por ejemplo, podemos cambiar la
posicion de la ventana de sugerencia antes de que aparezca.
Asi se ha hecho en el ejemplo CustHint, que muestra la sugerencia de la
etiqueta en el centro de su zona.
procedure TForml.ShowHint (var HintStr: string; var Canshow:
Boolean;
var Hint Inf o : THint Inf o) ;
begin
with HintInfo do
// s i e l c o n t r o l e s l a e t i q u e t a , r n u e s t r a l a s u g e r e n c i a e n
e l rnedio
if Hintcontrol = Label1 then
HintPos : = HintContro1.ClientToScreen (Point (
HintControl.Width div 2, HintControl.Height div 2 ) ) ;
end;

El codigo obtiene el centro del control generic0 (de H i n t I n f o .


H i n t c o n t r o 1 ) y despues convierte sus coordenadas a coordenadas de panta-
lla, aplicando el metodo C l i e n t To S c r e e n a1 propio control.
Ademas podemos actualizar el ejemplo CustHint de un mod0 diferente. El
control ListBos del formulario tiene algunos de 10s elementos de testo algo lar-
gos, asi que se podria desear mostrar el texto completo en una sugerencia mien-
tras que el raton se mueva sobre el elemento. Fijar una unica sugerencia para el
cuadro de lista no serviria, por supuesto. Una buena solucion es personalizar el
sistema de sugerencias proporcionando una sugerencia de manera dinamica que
se corresponda con el texto del elemento de la lista que se encuentre bajo el
cursor. Tambien se necesita indicar al sistema a que area pertenece la sugerencia,
para que al mover el cursor sobre la siguiente linea se muestra una nueva sugeren-
cia. Se puede realizar esto fijando el campo C u r s o r R e c t del registro
THi n t I nfo,que indica el area del componente sobre la que puede moverse el
cursor sin deshabilitar la sugerencia. Cuando el cursor sale de dicha zona, Delphi
oculta la ventana de sugerencia. Este es el fragment0 de codigo relacionado que se
ha aiiadido al metodo ShowHint:
else i f Hintcontrol = ListBoxl then
begin
nItem : = ListBoxl.ItemAtPos(
Point (CursorPos.X, CursorPos. Y) , True) ;
i f nItem >= 0 then
begin
// e s t a b l e c e l a cadena d e s u g e r e n c i a
HintStr : = ListBoxl. Items [nItem];
// determina el drea d e v a l i d e z d e l a sugerencia
CursorRect : = ListBoxl. ItemRect (nItem);
/ / se r n u e s t r a s o b r e e l e l e r n e n t o
HintPox : = HintControl.ClienteToScreen (Point ( 0 ,
ListBoxl.ItemHeight * (nItem - ListBoxl.TopIndex)));
end
else
Canshow : = False;
end :

El resultado final es que cada linea del cuadro de lista parece tener una suge-
rcncia especifica, como muestra la figura 5.10. La posicion de la sugerencia se
calcula de tal manera que cubra el testo del elemento actual, extendiendose mas
alla del borde del cuadro de lista.

Selection sequence: - a as in apple and


apples - b as In borland barbarians ~ l e z

Figura 5.10. El control ListBox del ejemplo CustHint rnuestra una sugerencia
diferente, dependiendo del elemento de la lista sobre el que se encuentre el raton.

Estilos y controles dibujados por el propietario


En Windows, el sistema es normalmente el responsable de dibujar botones,
cuadros de lista, cuadros de edicion, elementos del menu y elementos similares.
Basicamente, dichos controles saben como dibujarse. Sin embargo, como alterna-
tiva, cl sistema permite que el propietario de estos controles, un formulario por lo
general, 10s dibuje. Esta tecnica, disponible para botones, cuadros de lista, cua-
dros combinados y elementos de menu, se denominaowner-draw (dibujo por par-
te del propietario).
En la VCL, la situacion es ligeramente mas compleja. Los componentes pue-
den encargarse de dibujarse a si mismos en este caso (como en la clase T B i t Btn
para 10s botones de mapas de bits) y posiblemente de activar 10s eventos corres-
pondientes. El sistema envia la solicitud para dibujar al poseedor (normalmente el
formulario) y el formulario reenvia el evento de nuevo al control adecuado, acti-
vando sus controladores de eventos. En CLX, algunos de estos controles, como
ListBoxes y ComboBoxes, presentan eventos aparentemente muy similares a la
tecnica de dibujo por parte del propietario de Windows, pero 10s menus no 10s
tienen. El enfoque nativo de Qt consiste en usar estilos para establecer el compor-
tamiento grafico de todos 10s controles del sistema, de una aplicacion concreta o
de un control dado.
1
NOTA: La mayoria de . b s contmles cardm e s de Win32'poseen soporte
peta La t b i c a ownerdraw,d e n o m i d pc lo general dibujo personaliza-
". . -
do, Se puede particul&zar la aprwicda' de una ListView, TreeYiew,
TabCmtrol, Pagecontrol, HeaderConW,-swtus~aro I oolaar. LOS con-
". I
.. .
treks TdBar, ListView y T m V i e w tambih Soportan un mod0 avanzado
de dibqjb personalizado, una capacidad de dibujo mas ajustada introducida
por Microsoft en las riltimas versibdebde.la.b~b1iotecadc controles comu-
nes de Win32. El inconveniente de e s t a t h i c a es que a1 cambiar el cstilo de
la inter& de usuario de Windows. en ql fbfuro (y sicmpre succde), 10s
c o ~ ~ t r ~dibujados
les por el propidaria, que cncajan a la perfection en 10s
estibs de interfaz de usuario actual&. p a r e c e r h dcsfasados y 'hcra de
kg?. Como- - estamos creando una interfae de usuario ~ersonalizada.
r sera
nece*d o que la actualicemos nosbtros misrnos. Por I contraste. si sc usa la C

aparicmcia e s t h d a r de 10s contmlesr, lw appcaciones


appcac se adaptwan a ona
nueva1 v e r s i h de estos controlo9.
.. . i\ .-

Elementos del menu dibujados por el usuario


Gracias a la VCL, el desarrollo de elementos del menu graficos resultan bas-
tante sencillo en comparacion con el enfoque tradicional de la API de Windows:
dcfinimos la propiedad OwnerDraw de un componente elcmento del menu como
True y controlamos sus eventos OnMeasureItem y OnDrawItem. En el
evento OnMeasure I t em, se puede establecer el tamaiio de 10s elementos del
menu. Este controlador de eventos se activa una vez para cada elemento del menu,
cuando aparece el menu desplegable, y tiene dos parametros de referencia que
podemos configurar: W i d t h y H e i g h t . En el evento OnDraw I t em, dibujamos
la imagen real. Este controlador de eventos se activa cada vez que hay que volver
a dibujar el elemento. Esto ocurre cuando Windows muestra por primera vez 10s
elementos y cada vez que cambia su estado, por ejemplo, cuando el raton se
mueve sobre un elemento, deberia de aparecer resaltado.
Para dibujar 10s elementos del menu, tenemos que considerar todas las posibi-
lidades, como dibujar 10s elementos resaltados con colores especificos, dibujar
una marca de verificacion si fuese necesario, etc. Por suerte, el evento Delphi
pasa a1 controlador el objeto c a n v a s en que deberia pintarse, el rectangulo de
salida y el estado del elemento (si esta seleccionado o no). En el ejemplo ODMenu,
se controla el color de resaltado, per0 se omiten otros aspectos avanzados (corno
las marcas de verificacion). Se ha fijado la propiedad OwnerDraw del menu y se
han escrito controladores para algunos de 10s elementos del menu. Para escribir
un controlador unico para cada evento de 10s tres elementos del menu relaciona-
dos con el color, hemos configurado su propiedad Tag con el valor del color en el
controlador de eventos o n c r e a t e del formulario. Esto hace que el controlador
del actual evento o n c l i c k de 10s elementos resulte bastante sencillo:
procedure TForml.ColorClick(Sender: TObject);
begin
ShapeDemo.Brush.Co1or : = (Sender as TComponent).Tag
end ;

El controlador del evento O n M e a s u r e I t e m no depende de 10s elementos


reales, sin0 que emplea valores fijos (diferentes del controlador del otro menu
desplegable). La parte mas importante del codigo esta en 10s controladores de 10s
eventos OnDrawItem.
Para el color, empleamos el valor de la etiqueta (tag)para dibujar un rectangu-
lo del color dado, como muestra figura 5.11. Sin embargo, antes de hacer esto,
hemos de rellenar el fondo de 10s elementos del menu (la zona rectangular que se
pasa como un parametro) con el color esthdar para el menu (clMenu) o 10s
elementos del menu seleccionados ( c l H i g h l i g h t ) :
procedure TForml.ColorDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean) ;
begin
// f i j a e l c o l o r d e l fondo y l o p i n t a
i f Selected then
ACanvas.Brush.Color : = clHighlight
else
ACanvas.Brush.Co1or : = clMenu;
ACanvas. FillRect (ARect);
// m u e s t r a e l c o l o r
ACanvas.Brush.Color : = (Sender as TComponent).Tag;
Inf lateRect (ARect, -5, -5) ;
ACanvas.Rectangle (ARect.Left, ARect.Top, ARect.Right,
ARect .Bottom) ;
end;
Figura 5.11. El menu dibujado por el propietario del ejemplo ODMenu.

Los tres controladores para este evento de 10s elementos del menu desplegable
Shape son todos ellos distintos, aunque usan un codigo similar:
p r o c e d u r e TForml.EllipselDrawItem(Sender: TObject; ACanvas:
TCanvas ;
ARect : TRect; Selected: Boolean) ;
begin
// f i j a e l c o l o r d e l f o n d o y l o p i n t a
i f Selected then
ACanvas.Brush.Color : = clHighlight
else
ACanvas.Brush.Color : = clMenu;
ACanvas .FillRect (ARect);
// d i b u j a l a e l i p s e
ACanvas.Brush.Co1or : = clwhite;
InflateRect (ARect, -5, -5) ;
ACanvas.Ellipse (ARect.Left, ARect.Top, ARect.Right,
ARect .Bottom) ;
end;

-- .. .
NOTA: Para acomodar el cada vez mayor nhero de egtados en el estiko de
interfaz de usuario de Windows 2000 , belphi inahye el evento
OnAdvancedDraw I tern para 10s menu:

Una ListBox de colores


Los cuadros de lista tienen tambien una capacidad de dibujo personalizado,
que significa que un programa puede pintar 10s elementos de un cuadro de lista.
Este mismo soporte se ofrece en el caso de 10s cuadros combinados y tambien esta
disponible en la CLX. Para crear un cuadro de lista dibujado por el propietario,
configuramos su propiedad Style como 1bOwnerDrawFixed o IbOwner-
Drawvariable. El primer valor indica que vamos a configurar la altura de 10s
elementos del cuadro de lista estableciendo la propiedad ItemHeight y quc
esta sera la a h a de todos 10s elementos. El segundo estilo de dibujo personaliza-
do indica un cuadro dc lista con elementos de diferentes alturas. En este caso, cl
componentc desencadenara el evento OnMeasure It em de cada elemento, para
pedir a1 programa por sus alturas.
En el ejemplo ODList (y en su version QODList), nos quedaremos con cl
primcr enfoque, el mas sencillo. El cjcmplo contiene informacion sobre el color
junto con 10s elementos dcl cuadro dc lista y, a continuacion, dibuja 10s elementos
usando cstos colores (en lugar de usar un unico color para toda la lista).
El archivo DFM o XFM de todo formulario, como este entrc otros, tiene un
atributo TextHeight, que indica cl numero de necesarios para mostrar texto.
Estc cs el valor que deberiamos usar para la propiedad ItemHeight del cuadro
de lista. Una solucion alternativa consiste en calcular cstc valor en tiempo de
cjccucion, de forma que si mas tarde cambiamos la fuentc en tiempo de diseiio, no
tcngamos que recordar configurar la altura de 10s elcmentos en funcion de la
misma.

NOTA: Acabaq~psd e s c r i b i r Text Height como un atributo dcl for-


mulario, no toma"una propiedad. No se trata de una propiedad, sino de un
valor 1 4 dtsl fmulario, Si no as propiedad, cabria preguntarse cirmo es
queBelphi k Haida en-&arch*! DFM.La respuesta es que el mecanismo
de streamhg.deDelphi se basa en propiedades m i s unos clones especiales
Ile propjabdes krcadospor cl mctodo Def ineproperties.

Dado quc TextHeight no es una propiedad, aunque aparece en la lista de la


descripcion del formulario, no podemos acceder a el directamente. A1 estudiar el
codigo fuente dc la VCL, se ve que este valor se calcula mediante una llamada a
un metodo privado del formulario: GetTextHeight.A1 ser privado, no pode-
mos llamar a esta funcion, pero podemos duplicar su codigo dentro del metodo
Formcreate dcl formulario, tras haber seleccionado la fuentc dcl cuadro dc
lista:
Canvas.Font : = ListBoxl.Font;
ListBoxl. IternHeight := Canvas .TextHeight ( ' 0 ' ) ;

Lo siguiente es aiiadir algunos elementos a1 cuadro de lista. Como este es un


cuadro de lista de colores, queremos aiiadir nombres de colores a1 Items del
cuadro de lista y 10s valores de color correspondientes a1 almacenamiento de
datos Objects relacionado con cada elemcnto dc la lista. En lugar de aiiadir 10s
dos valores por separado, hemos escrito un procedimiento para aiiadir nuevos
elementos a la lista:
procedure T0DListForm.AddColors (Colors: array of TColor);
var
I: Integer;
begin
f o r I : = L o w (Colors) t o High (Colors) d o
ListBoxl.Items.Add0bject (ColorToString ( C o l o r s [ I ] ) ,
TObject ( C o l o r s [ I ]) ) ;
end ;

Este mctodo usa un parametro dc matriz abierta, una matriz de un numero no


dcterminado de clementos del mismo tipo. Para cada elemento pasado como
paramctro, aiiadimos el nombre dcl color a la lista y aiiadimos su valor a 10s datos
rclacionados, llamando a1 metodo AddOb je c t . Para obtener la cadcna corres-
pondicnte al color, llamamos a la funcion Delphi C o l o r T o S t r i n g . ~ s t de-
a
vuelve una cadcna que contiene la constante de color correspondiente, si existe, o
cl valor hexadecimal del color. Los datos de color se aiiaden a1 cuadro de lista
despues de comprobar su valor de acuerdo con el tipo de datos TOb j e c t (una
referencia de cuatro bytcs), segun lo requiere el metodo AddOb je c t .

TRUCO: Ademas de ColorToStr ing, que convierte un valor de color


en la correspondiente cadena con el identificador o el valor hexadecimal, la
funcion StringToColor de Delphi convierte una cadena de formato
apropiado en un color.

En el cjemplo ODList; este metodo se llama en el controlador de eventos


o n c r e a t e del formulario (despues de fijar la a h a de 10s elementos):
Addcolors ([clRed, clBlue, clYellow, clGreen, clFuchsia,
cllime, clpurple,
clGray, RGB (213, 23, 123), RGB (0, 0, O), clAqua, clNavy,
clOlive, clTeal] ) ;

Para compilar la version CLX de este codigo, hemos aiiadido la funcion RGB
descrita anteriormente. El codigo usado para dibujar 10s elementos no es especial-
mente complejo. Sencillamente obtenemos el color asociado con el elemento, lo
establecemos como el color de la fuente y despues dibujamos el testo:
p r o c e d u r e TODListForm.ListBoxlDrawItem(Control: Twincontrol;
Index: Integer;
Rect : TRect; State: TOwnerDrawState) ;
begin
w i t h Control as TListbox d o
begin
// elimina
Canvas. FillRect (Rect);
// dibuja el elemento
Canvas.Font.Color : = TColor ( I t e m s - O b j e c t s [Index]);
Canvas.TextOut(Rect.Left, R e c t - T o p , Listboxl.Items[Index]);
end ;
end :
El sistema ya establece el color de fondo adecuado, de mod0 que el elemento
seleccionado aparezca de forma adecuada, aunque no aiiadamos codigo adicional.
Aun mas, el programa permite aiiadir nuevos elementos a1 hacer doble clic sobre
el cuadro de lista:
procedure TODListForm.ListBoxlDblClick(Sender: TObject);
begin
i f ColorDialogl.Execute t h e n
Addcolors ([ColorDialogl.Color]);
end;

Si se intenta usar esta capacidad, se vera que algunos de 10s colores aiiadidos
se transforman en nombres de color (una de las constantes de color de Delphi),
mientras que otros se convierten en numeros hexadecimales.

Controles ListView y TreeView


Ya hemos presentado 10s diversos controles visuales que se pueden usar para
mostrar listas de valores. Los componentes estandar de cuadro de lista y de cua-
dro combinado son todavia muy comunes, per0 normalmente se sustituyen por 10s
controles mas potentes de ListView y TreeView. De nuevo, estos dos controles
forman parte de 10s controles comunes de Win32, guardados en la biblioteca
ComCt l 3 2 . DLL. En Qt y VisualCLX existen controles similares, tanto en
Windows como en Linux.

Una lista de referencias grafica


Cuando usamos un componente L i s t v i e w , podemos proporcionar mapas de
bits que indiquen el estado del elemento (por ejemplo, el elemento seleccionado) y
que describan 10s contenidos del elemento de un mod0 grafico.
Para conectar las imagenes a una lista o a un arbol, hay que recurrir a1 compo-
nente I m a g e L i s t que ya hemos empleado para las imagenes del menu. Un
ListView puede tener en realidad tres listas de imagenes: una para 10s iconos
grandes (la propiedad L a r g e I m a g e s ) , una para 10s iconos pequeiios (la propie-
dad S m a l l I m a g e s ) y otra para el estado de 10s elementos (la propiedad
s t a t e I m a g e s ) . En el ejemplo RefList, se han fijado las dos primeras propie-
dades empleando dos componentes I m a g e L i s t diferentes.
Cada uno de 10s elementos de ListView tiene un I m a g e I n d e x que se refiere
a su imagen en la lista. Para que esta tecnica funcione como es debido, 10s ele-
mentos de las dos listas de imagenes deberian seguir el mismo orden. Cuando
tengamos una lista de imagenes fija, podemos aiiadirle elementos usando el
ListView Item Editor de Delphi, que se encuentra conectado a la propiedad I t e m s .
En este editor, podemos definir elementos y tambien subelementos. Los
subelementos aparecen solo en la vista detallada (cuando configuramos el valor
vs R e p o r t de la propiedad Views t y l e ) y estan conectados con 10s titulos

Ft
definidos en la propiedad C o l u m n s :

W bbb

-
ADVERTENCIA: El conti01 Listview no tieno ep CLX fas vjstqs de icom
pequdoa y g r a d e s . En Qt,este tip0 de apariencia esddisponi.ble graciaa a
otro a m p a e f i t e . el IcanView. -

En el ejemplo RefList (una simple lista de referencias a libros, revistas,


CD-ROM y sitios Web), 10s elementos se almacenan en un archivo, puesto que
10s usuarios del programa pueden editar 10s contenidos de la lista, que se guardan
automaticamente cuando se abandona el programa. De este modo, las ediciones
que realiza el usuario se convierten en permanentes. Guardar y cargar 10s conte-
nidos de un ListView no son tareas insignificantes en absoluto, puesto que el tip0
TList I t e m s no dispone de un mecanismo automatico para guardar datos. Como
tCcnica alternativa sencilla, hemos copiado 10s datos en una lista de cadena, usan-
do un formato personalizado. A continuacion, la lista de cadena se puede guardar
en un archivo y cargar de nuevo con una unica orden
El formato de archivo es sencillo, como se puede ver en el siguiente codigo.
Para cada elemento de la lista, el programa guarda el titulo en una linea, el indice
de la imagen en otra linea (que lleva como prefijo el caracter @) y 10s subelementos
en las lineas siguientes, sangradas por un caracter de tabulacion:
procedure TForml.FormDestroy(Sender: TObject);
var
I, J : Integer;
List: TStringList;
begin
// almacena 10s elementos
List : = TStringList-Create;
try
for I : = 0 to ListViewl.1tems.Count - 1 do
begin
// g u a r d a e l t i t u l o
List .Add (ListViewl.Items [I] .Caption) ;
// g u a r d a e l i n d i c e
List .Add ( ' @ ' + IntToStr (ListViewl.Items [I].ImageIndex) ) ;
// g u a r d a 10s s u b e l e m e n t o s ( s a n g r a d o s )
for J : = 0 to ListViewl.Items[I].SubItems.Count - 1 do
List-Add (#9 + ListViewl. Items [I].SubItems [J]);
end;
List.SaveToFile (ExtractFilePath (App1ication.ExeName) +
I t e m s . txt ') ;
finally
List.Free;
end;
end;

A continuacion, 10s elementos se cargan de nuevo en el metodo Formcreate:


procedure TForml.FormCreate(Sender: TObject);
var
List: TStringList;
NewItem: TListItem;
I: Integer;
begin
// detiene el mensaje d e advertencia
NewItem : = nil;
// c a r g a 10s e l e m e n t o s
ListViewl.1tems.Clear;
List : = TStringList.Create;
try
List.LoadFromFi1e (
ExtractFilePath (App1ication.ExeName) + 'Items
for I : = 0 to List .Count - 1 do
i f List [I] [I] = # 9 then
NewItem.SubItems .Add (Trim (List [I]) )
else i f List [I] [I] = ' @ I then
NewItem.ImageIndex : = StrToIntDef (List [I][
else
begin
// u n n u e v o e l e m e n t o
NewItem : = ListViewl.Items.Add;
NewItem.Caption : = List [I] ;
end;
finally
List.Free;
end;
end;

El programa posee un menu que podemos emplear para escoger una de las
distintas vistas que soporta el control ListView y para aiiadir casillas de verifica-
cion a 10s elementos, como en un control CheckListBox. Se pueden ver las distin-
tas combinaciones de estos estilos en la figura 5.12.
Otra caracteristica importante, que es habitual en la vista detallada o de infor-
me del control, consiste en dejar que un usuario clasifique 10s elementos de una de
las columnas. En la VCL, esta tecnica requiere tres operaciones. La primera es
cstablecer la propiedad SortType de ListView como st Both o st Data. De
este modo, la ListView realizara la clasificacion no basandose en 10s titulos, sin0
llamando a1 evento Oncompare de cada uno de 10s dos elementos que ha de
clasificar.

Fle View Heb I

I-
Borland
Develo..
Delohi Delohi Delahi
ClienIfS ... Develo ... Informant
Masterinq
Delohi
The
Delphi ... I
in Java fib ~ i mr ~ l p
Borland Develooers Conference ...
.:-Delohi ClienVServer
00 Pelohi Develooer's Handbook
n ~ $ + D e l o hInformant
i
nQ Mastering D e b h i
n & T h e D e b h i Maaanne
q -Thinkina in Java
OW marco@marcocantu.com
OQwww.borland.com
O.dwww.marcocanlu.com

'igura 5.12. Diferentes ejemplos de las combinaciones de estilos de un componente


ListView en el programa RefList, obtenidos al cambiar la propiedad Viewstyle y
anadir las casillas de verificacion.

Como queremos realizar la clasificacion de cada una de las columnas de la


vista dctallada, tambien controlamos el evento OnColumnClick (quc ocurre
cuando el usuario hace clic sobre 10s titulos de la columna cn la vista detallada,
pero solo si la propiedad ShowColumnHeaders esta definida como True).
Cada vez que hacemos clic sobre una columna, el programa guarda el numero de
la misma en el campo privado nsortcol de la clase de formulario:
p r o c e d u r e TForml.ListViewlColumnClick(Sender: TObject;
Column: TListColumn);
begin
nSortCol : = Column.Index;
ListViewl.AlphaSort;
end;

A continuation; en el tercer paso, el codigo de clasificacion usa el titulo o uno


de 10s subelementos segun la columna de clasificacion en uso:
procedure TForml.ListViewlCompare(Sender: TObject;
Iteml, I tem2 : TListItem; Data: Integer; var Compare: Integer) ;
begin
i f nSortCol = 0 t h e n
Compare : = CompareStr (Iteml.Caption, Item2.Caption)
else
Compare : = CompareStr (1teml.SubItems [nSortCol - 11,
Item2 .SubItems [nSortCol - 11 ) ;
end;

En la version CLX del programa (llamada QRefList) no es necesario seguir


ninguno de estos pasos. El control ya es capaz de realizar la clasificacion por si
mismo cuando se hace clic sobre su titulo. Automaticamente se consiguen varias
columnas que se auto-ordenan (tanto ascendente como descendentemente).
Las caracteristicas finales que hemos afiadido a1 programa estan relacionadas
con las operaciones de raton. Cuando el usuario hace clic con el boton izquierdo
del raton sobre un elemento, el programa RefList muestra una descripcion del
elemento seleccionado. A1 hacer clic con el boton derecho del raton sobre el ele-
mento seleccionado, este pasa a su mod0 edicion y el usuario puede cambiarlo
(tengamos en cuenta que 10s cambios se guardan automaticamente cuando finali-
za el programa).
Veamos el codigo utilizado para ambas operaciones, en el controlador del
evento OnMouseDown del control ListView:
p r o c e d u r e TForml.ListViewlMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
strDescr: string;
I: Integer;
begin
// s i h a y u n e l e m e n t o s e l e c c i o n a d o
i f ListViewl.Se1ected <> n i l t h e n
i f Button = &Left then
begin
/ / crea y muestra una d e s c r i p c i o n
strDescr : = ListViewl.Columns [0] .Caption + # 9 +
ListViewl.Se1ected.Caption + #13;
f o r I : = 1 to ListViewl.Selected.SubItems.Count d o
strDescr : = strDescr + ListViewl.Columns
[I] .Caption + # 9 +
ListViewl.Selected.SubItems [I-l] + #13;
ShowMessage (strDescr);
end
e l s e i f Button = &Right then
/ / edita e l t i t u l o
ListViewl.Se1ected.EditCaption;
end;

Aunque este ejemplo no incluye todas las caracteristicas, muestra parte del
potencial del control ListView. Tambien hemos activado la caracteristica de "se-
guimiento activo", que permite que la vista en lista resalte y subraye el elemento
que se encuentra bajo el raton. Las propiedades mas relevantes de ListView pode-
mos verlas en su descripcion textual:
object ListViewl: TListView
Align = alClient
Columns = <
item
Caption = 'Referencia '
Width = 2 3 0
end
item
Caption = 'Autor'
Width = 1 8 0
end
item
Caption = 'Pais '
Width = 8 0
end>
Font.Height = - 1 3
Font .Name = 'MS Sans Serif'
Font.Style = [fsBold]
FullDrag = True
Hideselection = False
HotTrack = True
HotTrackStyles = [htHandPoint, htUnderlineHot]
SortType = stBoth
Viewstyle = vsList
OnColumnClick = ListViewlColumnClick
Oncompare = ListViewlCompare
OnMouseDown = ListViewlMouseDown
end

Para crear la version CLX de este ejemplo, QRefList, hub0 que emplear solo
una de las listas de imagenes y desactivar 10s menus de imagenes pequeiias e
imagenes grandes, dado que ListView esta limitado a 10s estilos de vista detallada
y en lista.
Los iconos grandes y pequeiios estan disponibles en un control diferente, deno-
minado Iconview. Como ya se comento, el soporte de clasificacion ya se encuen-
tra ahi, lo que podria haber ahorrado la mayoria del codigo de este ejemplo.

Un arbol de datos
Ahora que hemos visto un ejemplo basado en ListView, podemos examinar el
control TreeView (arbol de datos). El TreeView posee una interfaz de usuario que
es flexible y potente (con soporte para editar y arrastrar elementos). Tambien es
estandar, porque es la interfaz de usuario del explorador de Windows. Existen
propiedades y diversos modos de personalizar el mapa de bits de cada linea o de
cada tipo de linea.
Para definir la estructura de nodos del TreeView en tiempo de diseiio, podemos
emplear el editor de propiedades TreeView Items:
de
(3 eded
a rkd
. .

Sin cmbargo, cn este caso, hcmos decidido cargarlo en 10s datos del TreeView
a1 arrancar, de un mod0 similar a1 del ultimo ejemplo.
La propicdad Items del componente TrccView time muchas funciones mieni-
bro que podcmos emplear para modificar la jerarquia de cadenas. Por ejcmplo,
podemos crcar un arb01 de dos niveles con las siguientes lineas:
var
Node: TTreeNode;
begin
Node : = TreeViewl. Items .Add (nil, 'First level');
TreeViewl. Items .Addchild (Node, I Second level1) ;

Utilizando cstos dos metodos (Add y Addchild), podemos crear una com-
pleja estructura en tiempo de ejecucion. Para cargar la informacion, podemos
emplear dc nuevo una StringList en tiempo de ejecucion, cargar un archivo de
testo con la informacion y analizar la estructura gramaticalmente.
Sin embargo. dado que el control Treeview posee un mdtodo LoadFromFile,
10s ejemplos DragTree y QDragTree utilizan el siguiente codigo, mucho mas
sencillo:
procedure TForml.FormCreate(Sender: TObject);
begin
TreeViewl.LoadFromFile (ExtractFilePath
(Application.ExeName) +
'TreeText. txt ' ) ;
end;

El mCtodo LoadFromFile carga 10s datos en una lista de cadena y verifica


el nivel de cada elemento fijandose en el numero de caracteres de tabulacion. (Si
siente curiosidad, fijese en el metodo TTreeStrings .Get Buf Start que
puede encontrarse en la unidad ComCtrls en el codigo fuente de la VCL incluida
en Delphi.) Los datos que hemos preparado para TrccView corrcsponden a1 orga-
nigrama dc una cmpresa multinacional. Los datos preparados para el TrceView
forman cl diagrama de la organizacion de una emprcsa multinacional, como muestra
la figura 5 . 1 3 .

3 US Hsadquarle~s
Q Board d Dnectors

-
Mon~caRoss
Sales
Fat E j s t
Steve Fubens
Palls
Tck1o
Sirmoilre
Terty Merks

Q Sidney
John Calgary
Matk R m n
!3 J o h Rcitsr
Ian Green
El AdmLi~sl~ation

Figura 5.13. El ejernplo DragTree despues de cargar 10s datos y expandir las ramas.

En lugar dc cspandir 10s elementos de 10s nodos uno a uno. tambien se puedc
usar cl mcnu File>Expand All de este programa. quc llama a1 metodo
FullExpand del control TreeView o e.jecuta el codigo cquivalente (en este caso
especifico dc un arb01 con un elemento raiz):
TreeViewl. Items [0] .Expand ( T r u e );

Ademas de cargar 10s datos, el programa 10s guarda a1 finalizar y asi hacc que
10s cambios Sean permanentes. Tambien hay unos cuantos elementos de menu
para personalizar la fucnte del control TreeView y modificar otros sencillos
paramctros. La caracteristica especifica implementada en cste ejemplo es el so-
porte para arrastrar elementos y subarboles enteros. Hemos definido la propiedad
DragMode del componente como dmAutomatic y escrito 10s controladores de
eventos para 10s eventos OnDragOver y OnDragDrop.
En el primer0 de 10s dos controladores, el programa se asegura que el usuario
no esta intentando arrastrar un elemento sobre un elemento hijo (que seria despla-
zado junto con el elemento y originaria una repeticion infinita):
p r o c e d u r e TForml.TreeViewlDragOver(Sender, Source: TObject;
X, Y: Integer; State: TDragState; var Accept: Boolean);
var
TargetNode, SourceNode: TTreeNode;
begin
TargetNode : = TreeViewl GetNodeAt (X, Y ) ;.
/ / a c e p t a a r r a s t r e d e s d e e l mismo
i f (Source = Sender) a n d (TargetNode <> nil) t h e n
begin
Accept : = True;
/ / determina origen y destino
SourceNode : = TreeViewl-Selected;
// busca la cadena padre destino
while (TargetNode.Parent <> nil) and (TargetNode <>
SourceNode) d o
TargetNode : = TargetNode.Parent;
/ / si se encuentra el origen
if TargetNode = SourceNode then
/ / no permite el arrastre a un hijo
Accept : = False;
end
else
Accept : = False;
end;

El efecto conseguido por este codigo es que (a escepcion del caso concreto que
se necesita evitar) un usuario puede arrastrar un elemento del TreeView a otro. Es
sencillo escribir el codigo necesario para desplazar 10s elementos, porque el con-
trol TreeView ofrece soporte para dicha operation, mediante el metodo MoveTo
de la clase TTreeNode.
procedure TForml.TreeViewlDragDrop(Sender, Source: TObject; X,
Y: Integer) ;
var
TargetNode, SourceNode: TTreeNode;
begin
TargetNode : = TreeViewl .GetNodeAt ( X , Y) ;
if TargetNode <> nil then
begin
SourceNode : = TreeViewl.Selected;
SourceNode.MoveTo (TargetNode, naAddChildFirst);
TargetNode .Expand (False);
TreeViewl.Selected : = TargetNode;
end;
end;
- - - - - - . - - - -- - - --- -- - --

NOTA: Entre las demos incluidas con Delphi, hay una muy interesante que
muestra un control TreeView de dibujo personalizado. El ejemplo se en-
cuentra en el subdirectorio CustomDraw.

La version adaptada de DragTree


Y a que este programa puede usarse en demostraciones de adaptacion y
portabilidad, hemos creado una version que se puede compilar como una aplica-
cion VCL nativa con Delphi y como una aplicacion CLX con Kylix. Esto es algo
distinto de la mayor parte de 10s programas de este libro, que pueden adaptarse a
Delphi usando VisualCLX y tambien Qt sobre Windows. Seguir un camino dis-
tinto de vez en cuando puede resultar interesante. Lo primer0 que resulta necesa-
rio hacer es usar dos conjuntos distintos de sentencias uses,mediante la compi-
lacion condicional. La unidad del ejemplo PortableDragTree comienza de esta
manera:
unit TreeForm;

interface

uses
SysUtils, Classes,

{SIFDEF LINUX]
Qt, Libc, QGraphics, QControls, QForms, QDialogs,
QStdCtrls, QComCtrls, QMenus, QTypes, QGrids;
{SENDIF]

{ S I F D E F MSWINDOWS]
Windows, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, Menus, Grids;
{SENDIF)

Una directiva condicional similar se usa en la seccion inicial de la implemen-


tacion, para incluir el archivo de recursos adecuado para el formulario (10s dos
archivos de recursos son distintos):
{ $ IFDEF LINUX I
{ $ R * .xfm)
{SENDIF]

{ $ IFDEF MSWINDOWS]
{SR *.dfm]
{SENDIF]

He omitido algunas de las caracteristicas especificas de Windows, de manera


que la unica diferencia en el codigo se encuentra en el metodo FormCreate.El
programa carga el archivo de datos de la carpeta predefinida del usuario, no de la
misma carpeta que el ejecutable. Segun el sistema operativo del que se trate, la
carpeta del usuario sera el directorio home (y el archivo oculto comienza por un
punto) o el area especifica Mis Documentos (accesible con una llamada espe-
cial de la API):
procedure TForml.FormCreate (Sender: TObject) ;
var
path: string:
begin
{ $ IFDEF LINUX]
filename : = GetEnvironmentVariable ( ' H O M E ' ) + '/
.TreeText.txtt;
{$ELSE1
SetLength (path, 100) ;
ShGetSpecialFolderPath (Handle, P C h a r ( p a t h ) ,
CSIDL-PERSONAL, False) ;
path : = PChar (path); // cadena d e longitud fija
filename : = path + ' \TreeText.txt'
{SENDIP}
TreeViewl.LoadFromFile (filename);
end ;

Nodos de arbol personalizados


Delphi 6 aiiadio unas cuantas caracteristicas nuevas a 10s controles Treeview,
como l a seleccion multiple (veanse las propiedades M u 1t i S e 1e c t y
M u l t i s e l e c t S t y l e , y la matriz s e l e c t i o n s ) , una clasificacion mejorada
y diversos eventos nuevos. Sin embargo, la mejora clave es permitir que el pro-
gramador determine la clase de 10s elementos de nodo de la vista en arbol. Tener
elementos de nodo personalizados implica la capacidad de adjuntar datos
personalizados a 10s nodos de un mod0 simple, orientado a objetos. Para soportar
esta tecnica, existe un nuevo metodo A d d N o d e para la clase T T ree I t e m s y un
nuevo evento especifico, O n C r e a t e N o d e s C l a s s . En el controlador de este
evento, devolvemos la clase del objeto que se va a crear, que habra de heredar de
TTreeNode.
Como esta tecnica es muy comun, hemos creado un ejemplo para explicarla de
forma pormenorizada. El ejemplo CustomNodes no se centra en un caso del
mundo real, sin0 que ilustra una situacion bastante compleja, en la que existen
dos clases de nodos de arbol personalizados diferentes, derivados el uno del otro.
La clase basica aiiade una propiedad E x t r a C o d e , proyectada a metodos virtuales,
y la subclase sobrescribe uno de esos metodos. En el caso de la clase basica, la
funcion G e t E x t r a C o d e devuelve sencillamente el valor, mientras que en el de
la clase derivada, el valor se multiplica por el valor del nodo padre. Veamos las
clases y este segundo metodo:
type
TMyNode = c l a s s (TTreeNode)
private
FExtraCode: Integer;
protected
p r o c e d u r e SetExtraCode (const Value: Integer) ; virtual;
function GetExtraCode: Integer; virtual;
public
property ExtraCode: Integer r e a d GetExtraCode w r i t e
SetExtraCode;
end ;

TMySubNode = class (TMyNode)


protected
f u n c t i o n GetExtraCode: Integer; override;
end ;
f u n c t i o n TMySubNode.GetExtraCode: Integer;
begin
Result : = fExtraCode * (Parent a s TMyNode) .Extracode;
end:

A1 tener estas clases de nodo de arbol personalizadas, el programa crea un


arbol de elementos, usando el primer tipo para 10s nodos del primer nivel y la
segunda clase para 10s otros nodos. Como solo tenemos un controlador de eventos
O n C r e a t e N o d e C l a s s , este utiliza la referencia de clase almacenada en un
campo privado del formulario ( C u r r e n t N o d e C l a s s del tipo T T r e e N o -
declass):
p r o c e d u r e TForml.TreeViewlCreateNodeClass(Sender:
TCustomTreeView;
v a r NodeClass: TTreeNodeClass);
begin
NodeClass : = CurrentNodeClass;
end;

El programa establece esta referencia de clase antes de crear nodos para cada
tipo, por ejemplo, con un codigo como el siguiente:
var
MyNode : TMyNode ;
begin
CurrentNodeClass : = TMyNode;
MyNode : = TreeViewl. Items .Addchild (nil, 'item' + IntToStr
(nValue)) as TMyNode;
MyNode.ExtraCode : = nValue;

Cuando se ha creado el arbol completo, en el momento en que el usuario


selecciona un elemento, podemos convertir su tipo a TMyNode y acceder a las
propiedades adicionales (pero tambien a metodos y datos):
p r o c e d u r e TForml.TreeViewlClick(Sender: TObject);
var
MyNode: TMyNode;
begin
MyNode : = TreeViewl.Selected a s TMyNode;
Labell. Caption : = MyNode .Text + ' [ ' + MyNode .ClassName + 'I
- ! + IntToStr (MyNode.Extracode) ;
-
end;

Este es el codigo empleado en el ejemplo CustomNodes para mostrar la


descripcion del nodo seleccionado en una etiqueta. Fijese en que cuando seleccio-
namos un elemento dentro del arbol, su valor se multiplica por el de cada uno de
10s nodos padre. Aunque existen formas realmente mas sencillas de obtener este
mismo efecto, una vista en arbol con objetos de elementos creados a partir de
diferentes clases de una jerarquia ofrece una estructura orientada a objetos que
podemos emplear como base de un codigo mas complejo.
Creacion
de la interfaz
de usuario

Acabamos dk comentar 10s conceptos bisicos de la clase T C o n t rol y sus


clases derivadas en las bibliotecas VCL y VisualCLX. Despues hemos dado un
rapido repaso a 10s principales controles que se pueden usar para construir una
interfaz de usuario, tales como, 10s componentes de edicion, listas, selectores de
rango y muchos mas. En este capitulo varnos a centrarnos en otros controles
utilizados para definir el diseiio global de un formulario, como Pagecontrol y
Tabcontrol. Despues de estos componentes, vamos a comentar las barras de he-
rramientas y de estado, con algunas caracteristicas bastante avanzadas. Con esto
conseguiremos la base para el resto del capitulo, en el que se habla de acciones y
de la arquitectura Action Manager.
Las modernas aplicaciones de Windows suelen tener varios modos de ofrecer
ordenes, como elementos de menu, botones de la barra de herramientas, atajos de
menu y demas. Para separar las ordenes reales que puede dar un usuario de sus
multiples representaciones en la interfaz de usuario, Delphi usa el concept0 de
acciones.
En las ultimas versiones de Delphi, esta arquitectura se ha extendido para
hacer que la construccion de la interfaz de usuario sobre las acciones sea comple-
tamente visual. Ahora tambien se puede dejar que 10s usuarios del programa
personalicen esta interfaz facilmente, como sucede con muchos programas profe-
sionales. Finalmente, Delphi 7 afiade a 10s controles visuales que soportan la
arquitectura Action Manager una interfaz mejor y mas moderna, que soporta la
apariencia y comportamiento de XP. En Windows XP se pueden crear aplicacio-
nes que se adapten a1 tema activo, gracias sobre todo a1 nuevo codigo interno de la
VCL. Este capitulo trata 10s siguientes temas:
Formularios de varias paginas.
Paginas y pestafias.
Componentes ToolBar y StatusBar.
Temas y estilos.
Acciones y listas de acciones.
Acciones predefinidas en Delphi.
Los componentes ControlBar y CoolBar.
Anclaje de barras de herramientas y otros controles
La arquitectura Action Manager.

Formularios de varias paginas


Cuando tenemos que mostrar mucha informacion y muchos controles en un
cuadro de dialog0 o en un formulario, podemos emplear varias paginas o fichas.
La metafora es la de un cuaderno de notas: usando solapas o pestafias, un usuario
puede seleccionar una de las fichas posibles. Existen dos controles que podemos
emplear para crear una aplicacion de varias fichas en Delphi:
El componente PageControl: Tiene solapas en uno de 10s laterales y va-
rias fichas (parecidas a 10s paneles) que cubren el resto de su superficie.
Como hay una ficha por solapa, podemos simplemente colocar componen-
tes en cada ficha para obtener el efecto adecuado tanto en tiempo de dise-
iio, como en tiempo de ejecucion.
Tabcontrol: Solo tiene la parte de la solapa per0 no ofrece fichas en las
que almacenar la informacion. En este caso, sera conveniente emplear uno
o mas componentes para imitar la operacion cambio de ficha, o podremos
colocar distintos fonnularios dentro de las pestaiias para simular las pagi-
nas .
Una tercera clase relacionada, TabSheet, representa una unica ficha de
PageControl. Este componente no es independiente ni esta en la Component
Palette. Una TabSheet (o pagina de pestafia) se crea en tiempo de diseiio usan-
do el menu local de PageControl o, en tiempo de ejecucion, usando metodos del
mismo control.
NOTA: Delphi incluye todavia (en la pestaiia Win 3.1 de la Component
Palette) 10scomponentes Notebook, TabSet y TabbedNotebook introduci-
dos en las versiones de 32 bits (es decir, desde Delphi 2). Para cualquier
otro fin, 10s componentes PageControl y Tabcontrol, que encapsulan
controles comullei de Win32, ofrecen una ikterfaz de usuario mbs modema
- - - . - - - -. -
.
En realidad. en las verslones de 32 b ~ t sde U e l ~ h,i .el c o m ~ o n e n t e
rabbedNotebook se implement0 de nuevo usando el control PageControI
(je Win32 de forma interna, para reducir el tamaiio del codigo y actualizarr
. .

Pagecontrols y Tabsheets
Como es habitual, cn lugar de repetir la lista de propiedades y metodos dcl
sistcma dc ayuda del componente PageControl. hemos creado un ejenlplo quc
bosqueja sus capacidadcs y permite modificar su comportamiento en ticmpo de
ejccucion. El cjcmplo, denominado Pagcs, tienc un PageControl con tres paginas.
La estructura del PageControl y de otros componentes clave se muestra en el
listado 6.1.

Listado 6.1. Secciones clave del DFM del ejemplo Pages.

object Farml: TForml


BorderIcons = [biSystemMenu, biMinimize]
Borderstyle = bssingle
Caption = ' P a g e s T e s t '
OnCreate = Formcreate
object PageControll: TPageControl
Activepage = TabSheetl
Align = alClient
HotTrack = True
Images = ImageListl
MultiLine = True
object TabSheetl: TTabSheet
Caption = ' P a g e s '
object Label3 : TLabel
object ListBoxl: TListBox
end
object TabSheet2: TTabSheet
Caption = 'Tab S i z e '
ImageIndex = 1
object Labell: TLabel
// o t r o s c o n t r o l e s
end
object TabSheet3: TTabSheet
Caption = ' T a b t e x t '
ImageIndex = 2
object Memol: TMemo
Anchors = [akLeft, akTop, akRiqht , akBottom]
OnChanqe = MemolChanqe
end
o b j e c t BitBtnChanqe: TBitBtn
Anchors = [akTop, akRiqht]
Caption = '&Change '
end
end
end
o b j e c t BitBtnPrevious: TBitBtn
Anchors = [akRiqht, akBottom]
Caption = '&Previous '
OnClick = BitBtnPreviousClick
end
o b j e c t BitBtnNext : TBitBtn
Anchors = [akRiqht, akBottom]
Caption = '&Nextf
OnClick = BitBtnNextClick
end
o b j e c t ImaqeListl: TImaqeList
Bitmap = { . . . I
end
end

Fijese en que las solapas estan conectadas a mapas de bits mediante un control
ImageList y en que algunos controles usan la propiedad A n c h o r s para mantener
una distancia fija con 10s bordes derecho o inferior del formulario. Aunque el
formulario soporte un reajuste del tamaiio (esto hubiera resultado mucho mas
complicado de realizar con tantos controles), las posiciones pueden variar cuando
las solapas aparecen en varias lineas (simplemente aumenta la longitud de 10s
titulos) o en el lateral izquierdo del formulario.
Cada objeto T a b S h e e t tiene su propio C a p t i o n , que aparecera en la sola-
pa de la hoja. En tiempo de diseiio, podemos usar el menu local para crear fichas
nuevas y para movernos por ellas. Podemos ver el menu local del componente
P a g e C o n t r o 1 en la figura 6.1, junto con la primera ficha. Esta ficha contiene
un cuadro de lista y un pequeiio titulo y comparte dos botones con las otras fichas.
Si colocamos un componente en una ficha, esta disponible solo en dicha ficha.
Para tener el mismo componente (en este caso, dos botones de mapas de bits) en
cada ficha, sin duplicarlo, sencillamente hay que colocar el componente en el
formulario, fuera del Pagecontrol (o antes de alinearlo con la zona de cliente) y,
a continuacion, moverlo hacia delante de las fichas, mediante la orden B r i n g
To F r o n t del menu local del formulario. Los dos botones que hemos colocado
en cada ficha se pueden usar para mover las fichas adelante y atras, y ofrecen una
alternativa a1 uso de las solapas. Veamos el codigo asociado a uno de ellos:
procedure TForml.BitBtnNextClick(Sender: TObject);
begin
PaqeControll.SelectNextPaqe (True);
end;
I Paged ) @ Tebr Sue I
.
A Tabs T& I
Ckk on lha Mbcx

a, to change papc

New_P q e
Mxt Pagl

Control

Add tu R_epo.;itory.. .

1
frcw as Text
TextDFM
- .-

Figura 6.1. La prlmera h o p de Pagecontrol del ejemplo Pages con su menu local.
- I
El otro boton llama a1 mismo procedimiento y pasa False como su parametro
para selcccionar la ficha anterior. Fi-jese cn que no es neccsario verificar si esta-
mos cn la primcra o en la ultima ficha, porque el metodo SelectNext Page
considera quc la ultima ficha es la quc csta antes de la primcra y nos llevara
directamcnte cntre estas dos fichas.
Ahora podemos centrarnos de nucvo en la primera ficha. Posce un cuadro de
lista, que cn tiempo de ejecucion contiene 10s nombres de las solapas. Si un usua-
rio hace clic sobre un elemento del cuadro dc lista, la pagina actual cambia. Este
cs el terccr mctodo del que disponemos para cambiar de ficha (despues de las
solapas y de 10s botones Next y Previous). El cuadro de lista se rcllena con cl
metodo Formcreate, asociado con el evento Oncreate dcl formulario, y
copia cl titulo de cada ficha (la propicdad Page contiene una lista de objetos
Tabsheet):
for I : = 0 to Pagecontroll. Pagecount - 1 do
ListBoxl.Items.Add (PageContro1l.Pages.Caption);
Cuando hacemos clic sobre un elemento de la lista, podemos seleccionar la
pagina correspondiente:
procedure TForml.ListBoxlClick(Sender: TObject);
begin
Pagecontroll-Activepage : = Pagecontroll-Pages
[ListBoxl.ItemIndex];
end;

La segunda pagina contiene dos cuadros de edicion (conectados a dos compo-


nentes UpDown), dos casillas de verificacion y dos botones de radio, como mues-
tra la figura 6.2. El usuario puede escribir un numero (o escogerlo, pulsando
sobre 10s botones de Flecha arriba o Flecha abajo con el raton o pulsando las
teclas de cursor arriba o abajo mientras el foco esta en el cuadro de edicion que
corresponda), marcar las casillas de verificacion y 10s botones de radio y, a con-
tinuacion, hacer clic sobre el boton Apply para realizar las modificaciones:
p r o c e d u r e TForml.BitBtnApplyClick(Sender: TObject);
begin
// e s t a b l e c e a n c h o , a l t o y l i n e a s de l a s o l a p a
PageControll.TabWidth : = StrToInt (EditWidth.Text);
PageControll.TabHeight : = StrToInt (EditHeight.Text);
PageControll.MultiLine : = CheckBoxMu1tiLine.Checked;
// muestra u o c u l t a l a dltima solapa
TabSheet3.TabVisible : = C h e c k B o x V i s i b l e . C h e c k e d ;
/ / f i j a l a p o s i c i o n de l a s o l a p a
i f RadioButtonl-Checked t h e n
PageControll.TabPosition : = tpTop
else
PageControll.TabPosition : = tpleft;
end;

Figura 6.2. La segunda pagina del ejemplo puede ser usada para ajustar el tamaiio y
posicion de las pestaiias, que aqui se muestran a la izquierda de la pagina.

Con este codigo, podemos cambiar cl ancho y la altura de cada solapa (recuer-
de que 0 significa que el tamaiio se calcula automaticamente a partir del espacio
que ocupa cada cadena). Podemos escoger tener diversas lineas de solapas o dos
pequeiias flechas para recorrer la zona de la solapa. y podemos moverlas al late-
ral izquierdo de la ventana. El control tambien permite situar las solapas en la
parte inferior o a la derecha, pero no nuestro programa, porque de ese mod0 la
colocacion de 10s otros controles resultaria bastante compleja. Tambien podemos
ocultar la ultima solapa del Pagecontrol, que corresponde a1 componente
T a b s h e e t 3 . Si ocultamos una de las solapas definiendo su propiedad
T a b V i s i b l e como F a l s e , no podemos alcanzar dicha solapa haciendo clic
sobre 10s botones Next ni Previous, que se basan en el metodo S e l e c t N e x t Page.
En lugar de eso, habria que usar la funcion F i n d N e x t P a g e , que seleccionara
esa pagina incluso aunque la pestaiia no sea visible. La Nueva version del contro-
lador de eventos OnCl i c k del boton Next muestra una llamada a1 metodo
FindNext Page:
procedure TForml.BitBtnNextClick(Sender: TObject);
begin
PageControl1.ActivePage : = Pagecontroll-FindNextPage (
PageControll.ActivePage, True, False);
end;

La ultima ficha posee un componente de memo, de nuevo con 10s nombres de


las fichas (aiiadidas en el metodo FormCrea t e ) . Podemos editar 10s nombres de
las fichas y hacer clic sobre el boton Change para modificar el texto de las
solapas, per0 solo si el numero de cadenas se corresponde con el numero de
pestaiias :
procedure TForml.BitBtnChangeClick(Sender: TObject);
var
I: Integer;
begin
if Memol.Lines.Count <> PageControll.PageCount then
MessageDlg ( ' U n a l i n e d p o r p e s t a d a , p o r f a v o r I , mtError,
[mbOKl, 0 )
else
for I : = 0 to PageControl1.PageCount -1 do
PageControll.Pages [I] .Caption : = Memo1 .Lines [I];
BitBtnChange.Enabled : = False;
end ;

Finalmente, el ultimo boton, Add Page, nos permite aiiadir una nueva hoja de
solapa a1 control de ficha, aunque el programa no aiiada ningun componente a la
misma. El objeto hoja de solapa (en blanco) se crea usando el control ficha como
su propietario, per0 no funcionara a no ser que tambien se configure la propiedad
P a g e c o n t r o l . Sin embargo, antes de hacer esto, deberiamos hacer que la nue-
va solapa fuera visible. Veamos el codigo:
procedure TForml.BitBtnAddClick(Sender: TObject);
var
strcaption: string;
NewTabSheet: TTabSheet;
begin
strcaption : = ' N e w t a b ' ;
if InputQuery ( ' N e w t a b ' , ' T a b C a p t i o n ' , strcaption) then
begin
/ / s e a d a d e una n u e v a f i c h a e n b l a n c o a 1 c o n t r o l
NewTabSheet : = TTabSheet-Create (PageControll);
NewTabSheet.Visible : = True;
NewTabSheet.Caption : = strcaption;
NewTabSheet-Pagecontrol : = PageControll;
PageControl1.ActivePage : = NewTabSheet;
// s e a d a d e a ambas l i s t a s
Memol. Lines .Add (strcaption);
ListBoxl .Items .Add (strcaption);
end;
end;

TRUCO:Siempre que escribimos un formulario basado en un PageCon-


trol, debemos recordar que la primera ficha que aparece en tiempo de ejecu-
cion es la ficha en la que nos encontrabamos antes de compilar el codigo.
Esto significa que si estamos trabajando en la tercera ficha y a continua-
cion compilamos y ejecutamos el programa, este arrancara mostrando di-
cha ficha. Una forma comun de resolver este problema consiste en aiiadir
una linea de codigo al metodo Formcreate para fijar el Pagecontrol o
el cuaderno de notas en la primera ficha. De este modo, la ficha actual en
' tiempo de disefio no determinara la ficha inicial en tiempo de ejecucion.

Un visor de imagenes con solapas dibujadas


por el propietario
El uso del TabControl y de una tecnica dinamica. como se describio en cl
ultimo ejemplo, se pucde aplicar tambikn cn casos mas generales (y mas senci-
110s). Cada vez quc necesitamos divcrsas fichas que tengan el mismo tip0 dc
contenido, en lugar de duplicar 10s controles en cada ficha, podemos usar un
TabControl y modificar sus contenidos cuando se selecciona una nueva solapa.
Esto es lo que haremos en el ejemplo de mapa de bits de varias fichas, llamado
BmpViewer, La imagen que aparece en el TabControl de este formulario, alinca-
do con toda la zona de cliente, depende de la selection de la solapa que esta sobre
ella (como muestra la figura 6.3).
A1 principio, cl TabControl est6 vacio. Despucs de seleccionar File>Open. el
usuario puede escoger varios archivos en el cuadro de dialog0 Abrir, y la matriz
de cadenas con 10s nombrcs de 10s archivos (la propiedad Files del componente
OpenDialog 1)se aiiade a las solapas (la propiedad Tabs de TabControl 1):
p r o c e d u r e TFormBmpViewer.OpenlClick(Sender: TObject);
begin
i f 0penDialogl.Execute then
begin
TabControll.Tabs.AddStrings (0penDialogl.Files);
TabControll.TabIndex : = 0;
TabControl lchange (Tabcontroll) ;
end ;
end;

I ADVERTENCIA:La propiedad Tabs de uo control TabControl en CLX


es una coleccion, mientras que en la VCL es una simple lista de cadena.
I
Figura 6.3. La interfaz del visor de mapas de bits del ejemplo BmpViewer, con
pestahas dibujadas por el propietario.

Dcspues de mostrar las nuevas solapas, tenemos que actualizar la imagen para
que se corresponda con la primera solapa. Para esto, el programa llama a1 metodo
concctado con el evento OnChange de Tabcontrol, que carga el archivo
correspondiente a la solapa actual en el componente imagen:
procedure TFormBmpViewer.TabControllChange(Sender: TObject);
begin
Imagel.Picture.LoadFromFi1e (TabControll.Tabs
[TabControll.TabIndex]);
end;

Este ejemplo fbnciona, a no ser que seleccionemos un archivo que no contenga


un mapa de bits. El programa advertira a1 usuario con una escepcion estandar,
ignorara el archivo y continuara ejecutandose.
El programa permite tambien pegar el mapa de bits en el portapapeles (aunque
sin copiarlo en realidad, sino solamente aiiadiendo una pestaiia que realizara la
operacion de pegado real cuando se seleccione) y copiar el mapa de bits actual en
el. El soporte para el portapapeles esta disponible en Delphi mediante un objeto
Clipboard definido en la unidad ClipBrd. Para copiar y pegar mapas de bits,
podemos usar el metodo A s s i g n de las clases TClipboard y TBitmap.
Cuando seleccionamos la orden Edit>Paste del ejemplo, se aiiade una nueva
solapa, cuyo nombre es Clipboard, a1 conjunto de solapas (a no ser que ya este
prescnte). A continuacion, el numero de la nueva solapa se usa para modificar la
solapa activa:
procedure TFormBmpViewer.PastelClick(Sender: TObject);
var
TabNum: Integer;
begin
// i n t e n t a c o l o c a r l a f i c h a
T a b N u m : = T a b C o n t r o l l .Tabs. IndexOf ( ' C l i p b o a r d ' ) ;
i f TabNum < 0 then
// c r e a u n a n u e v a f i c h a p a r a C l i p b o a r d
T a b N u m : = TabControll. Tabs .Add ( ' C l i p b o a r d ' ) ;
// v a a l a f i c h a C l i p b o a r d y h a c e q u e s e p i n t e d e n u e v o
TabControll.TabIndex : = TabNum;
TabControllChange (Self);
end;

En cambio, la operacion EditXopy resulta tan sencilla como copiar el mapa


de bits que esta actualmente en el control de imagen:

Para tener en cuenta la posible presencia de la solapa Clipboard, el codigo del


metodo TabControllChange se transforma en:
p r o c e d u r e TFormBmpViewer.TabControllChange(Sender: TObject);
var
TabText : string;
begin
1magel.Visible : = True;
TabText : = TabControll.Tabs [TabControll.TabIndex];
i f TabText <> ' C l i p b o a r d ' t h e n
// c a r g a e l a r c h i v o i n d i c a d o en l a s o l a p a
1magel.Picture.LoadFromFile (TabText)
else
{ s i l a s o l a p a es ' C l i p b o a r d ' y u n mapa d e b i t s
e s t d d i s p o n i b l e en e l p o r t a p a p e l e s )
i f Clipboard.HasFormat (cf-Bitmap) t h e n
1magel.Picture.Assign (Clipboard)
else
begin
/ / s i no e l i r n i n a l a s o l a p a c l i p b o a r d
TabControll.Tabs.Delete (TabControll.Tab1ndex);
i f TabControll. Tabs. Count = 0 t h e n
1magel.Visible : = False;
end ;

Este programa pega el mapa de bits del portapapeles cada vez que cambiamos
de solapa. El programa almacena solo una imagen cada vez y no tiene forma de
almacenar el mapa de bits Clipboard. Sin embargo, si el contenido del portapapeles
cambia y el formato de mapa de bits ya no esta disponible, la solapa Clipboard se
borra automaticamente (como se puede ver en el listado anterior). Si no quedan
mas solapas, el componente Image esta oculto.
Una imagen tambien puede eliminarse utilizando una de las dos ordenes del
menu: Cut o Delete . Cut elimina la solapa despues de hacer una copia del
mapa de bits en el portapapeles. En la practica, el metodo Cut lClick no hace
nada a parte de llamar a 10s metodos CopylClick y DeletelClick. El
metodo CopylClick se encarga de copiar la imagen actual a1 portapapeles,
Delete1C 1ic k sencillamente elimina la solapa actual. Veamos su codigo:
procedure TFormBmpViewer.CopylClick(Sender: TObject);
begin
Clipboard.Assign (1magel.Picture.Graphic);
end;

procedure TFormBmpViewer.DeletelClick(Sender: TObject);


begin
with Tabcontroll do
begin
if TabIndex >= 0 then
Tabs.Delete (TabIndex);
if Tabs.Count = 0 then
Imagel-Visible : = False;
end;
end;

Una de las caracteristicas especiales del ejemplo es que el Tabcontrol tiene la


propiedad OwnerDraw definida como True.Esto significa que el control no
pintara las solapas (que estarin vacias en tiempo de diseiio), sin0 que lo hara la
aplicacion, llamando a1 evento OnDrawTab.En su codigo, el programa muestra
el texto centrado verticalmente, usando la funcion DrawText de la API. El texto
que aparece no es la ruta del archivo completa sino solo el nombre del archivo. A
continuacion, si el texto es distinto de None, el programa lee el mapa de bits a1
que se refiere la solapa y pinta una version reducida del mismo en la propia
solapa. Para ello, el programa usa el objeto TabBmp,que es del tipo TBitmap
y se crea y destruye junto con el formulario. El programa usa tambien la constan-
te BmpSide para colocar de forma adecuada el mapa de bits y el texto:
procedure TFormBmpViewer.TabControllDrawTab(Control:
TCustomTabControl;
TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
TabText: string;
OutRect : TRect;
begin
TabText : = TabControll.Tabs [TabIndex];
OutRect : = Rect;
InflateRect (OutRect, -3, -3) ;
0utRect.Left : = 0utRect.Left + BmpSide + 3;
DrawText (Control.Canvas.Handle, PChar (ExtractFileName
(TabText) ) ,
Length (ExtractFileName (TabText)), OutRect,
dt-Left or dt-SingleLine or dt-VCenter);
if TabText = 'Clipboard' then
if Clipboard .HasFormat (cf-Bitmap) then
TabBmp-Assign (Clipboard) '
else
TabBmp.FreeImage
else
TabBmp.LoadFromFile (TabText);
OutRect .Left : = 0utRect.Left - BmpSide - 3;
OutRect .Right : = OutRect .Left + BmpSide;
Contro1.Canvas.StretchDraw (OutRect, TabBmp);
end;

El programa tiene tambien soporte para imprimir el mapa de bits actual, tras
haber mostrado un formulario de vista previa de la ficha, en el que el usuario
puede seleccionar la escala apropiada. Esta parte adicional del programa no se
comenta en detalle, per0 el codigo esta ahi para quien desee examinarlo.

La interfaz de usuario de un asistente


Exactamente del mismo mod0 que usamos un Tabcontrol sin fichas, tambien
podemos utilizar la tecnica opuesta y emplear un PageControl sin solapas. Ahora
nos centraremos en el desarrollo de la interfaz de usuario de un asistente. En un
asistente, dirigimos a1 usuario mediante una serie de pasos, en una pantalla por
paso y, en cada paso, normalmente queremos ofrecer la oportunidad de pasar al
siguiente o volver atras para corregir la entrada realizada en un paso anterior.
Asi, en lugar de solapas que se puedan seleccionar en un orden cualquiera, 10s
asistentes suelen ofrecer 10s botones Siguiente (Next) y Atras (Back)para despla-
zarse. Este ejemplo no sera complejo, su funcion consiste unicamente en propor-
cionar unas cuantas directrices. El ejemplo se llama WizardUI.
El punto de arranque es crear una serie de paginas en un PageControl y confi-
gurar la propiedad T a b v i s i b l e de cada TabSheet como F a l s e (mientras
mantenemos la propiedad V i s i b l e como T r u e ) . Desde Delphi 5 tambien pode-
mos ocultar las solapas en tiempo de disefio. En este caso, sera necesario utilizar
el menu de metodo abreviado del control de pagina, el cuadro combinado del
Object Inspector, o la Object Tree View para desplazarse a otra ficha, en lugar de
las solapas. Pero, podriamos preguntarnos el porque de no querer ver las solapas
en tiempo de disefio.
Podemos colocar 10s controles en las fichas y, a continuacion, colocar contro-
les adicionales delante de las fichas (como en el ejemplo), sin que sus posiciones
relativas cambien en tiempo de ejecucion. Tambien podriamos querer eliminar 10s
titulos inutiles de las pestafias, que ocupan espacio en memoria y entre 10s recur-
sos de la aplicacion.
En la primera ficha, hemos colocado a un lado una imagen y un control de
biselado y a1 otro lado algo de texto, una casilla de verificacion y dos botones. En
realidad, el boton Next esta dentro de la ficha, mientras que el boton Back esta
sobre ella (y lo comparten todas las fichas). Podemos ver la primera ficha en
tiempo de disefio en la figura 6.4. Las fichas siguientes tienen una apariencia
similar, con una etiqueta, casillas de verificacion y botones en el lateral derecho y
nada en el izquierdo.
Figura 6.4. La primera ficha del ejemplo WizardUl en tiempo de disefio.

Cuando haccmos clic sobrc el boton Next de la primera pagina, el programa


mira el cstado de la casilla dc verification y decidc que ficha cs la siguiente.
Podriamos habcr escrito el codigo de este modo:
p r o c e d u r e TForml.btnNextlClick(Sender: T O b j e c t ) ;
begin
B t n B a c k - E n a b l e d : = True;
i f CheckInprise.Checked then
PageControl1.ActivePage : = TabSheet2
else
PageControl1.ActivePage : = TabSheet3;
// m u e v e l a i m a g e n y e l b i s e l a d o
B e v e l l - P a r e n t : = PageControl1.ActivePage;
1 m a g e l . P a r e n t : = PageControl1.ActivePage;
end;

Tras activar el boton Back comiin. el programa cambia la ficha activa y, por
ultimo, dcsplaza la park grafica a la nucva ficha. Como este codigo ha de repetir-
se para cada boton, lo hemos colocado en un metodo despues de aiiadir unas
cuantas caractcristicas adicionales. Este es cl codigo actual:
p r o c e d u r e TForml.btnNextlClick(Sender: TObject);
begin
i f Check1nprise.Checked then
MoveTo (TabSheet2)
else
M o v e T o ( T a b S h e e t 3 );
end;

procedure TForml.MoveTo(TabSheet: TTabSheet);


begin
// adade l a illtima f i c h a a l a l i s t a
B a c k P a g e s . A d d (PageControl1.ActivePage);
BtnBack.Enabled := True;
// c a m b i a d e f i c h a
PageControl1.ActivePage : = Tabsheet;
// m u e v e l a i m a g e n y el B e v e l
Bevell-Parent : = PageControll.ActivePage;
Imagel-Parent : = PageContro1l.ActivePage;
end;

A d e m b del codigo que ya hemos explicado, el metodo MoveTo aiiade la


ultima ficha (la que esta antes del cambio de ficha) a una lista de fichas visitadas,
que se comporta como una pila. De hecho, el objeto BackPages de la clase
TList se crea a1 arrancar el programa y la ultima ficha siempre se aiiade a1
final. Cuando el usuario hace clic sobre el boton Back, que no depende de la
ficha, el programa extrae la ultima ficha de la lista, borra su entrada y se mueve a
dicha ficha:
procedure TForml.btnBackClick(Sender: TObject);
var
LastPage: TTabSheet;
begin
// o b t i e n e l a u l t i m a f i c h a y s a l t a a e l l a
LastPage := TTabSheet (BackPages [BackPages .Count - 11) ;
PageControl1.ActivePage : = LastPage;
// b o r r a l a u l t i m a f i c h a d e l a l i s t a
BackPages .Delete (BackPages.Count - 1) ;
// f i n a l m e n t e d e s a c t i v a e l b o t o n b a c k
BtnBack. Enabled := not (BackPages.Count = 0) ;
// mueve l a imagen y e l B e v e l
Bevell-Parent : = PageControll.ActivePage;
Imagel-Parent : = PageControl1.ActivePage;
end:

Con este codigo, el usuario puede volver varias fichas a t r b hasta que la lista
quede vacia, punto en el que el boton Back se desactiva. La complicacion a la que
hemos de enfrentarnos es que mientras nos movemos desde una ficha concreta,
sabemos que ficha es su ficha "posterior" y "anterior", per0 no sabemos de que
ficha se procede, porque hay diversas rutas para llegar a una ficha. Solo podemos
retroceder de forma segura, si guardamos la pista de 10s movimientos con una
lista.
El resto del codigo del programa, que simplemente muestra algunas direccio-
nes de sitios Web, es muy sencillo. Lo bueno es que podemos reutilizar la estruc-
tura de desplazamiento de este ejemplo en nuestros programas y modificar solo la
parte grafica y el contenido de las paginas. En realidad, como la mayoria de las
etiquetas de 10s programas muestran direcciones HTTP, un usuario puede hacer
clic sobre dichas etiquetas para abrir el explorador predefinido para que muestre
esa pagina. Para ello, se extrae la direccion HTTP de la etiqueta y se llama a la
funcion ShellExecute.
procedure TForml.LabelLinkClick(Sender: TObject);
var
Caption, StrUrl: string;
begin
Caption : = (Sender as TLabel) .Caption;
StrUrl : = C o p y (Caption, Pos ('http://', Caption), 1000) ;
ShellExecute (Handle, 'open ', PChar (StrUrl) , ' ', ' ', sw-Show) ;
end;

Este metodo esta enganchado a1 evento oncl i c k de varias etiquetas del for-
mulario, que se han transformado en enlaces a1 configurar su cursor como una
mano. Esta es una de las etiquetas:
o b j e c t Label2 : TLabel
Cursor = crHandPoint
Caption = 'Main site: http://www. borland. corn'
OnClick = LabelLinkClick
end

El control ToolBar
Para crear una barra de herramientas, Delphi incluye un componente ToolBar
especifico, que encapsula el control comun de Win32 correspondiente o el widget
Qt correspondiente en VisualCLX. Dicho componente proporciona una barra de
herramientas, con sus propios botones y tiene muchas capacidades avanzadas.
Para usarlo, lo colocamos en un formulario y, a continuacion, usamos el editor de
componentes (el menu de metodo abreviado activado con un clic del boton dere-
cho del raton) para crear unos cuantos botones y separadores.
L a b a r r a de herramientas esta compuesta por objetos de l a clase
TToo lBut ton.Dichos objetos tienen una propiedad bbica, Style,que deter-
mina su comportamiento:
El estilo tbsButton: Indica un boton pulsador estandar.
El estilo tbscheck: Indica un boton con el comportamiento de una casilla
de verificacion, o de un boton de radio si el boton esta agrupado con otros
en su bloque (determinado por la presencia de separadores).
El estilo tbsDropDown: Indica un boton desplegable, una especie de cua-
dro combinado. La parte desplegable se puede implementar facilmente en
Delphi conectando un control PopupMenu a la propiedad Dropdown-
Menu del control.
Los estilos tbsseparator y tbsDivider: Indican separadores con lineas
verticales diferentes o sin ellas (dependiendo de la propiedad Flat de la
barra de herramientas).
Para crear una barra de herramientas grafica, podemos aiiadir un componente
ImageLis t a1 formulario, cargar algunos mapas de bits en el y a continuacion,
conectar la ImageList con la propiedad Images de la barra de herramienta. Por
defecto, las imagenes se asignaran a 10s botones en el orden en el que aparecen,
per0 podemos cambiar facilmente este comportamiento fijando la propiedad
ImageIndex de cada boton de la barra de herramientas. Podemos preparar
listas de imagenes adicionales para condiciones especiales de 10s botones y asig-
narlas a las propiedades DisabledImages y HotImages de la barra de
herramientas. El primer grupo se usa para 10s botones desactivados, el segundo
para el boton actual que esta bajo el raton.
tr -

NOTA: En una aplicacitin, por lo general deberiamos crear barras de he-


rramientas utilizando una ActionList o la reciente arquitectura Action Ma-
w .
naizer. En ese caso.,aDenas asinnaremos com~ortamientoalrmno a 10s botones
w w

de la barra de herramientas, puesto que sus propiedades y eventos serhn


administrados por 10s componentes de accion. Aun mas, se acabad usando
m a herramienta de la c l a w esnecifica T A c t ionToo 1 R a r

El ejemplo RichBar
Como ejemplo del uso de una barra de herramientas, hemos creado la aplica-
cion RichBar, que tiene un componente RichEdi t con el que se puede trabajar
utilizando la barra de herramientas. El programa tiene botones para cargar y
guardar archivos, para las operaciones de copiar y pegar y para cambiar algunos
de 10s atributos de la fuente en uso.
Pretendemos centrarnos aqui en caracteristicas especificas de la ToolBar utili-
zadas por el ejemplo y visibles en la figura 6.5. Esta barra de herramientas tiene
botones, separadores e incluso un menu desplegable y dos cuadros combinados.

An inllotluclion lo the Ijasic fealules o f the RichBa~example. ilisc~rssetlin Chapter 6 of the


book "lvlaste~ingDelphi 7". W ~ i n e nanti copyrighted Ily Mmco 13ant11.

This document explains how do you create a simple editor based on the RichEdit control using Delphi
I
6 The program has a toolbar andmplements a number of features, mcluding a complete scheme for
opening and saving the text Ues, drscussed m this document. In fact, we want to be able to ask the
user to save any modified Ue before opening a new one, to avoid losmg any changes. Sounds k e a
professional apphcaboh doesn't it?

1 l ~ i l eOperations
The most complex part of this program is implemenhng the commands of the File pull-down menu-
New. Ope& Save, and Save As. In tach case. we need to track Mether the current Ue has changed,
C I A ,,l..:C:rLr. nr- AA..ld .,. *.I., ...,- .,..,
a- &- CI, -,-L i,- ,
,-,
- , 6,.
,i'
Figura 6.5. La barra de herramientas del ejemplo RichBar.
Los distintos botones implementan caracteristicas, una de las cuales consiste
en un esquema completo para abrir y guardar archivos de texto (se pide a1 usuario
que guarde cualquier archivo modificado antes de abrir uno nuevo, para no perder
ningun cambio). La parte del programa encargada de la administracion de archi-
vos es bastante compleja, per0 vale la pena explorarla, dado que muchas aplica-
ciones basadas en archivos utilizan un codigo similar.
Ademas de las operaciones de archivo, el programa soporta las operaciones de
copiar y pegar, y la administracion de fuentes. Para las operaciones de copiar y
pegar no es necesario una interaccion real con el portapapeles, dado que el com-
ponente puede controlarlas con ordenes sencillas como:

Es un poco mas avanzado conocer cuando deberian habilitarse estas operacio-


nes (y 10s botones correspondientes). Podemos activar 10s botones Copy y Cut
cuando se selecciona algo de texto, en el evento onselect ionchange del
control RichEdit:
p r o c e d u r e TFormRichNote.RichEditSelectionChange(Sender:
TObject) ;
begin
tbtnCut.Enabled : = R i c h E d i t - S e l L e n g t h > 0;
t b t n C o p y . E n a b l e d : = tbtnCut.Enabled;
end;

La operacion de copia, en cambio, no se puede decidir mediante una accion del


usuario, puesto que depende del contenido del portapapeles, que esta influido
tambien por otras aplicaciones. Un enfoque es utilizar un temporizador y verifi-
car el contenido del portapapeles de vez en cuando. Otra mejor consiste en utilizar
el evento OnIdle del objeto Application (o el componente ApplicationEvents).
Dado que el control RichEdit soporta diversos formatos de portapapeles, el codi-
go no puede fijarse simplemente en ellos, sino que deberia preguntar a1 propio
componente, usando una caracteristica de bajo nivel no exteriorizada por el con-
trol Delphi:
p r o c e d u r e TForrnRichNote.ApplicationEventslIdle(Sender: TObject;
v a r Done: B o o l e a n ) ;
begin
// a c t u a l i z a b o t o n e s d e la b a r r a d e h e r r a m i e n t a s
tbtnPaste.Enabled : = S e n d M e s s a g e (RichEdit.Handle,
em-CanPaste, 0, 0) <> 0;
end;

La administracion basica de la fuente se realiza mediante 10s botones Bold e


Italic, que poseen un codigo similar. El boton Bold alterna el atributo relativo del
texto seleccionado (o cambia el estilo de la posicion de edicion activa):
procedure TFormRichNote.BoldExecute(Sender: TObject);
begin
with RichEdit.SelAttributes do
i f fsBold i n Style then
Style : = Style - [fsBold]
else
Style : = Style + [fsBold];
end;

De nuevo, el estado actual del boton se establece mediante la seleccion activa,


por lo que habra que aiiadir la siguiente linea a1 metodo R i c h E d i t S e l e c -
tionchange:

Un menu y un cuadro combinado en una barra


de herramientas
Ademb de una serie de botones, el ejemplo RichBar posee un menu desplega-
ble y un par de cuadros combinados, una caracteristica compartida por muchas
aplicaciones habituales. El boton desplegable permite seleccionar el tamaiio de la
fuente, mientras que 10s cuadros combinados permiten seleccionar rapidamente la
familia y color de la fuente. Este segundo cuadro combinado se crea, en realidad,
utilizando un control ColorBox.
El boton Size esta conectado a un componente PopupMenu (llamado
S i zeMenu), empleando la propiedad DropdownMenu. Un usuario puede pul-
sar el boton, activar su evento o n c l i c k normalmente o seleccionar la flecha
desplegable, abrir el menu contextual (vease de nuevo la figura 6.5) y escoger una
de sus opciones. Este caso tiene tres tamaiios de fuente posibles, por la definition
del menu:
object SizeMenu: TPopupMenu
object Smalll: TMenuItem
Tag = 10
Caption = 'Small '
OnClick = SetFontSize
end
object Mediuml: TMenuItem
Tag = 16
Caption = 'Medi urn'
OnClick = SetFontSize
end
object Largel: TMenuItem
Tag = 32
Caption = 'Large '
OnClick = SetFontSize
end
end
Cada elemento del menu tiene un indicador del tamaiio real de la fuente, que se
activa mediante un controlador de eventos compartido:
p r o c e d u r e TFormRichNote.SetFontSize(Sender: TObject);
begin
RichEdit.SelAttributes.Size : = (Sender as TMenuItem) .Tag;
end;

Como el control ToolBar es un contenedor de control muy complete, podemos


coger directamente un cuadro de edicion, un cuadro combinado y otros controles
y colocarlos en la barra de herramientas. El cuadro combinado de la barra de
herramientas se inicia con el metodo Formcreate, que extrae las fuentes de
pantalla disponibles en el sistema:
ComboFont . Items : = Screen. Fonts;
ComboFont.ItemIndex : = ComboFont.Items.IndexOf (RichEdit.Font.Name)

El cuadro combinado muestra inicialmente el nombre de la fuente predetermi-


nada utilizada en el control RichEdit, configurada en tiempo de diseiio. Ese valor
se calcula de nuevo cada vez que la seleccion actual cambia, utilizando la fuente
del texto seleccionado, junto con el color actual para el ColorBox:
p r o c e d u r e TFormRichNote.RichEditSelectionChange(Sender:
TObject) ;
begin
ComboFont.ItemIndex : = ComboFont.Items.IndexOf
(RichEdit.Se1Attributes.Name);
ColorBoxl.Selected : = RichEdit.SelAttributes.Co1or;
end;

Cuando seleccionamos una nueva fuente del cuadro combinado, ocurre lo con-
trario. El texto del elemento en uso del cuadro combinado se asigna como nombre
de la fuente para cualquier texto seleccionado en el control RichEdit:

La seleccion de un color en el ColorBox activa un codigo similar

Una barra de estado simple


Crear una barra de estado es aun mas sencillo que crear una barra de herra-
mientas. Delphi incluye un componente StatusBar especifico, basado en el control
comun de Windows correspondiente (en VisualCLX hay tambien un control simi-
lar). Este componente se puede usar casi como un panel, cuando su propiedad
S i m p l e P a n e 1 vale T r u e . En este caso, podemos usar la propiedad
SimpleText para producir texto. Sin embargo, la verdadera ventaja de este
componente, esta en que nos permite definir un numero de subpaneles, activando
simplemente el editor de su propiedad Panels.(Tambien podemos mostrar este
editor de propiedad haciendo doble clic sobre el control de la barra de estado o
realizar las mismas operaciones mediante el Object Tree View.) Cada subpanel
posee sus propios atributos graficos. que podemos personalizar usando el Object
Inspector. Otra caracteristica del componente barra de estado es la zona de
"control del tamaiio", aiiadida en la esquina inferior derecha de la barra, que
rcsulta muy util para ajustar el tamaiio del propio formulario. Sc trata de un
elemento comun de la interfaz de usuario de Windows y que podemos controlar en
parte con la propiedad SizeGrip (se auto-inhabilita cuando el formulario no
resulta redimensionable).
Una barra de estado tiene varias funciones. La mas comun es mostrar informa-
cion sobre el elemento dcl menu que el usuario haya seleccionado. Ademas de
esto. una barra de estado normalmente muestra otra informacion sobre el estado
de un programa: la posicion del cursor en una aplicacion grafica, la linea de testo
actual en un procesador de textos, el estado de las teclas de bloqueo de mayuscu-
las y del teclado numkrico, la hora y la fecha, etc. Para mostrar informacion en un
panel, simplemente usamos su propiedad T e x t , por lo general utilizando una
expresion como:
StatusBarl. Panels [l] .Text := 'rnensaje';

En el ejemplo RichBar, hay una barra de estado con tres paneles: para suge-
rencias sobre ordenes: el estado de la tecla BloqMayiis y la posicion de edicion
activa. El componente StatusBar del ejemplo tiene en realidad cuatro paneles (es
necesario definir el cuarto para delimitar la zona del tercer panel). El ultimo panel
siempre es lo suficientemente grande como para cubrir la superficie que queda en
la barra de estado.

( NOTA: En el c6digo se puede ver que lsts sugermaias se Guestran en el I


primer panel de Ia barra de estado. Esto se podria haber sinapWoado en el
codigo mediante el aso de la propiedad AutoHint, pas mstrar un ckb-
go d s detallado permite personalizar este wmportami&ct,

Los paneles no son componentes independientes, por lo que no podemos acce-


der a ellos por su nombre, solo por posicion, como en el anterior fragment0 de
codigo. Una buena solucion para mejorar la facilidad de lectura de un programa
consiste en definir una constante para cada panel que queramos usar y, a conti-
nuacion, usar dichas constantes al hacer referencia a 10s paneles. Este es el codigo
de ejemplo:

En el primer panel de la barra de estado se va a mostrar el mensaje de sugeren-


cia del boton de la barra de herramientas. El programa consigue este efecto con-
trolando el evento 0 n H i n t de la aplicacion, utilizando el componente
A p p l i c a t ionEvent s y copiando el valor actual de la propiedad Hint de la
aplicacion a la barra de estado:
procedure TFormRichNote.ApplicationEventslHint (Sender: TObject);
begin
StatusBarl.Panels[sbpMessage].Text : = Application.Hint;
end;

Este codigo muestra de manera predefinida en la barra de estado el mismo


testo de las sugerencias contextuales, que no son generadas por 10s elementos de
mcnu. En realidad, podemos usar la propiedad H i n t para especificar cadenas
diferentes para 10s dos casos; escribiendo una cadena dividida cn dos partes me-
diante un separador, el caracter "tuberia" (().Por ejemplo, podriamos introducir
el siguiente como valor de la propiedad H i n t :
' N u e v o 1 C r e a r un n u e v o d o c u m e n t o '

La primera parte de la cadena, Nuevo, la usan las sugerencias contextuales y


la segunda parte, Crenr un nztevo documento, la barra de estado. La figura 6.6
muestra un ejemplo.

II AII i n t ~ o d ~ ~ ctot i the


o ~ ~11asic fe.itt~~es
book "Maste~itrgD e l p l ~7".
~ al p t e ~G nf the
of the RicllBar example. ~ l i s c ~ ~ sinr et h
i W l i n e n an11 copyriglrted I)y I r l a ~ c oL.11rti1.

l h s document explams how do you create a sunple ecttor based on the F x h a t control usmg Delph
6 The program has a toolbar and unplements a number of feahues, mcludmg a complete scheme for
opening and savmg the text files, hscussed m h s document In fact, we want to be able to ask the
user to save any modified file before opening a new one, to avold losmg any changes Sounds Wte a
profess~onalapphcahon doesn't ~ t ?

1 I~ile
Operations
The most complex p a t o f h s program 1s rnplemenhng the commands of the Fie pull-down menu-
*----. .-
New, Open Save, and Save As In each case, we need to track whether the current file has changed, ,
.LA CIA -1..

rCIA- Ihs
.c.* L A -

- s c k t m to the dpboard
.Z," -L-..IA .L*

7
*-..* .LA CIA

7-
h-- *L- A-a-*-.

s
Figura 6.6. La barra de estado del ejernplo RichBar muestra una descripcion mas
detallada que la sugerencia contextual

,. . .- . -

I
< . >

I TRUCO:Cuando la sugerencia de un mntrol estP Eompuesta dc dos cadL-


nas, podemos usar 10s rnktodos GetSho~tHinty GetLongHint par#
e x h e r la primera (corta) y la segunda Wga) subcad- a partir da
cadePa qge pasamos como pmbnetxo, qu+ sronnaltneate es.gl valor de la
brbpiedad Hint,
El segundo panel muestra el estado de la tecla BloqMayus, que se obtiene a1
llamar a la funcion G e t K e y s t ate de la API, que devuelve un numero de
estado.
Si se activa el bit menos significativo de dicho numero (si el numero es impar),
quiere decir que la tecla esta activada. Este estado se verifica cuando la aplica-
cion esta en espera, de forma que la comprobacion se realice cada vez que se
pulsa una tecla, per0 tambien desde el momento en que un mensaje alcanza la
ventana (en caso de que el usuario cambie esta configuracion mientras trabaja
con otro programa). Hemos aiiadido a1 controlador A p p 1i cat io n E v e n t s 1-
I d 1e una llamada a1 metodo personalizado C h e c k c aps 1o c k , implementado
del siguiente modo:
procedure TFormRichNote.CheckCapslock;
begin
if Odd (GetKeyState (VK-CAPITAL)) then
StatusBarl.Panels [sbpcaps].Text : = ' C A P S '
else
StatusBarl. Panels [sbpcaps].Text : = ' ';
end ;

Por ultimo, el programa usa el tercer panel para mostrar la posicion actual de
cursor (medida en lineas y caracteres por linea) cada vez que cambia la seleccion.
Debido a que 10s valores C a r e t P O S se basan en cero (es decir, la esquina supe-
rior derecha es la linea 0, caracter O), hemos decidido aiiadir uno a cada valor
para que resulten mas razonables para un usuario que desconozca este detalle:
procedure TFormRichNote.RichEditSelectionChange(Sender: TObject);
begin
...
// a c t u a l i z a l a p o s i c i o n e n l a b a r r a d e e s t a d o
Status~ar. Panels [sbpposition] .Text : = Format ( ' % d / % d l ,
[RichEdit .CaretPos .Y + 1, RichEdit .CaretPos.X + 11 ) ;
end;

Temas y estilos
En el pasado, un sistema operativo basado en una interfaz grafica determinaba
todos 10s elementos de la interfaz de usuario para 10s programas que se ejecuta-
ban sobre el.
~ltimamente,Linux ha comenzado a permitir que 10s usuarios personalicen la
apariencia tanto de la ventana principal de las aplicaciones como de 10s controles
de la interfaz de usuario como 10s botones. La misma idea (que suele referirse
como slnn, pie1 o tema) ha aparecido en numerosos programas con un impact0 tan
positivo que incluso Microsoft ha comenzado a integrar este concept0 (a1 princi-
pio en programas y despues en todo el sistema operativo).
Estilos CLX
Como ya se ha comentado. en Linux (para ser mas precisos en X Window) el
usuario generalmente puede escoger el estilo de la interfaz de usuario de 10s
controles. Este enfoque esta completamente soportado por Qt y por el sistema
KDE que se basa en el. Qt ofrece unos cuantos estilos basicos, como la apariencia
de Windows, el estilo Motif y otros. Un usuario tambien puede instalar nuevos
estilos en el sistema y ponerlos a disposicion de las aplicaciones.
- -
- --- -- - .- - - - - - --

I NOTA: Los estilos de 10s que hablaremos se refieren a la interfaz de usua- I


rio de 10s controles, no de 10s formularios y sus bordes. Nonnalmente esto
es configurable en 10s sistemas Linux, per0 tbcnicamente se trata de un
elemento separado de la interfaz de usuario.

Ya que esta tecnica se encuentra incrustada en Qt, tambien esta disponible en


la version para Windows de la biblioteca; CLX la pone a disposicion de 10s
desarrolladores en Delphi, de manera que una aplicacion puede tener una aparien-
cia de Motif en un sistema operativo de Microsoft. El objeto global A p p l i c a t ion
de la CLX tiene una propiedad s t y l e que se puede usar para establecer un estilo
pcrsonalizado o uno predefinido, indicado por la subpropiedad D e f a u l t S t y l e .
Por ejemplo, se puede seleccionar una apariencia de Motif mediante este codigo:

En el programa StylesDemo, entre varios controles de muestra, se ha incluido


un cuadro de lista con 10s nombres de 10s estilos predefinidos, tal y como se
indican en la enumeracion T D e f a u l t S t y l e y este codigo para su evento
OnDblClick:
procedure TForml.ListBoxlDblClick(Sender: T O b j e c t ) ;
begin
Application.Style.Defau1tStyle : = TDefaultStyle
(ListBoxl.Item1ndex);
end

El efecto es que a1 hacer doble clic sobre el cuadro de lista, se puede cambiar
el estilo actual de la aplicacion y comprobar inmediatamente su efecto en panta-
lla, como muestra la figura 6.7.

Temas de Windows XP
Con la aparicion de Windows XP, Microsoft ha creado una nueva version,
independiente, de la biblioteca de controles habituales. La antigua biblioteca si-
gue estando disponible por cuestiones de compatibilidad, de manera que un pro-
grama que se ejecute sobre XP puede escoger cual de las dos bibliotecas usar. La
principal difercncia de la nueva biblioteca es que no tiene un motor de representa-
cion fijo, sino que confia en cl motor de temas de XP y delcga la interfaz de
usuario de 10s controles sobre cl tema actual.

Figura 6.7. El prograrna StylesDernos, una aplicacion para Windows que tiene en
este momento un poco habitual aspect0 Motif.

En Delphi 7, la VCL soporta completamentc temas, debido a una gran canti-


dad de codigo intcrno y a la biblioteca de administracion de temas desarrollada
originalmente por Mike Lischke. Algunas de estas nuevas caracteristicas de re-
prescntacion son utilizadas por 10s controles visuales de la arquitectura Action
Manager. independientemente del sistema operativo sobre el que funcione. Sin
embargo, el soporte total de temas solo esta disponible para un sistema operativo
que disponga de esta caracteristicas (por el momento, Windows XP).
Incluso en XP, las aplicaciones de Delphi usan de manera predefinida el enfo-
que tradicional. Para soportar temas XP, se debc incluir un archivo de manifiesto
cn el programa. Se puede hacer de muchas maneras:
Colocar un archivo de manifiesto en la misma carpeta que la aplicacion. Se
trata de un archivo XML que indica la identidad y las dependencias del
programa. El archivo tiene el mismo nombre que el programa ejecutable
con una estension adicional .manifest a1 final (como MiPrograma.
exe .manifest). El listado 6.2 muestra un ejemplo de este tip0 de
archivo.
Afiadir la misma informacion en un archivo de recurso compilado dentro
de la apIicacion. Se debe escribir un archivo de recurso que incluya un
archivo de manifiesto. En Delphi 7, la VCL tiene un archivo de recurso
compilado WindowsXP .res, que se consigue a1 recompilar el archivo
WindowsXP . rc disponible entre 10s archivos fuente de la VCL. El
archivo de recurso incluye el archivo s a m p l e . manifest,que esta dis-
ponible en el mismo sitio.
Usar el componente XpManifest, que Borland ha aiiadido en Delphi 7
para simplificar aun mas estas tareas. Al dejar este componente aparente-
mente inutil sobre el formulario de un programa, Delphi incluira
automaticamente su unidad XPMan, que importa el archivo de recurso
VCL comentado anteriormente.

ADVERTENCIA: Cuando se elimina el componente XpMani fe s t de


una aplicacion, tambien se debe borrar la unidad XPMan de la sentencia
uses manualmente (Delohi no lo hace). Si no se hace esto. incluso sin el

hace pregunrarse por que aorlana creo el componenre en lugar ae propor-


cionar la unidad o el archivo de recurso relacionado). Ademb, este compo-
nente no esth en absoluto documentado.

Listado 6.2. Un archivo de rnanifiesto de rnuestra (Pages.exe.rnanifest).

<?xml version="l.O" encoding="UTP-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft~com:asm.vl"
manifestVersion="l.O">
<assemblyIdentity
version="l.O.O.O"
processorArchitecture="X86"
name="Pages.exe"
type="win32"
/>
<description>Demo de la biblia de Delphi</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X8 6 "
publicKeyToken="6595b64144ccfldf"
language=""
/>
</dependentAssernbly>
</dependency>
</assembly>

Como muestra, en la carpeta del ejemplo Pages comentado anteriormente se


incluye el archivo de manifiesto del listado 6.2. A1 ejecutarlo sobre Windows XP
con el tema estandar de XP, se conseguira un resultado similar a1 mostrado en la
figura 6.8. Se puede comparar con las figuras 6.1 y 6.2 que muestran el mismo
programa con el tema clasico de Windows XP

Pages Click on the listtaw


Tabs S~ze 10 change page
Tabs Text

[F]

Figura 6.8. El ejemplo Pages usa el tema de Windows XP actual, ya que incluye un
archivo de manifiesto.

El Componente ActionList
La arquitcctura de eventos de Delphi es mug abierta: se puede escribir un
scncillo controlador de eventos y conectarlo a 10s eventos O n C l i c k de un boton
de la barra dc hcrramientas y a un menil. Se puede incluso conectar el mismo
controlador de eventos a diferentes botones o elementos de menu, dado que el
controlador puede utilizar el parametro S e n d e r para referirse a1 objeto que
lanzo el evento. Es algo mas dificil sincronizar el estado de 10s botones de la barra
de herramientas y 10s elementos de menu. Si tenemos un elemento de menu y un
boton de la barra de herramientas y ambos accionan la misma operacion, cada vez
que se activa dicha operacion, hay que aiiadir la marca de comprobacion a1 ele-
mento de menu y cambiar el estado del boton para que aparezca como pulsado.
Para superar este problema, Delphi incluye una estructura de gestion de even-
tos basada en acciones. Una accion (u orden) indica tanto la operacion que se
realiza cuando se pulsa un elemento de menu o boton que determina el estado de
todos 10s elementos conectados a dicha accion. La conexion de la accion con la
interfaz de usuario de 10s controles enlazados resulta muy importante y es el
ambito en el que podemos entender las autenticas ventajas de esta estructura.
En esta estructura de manipulacion de eventos participan diversos agentes. La
accion principal la realizan 10s objetos de la accion. Un objeto de accion tiem un
nombre, como cualquier otro componente y otras propiedades que se aplicaran a
10s controles enlazados (llamados tambien clientes de la accion). Entre dichas
propiedades estan C a p t i o n , la representacion grafica ( I m a g e I n d e x ) , el esta-
do ( C h e c k e d , E n a b l e y V i s i b l e ) y la information para el usuario ( H i n t y
H e l p c o n t e x t ) . Tambien estan S h o r t c u t y una lista de S e c o n d a r y S h o r t -
C u t s , la propiedad A u t o c h e c k para acciones de dos estados, el soporte de
ayuda y una propiedad C a t e g o r y utilizada para organizar las acciones en gru-
pos logicos.
La clase basica para todos 10s objetos de accion es TBas i c A c t i o n , que
introduce el comportamiento abstract0 fundamental de una accion, sin ningun
enlace especifico ni correccion (ni siquiera a elementos de menu ni controles). La
clase derivada TC o n t a i n e dAc t i o n introduce propiedades y metodos que per-
miten que las acciones aparezcan en una lista de acciones o administrador de
acciones. La clase derivada TCus t omAc t i o n introduce soporte para las pro-
piedades y metodos de 10s elementos de menu y controles que estan enlazados a
10s objetos de accion. Por ultimo, esta la clase derivada lista para ser usada,
TAction.
Cada objeto de accion esta conectado a uno o mas objetos clientes a traves de
un objeto A c t i o n L i n k. Como indica su propiedad A c t i o n , posiblemente va-
rios controles de diferentes tipos pueden compartir el mismo objeto de accion.
Tecnicamente, 10s objetos A c t i o nL i n k mantienen una conexion bidireccional
entre el objeto cliente y la accion. El objeto ~ ci otn ~ i n k es necesario porque la
conexion funciona en ambas direcciones. Una operacion realizada sobre el objeto
(como un clic) se reenvia a1 objeto de accion y origina una llamada a su evento
O n E x e c u t e ; y una actualizacion del estado del objeto de accion se refleja en 10s
controles clientes conectados. En otras palabras, uno o mas controles cliente pue-
den crear un ActionLink, que se registra con el objeto de accion.
No se deberian definir las propiedades de 10s controles de cliente que se conec-
ten a una accion, ya que esta accion sobrescribe 10s valores de propiedad de 10s
control& de cliente. Por esa razon, normalmente se deberian escribir primer0 las
acciones y despues crear 10s elementos de menu y 10s botones que se quieran
conectar con ellas. Fijese en que cuando una accion no tiene un controlador
O n E x e c u t e , el control de cliente se desactiva automaticamente (o aparece en
gris), a menos que se haya definido la propiedad D i s a b l e I f N o H a n d l e r como
False.
Normalmente, 10s controles de cliente que se conectan a acciones son elemen-
tos de menu y diversos tipos de botones (botones pulsador, casillas de verifica-
cion, botones de radio, botones de velocidad, botones de la barra de herramientas
y similares), per0 tambien se pueden crear nuevos componentes que encajen en
esta estructura. Incluso se pueden definir nuevas acciones y nuevos objetos de
accion de enlace. Ademas de un control de cliente, algunas acciones pueden tener
tambien un componente destino. Algunas acciones predefinidas se conectan con
un componente destino especifico. Otras acciones buscan automaticamente un
componente destino en el formulario que soporte la accion especificada, empe-
zando por el control activo.
Por ultimo, 10s objetos de accion se encuentran dentro de un componente
A c t i o n L i s t o A c t i o n M a n a g e r , la unica clase de la estructura basica que
aparece en la Component Palette. La lista de acciones recibe las acciones
ejecutadas que no controlan 10s objetos de accion especificos y activa
O n E x e c u t e A c t i o n . Si la lista de acciones no controla la accion, Delphi hace
una llamada a1 evento O n E x e c u t e A c t i o n del o b j e t o A p p 1 i c a t i o n . El com-
ponente ActionList tiene un editor especial que se puede utilizar para crear diver-
sas acciones, como se muestra la figura 6.9.

gvaibbk Achm Classes AI I

Figura 6.9. El editor del cornponente ActionList, con una lista de acciones predefinidas
que se pueden w a r .

En el editor, las acciones aparccen en grupos, como indica su propiedad


C a t e g o r y . A1 definir esta propiedad con un valor nuevo, se le indica a1 editor
que introduzca una nueva categoria.
Estas categorias son basicamente grupos logicos, aunque en algunos casos un
grupo de acciones puede funcionar so10 con un tip0 especifico de componente de
destino. Se podria querer definir una categoria para cada menu desplegable o
agruparlos logicamente de otro modo.

Acciones predefinidas en Delphi


Con la lista de acciones y el editor ActionManager, se puede crear una accion
nueva o escoger una de las acciones ya existentes registradas en el sistema, lista-
das en un cuadro de dialog0 secundario, como se ha visto en la figura 6.9. Hay
muchas acciones predefinidas que pueden dividirse en grupos logicos:
Acciones de archivo: Como abrir, guardar corno, abrir con, e.jecutar, pre-
parar para impresion y salir.
Acciones de edicion: Reflejadas en el ejemplo siguiente. Son entre otras:
cortar, copiar, pegar, seleccionar todo, deshacer y borrar.
Acciones RichEdit: Complementan las acciones de edicion para 10s con-
troles RichEdit y son entre otras: negrita, cursiva, subrayado, resaltar,
viiietas y varias acciones de alineacion.
Acciones de ventana MDI: Son todas las operaciones MDI mas comunes:
organizar, cascada, cerrar, dividir (horizontal o verticalmente) y minimi-
zar todo.
Acciones de conjuntos de datos: Relacionadas con tablas de bases de
datos y con consultas. Todas las operaciones que se pueden realizar en un
conjunto de datos. Delphi 7 aiiade a las acciones de conjuntos de datos
basicas un grupo de acciones especificamente adaptadas a1 componente
Client DataSet,incluyendo: aplicar, invertir, deshacer.
Acciones de ayuda: Permiten activar la pagina de contenidos o el indice
del archivo de ayuda de la aplicacion.
Acciones de busqueda: Buscar, buscar primero, buscar siguiente y reem-
plazar.
Acciones de 10s controles solapa y pagina: El desplazamiento pagina
anterior y pagina siguiente.
Acciones de dialogo: Activan color, fuente, abrir, guardar e imprimir dia-
logos.
Acciones de lista: Borrar, copiar, mover, eliminar y seleccionar todo. Es-
tas acciones permiten interactuar con un control de lista. Otro grupo de
acciones, como la lista estatica, la lista virtual y algunas clases de soporte,
permiten definir listas que se pueden conectar a la interfaz de usuario.
Acciones Web: Explorar el URL, descargar el URL y enviar correo elec-
tronic~.
Acciones de herramientas: Solo incluyen el dialogo para personalizar las
barras de accion.
Ademas de manejar el evento OnExecute de la accion y cambiar el estado de
la accion para causar un efecto en la interfaz de usuario de 10s controles clientes,
una accion puede controlar tambien el evento Onupdate,que se activa cuando
la aplicacion no esta en uso.
Esto proporciona la oportunidad de verificar el estado de la aplicacion o del
sistema y cambiar la interfaz de usuario de 10s controles en funcion de ello. Por
ejemplo, la accion estandar PasteEdit activa 10s controles de cliente solo cuando
hay algun texto seleccionado en el portapapeles.
Las acciones en la practica
Ahora que se comprenden las ideas principales de esta caracteristica de Delphi
tan importante, para ver las acciones en la practica estudiaremos el programa
Actions, en el que hemos colocado un nuevo componente ActionList en el
formulario y aiiadido tres acciones de edicion estandar y algunas personalizadas.
El formulario tiene tambien un panel con algunos botones de velocidad, un menu
principal y un control de memo (el objetivo automatic0 de las acciones de edi-
cion). En el listado 6.3 aparecen las acciones, extraidas del archivo DFM.

Listado 6.3. Las acciones del ejemplo Actions.

object ActionListl: TActionList


Images = ImageListl
object Actioncopy: TEditCopy
Category = ' E d i t '
Caption = ' & C o p y 1
Shortcut = <Ctrl+C>
end
object Actioncut: TEditCut
Category = ' E d i t '
Caption = ' C u & t t
Shortcut = <Ctrl+X>
end
object Actionpaste: TEditPaste
Category = ' E d i t '
Caption = ' & P a s t e 1
Shortcut = <Ctrl+V>
end
object ActionNew: TAction
Category = ' P i l e '
Caption = ' & N e w 1
Shortcut = <Ctrl+N>
OnExecute = ActionNewExecute
end
object ActionExit: TAction
Category = ' P i l e '
Caption = ' E & x i t l
Shortcut = <Alt+F4>
OnExecute = ActionExitExecute
end
object NoAction: TAction
Category = ' T e s t '
Caption = ' & N o A c t i o n '
end
object Actioncount: TAction
Category = ' T e s t '
Caption = ' & C o u n t C h a r s '
OnExecute = ActionCountExecute
OnUpdate = Actioncountupdate
end
o b j e c t ActionBold: TAction
Category = ' E d i t '
Caption = ' & B o l d 1
Shortcut = <Ctrl+B>
OnExecute = ActionBoldExecute
end
o b j e c t ActionEnable: TAction
Category = ' Test'
Caption = ' & E n a b l e N o A c t i o n '
OnExecute = ActionEnableExecute
end
o b j e c t ActionSender: TAction
Category = ' T e s t '
Caption = ' T e s t & S e n d e r 1
OnExecute = ActionSenderExecute
end
end

-
, - -
NOTA: Las teclas de mktodo abreviado e s t h almacenadas en 10s archivos
DFM usando numeros de teclas virtuales, entre 10s que hay valores para las
teclas Control y Alt. En este y otros listados a lo largo del libro, se han
reemplazado 10s numeros por 10s valores literales, que se insertan entre 10s
simbolos < y >.
-

Todas estas acciones estan conectadas a 10s elementos de un componente


MainMenu y algunas de ellas tambitn a 10s botones de un control T o o l B a r .
Como muestra la figura 6.10, las imagenes seleccionadas en el control ActionList
afectan solamente a las acciones del editor. Para que las imagenes del lmageList
aparezcan en 10s elementos del menu y en 10s botones de la barra de herramientas,
hay que seleccionar tambien la lista de imagenes en 10s componentes MainMenu
y ToolBar

1 Calmofies: Actions:

Figura 6.10. El editor ActionList del ejemplo Actions.

BQ
Las tres acciones predeterminadas del menu Edit no tienen controladores aso-
ciados, per0 estos objetos especiales tienen un codigo interno para realizar la
accion relacionada con el control de edicion o de memo activo. Estas acciones se
activan y desactivan tambien a si mismas, dependiendo del contenido del
portapapeles y de la existencia de texto seleccionado en el control de edicion
activo. La mayoria de las otras acciones tienen un codigo personalizado, menos
en el caso del objeto NoAction. A1 no tener codigo, el elemento de menu y el
boton asociado a esta orden estan desactivados, aunque la propiedad Enabled
de esta accion esta definida como True.
Hemos afiadido a1 ejemplo y a1 menu Test otra accion que activa el elemento
de menu conectado a1 objeto NoAct ion:
procedure TForml.ActionEnableExecute(Sender: TObject);
begin
NoAction.Disable1fNoHandler : = False;
NoAction.Enabled : = True;
ActionEnable.Enabled : = False;
end ;

Definir Enabled como True,producira el resultado durante un corto perio-


do de tiempo, a menos que se defina la propiedad DisableIfNoHandler,
como se ha visto en el apartado anterior. Tras haber realizado esta operacion, hay
que desactivar la accion en uso, porque no es necesario dar de nuevo la misma
orden. Esta situacion es distinta a la que se produce cuando activamos una ac-
cion, como el elemento del menu Edit>Bold y su correspondiente boton de veloci-
dad. A continuacion, vemos el codigo para la accion Bold (que tiene su propiedad
Aut oChec k fijada como True,para que no resulte necesario modificar el esta-
do de la propiedad Checked en el codigo):
procedure TForml.ActionBoldExecute(Sender: TObject);
begin
with Memo1 . Font do
i f fsBold i n Style then
Style := Style - [fsBold]
else
Style := Style + [fsBold] ;
end ;

El objeto Actioncount tiene un codigo muy sencillo, per0 muestra el fun-


cionamiento de un controlador Onupdate. Cuando el control de memo esta
vacio, se desactiva automaticamente. Se podria haber conseguido el mismo resul-
tad0 controlando el evento OnChange del control de memo, per0 normalmente
no es posible ni facil determinar el estado de un control controlando simplemente
uno de sus eventos. A continuacion, aparece el codigo de 10s dos controladores de
esta accion:
procedure TForml.ActionCountExecute(Sender: TObject);
begin
ShowMessage ( ' C h a r a c t e r s : ' + IntToStr (Length (Memol.Text)) ) ;
end;

procedure TForml.ActionCountUpdate(Sender: TObject) ;


begin
ActionCount.Enabled : = Memol.Text <> " ;
end;

Por i~ltimo,hemos aiiadido una accion especial que comprueba el objeto remi-
tente del controlador de eventos de la accion y obtiene otra informacion sobre el
sistema. Ademis de mostrar la clase y nombre del objeto, hemos aiiadido un
codigo que accede a1 objeto de la lista de acciones, bisicamente para mostrar
como acceder a esta informacion:
procedure TForml.ActionSenderExecute(Sender: TObject);
begin
Memol .Lines .Add ( ' C l a s e r e m i t e n t e : ' + Sender .ClassName);
Memol.Lines.Add ( ' N o m b r e d e l r e m i t e n t e : ' + (Sender as
TComponent) .Name) ;
Memol. Lines .Add ( ' C a t e g o r i a : ' + (Sender as TAction) .Category) ;
Memol.Lines.Add ('Action l i s t n a m e : ' + (Sender as
TAction) .ActionList.Name);
end;

Se puede ver el resultado de este codigo en la figura 6.1 1, junto con la interfaz
dc usuario del ejemplo. Observe que el S e n d e r no es el elemento de menu selec-
cionado, aunqbe el controlador esta conectado a el. El objeto S e n d e r que activa
el evcnto es la accion que intercepta la operacion de usuario.

I Fle Edt Test

Figura 6.11. El ejemplo Actions, con una descripcion detallada del Sender del evento
OnExecute de un objeto de accion.
Por ultimo. hay que tcner presente que tambien se pueden escribir controladorcs
para evcntos del propio objeto ActionList. que jueguen el papel de controladores
globales para todas las acciones de la lista y para el objeto global Appl i c a t i o n ;
que se dispara para todas las acciones de la aplicacion. Antes de invocar a1 evento
O n E x e c u t e de la accion, Delphi activa el evento O n E x e c u t e de la A c t i o n -
L i s t y el evento OnAct i o n E v e n t del objeto global A p p l i c a t ion.Estos
eventos se fiaran en la accion, ejecutando eventualmente algo de codigo compar-
tido, y despues detendran la ejecucion (mediante el parametro H a n d l e d ) o deja-
ran que se propague hasta cl siguiente nivel.
Si no se asigna ningun controlador de eventos para responder a la accion, ni en
la lista de acciones, ni la aplicacion, ni en el ambito accion, la aplicacion trata de
identificar un objetivo a1 quc se pueda aplicar dicha accion.
- - - - - - - - -- .-- . - -

NOTA: Cuando se ejecuta una accion, esta busca un control como destino
de la accion, fijhdose en el control activo, el formulario activo y en otros
controles del formulario. Por ejemplo, las acciones de edicion se refieren a1
control activo en cada momento (si hereda de T C u s tomEdi t ) y 10s con-
troles de conjuntos de datos buscan el conjunto de datos conectado con la
fuente de datos del control data-aware que tiene el foco de entrada. Otras
acciones seguirin distintos enfoques para encontrar un componente desti-
no, pero la idea general es compartida por la mayoria de las acciones
esthdar.

La barra de herramientas y la lista de acciones


de un editor
En un ejemplo anterior (RichBar) se demostro el desarrollo de un editor con
una barra de herramientas y una barra de estado. Tambien podria haberse aiiadi-
do una barra de menu a1 formulario, pero a1 hacerlo hubieramos creado unos
cuantos problemas de sincronizacion del estado de 10s botones de la barra de
herramientas con 10s elementos del menu. Una solucion adecuada a este problema
es usar acciones, como en el ejemplo MdEditl que vamos a comentar.
La aplicacion se basa en un componente ActionList, que incluye acciones para
el manejo de archivos y soporte de portapapeles, con un codigo similar a1 de la
version RichBar. La seleccion del tip0 dc fuente y de color se basa en cuadros
combinados, por lo que no incumbe a acciones (lo mismo que en el caso del menu
desplegable del boton Size). Sin embargo, el menu tiene unas cuantas ordenes
adicionales, como una para el recuento de caracteres y otra para cambiar el color
de fondo. sta as se basan en acciones y lo mismo sucede con 10s tres botones (y
ordenes de menu) nuevos de justificacion de parrafo. Una de las diferencias clave
en esta nueva version es que el codigo nunca se refiere a1 estado de 10s botones de
la barra de herramientas, sin0 que modifica el estado de las acciones. El metodo
RichEdi t Se lec t ionchange no actualiza el estado del boton de negrita
(Bold), que esta conectado a una accion con el siguiente controlador OnUpdate:
p r o c e d u r e TFormRichNote.acBoldUpdate(Sender: T O b j e c t ) ;
begin
acBold.Checked : = fsBold i n RichEdit.SelAttributes.Sty1e;
end;

Para la mayoria de las acciones existen otros controladores de eventos


OnUpdate similares, como por ejemplo para operaciones de recuento (disponi-
ble solo si hay algun texto en el control RichEdit), la operacion save (disponible
si el texto ha sido modificado) y las operaciones Cut y Paste (solo disponibles
si hay texto seleccionado):
p r o c e d u r e TFormRichNote.acCountcharsUpdate(Sender: TObject);
begin
acCountChars.Enab1ed : = RichEdit.GetTextLen > 0;
end;

p r o c e d u r e TFormRichNote.acSaveUpdate(Sender: TObject);
begin
acSave.Enabled : = Modified;
end;

p r o c e d u r e ~ ~ o r m ~ i c h ~ o t e . a c C u t U p d a t e ( S e n d eTObject);
r:
begin
acCut.Enabled : = RichEdit.SelLength > 0;
acCopy.Enabled : = acCut.Enabled;
end :

En el ejemplo antiguo, el estado del boton Paste se actualizaba en el evento


OnIdle del objeto Application. Ahora que se utilizan acciones, se puede
convertir en otro controlador OnUpdate mas:
p r o c e d u r e TFormRichNote.acPasteUpdate(Sender: TObject);
begin
a c P a s t e - E n a b l e d : = S e n m e s s a g e (RichEdit.Handle,
em-CanPaste, 0, 0 ) <> 0;
end ;

Los tres botones de la barra de herramientas para la justificacion de parrafos y


10s elementos de menu asociados deberian funcionar como botones de radio, sien-
do mutuamente exclusivos en cada una de las tres opciones seleccionadas. Por
ello, las acciones tienen un GroupIndex definido como 1,los correspondientes
elementos de menu tienen la propiedad Radio1 tern definida como True y 10s
tres botones de la barra de herramientas tienen su propiedad Grouped definida
como T r u e y la propiedad A l l o w A l l U p como False. (Ademas estan
visualmente encerrados entre dos separadores). Esto es necesario para que el
programa defina la propiedad Checked de la accion correspondiente con el
estilo actual, lo cual evita que no se elimine la marca de las otras dos acciones
directamente.
Este codigo es parte del evento OnUpdate de la lista de accion, ya que se
aplica a multiples acciones:
procedure TFormRichNote.ActionListUpdate(Action: TBasicAction;
v a r Handled: Boolean);
begin
// v e r i f i c a l a a l i n e a c i o n d e l p d r r a f o c o r r e s p o n d i e n t e
c a s e RichEdit.Paragraph.Alignment o f
taLeftJustify: acLeftAligned.Checked : = True;
taRightJustify: acRightAligned.Checked : = True;
tacenter: acCentered.Checked : = True;
end;
// v e r i f i c a e l e s t a d o d e l a t e c l a BloqMayus
Checkcapslock;
end ;

Cuando se selecciona uno de estos botones, el controlador compartido utiliza


el valor de Tag, definido como el valor correspondiente de la enumeracion
TAl ignme nt , para determinar la justification correcta:
procedure TFormRichNote.ChangeAlignment(Sender: TObject);
begin
RichEdit.Paragraph.Alignment : = TAlignment ((Sender a s
T A c t i o n ) .Tag) ;
end;

Los contenedores de barra de herramientas


Muchas de las aplicaciones modernas tienen varias barras de herramientas
alojadas normalmente en un contenedor especifico. Microsoft Internet Explorer,
algunas aplicaciones empresariales estandar y el IDE de Delphi usan esta tecnica.
Sin embargo, cada uno de ellos la ha implementado de forma diferente. Delphi
tiene dos contenedores listos para usar:
El componente CoolBar: Es un control comun de Win32 introducido por
Internet Explorer y usado por algunas aplicaciones de Microsoft.
El componente ControlBar: Esta totalmente basado en la VCL, sin de-
pendencias de bibliotecas externas.
Ambos componentes pueden almacenar controles de barra de herramientas asi
como algunos elementos adicionales, como cuadros combinados y otros contro-
les. En realidad, una barra de herramientas puede reemplazar tambien a1 menu de
una aplicacion.
Ya que el componente CoolBar no se suele usar en las aplicaciones Delphi,
hablaremos brevemente de el a continuacion.
- .-- .

Una bonita barra de herramientas


El componente CoolBar de Win32 es. basicamente. un coniunto .- de obietos
T C c o l B a n d que se pueden activar a1 usar el editor de la propiedad Band.
disponible tambien en el menu del editor del componente o mediante la
. C . .
Object Tree View. Se puede personalizar el componente CoolBar de
* ..
4 .
mucnas rormas: se pueae esramecer un mapa ae bits para el ronao, anaair
.,. . r ..
algunas bandas a la coleccion B a n d s y asignarles despues a cada una un
componente existente o un contenedor de componentes a cada banda. Se
puede usar cualquier control basado en una ventana (no controles graficos)
per0 solo algunos dc ellos se mostrarhn del mono apropiado. Si sc quiere
tonor . a n m n n g AD h;tn onmn f n n A n AD I n n r r n m n n n n n t m
. r u r a u u r r l u p u uu u l c a r w l r l w r w m u w ur u u u w l l r p w u r l l r u
Pnn 1R
LL' uL
3 r nnr
L ~ ~ pL1 ,1
D ; D ~ ~ ] ~ .
rjr111

1lay que utilizar controlcs parcialmentt: transparentcs. El componentc ti1pic0


1~tilizadoen una CoolBar es el ToolBar per0 10s cuadros combinados. (:ua-
a,,, A., -A:,:-, .. 1.,-, A, ,
a:
-,
,.
-,
. , .,,t:..L , ,, t,,,,,*, ",.,
0 1 ~ U5C GUIGIUII y GUIILIUIG3 U C i l l l l l l l i l G I U 1 1 L;d1IIUICII 5 U I I USISLillllC GUIIIUIICS.

Se puede colocar una banda en cada linea o todas ellas en la misma. Cada
una utilizara una parte de la superficie disponible y aumentara de tamaiio
automaticamente cuando el usuario pinche sobre su titulo. Resulta mas
facil utilizar este componente que explicarlo. Se puede probar con el ejem-
plo CoolDemo:

his IS the text of the label, very bare if you compare ii

El formulario del ejemplo CoolDemo tiene un componente Tco o 1Bar con


cuatro bandas, dos por cada una de las dos lineas. La primera banda incluye
un subconjunto de la barra de herramientas del ejemplo anterior, akdiendo
ahora una ImageList para las imageries resaltadas. La segunda tiene un cua-
dro de edicion utilizado para establecer la fuente del texto; la tercera tiene un
componente C o l o r G r i d , usado para escoger el color de la fuente y el de
fondo. La ultima banda tiene un control ComboBox con las fuentes disponi-
es. La interfaz de usuario del componente ~ o o l w
Microsoft la utiliza en sus aplicaciones, pero alternativas como el compo-
nente C o n t r o l B a r ofrecen una interfaz de usuario similar sin ningtin
tip0 de problema ahdido. El control CoolBar de Windows ha tenido mu-
chas versiones distintas e incompatibles, ya que Microsoft ha hecho publi-
--" -
a"-
--" -- -----------dp
.--"-----"
r n c rlictintsrc v ~ r e i n n ~dp -- rnntrnlpr
-------" r-----"
c 1% h i h l i n t p r n --- diqtintac
n m i r n t w mn -"w-

versiones de hternet Explorer. AIgunas de estas versiones "estropean" 10s


programas existentes creados con Delphi, lo c u a es una b u m razon para
no usarlo ahora, induso aunque sea mis estable.

ControlBar
La barra de control (ControlBar) es un contenedor de controles y se crea sim-
plemente colocando otros controles dentro de ella, como si lo hicieramos en un
panel (en ella no hay lista de B a n d s ) . Cada control colocado en la barra consigue
su propia zona de arrastre o agarradera (un pequeiio panel con dos lineas vertica-
les, a la izquierda del control), incluso un boton solitario:

Por ello. generalmente, se deberia evitar colocar botones especificos dentro del
ControlBar, en su lugar se deberian colocar contenedores en 10s que se incluyan
botones. En lugar de un panel, como norma general, se deberia usar un control
ToolBar para cada seccion del ControlBar.
El ejemplo MdEdit2 es otra version de la prueba creada en este capitulo. Basi-
camente, se han agrupado 10s botones en tres barras de herramientas (en lugar de en
una) y dejado 10s dos cuadros combinados como controles independientes. Todos
estos componentes estan dentro del componente C o n t r o l B a r , para que el usua-
rio 10s pueda organizar en tiempo de ejecucion, como muestra la figura 6.12.
El siguiente fragment0 de listado DFM del ejemplo MdEdit2 muestra la forma
en que se incluyen varias barras de herramientas y controles en un componente
ControlBar:
object ControlBarl: TControlBar
A l i g n = alTop
AutoSize = True
ShowHint = True
PopupMenu = BarMenu
object ToolBarFile: TToolBar
Flat = True
Images = Images
Wrapable = False
object ToolButtonl: TToolButton
Action = acNew
end
/ / mds botones. . .
end
object ToolBarEdit: TToolBar . . .
object ToolBarFont: TToolBar . . .
object ToolBarMenu: TToolBar
AutoSize = True
Flat = True
Menu = MainMenu
end
object ComboFont : TComboBox
Hint = ' F a m i l y fonts'
Style = csDropDownList
OnClick = ComboFontClick
end
object ColorBoxl: TColorBox.. .
end

Figura 6.12. El ejemplo MdEdit2 en tiempo de ejecucion, mientras que un usuario


reordena las barras de herramientas.

Para conseguir el efecto estandar, hay que desactivar 10s bordes de 10s contro-
les de la barra de herramientas y definir su estilo como plano. Ajustar el tamaiio
de 10s controles del mismo modo, para poder obtener una o dos filas de elementos
con la misma altura, no es tan facil como parece. Algunos controles tienen ajuste
de tamaiio automatico o diversas restricciones. Concretamente, para que el cua-
dro combinado tenga la misma altura que la barra de herramientas, hay que ajus-
tar el tip0 y tamaiio de su fuente. Reajustar el tamaiio del control no tiene ningun
efecto.
La barra de control tiene tambien un menu de metodo abreviado que permite
mostrar u ocultar cada uno de 10s controles que contiene. En lugar de escribir un
codigo especifico para este ejemplo, hemos implementado una solucion mas gene-
rica (y reutilizable). El menu de metodo abreviado, llamado BarMenu, esta vacio
en tiempo de diseiio y se llena cuando arranca el programa:
procedure TFormRichNote.FormCreate(Sender: TObject);
var
I: Integer;
mItem: TMenuItem;
begin
...
// l l e n a e l m e n u d e l a b a r r a d e c o n t r o l
for I : = 0 to ControlBar .Controlcount - 1 do
begin
mItem : = TMenuItem-Create (Self);
mItem.Caption : = ControlBar.Controls [I].Name;
mItem.Tag : = Integer (ControlBar.Controls [I]) ;
mItem-OnClick : = BarMenuClick;
BarMenu.Items.Add (mItem);
end;

El procedimiento BarMenuCl i c k es un controlador de eventos sencillo, uti-


lizado por todos 10s elementos de menu. Usa la propiedad T a g del elemento de
menu Sender para referirse a1 elemento de la barra de control asociado a1 ele-
mento en el metodo F o r m c r e a t e :
procedure TFormRichNote.BarMenuClick(Sender: TObject);
var
aCtrl: TControl;
begin
aCtrl : = TControl ( (Sender as TComponent) .Tag);
aCtrl-Visible : = not aCtrl.Visible;
end;
Por ultimo, el evento OnPopup del menu se usa para refrescar la marca de
verificacion de 10s elementos del menu:
procedure TFormRichNote.BarMenuPopup(Sender: TObject);
var
I: Integer;
begin
// a c t u a l i z a l a s r n a r c a s d e v e r i f i c a c i o n d e l m e n u
for I : = 0 to BarMenu.Items .Count - 1 do
BarMenu. Items [I].Checked : = TControl (BarMenu.Items
[I] .Tag) .Visible;
end ;
Un menu en una barra de control
Si miramos la interfaz de usuario de la aplicacion MdEdit2 en la figura 6.12,
veremos que el menu del formulario en realidad aparece dentro de una barra de
herramientas, que, a su vez, esta dentro de la barra de control y bajo el titulo de la
aplicacion. Todo lo que hay que hacer es fijar la propiedad Menu de la barra de
herramientas. Tambien hay que eliminar el menu principal de la propiedad Menu
del formulario (manteniendo el componente MainMenu en el formulario), para
no tener dos menus.

Soporte de anclaje en Delphi


Otra caracteristica disponible en Delphi es el soporte para barras de herra-
mientas y controles que se pueden anclar. Es decir, se puede crear una barra de
herramientas y llevarla hacia cualquier lado de un formulario o moverla libremen-
te por la pantalla, sin anclarla. Sin embargo, configurar un prograrna adecuada-
mente para obtener este efecto no resulta tan facil como suena.
En primer lugar, el soporte de anclaje de Delphi esta conectado a controles de
contenedores, no a formularios. Se puede definir como destino del anclaje un
panel, una barra de control y otros contenedores (tecnicamente, cualquier control
derivado de TW i n C o n t r o 1 ) activando su propiedad Doc k S i t e . Tambien se
puede definir la propiedad A u t o S i z e de dichos contenedores para que aparez-
can solamente si contienen un control.
Para poder arrastrar un control (un objeto de cualquier clase derivada de
TCon t r o 1 ) hacia el lugar de anclaje, simplemente hay que definir la propiedad
D r a g K i n d como dkDock y su propiedad DragMode como d m A u t o m a t i c .
De esta forma, se puede arrastrar el control desde su posicion actual a un nuevo
contenedor de anclaje. Para desanclar un componente y llevarlo a un formulario
especial, se puede definir su propiedad F l o a t i ngDoc k S i t e C l a s s como
TCustomDoc kForm (para utilizar un formulario independiente predefinido con
un pequeiio titulo).
Se puede realizar un seguimiento de todas las operaciones de anclaje y desanclaje
utilizando eventos especiales del componente arrastrado ( o n S t a r t D o c k y
OnEndDoc k) y del componente que recibira el control anclado (OnDragOver
y OnDrag Drop). Estos eventos de anclaje son muy similares a 10s eventos de
arrastre en anteriores versiones de Delphi.
Tambien hay ordenes para realizar operaciones de anclaje mediante codigo y
explorar el estado del contenedor de anclaje. Se puede mover cada control a una
posicion diferente usando 10s metodos Dock, ManualDock y M a n u a l F l o a t .
Un contenedor tiene una propiedad Doc k c 1 i e n t Coun t , que indica el numero
de controles anclados, y otra Doc k c 1i e n t s , que contiene la matriz de dichos
controles.
Ademas, si el contenedor de anclaje tiene la propiedad UseDockManager
definida como T r u e , se puede utilizar la propiedad Doc k M a n a g e r , que
implementa la interfaz IDockManager. Esta interfaz tiene muchas finciones
para personalizar el comportamiento de un contenedor de anclaje, como el soporte
para streaming de su estado.
Como se puede ver en esta pequeiia descripcion, el soporte de anclaje en Delphi
se basa en un extenso numero de propiedades, eventos y metodos. El siguiente
ejemplo introduce las principales caracteristicas que necesitaremos normalmente.

Anclaje de barras de herramientas en barras


de control
Hemos incluido soporte de anclaje en el ejemplo MdEdit2. El programa tiene
una segunda barra de control en la parte inferior del formulario, que acepta el
arrastre de una de las barras de herramientas de la barra de control situada en la
parte superior. Como ambos contenedores de barra de herramientas tienen la
propiedad A u t o s i z e definida como T r u e , si no contienen ningun control se
eliminan automaticamente. Tambien hemos definido como T r u e la propiedad
A u t o D r a g y Aut oDoc k de ambas barras de control.
Hemos colocado la barra de control inferior dentro de un panel, junto con el
control RichEdit. Sin este truco, la barra de control seguiria moviendose por
debajo de la barra de estado cuando se activara y ajustara su tamaiio
automaticamente, lo que no supone un comportamiento correcto. En el ejemplo, la
barra de control es el unico panel alineado con la parte inferior, asi que no existe
ninguna confusion posible.
Para permitir a 10s usuarios arrastrar las barras de herramientas fiera de su
contenedor original, todo lo que hay que hacer es definir, una vez mas, su propie-
dad DragKind como dkDoc k y su propiedad DragMode como drnAutomat i c .
Las dos unicas excepciones son la barra de herramientas del menu, que se ha
mantenido cerca de una posicion tipica para una barra de menu, y el control
ColorBox, ya que, a diferencia del cuadro combinado, este componente no mues-
tra las propiedades DragMode y D r a g K i n d . (En el metodo F o r m c r e a t e del
ejemplo, se puede encontrar codigo encargado de activar el anclaje del componen-
te, basado en el truco de la palabra clave p r o t e c t e d ya comentado con anterio-
ridad.) El cuadro combinado de fientes se puede arrastrar, per0 no se va a permitir
que el usuario lo ancle en la barra de control inferior. Para implementar esta
restriccion, hemos usado el controlador de eventos OnDockOver de la barra de
control, que acepta la operacion de anclaje solo para barras de herramientas:
procedure TFormRichNote.ControlBarLowerDockOver(Sender:
TObject;
Source: TDragDockObject; X, Y: Integer; State: TDragState;
v a r Accept: Boolean) ;
begin
Accept : = Source.Contro1 is TToolbar;
end;
-
-TENCIA: h a s t a r directamente una ba<a de h e n a m i e n t a s m
, de la barra de c o n t d superior a la inferior no fimciona. La barra de control
no ajusta su tamaiio @ra dojar la barra de herramientas durante la opera-
ci6n de arra&re, corn hace si se arrastra la barra de herramientas a una
posicion flotantd y d&ub a la barra de conttol inferior. Se trata de un
fa110 en la VCL,,y &I muy dificil encontrar un rodeo.Como se vera en el
ejemplo MdEdit3. sepuede conseguir el efecto correct0 con un codigo dis-
tinto de soporte a la Vm.

Cuando se saca una de las barras de herramientas del contenedor, Delphi crea
automaticamente un formulario flotante. Podriamos sentirnos tentados a recupe-
rarla cerrando el formulario flotante. Pero, eso no funciona, porque el formulario
flotante se elimina junto con la barra de herramientas que contiene. Sin embargo,
sc puede utilizar el menu de metodo abreviado de la barra de control superior,
unido tambitn a la otra barra de control, para mostrar esta barra de herramientas
oculta.
El formulario flotante creado por Delphi para albergar controles no anclados
tienc un titulo muy pequeiio, llamado "titulo de barra de herramientas", que por
defect0 no tiene ningun testo. Por ello, hemos aiiadido algo de codigo a1 evento
OnEndDock de cada control anclable para fijar el titulo del formulario recien
creado en que se ancla el control.
Para evitar una estructura de datos personalizada para esta informacion, he-
mos usado el texto de la propiedad Hint de estos controles (que basicamente no
se utiliza) para proporcionar un titulo aceptable:
procedure TFormRichNote.EndDock(Sender, Target: TObject; X, Y:
Integer) ;
begin
i f Target i s TCustomForm then
TCustomForm(Target) .Caption : = GetShortHint ( (Sender as
TCont rol) .Hint) ;
end;

Se puede ver el resultado del ejemplo MdEdit2 en la figura 6.13. Otra posible
ampliacion de este ejemplo podria ser aiiadir zonas de anclaje en ambos laterales
del formulario. El unico esfuerzo adicional necesario seria una rutina para orien-
tar las barras de herramientas en vertical en lugar de en horizontal. Hacer esto
requiere conmutar las propiedades L e f t y Top de cada boton despues de inhabi-
litar el dimensionamiento automatico.
Control de las operaciones de anclaje
Delphi ofrece muchos eventos y metodos para controlar las operaciones de
anclaje, entre ellas un administrador de anclaje. El ejemplo DockTest es una
prueba para operaciones de anclaje, y se muestra en la figura 6.14.
Figura 6.13. El ejemplo MdEdit2 permite anclar las barras de herramientas (pero no el
menu) en la parte superior o inferior del formulario, o hacerlas flotar.

Figura 6.14. El ejemplo DockTest con tres controles anclados en el formulario principal.

El programa controla 10s eventos O n D o c k O v e r y O n D o c k D r o p de un panel


de anclaje anfitrion para mostrar mensajes a1 usuario, como el numero de contro-
les anclados en ese momento.
procedure TForml.PanellDockDrop(Sender: TObject; Source:
TDragDockObject;
X, Y: Integer);
begin
Caption : = 'Docked: ' + IntToStr (Panell.DockC1ientCount);
end;

De la misma forma, el programa controla tambien 10s eventos de anclaje del


formulario principal. Los controles tienen un menu de metodo abreviado a1 que se
puede recurrir para realizar operaciones de anclaje y desanclaje, sin necesidad del
arrastre con el raton, con un codigo como este:
procedure TForml.menuFloatPanelClick (Sender: TObject);
begin
Panel2 .ManualFloat (Rect (100, 100, 200, 300) ) ;
end ;

procedure TForml.FloatinglClick(Sender: TObject);


var
aCtrl: TControl;
begin
aCtrol : = Sender as TControl;
/ / conmuta a estado flotante
if aCtrl. Floating then
aCtrl .ManualDock (Panell, nil, alBottom) ;
else
aCtrl.ManualFloat (Rect (100, 100, 200, 300));
end :

Para hacer que el programa se ejecute de manera correcta en el arranque,


deberian anclarse 10s controles a1 panel principal en el codigo inicial; de no ser asi
se observaria un efecto algo extraiio. Aunque resulte raro, para que el programa
se comporte de manera adecuada, se necesita aiiadir controles a1 administrador de
anclaje y anclarlos tambien a1 panel (una operacion no activa automaticamente la
otra):
// anclar memo
Memol.Dock (Panell, Rect (0, 0, 100, 100) ) ;
Panell.DockManager.InsertControl(Memol, alTop, Panell);
/ / anclar cuadro de lista
ListBoxl-Dock(Panell, Rect (0, 100, 100, 100) ) ;
Panell.DockManager.Inse~:tContr01(ListBoxl, alleft, Panell);
// anclar panel2
Panel2Dock (Panell, Rect (100, 0, 100, 100) ) ;
Panell.DockManager.InsertControl(Panel2, alBottom, Panell);

La caracteristica final del ejemplo es, probablemente, la mas interesante (y la


mas complicada de implementar correctamente). Cada vez que se cierra el progra-
ma, se guardan 10s estados actuales de anclaje del panel, utilizando el soporte del
administrador de anclaje. Cuando se vuelve a abrir el programa, se vuelve a
aplicar la informacion de anclaje, restaurando la configuracion previa de la ven-
tana. Este es el codigo que se podria escribir para guardar y cargar este estado:
procedure TForml .Fordestroy (Sender: TObject) ;
var
FileStr: TFileStream;
begin
if Panell.DockClientCount > 0 then
begin
FileStr : = TFileStream.Create (DockFileName, fmCreate or
fmOpenWrite) ;
try
Panel1.DockManager.SaveToStream (FileStr);
finally
FileStr-Free;
end ;
end
else
// e l i m i n a e l a r c h i v o
DeleteFile (DockFileName);
end;

procedure TForml. Formcreate (Sender: TObject) ;


var
FileStr: TFileStream;
begin
// c o d i g o d e i n i c i a l i z a c i o n . ..

// v u e l v e a c a r g a r l a c o n f i g u r a c i o n
DockFileName : = ExtractFilePath (Application-Exename) +
'dock-dck';
if FileExists (DockFileName) then
begin
FileStr := TFileStream. Create (DockFileName, fmOpenRead) ;
try
Panell.DockManager.LoadFromStream (FileStr);
finally
FileStr.Free;
end:
end ;
Panel1.DockManager.ResetBounds (True);
end;

Este codigo funciona bien mientras que todos 10s controles estan anclados
inicialmente. Cuando se guarda el programa, si algun control permanece flotante,
no se vera cuando se vuelvan a cargar 10s parametros. Sin embargo, debido a1
codigo de inicializacion insertado con anterioridad, el control aparecera de todos
modos anclado a1 panel, y aparecera cuando se arrastren otros controles. No es
necesario decir que se trata de una situacion complicada. Por este motivo, des-
pues de cargar 10s parametros, hemos aiiadido este codigo:
for i : = Panell.DockClientCount - 1 downto 0 do
begin
aCtrl : = Panell.DockClientes[i];
Panell.DockManager.GetControlBounds(aCtr1, aRect);
if (aRect.Bottom - aRect .Top <= 0 ) then
begin
aCtrl-ManualFloat (aCtrl-ClientRect);
Panell.DockManager.RemoveControl(aCtrl);
end ;
end;

El listado cornpleto incluye codigo mas comentado, que se ha usado durante el


desarrollo de este programa; podria usarse para comprender lo que sucede (que
suele ser algo distinto de lo esperado). En pocas palabras, 10s controles que no
tienen un tamaiio especificado en el administrador de anclaje (el unico mod0 en
que se pucde detectar que no estan anclados) se muestran en una ventana flotante
y se eliminan de la lista del administrador de anclaje.
Si se analiza el codigo completo del controlador del cvento oncreate, se
vera una gran cantidad de codigo complejo, solo para conseguir un comporta-
miento sencillo. Se podrian aiiadir mas caracteristicas a un programa de anclaje,
per0 para hacer eso deberian eliminarse otras caracteristicas, ya que algunas
podrian entrar en conflicto. Aiiadir un formulario de anclaje personalizado choca
con las caracteristicas del administrador de anclaje. Los alineamientos automati-
cos no se llevan bien con el codigo del administrador de anclaje para recuperar el
estado. Lo me-jor es tomar este programa y explorar su comportamiento, amplian-
dolo para soportar el tip0 de interfaz de usuario que se prefiera.
.- - --- --
I N0TA:May quc recordar que, awque 10s panebs de h & j e hacen que una

que sur banas de herramientas puedan desaparecer o ~ s t n ren unsporic ih


diferente a la que e s h acostumbrados. No conviene abusar de 1;iS oarac-
tensticas de anclaje, ya que a l g h usuario inexpcrto podrh-pcrderse,

Anclaje a un PageControl
Otra caracteristica importante de 10s controles de ficha es su soporte especifi-
co para anclaje. Al anclar un nuevo control sobre un PageControl, automaticamente
se aiiade una nueva ficha que lo alberga, como se puede ver en el entorno Delphi.
Para realizar esto, simplemente hay que designar el PageControl como anclaje
anfitrion y activar el anclaje para 10s controles clientes. Esto funciona mejor si
tenemos formularios secundarios que queremos albergar. Ademas, para mover el
PageControl cornpleto a una ventana flotante y despues anclarlo otra vez, sera
necesario un panel de anclaje en el formulario principal.
Esto es exactamente lo que hemos hecho en el ejemplo Dockpage, que tiene
un formulario principal con 10s siguientes valores:
object Forml: TForml
Caption = ' Docking p a g e s '
object Panell: TPanel
Align = alLeft
DockSite = True
OnMouseDown = PanellMouseDown
object Pagecontroll: TPageControl
Activepage = TabSheetl
Align = alClient
DockSite = True
DragKind = dkDock
object TabSheetl: TTabSheet
Caption = ' List'
object ListBoxl: TListBox
Align = alClient
end
end
end
end
object Splitterl: TSplitter
Cursor = crHSplit
end
object Memol: TMemo
Align = alClient
end
end

Observe que el panel tiene la propiedad UseDockManage definida como


True y que el PageControl siempre alberga una pagina con un cuadro de lista
porque a1 quitar todas las fichas, el codigo utilizado para el ajuste automatico del
tamaiio de 10s contenedores de anclaje podria causar algun problema.
Ahora el programa tiene otros dos formularios mas, con valores similares
(aunque albergan controles diferentes):
object Form2: TForm2
Caption = 'Small Editor'
DragKind = dkDock
DragMode = dmAutomatic
object Memol: TMemo
Align = alClient
end
end

Se puede arrastrar estos fomularios a1 control de ficha para aiiadirle nuevas


fichas, con 10s titulos correspondientes a cada formulario. Se puede incluso des-
anclar cada uno de estos controles e incluso el PageControl completo. Para ello,
el programa no activa el arrastre automaticamente, lo cual haria que el carnbio
entre fichas ya no fuese posible. En carnbio, la caracteristica se activa cuando el
usuario hace clic en la zona sin solapas del PageControl, es decir, en el panel
subyacente:
procedure TForml.PanellMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X , Y: Integer);
begin
PageControl1.BeginDrag (False, 10);
end;

Si se ejecuta el ejemplo DockPage, se puede comprobar este comportamiento,


que trata de mostrar la figura 6.15. Observe que a1 quitar el Pagecontrol del
formulario principal, no se pueden anclar directamente 10s demas formularios al
panel, ya que lo impide el codigo especifico del programa (simplemente porque en
ocasiones no se tratara de un comportamiento correcto).

eat

lun mar n d iue we .ab dom


12 3 4
5 6 7 8 9 1 0 1 1
12 13 14 15 16 18
19 20 21 22 23 24 25
26 27 28 29 30 31

tun mar nw lue vle db durn


1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
n 24 25 26 27 28 29
30
+3Hoy: 17/05/2003

Figura 6.15. El formulario principal del ejemplo DockPage despues de que se haya
anclado un formulario al control de ficha de la izquierda.

La arquitectura de ActionManager
Hemos visto que las acciones y el componente A c t ionManager pueden
representar un papel principal en el desarrollo de las aplicaciones Delphi, ya que
permiten separar mejor la interfaz de usuario del codigo real de la aplicacion. Asi,
la interfaz de usuario puede cambiar ahora sin que eso tenga un gran impact0 en
el codigo. El inconveniente de esta tecnica es que el programador tiene mas traba-
jo. Para crear un nuevo elemento de menu, hay que aiiadir primer0 la accion
correspondiente, moverse a1 menu, aiiadir el elemento de menu y conectarlo con la
accion.
Para resolver este asunto y para ofrecer a 10s desarrolladores y usuarios fina-
les algunas caracteristicas avanzadas, Delphi 6 introdujo un nuevo tip0 de estruc-
tura, basada en el componente ActionManager, que amplia sobremanera la funcion
de las acciones. De hecho, el ActionManager no solo posee una coleccion de
acciones, sino tambien una coleccion de barras de herramientas y menus asocia-
dos a ellas. El desarrollo de estas barras de herramientas y menus es completa-
mente visual: se arrastran las acciones desde un editor de componente especial del
ActionManager hacia las barras de herramientas para acceder a 10s botones nece-
sarios. Ademas, se puede permitir a1 usuario final de 10s programas realizar la
misma operacion y reagrupar sus propias barras de herramientas y menus, empe-
zando por las acciones que se le ofrezcan.
En otras palabras, utilizar esta arquitectura permite construir aplicaciones con
una interfaz de usuario moderna y personalizable por el propio usuario. El menu
puede mostrar solo 10s elementos usados mas recientemente (como muchos pro-
gramas de Microsoft), permitir animaciones, y muchos detalles mas. Esta estruc-
tura se centra en el componente ActionManager, per0 incluye tambien otros
componentes que se encuentran al final de la ficha Additional de la paleta:
El componente ActionManager: Es un sustituto de ActionList (pero pue-
de utilizar tambien uno o mas ActionList existentes).
El control ActionMainMenuBar: Es una barra de herramientas usada
para mostrar el menu de una aplicacion basada en las acciones de un com-
ponente ActionManager.
El control ActionToolBar es una barra de herramientas utilizada para
albergar botones basados en las acciones de un componente ActionManager.
El componente CustomizeDlg: Contiene el cuadro de dialog0 que se pue-
de utilizar para permitir a 10s usuarios personalizar la interfaz de una
aplicacion basada en el componente A c t ionManager.
El componente PopupActionBarEx: Es un componente adicional que de-
beria usarse para permitir que 10s menus desplegables sigan la misma
interfaz de usuario que 10s menus principales. Este componente no se in-
cluye con Delphi 7, sino que se encuentra disponible como una descarga
separada.

bitn llamado AC t i o n ~ o ~ u ~ ~ e n uel)repositorio


ka Web CodeCentraI de
Borland (numero 1 8870). Ademis, se puede cncontrar mas infomacion en
el sitio Web del autor deI componente (homepages.borland.com/strefhen).
Es un miembro deI equipo de I+D de Delphi en Borland. El componente se
encuentra en el sitio Web, pero no esta oficialmente soportado.

Construir una sencilla demostracion


Debido a que esta estructura es principalmente visual, una demostracion sera
probablemente de mayor ayuda que una exposicion general (aunque un libro no es
el mejor soporte para este tip0 de descripcion). Para crear un programa de ejem-
plo basado en esta estructura, hay que poner en un formulario un componente
A c t ionManager,hacer doble clic sobre 61 para abrir su editor de componente,
que se muestra en la figura 6.16. Observe que este editor no es modal, asi que se
puede mantener abierto mientras realizamos otras operaciones en Delphi. Ademas
hay que tener en cuenta que este cuadro de dialogo lo muestra tambien el compo-
nente C u s tomizeDlg,aunque con algunas caracteristicas limitadas (por ejem-
plo, aAadir nuevas acciones esta desactivado).

Figura 6.16. Las tres paginas del cuadro de dialogo del editor de ActionManager.

rn
Las tres paginas del editor son asi:
La primera ficha proporciona una lista visual de contenedores de acciones
(barras de herramientas o menus). Para aiiadir nuevas barras de herra-
mientas, se hace clic en el boton New. Para aiiadir nuevos menus, hay que
aiiadir el componente correspondiente a1 formulario, despues abrir la co-
leccion A c t i o n B a r s del ActionManager, seleccionar una barra de ac-
ciones o aiiadir una nueva y engancharle el menu usando la propiedad
A c t i o n B a r . Estos son 10s mismos pasos que podriamos seguir para co-
nectar una nueva barra de herramientas a esta estructura en tiempo de
ejecucion.
La segunda ficha del editor de ActionManager es muy similar a la del
editor de ActionList, que ofrece una forma estandar de aiiadir acciones
nuevas o personalizadas, organizarlas en categorias y modificar su orden.
Sin embargo, la caracteristica importante de esta ficha consiste en que se
puede arrastrar una categoria o una simple accion desde la misma y soltar-
la en un control de una barra de acciones. Si se arrastra una categoria a un
menu, se consigue un menu desplegable con todos 10s elementos de la
categoria. Si se arrastra a una barra de herramientas, cada una de las
acciones de la categoria genera un boton en la barra de herramientas. Si se
arrastra una orden sencilla a una barra de herramientas, se obtiene el co-
rrespondiente boton, si se arrastra al menu, se obtiene una orden directa de
menu, algo que como norma general se deberia evitar.
La ultima pagina del editor ActionManager permite (a1 programador y
opcionalmente a un usuario final) activar el visor de 10s elementos de menu
usados recientemente y modificar algunas de las propiedades visuales de la
barra de herramientas.
El programa AcManTest es un ejemplo que usa algunas de las acciones
estandar y un control RichEdit para explicar el uso de esta estructura (no se ha
escrito nada de codigo personalizado para hacer que las acciones funcionen me-
jor, porque el objetivo era solo el administrador de acciones). Se puede experi-
mentar con el en tiempo de diseiio o ejecutarlo, hacer clic en el boton Customize
y ver lo que el usuario final puede hacer para personalizar la aplicacion. (Vease la
figura 6.17.) En realidad, en el programa se puede evitar que el usuario realice
algunas operaciones sobre acciones. Cualquier elemento especifico de la interfaz
de usuario (un objeto T ~ c t i o n ~ l i e ntiene
t ) una propiedad C h a n g e A l l o w e d
que se puede usar para desactivar la modificacion, el desplazamiento y la elimina-
cion de operaciones. Cualquier contenedor de clientes de accion (las barras visua-
les) tiene una propiedad para inhabilitar su ocultacion (A1 l owH i d i ng , fijada
por defect0 a T r u e ) . Cada coleccion de It e m s de una barra de accion tiene una
opcion C u s t o m i z a b l e que se puede inhabilitar para desactivar todos 10s cam-
bios de usuario en toda la barra.
Figura 6.17. Mediante el componente CustomizeDlg se puede permitir que un usuario
personalice las barras de herramientas y el menu de una aplicacibn arrastrando
elementos desde el cuadro de dialog0 o moviendolos en las barras de accion.

1. TRUCO: ~ & d o mencionarnos ActionBar no nos referimos a las b a n s


de herramientas visuales que contienen elementos de accion del componente
ActionManager, que a su vez dispone de una coIeccion Items. El mejor
modo de comprender esta estnrctura es fijarse en el subarbol mostrado por
el Object TreeView para un componente ActionManager. Cada elemento
de la coleccion TAc t i o n B a r tiene un componente visual Tcus tom-
Act ionBar conectado, pero lo contrario no ocurre (por eso, por ejemplo,
no se puede alcanzar la propiedad Customizable, si se inicia seleccio-
nando la barra de herramientas visual). Debido a la sirnilitud de 10s dos
pombres, puede llevar un tiempo entender a q u e se refiere realmente la
1 ayuda de Delphi.

Para que 10s valores de usuario sean permanentes, hemos conectado un archivo
(llamado settings) a la propiedad Fi leName del componente A c t ionManager.
Cuando se asigna esta propiedad, hay que introducir el nombre del archivo que se
quiere usar; a1 iniciar el programa, el ActionManager creara el archivo. La perma-
nencia se consigue mediante streaming de cada ActionClientItem conectado con el
administrador de acciones. Como estos elementos de cliente de accion estan basa-
dos en la configuracion de usuario y mantienen la informacion de estado, un
simple archivo recoge tanto 10s cambios que el usuario ha realizado en la interfaz
como 10s datos de uso.
Dado que Delphi almacena valores de usuario e informacion de estado en un
archivo que nosotros ofrecemos, se puede hacer que la aplicacion soporte varios
usuarios en un solo ordenador. Simplemente, hay que usar un archivo de configu-
raciones para cada uno de ellos (dentro de la carpeta Mi s document 0s) y
conectarlo con el administrador de accion cuando el programa arranque (utilizan-
do el usuario actual del ordenador o despues de algun nombre de usuario que se
solicite). Otra posibilidad es almacenar estas configuraciones en la red, de forma
que si un usuario esta en otro ordenador, su configuracion personal viaje con el.
En el programa hemos decidido guardar 10s valores en un archivo dentro de la
misma carpeta que el programa, asignado la ruta relativa (el nombre de archivo)
a la propiedad FileName del ActionManager. El componente rellenara el nom-
bre de archivo completo con la carpeta del programa, encontrando sin problemas
el archivo que cargar. Sin embargo, el archivo incluye entre sus datos su propio
nombre, con una ruta absoluta. Por eso, cuando llegue el momento de guardar el
archivo, la operacion puede referirse a la ruta antigua. Esto impediria que se
copiara este programa con sus configuraciones a una carpeta distinta (por ejem-
plo, esto es un problema para la prueba AcManTest). Se puede devolver su valor
a la propiedad FileName despues de cargar el archivo. Como otra alternativa
mejor, podria establecerse el nombre de archivo en tiempo de ejecucion, en el
evento oncreate del formulario. En este caso tambien habria que obligar a que
el archivo se recargase, ya que se asigna despues de que ya se hayan creado e
inicializado 10s componentes ActionManager y 10s distintos ActionBar. Sin
embargo, podria desearse forzar el nombre de usuario despues de la carga:
procedure TForml. Formcrate (Sender:TObject) ;
begin
ActionManagerl.Fi1eName : = ExtractFilePath
(Application.ExeName) + ' s e t t i n g s ' ;
ActionManagerl.LoadFromFi1e(ActionManagerl.FileName);
// devolvemos el nombre a 1 fichero d e configuracion despues
d e cargarlo (ruta relativa)
ActionManagerl.FileName : = ExtractFilepath
(Application.ExeName) + 'settings';
end ;

Objetos del menli utilizados con menos


frecuencia
Cuando tengamos el archivo para las configuraciones de usuario, el
ActionManager guardara en el las preferencias del usuario y tambien lo usara
para realizar un seguimiento de la actividad del usuario. Esto es esencial para que
el sistema pueda eliminar elementos del menu que no se hayan utilizado durante
algun tiempo, desplazandolos a un menu extendido, de manera que se use la
misma interfaz de usuario adoptada por Microsoft (vease la figura 6.18).

Figura 6.18. El ActionManager desactiva 10s elementos de menu menos utilizados


recientemente. Alin pueden verse mediante el comando de extension del menu.

El ActionManager no solo muestra 10s elementos utilizados con menos fre-


cuencia, tambien permite personalizar este comportamiento de una forma mu?;
precisa. Cada barra de accion tiene una propiedad S e s s i o n c o u n t que realiza
el seguimiento dcl numero de veces que se ha ejecutado la aplicacion. Cada
A c t i o n C l i e n t e I t e m tiene una propiedad L a s t S e s s i o n y una propiedad
u s a g e c o u n t utilizada para el seguimiento de las operaciones del usuario. Ob-
serve que el usuario puede volver a poner a cero toda esta informacion dinamica
usando el boton Reset Usage Data del dialog0 de personalizacion.
El sistema calcula el numero de sesiones en las que no se ha utilizado la accion
y procesa la diferencia entre el numero de veces que se ha ejecutado la aplicacion
( s e s s i o n c o u n t ) y la ultima sesion en la que se us6 dicha accion ( L a s t -
s e s s i o n ) . El valor de U s a g e c o u n t se usa para mirar en el P r i o r i t y -
S c h e d u l e el numero de sesiones en las que no se usa el elemento que hay
establecidas para eliminarlo. En otras palabras, modificando el P r i o r i t y -
S c h e d u l e se puede determinar la velocidad con la que se eliminan 10s elemen-
tos, en caso de que no se usen. Tambien se puede evitar que se active este sistema
en el caso de acciones especificas o grupos de acciones. La propiedad I terns de
A c t i o n B a r s del ActionManager tiene una propiedad H i d e u n u s e d que pode-
mos cambiar para desactivar esta caracteristica para todo un menu. Para que un
elemento especifico sea siempre visible, no importa cual sea su uso real, tambien
se puede fijar su propiedad U s a g e c o u n t como - 1.Sin embargo, las configura-
ciones de usuario pueden sobrescribir este valor.
Para entender un poco mejor el funcionamiento de este sistema, hemos aiiadido
una accion personalizada (Act i o n s h o w s t a t u s ) a1 ejemplo AcManTest. La
accion tiene el siguiente codigo que guarda la configuration actual del adminis-
trador de accion en un stream de memoria, lo convierte en texto y lo muestra
dentro del campo de memo:
procedure TForml.ActionShowStatusExecute(Sender: TObject);
var
memStr, memStr2: TMemoryStream;
begin
memStr : = TMemoryStream.Create;
try
memStr2 : = TMemoryStream.Create;
try
ActionManagerl.SaveToStream(memStr);
memStr.Position : = 0;
ObjectBinaryToText(memStr, memStr2);
memStr2.Position : = 0;
RichEditl.Lines.LoadFromStream(memStr2);
finally
memStr2. Free;
end ;
finally
memStr. Free;
end ;
end;

El resultado obtenido es la version textual del archivo settings actualizado


automaticamente con cada ejecucion del programa. A continuacion, aparece una
pequeiia parte del archivo, con 10s datos de uno de 10s menus desplegables y
muchos comentarios adicionales:
item / / desplegable File de la barra de acciones del menu
principal
Items = <
item
Action = Forml.FileOpen1
LastSession = 19 // utilizado en la ultima sesion
Usagecount = 4 // utilizado cuatro veces
end
item
Action = Forml.FileSaveAs1 / / no usado nunca
end
item
Action = Forml.FilePrintSetup1
LastSession = 7 / / usado hace algun tiempo
UsageCount = 1 // s o l o una vez
end
item
Action = Forml.FileRun1 // no u s a d o nunca
end
item
Action = Forml.FileExit1 // no usado nunca
end>
Caption = ' & F i l e 1
Lastsession = 19
UsageCount = 5 / / la suma del r e c u e n t o d e uso d e 10s e l e m e n t o s
end

Modificar un programa existente


Si esta arquitectura es util, probablemente sera necesario rehacer la mayoria
de las aplicaciones para poder sacarle partido. Sin embargo, si ya se han utilizado
acciones (con el componente ActionList), la conversion sera mucho mas sencilla.
De hecho, el ActionManager posee su propio conjunto de acciones, per0 tambien
puede usar acciones de otro ActionManager o ActionList. La propiedad
L i n k A c t i o nL i s t del ActionManager es un conjunto de otros contenedores de
acciones (componentes ActionList o ActionManager), que se pueden asociar con
el ActionManager actual. Asociar todos 10s diversos grupos de accion es util
porque se puede permitir a un usuario personalizar toda la interfaz de usuario con
un sencillo cuadro de dialogo.
Si enganchamos acciones externas y abrimos el editor del ActionManager,
veremos en la ficha Actions, un cuadro combinado que lista el ActionManager
actual mas 10s otros contenedores de acciones asociados a el. Se puede escoger
uno de estos contenedores para ver su conjunto de acciones y cambiar sus propie-
dades. La opcion A l l A c t i o n de este cuadro combinado permite trabajar en
todas las acciones de 10s diversos contenedores a1 mismo tiempo, per0 se ha
observado que se selecciona automaticamente a1 arrancar y no siempre resulta
eficaz. Hay que seleccionarlo de nuevo, para poder ver todas las acciones.
Como ejemplo de modificacibn de una aplicacion existente, hemos ampliado el
programa desarrollado a lo largo de este capitulo, en forma de ejemplo MdEdit3.
Este ejemplo utiliza la misma lista de acciones que la version anterior, engancha-
da a un ActionManager con propiedades personalizadas adicionales para permi-
tir a 10s usuarios reorganizar la interfaz. A diferencia del anterior programa
AcManTest, el ejemplo MdEdit3 utiliza un ControlBar como contenedor para las
barras de acciones (un menu, tres barras de herramientas y 10s cuadros combina-
dos habituales) y posee total soporte para arrastrarlos fuera del contenedor como
barras flotantes y dejarlas en la barra de control inferior. Para esto, solo hub0 que
modificar ligeramente el codigo fuente para hacer referencia a las nuevas clases
de contenedores (esto es, TCus t o m A c t i o n T o o l B a r , en lugar de T t o o l B a r )
en el metodo ControlBarLowerDockOver.El evento OnEndDock del com-
ponente ActionToolBar, pasa como uno de sus parametros un destino en
blanco cuando el sistema crea un formulario flotante para albergar el control, por
ese motivo no fue facil dotar a estos formularies de un nuevo titulo personalizado.
(Vease el metodo EndDock del formulario.)

Emplear las acciones de las listas


Como ejemplo adicional, se muestra el mod0 de empleo de un grupo bastante
complejo de acciones estandar: las acciones de lista. Las acciones de lista engloban
dos grupos diferentes. Algunas de ellas (como Move, Copy, Delete, Clear y Select
All) son acciones normales que funcionan en cuadros de lista u otras listas. Sin
embargo, VirtualListAction y StaticListAction definen acciones
que proporcionan una lista de elementos que se van a visualizar en una barra de
herramientas como cuadro combinado.
El ejemplo ListActions destaca ambos grupos de acciones, ya que su
ActionManager tiene cinco, visualizadas en dos barras de herramientas separa-
das. Este es un resumen de las acciones del administrador de acciones (omitiendo
las seccion de las barras de accion del archivo DFM del componente):
object ActionManagerl: TActionManager
ActionBars.SessionCount = 1
ActionBars = < . . . >
object StaticListActionl: TStaticListAction
Caption = 'Numbers'
1tems.CaseSensitive = False
1tems.SortType = stNone
Items = <
item
Caption = ' one'
end
item
Caption = ' two'
end
. . .>
OnItemSelected = ListActionItemSelected
end
o b j e c t VirtualListActionl: TVirtualListAction
Caption = ' Items'
OnGetItem = VirtualListActionlGetItem
OnGetItemCount = VirtualListActionlGetItemCount
OnItemSelected = ListActionItemSelected
end
object ListControlCopySelectionl: TListControlCopySelection
Caption = ' Copy'
Destination = ListBox2
Listcontrol = ListBoxl
end
object ListControlDeleteSelectionl: TListControlDeleteSelection
Caption = 'Delete'
end
object ListControlMoveSelection2: TListControlMoveSelection
Caption = 'Move'
Destination = ListBox2
Listcontrol = ListBoxl
end
end

El programa tiene tambien dos cuadros de lista en su formulario, utilizados


como objetos de accion. Las acciones Copy y Move estan ligadas a estos dos
cuadros de lista mediante sus propiedades Listcontrol y Destination.
Sin embargo, la accion Delete trabaja automaticamente con el cuadro de lista que
tiene el foco de entrada.
En su coleccion Items, la StaticListAction define una serie de elementos
alternativos. Esta no es una simple lista de cadena, ya que cada elemento tambien
t i m e un ImageIndex que permite aiiadir elementos graficos a1 control que
muestra la lista. Por supuesto. se pueden aiiadir mas elementos mediante progra-
macion a esta lista. Sin embargo, en el caso de una lista altamente dinamica.
tambien s e puede utilizar 1aVirtualListAct ion.Este accion no solo define
una lista de elementos. sino que tiene dos eventos que se pueden usar para propor-
cionar cadenas e imagenes para la lista. El evento OnGetItemCount permite
indicar el numero de elementos a mostrar y OnGe t Item se llama entonces para
cada elemento especifico.
En el ejemplo ListActions, la VirtualListAction t h e 10s siguientes
controladores de eventos para su definicion y produce la lista que se aparece en el
cuadro combinado de la figura 6.19.
procedure TForml.VirtualListActionlGetItemCount(Sender:
TCustornListAction;
var Count: Integer) ;
begin
Count : = 100;
end;

procedure TForml.VirtualListActionlGetItem(Sender:
TCustornListAction;
const Index: Integer; var Value: String;
var ImageIndex: Integer; var Data: Pointer) ;
begin
Value : = 'Item' + IntToStr (Index);
end;

NOTA: En lugar de que 10s elementos de acci~nvirtuales sean solicitados


solo cuando. se necesita mostrarlos, se crean de todas fonnas. Se puede
probar si se habilita el &go comentado en el metodo VirtualLis t -
A c t ionlGe tI t e m (no incluido en el listado anterior), que aiiade a cada
elemento la hora en que se solicita su cadena.
Figura 6.19. La aplicacion ListActions tiene una barra de herrarnientas que hospeda
una lista estatica y una lista virtual.

Ambas listas, la estatica y la virtual, tienen un controlador de eventos


On1 temSe lected.En el controlador de eventos compartido, hemos escrito el
codigo siguiente, para aiiadir el elemento actual a1 primer cuadro de lista del
formulario:
procedure TForml.ListActionItemSe1ected(Sender:
TCustomListAction; .
Control: TControl) ;
begin
ListBoxl.Items.Add ((Control as
TCustornActionCombo) .SelText) ;
end:

En este caso, el remitente es la lista de accion personalizada, pero la propiedad


I temIndex de esta lista no se actualiza con el elemento seleccionado. Sin em-
bargo, accediendo a1 control visual que muestra la lista, obtenemos el valor del
elemento seleccionado.
0 Trabajo
con formularios

Si se han leido 10s capitulos anteriores, ahora deberia poder utilizar 10s com-
ponentes visuales de Delphi para crear la interfaz de usuario de una aplicacion.
Es hora de fijarse en otro elemento central del desarrollo en Delphi: 10s formula-
rios. Se han venido usando desde el principio del libro, per0 nunca se ha descrito
en detalle lo que se puede hacer con un formulario, que propiedades puede usar o
que metodos de la clase TForm resultan de interes particular.
Este capitulo analiza algunas de las propiedades y estilos de formularios y las
tecnicas de dimcnsionamiento y position, a1 igual que su escalado y desplaza-
miento. Tambien hablaremos de las aplicaciones con varios formularios, el uso de
10s cuadros de dialog0 (personalizados y predefinidos), marcos y herencia visual
de formularios. Por ultimo, dedicaremos algo de tiempo a1 sistema de entrada en
un formulario, tanto mediante el teclado como mediante el raton.
Este capitulo trata 10s siguientes temas:
Estilos de formularios, de bordes e iconos de bordes.
Entrada de raton y teclado.
Dibujo direct0 sobre el formulario y efectos especiales.
Posicion, escala y desplazamiento de formularios.
Creacion y cierre de formularios.
Cuadros de dialogo y formularios modales y no modales.
Creacion dinamica de formularios secundarios
Cuadros de dialogo predefinidos.
Construccion de una pantalla de inicio.

La clase TForm
La clase T Form,incluida en la unidad Forms de la VCL, define 10s formula-
rios en Delphi. Ahora, existe tambien una segunda definicion de 10s formularios
en la biblioteca VisualCLX. Aunque a lo largo del presente capitulo, nos referire-
mos principalmente a la clase de la VCL, intentaremos resaltar tambien las dife-
rencias con la version multiplataforma que proporciona la biblioteca CLX.
La clase TForm forma parte de la jerarquia de controles de ventana, que
comienza con la clase TWinControl (o TWidgetControl). En realidad,
TForm hereda de la "casi completa" TCustomForm,que a su vez hereda de
TScrollingWinControl (o TScrollingWidget). A1 tener todas las
funciones de sus clases basicas, 10s formularios tienen una gran serie de metodos,
propiedades y eventos. Por este motivo, no vamos a intentar mostrar una lista de
todos ellos. En su lugar, a lo largo del capitulo, explicaremos una serie de tecni-
cas interesantes relacionadas con 10s formularios. Remarcaremos las pocas dife-
rencias existentes entre 10s formularios VCL y 10s formularios CLX. Para la
mayoria de 10s ejemplos existe una version CLX, para que se pueda comenzar a
experimentar a1 instante con formularios y cuadros de dialogo en la CLX, a1 igual
que con la VCL. La inicial de estas versiones CLX de cada ejemplo sera la Q.

Usar formularios normales


Normalmente, 10s desarrolladores en Delphi suelen crear formularios en tiem-
po dc diseiio, lo cual implica la derivacion de una nueva clase a partir de la basica
y la creacion del contenido del formulario de un mod0 visual. Aunque esta es una
tecnica muy comun, no es obligatorio crear un descendiente de la clase TForm
para mostrar un formulario, sobre todo cuando se trata de uno sencillo.
Pensemos en el siguiente ejemplo: tenemos que mostrar un mensaje largo a1
usuario (basado en una cadena), per0 no queremos usar el sencillo cuadro de
mensaje predefinido, porque sera demasiado grande y no tendra barras de despla-
zamiento. Podemos crear un formulario con un componente de memo y mostrar la
cadena en su interior. Nada impide crear este formulario del mod0 visual acos-
tumbrado, per0 podria tomarse en consideracion hacerlo mediante codigo, en par-
ticular si se necesita un amplio grado de flexibilidad. Los ejemplos DynaForm y
QDynaForm, que son algo radicales, no tienen un formulario definido en tiempo
de diseiio, sino que incluyen una unidad con esta funcion:
procedure ShowStringForm (str: string) ;
var
form: TForm;
begin
Application.CreateForm (TForm, form);
form.caption : = 'DynaForrn';
form-Position : = poScreenCenter;
with TMemo.Create (form) do
begin
Parent : = form;
Align : = alclient;
Scrollbars : = ssvertical;
ReadOnly : = True;
Color : = form.Color;
Borderstyle : = bsNone;
WordWrap : = True;
Text : = str;
end;
form.Show;
end;

Se crea el formulario usando el metodo CreateForm del objeto global


A p p 1i cation (una caracteristica necesaria para las aplicaciones de Delphi);
ademas de esto, este codigo realiza de forma dinarnica algo que, por lo general, se
hace con el disefiador de formularios.
Escribir este codigo es sin duda alguna pesado, per0 permite una gran flexibi-
lidad, porque cualquier parametro puede depender de las configuraciones
externas.
La funcion Showstring Form anterior no la ejecuta un evento de otro for-
mulario, puesto que en este programa no hay formularios tradicionales. En cam-
bio, hemos modificado el codigo fuente del proyecto del siguiente modo:
program DynaForm;

uses
Forms,
DynaMemo in ' DynaMerno .pas ' ;

var
str: string;

begin
str : = 1 1 .
Randomize;
while Length (str) < 2000 do
str : = str + Char (32 + Random (74));
ShowStringForm (str);
Application.Run;
end.
A1 ejecutar el programa DynaForm, se obtiene un formulario de extraAa apa-
riencia cubierto con caracteres aleatorios (como muestra la figura 7.1).

Figura 7.1. El formulario dinamico generado por el ejemplo DynaForm se crea


completamente en tiempo de ejecucion.
--
TRUCO: Una ventaja indirecta de esta tecnica, comparada con el uso de
archivos DFM para formularios en tiempo de disefio, es que supondria una
mayor dificultad para un programador externo conseguir informacion so-
L-- l- 3- l- --l:---:A-
r a la ayl1c;ac;lutl. p..--
VIC la ~ s n u c ~ u u~
--&---A *--- L ---- -- ----A- ---- -1
LUIIIU nernus vlsrv, sr; pur;ur; r;xrrar;i GI
-.:-A-

DFM del archivo ejecutable real de Delphi, per0 tambien se puede hacer lo
mismo con cualquier archivo ejecutable compilado con Delphi del que no
tengamos el c6digo fuente. Si es importante guardarse un conjunto especifi-
co de componentes que se utilicen (quizas en un formulario especifico),
junto con 10s valores predefinidos para sus propiedades, escribir el codigo
adicional puede merecer la pena.

El estilo del formulario


La propiedad F o r m S t y l e permite escoger entre un formulario normal
(f s N o r m a l ) y las ventanas que componen una aplicacion de hterfaz de Docu-
mentos Multiples (Multrple Doctment Interface, MD1). En este caso, usaremos
cl estilo fsMDIForm para la ventana padre MDI (es decir, la ventana marco de
la aplicacion MDI) y el estilo f s M D I C h i l d para la ventanas MDI hijo.
Una cuarta opcion es el estilo fs S t a y O n T o p , que establece si el formulario
ha de permanecer siemprc sobre todas las demas ventanas, a escepcion de algunas
que puedan ser ventanas "fijadas por encima". Para crear un formulario superior
(un formulario cuya ventana permanece siempre por encima), es necesario definir
la propiedad F o r m S t y l e . como se indico anteriormente. Dicha propiedad causa
dos efectos distintos, dependiendo del tip0 de formulario a1 que se aplique:
El formulario principal de una aplicacion permanecera por encima de to-
das las demas aplicaciones (a menos que las demas aplicaciones tengan
tambien este mismo estilo de ventana). A veces. esto genera un efecto
visual bastante desagradable, por lo que solo tiene sentido en programas de
alerta para usos especiales.
Un formulario secundario permanecera por encima de 10s demas formula-
rios de la aplicacion a la que pertenecc. Pero las ventanas de otras aplica-
ciones no se veran afectadas. Esto se usa normalmente para barras de
herramientas flotantes que deberian estar sobre la ventana principal.

El estilo del borde


Otra propiedad importante de un formulario es su propiedad B o r d e r S t y l e .
Esta se refiere a un elemento visual del formulario, per0 tiene una influencia
mucho mayor en el comportamiento de la ventana, como muestra la figura 7.2.
En tiempo de diseiio, el formulario siempre aparece utilizando el valor
predefinido de la propiedad B o r d e r S t y l e , b s s i z e a b l e . Este corresponde a
un estilo Windows que se conoce como "marco grueso". Cuando una ventana
principal esta rodeada por un marco grueso, el usuario puede adaptar su tamaiio
arrastrando el borde. Esto se consigue con 10s cursores especiales de ajuste del
tamaiio que tienen forma de flecha de doble punta y aparecen cuando el usuario
mueve el raton sobre ese borde grueso de la ventana.

Figura 7.2. Forrnularios de ejemplo con diversos estilos de borde, creados por el
ejernplo Borders.
En tiempo de diseiio, el formulario siempre se muestra con el valor predetermi-
nado para la propiedad BorderSylte, bs Si zeab le. Este valor se corresponde
con un estilo de Windows conocido como "marco fino". Cuando una ventana
principal tiene un marco fino a su alrededor, un usuario puede ajustar su tamaiio
arrastrando su borde. Este estado se manifiesta mediante unos cursores de
redimensionamiento especiales (con la forma de una flecha de dos puntas) que
aparecen cuando el usuario mueve el raton sobre el borde de esta ventana.
Una segunda opcion bastante importante de esta propiedad es bsDialog. Si
la seleccionamos, el formulario utiliza como borde el tipico marco de cuadro de
dialogo (un marco grueso que no permite que se reajuste su tamaiio).
Ademas de este elemento grafico, observe que si seleccionamos este valor
bsDialog, el formulario se transforma en un cuadro de dialogo. Esto implica
una serie de cambios: por e.jemplo, 10s elementos de su menu de sistema son
distintos y el formulario ignora algunos de 10s elementos de la propiedad
Border Icons.

ADVERTENCIA: Definir la propiedad Border style en tiempo de di-


seiio no ~ r o d u c ninein
eY
efecto visible. De hecho. hav
za .
diversas ~ r or ~ i e d a d e s
de componentes que no tienen n i n g h efecto en tiempo de diseilo, porque
evitarian que pudiesemos trabajar en el ~ o m ~ o n e n ~ m i e n tdeshrofia-
ras
-
mos -I
el -:-..--- ..-- _ > - r
programa. rno-r-ejemplo,i
no poariamos reajusrar el ramano aer Pror-
~.-:---L-- -1 A --
-- 3-1

mulario con el rat6n si se convirtiera en un cuadro de c2ihlogo. Pero hay que


recordar que cuando se ejecuta la aplicaci6n, el fomulario usarh el borde
indicado.

Podemos asignar cuatro valores a la propiedad Borders t yle :


El estilo bssingle: Se puede usar para crear una ventana principal en la
que no podamos modificar el tamaiio. Muchos juegos y aplicaciones basa-
dos en ventanas con controles (corno 10s formularios para introducir datos)
utilizan este valor, simplemente porque no tiene sentido ajustar el tamaiio
de estos formularios. Aumentar un formulario para ver un area vacia o
reducir su tamaiio para que algunos componentes sean menos visibles no
suele ayudar al usuario de un programa (aunque las barras de desplaza-
miento automaticas de Delphi resuelvan en parte este problema.
El estilo bsNone: Se usa solo en situaciones muy especiales y dentro de
otros formularios. Jamas se vera una aplicacion con una ventana principal
que no tenga borde o titulo (except0 quizas como ejemplo en un libro de
programacion para demostrar que no tiene sentido).
Los valores, bsToolWindow y bsSizeToolWin: Estan relacionados con
el estilo especifico ampliado de Win32, ws ex Toolwindow. Este es-
tilo transforma la ventana en un cuadro deherramientas flotante, con un
titulo en una fuente pequeiia y un boton de cierre. Para la ventana principal
de una aplicacion no se deberia usar este estilo.

lag &nu -fbs Iform border style, estilo del h d e del formuhrio). Asi
tdremos 'Pbssingle, fbsDialog, etc.

Para comprobar el efecto y comportamiento de 10s distintos valores de la pro-


piedad Borderstyle, hemos creado un sencillo programa llamado Borders,
tambidn disponible en version CLX como QBorders. En la figura 7.2 ya hemos
visto su resultado, per0 si se e.jecuta el e.jemplo y se observa su funcionamiento
durante algun tiempo, se entenderan mejor las diferencias entre 10s formularios.
El formulario principal dc este programa contiene solo un grupo de radio y un
boton. Tambien hay un formulario secundario, sin componentes y con la propie-
dad Posit ion predefinida como poDef a u l t PosOnly. Esto afecta a la po-
sicion inicial del formulario secundario que crearemos a1 hacer clic sobre el boton.
El codigo del programa es muy sencillo. Al hacer clic sobre el boton, se crea
un nuevo formulario de forma d i n h i c a , dependiendo del elemento seleccionado
del grupo de radio:
p r o c e d u r e TForml.BtnNewFormClick(Sender: TObject);
var
NewForm: TForm2 ;
begin
NewForm : = TForm2.Create (Application);
NewForm.BorderStyle : = TFormBorderStyle
(BorderRadioGroup.ItemIndex);
NewForm. Caption : =
BorderRadioGroup.Items[BorderRadioGroup.ItemIndex];
NewForm. Show;
end;

Este codigo usa en realidad un truco: convierte el numero del elemento selec-
cionado en la enumeracion T FormBorderSt yle. Esta tecnica funciona por-
que hemos colocado 10s botones de radio en el mismo orden que 10s valores de
esta enumeracion.
El metodo BtnNew FormClic k copia a continuation el testo del boton de
radio en el titulo del formulario secundario. Este programa remite a1 T Form2, el
formulario secundario definido en una unidad secundaria del programa, guardado
como SECOND.PAS. Por esa razon, para compilar el ejemplo, habra que aiiadir
las siguientes lineas a la seccion de implernentacion de la unidad del formulario
principal:
uses
Second;
TRUCO: Siempre que bay6 quq fiferirse a otra unidRd de un pi@mha,
hay que .colocq 1a correspamdiente sentencia udes en lrr3ec&n
irnp;lemsn$ation y no en la secci6n inter face, 3 se; podble, E s t ~
rrcelerie#pptoceso de compilaci6n. origins un cbdigo miis limpio (porque
laa pnfdadses'que se incluyen esdn sep&adas de lasque incluie ~ e l ~ hyi )
evita emks de @qilacion ~irculaPdeunidades. Para hacer referencia ti
otr& archivos del Ynismo proyecto, tambitn se puede usar la opcion de
m d FileNse UNt.

Los iconos del borde


Otro importante elemento de un formulario es la presencia de iconos en su
borde. De manera predeterminada, una ventana tiene un pequeiio icono conectado
a1 menu del sistema y unos botones Minimizar, Maximizar y Cerrar a la derecha
del todo. Se pueden fijar distintas opciones mediante la propiedad Border Icons
que tiene cuatro valores posibles: b i S y s t e m M e n u , b i M i n i m i z e ,

NOTA:El icana de b a r d e - h i ~ edi i~v a laayuda ni


jQvi es esto?". Cuan-
do se b c h y e ester estilo y se .excluyen 10s estilos biMinirnize y
biMaximi ze, w c 4 Una interrogation en la barra de fitula del f~&-
lark. Si st h w e &mbre ellag,9 continuacion.~obrewwmponenfe que
est6 dentr~dd f'uhrio, belphi.&tiva la ayuda sobre el objeto (e*una
ventaaa eaftteXW en Windows 9x o ?enuna tipica ventana de a y d a b
Windows cn Windows 2WOlXP). Este comportarniento se demuestra en e!
ejemplo BXams. que tkne uri Simple archivo de ayuda con una pagina co-
nectada a hpropied&)-Ie~p~on t e x t del boton que se encuentra en me-
dio w fcmrddo.

El ejemplo Blcons demuestra el comportamiento de un formulario con dife-


rentes iconos en el borde y muestra el mod0 de cambiar esta propiedad en tiempo
de ejecucion. El formulario de este ejemplo es muy sencillo: solo tiene un menu,
con un desplegable que contiene cuatro elementos de menu, uno para cada ele-
mento posible del conjunto de iconos de borde. Hemos creado un unico metodo,
conectado con las cuatro opciones, que lee las marcas de verificacion de 10s
elementos del menu para establecer el valor de la propiedad BorderIcons.Por
tanto, este codigo sirve tambien para practicar con el trabajo con conjuntos:
procedure TForml.SetIcons(Sender: TObject);
var
BorIco: TBorderIcons;
begin
(Sender a s TMenuItem) .Checked : = not (Sender a s
T M e n u I t e m ) .Checked;
i f SystemMenul.Checked t h e n
BorIco : = [biSystemMenu]
else
BorIco : = [ I ;
i f MaximizeBoxl.Checked t h e n
Include (BorIco, biMaximize) ;
i f MinimizeBoxl.Checked t h e n
Include (BorIco, biMinimize) ;
i f Helpl.Checked t h e n
Include (BorIco, biHelp) ;
BorderIcons : = BorIco;
end;

Mientras se ejecuta el ejemplo Blcons, se pueden fijar y eliminar facilmente


10s diversos elementos visuales del borde del formulario. Enseguida se vera que
algunos de estos elementos se encuentran intimamente relacionados. Si elimina-
mos el menu de sistema, desapareceran todos 10s iconos del borde. Si eliminamos
el boton de minimizar o el de masimizar, se pondran en gris. Si eliminamos ambos
botones, desapareceran. Fijese en que ademas, en estos dos ultimos casos, 10s
elementos correspondientes del menu de sistema se desactivan automaticamente.
Este es el comportamiento estandar de cualquier aplicacion Windows. Cuando se
han desactivado 10s botones de maximizar y minimizar, se puede activar el boton
de ayuda. Actualmente en Windows 2000, si solo se ha inhabilitado uno de 10s
botones de maximizar o minimizar, aparecera cl boton de ayuda per0 no sera
funcional. Como metodo abreviado para conseguir este efecto, se puede hacer clic
sobre el boton que esta dentro del formulario. Ademas, tambien se puede hacer
clic sobre el boton despues de habcr hecho clic sobre el icono del menu de ayuda
para que aparezca un mensaje de ayuda, como muestra la figura 7.3. Como fun-
cion adicional, el programa muestra tambikn en el titulo el momento en que se
activa la ayuda, controlando el evento O n H e l p del formulario.

Figura 7.3. El ejemplo Blcons. Al seleccionar el icono de ayuda dt borde y hacer clic
sobre el boton. aparece la ayuda.

rn
ADVERTENCIA: Si se analiza la versi6n QBIcons, creada con CLX, se
puede comprobar que un fallo de la biblioteca impide modificar 10s iconos
de 10s bordes en tiempo de ejecuci6n. Las distintas configuraciones en tiem-
po de diseiio h n c i o n a r b completamente, asi que sera necesario modificar
el programa antes de ejecutarlo si se quiere ver algun tipo de efecto. Este
programa no hace nada en tiempo de ejecucion.

Definicion de mas estilos de ventana


El cstilo y 10s iconos del borde estan indicados en dos propiedades de Delphi
distintas, quc sc pueden utilizar para filar el valor inicial de 10s correspondientes
clementos de la interfaz de usuario. Ademas de cambiar la interfaz de usuario, las
propiedades de 10s iconos de borde afectan a1 comportamiento de la ventana. Es
importante saber que en la VCL (y obviamente no en la CLX), estas propiedades
relacionadas con el borde y con la propiedad Formstyle corresponden princi-
palmente a diferentes configuracioncs del "estilo" y del "estilo ampliado" de la
ventana. Estos dos terminos reflejan dos parametros de la funcion create-
WindowEx de la API que Delphi usa para crear formularios.
Es importante saber esto, porque Delphi permite modificar estos dos parametros
libremente, sobrescribiendo el n~etodovirtual createparams:
p u b 1 ic
procedure CreateParams ( v a r Params: T C r e a t e P a r a m s ) ; override;

Este es el unico mod0 de usar 10s peculiares estilos de ventana que no cstan
directamente disponibles mediante las propiedades del formulario. Para ver una
lista de 10s estilos y estilos ampliados de ventana, se pueden estudiar en la ayuda
de la API temas como "CreateWindowMy "CreateWindowEs". Se vera que la API
de Win32 tiene estilos para estas funciones, incluidos aquellos relacionados con
las ventanas de herramientas.
Para mostrar la utilization de este metodo, hemos creado el ejemplo NoTitle,
que pcrmite crear un programa con un titulo personalizado. Primero tenemos que
eliminar el titulo estandar. per0 mantener el marco que permite ajustar el tamaiio,
definiendo 10s estilos correspondientes:
p r o c e d u r e TForml.CreateParams ( v a r Params: T C r e a t e P a r a m s ) ;
begin
i n h e r i t e d C r e a t e P a r a m s ( P a r a m s );
P a r a m s . S t y l e : = (Params.Style or ws-Popup) a n d n o t ws-Caption;
end;

Para eliminar el titulo, es necesario cambiar el estilo solapado por un estilo


contextual, puesto que de otro modo, el titulo se quedara adherido. Para aiiadir un
titulo personalizado, hemos colocado una etiqueta alineada con el borde superior
del formulario y un pequeiio boton en la esquina superior. Se puede ver en tiempo
de ejecucion en la figura 7.4

Dmg Oh ba to move widow

Figura 7.4. El ejemplo NoTitle no posee un titulo real sin0 uno falso creado con una
etiqueta.

Para que el titulo falso funcione, debemos decirle a1 sistema que una operacion
dc raton en esta zona se corresponde con una operacion de raton sobre el titulo.
Para ello, sc puede interceptar el mensaje de Windows wm N C H i t T e s t , que
normalmente se le envia a Windows para establecer el lugar en el que esti en ese
momcnto cl raton. Cuando la accion de pulsado se realiza en la zona del cliente y
cn la etiqueta, podemos simular que el raton esta sobre el titulo definiendo el
rcsultado adecuado:
procedure TForml.HitTest ( v a r Msg: TWrnNCHitTest);
// mensaje w m _ N c H i t T e s t
begin
inherited;
i f (Msg.Result = htclient) and
(Msg.YPos < Labell.Height + Top + GetSystemMetrics
(sm-cyFrame) ) then
Msg.Result : = htcaption;
end;

La funcion G e t s y s temMetrics de la API utilizada en el listado anterior


es una consulta al sistema operativo sobre el grosor vertical ( c y ) en piseles del
borde que hay alrededor de una ventana con un titulo no redimensionable. Es
importante realizar esta peticion cada vez porque 10s usuarios pueden personali-
zar la mayoria de estos elementos utilizando la ficha Apariencia de las opciones
Propiedades de pantalla (en el Panel de Control) y otros parametros de
Windows.
En cambio, el boton pequeiio tiene una llamada a1 metodo C l o s e en su con-
trolador del evento O n c l i c k . El boton se mantiene en su posicion aunque se
ajuste el tamafio de la ventana, usando el valor LakTop, a k R i g h t ] para la
propiedad A n c h o r s .
El formulario tiene tambien restricciones de tamaiio, para que un usuario no
pueda reducirlo en exceso.
Entrada directa en un formulario
Pasaremos ahora a tratar un tema muy importante: la entrada del usuario en un
formulario. Si decidimos hacer un uso limitado de 10s componentes, podriamos
escribir tambien programas complejos que reciban la entrada del raton y del teclado.

Supervision de la entrada del teclado


Por lo general, 10s formularios no controlan directamcnte la cntrada del tecla-
do. Si un usuario tiene que teclear algo, el formulario deberia incluir un compo-
nente de edicion u otro componente de entrada. Si queremos controlar 10s metodos
abreviados del teclado, se pueden usar 10s que estan conectados a menus (utili-
zando probablemente un menu contestual oculto).
Sin embargo, otras veces para controlar la entrada de un mod0 especifico para
un proposito particular se puede activar la propiedad KeyPreview del formula-
rio. Entonces, aunque tengamos algunos controles de entrada, el evento
OnKeyPress del formulario se activara siempre para cada operacion de entra-
da mediante el teclado (except0 las teclas de metodo abreviado y de sistema). La
entrada de teclado llegara entonces hasta el componente destino, a menos que se
detenga en el formulario, fijando el valor del caracter como cero (no el caracter 0,
sino el valor 0 del conjunto de caracteres, indicado como #O).
El ejemplo creado para ilustrar esta tecnica es KPreview, que ticne un formu-
lario sin propiedades especiales (ni siquiera KeyPreview), un grupo de radio
con cuatro opciones y algunos cuadros de edicion, como se muestra la figura 7.5.

. . . . . . . Aid,
..
. .
..
..
..
.... . . . . . . . . . . . . . . . . .
: , I '- ,"On= . . . .l~dill
......................
. .
. .
..I
.. ::::I
. , . . Edit2
.
. . . . . . . .
. . . . . . . . . . .
. .
.. .... . . . . . . . . . . .
, .... . . . . . . . . . . . . . . . .
, . . , Edit3 . . . . . .
.... . . . . . . . . . . . .
I . . . . . . . . .: : : : : . : : : : . . .
I . . . . .
.. . . . .
.. I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Figura 7.5. El programa KPreview en tiempo de diseiio.

Por defect0 el programa no hace nada especial, a escepcion de que 10s diversos
botones de radio que se usan para activar la vista previa de la tecla:
procedure TForml.RadioPreviewClick(Sender: TObject);
begin
KeyPreview : = RadioPreview.ItemIndex <> 0;
end;
Ahora empezaremos a recibir 10s eventos OnKeyPress y podremos realizar
una de las tres acciones solicitadas por 10s tres botones especiales del grupo de
radio. La accion depende del valor de la propiedad ItemIndex del componente
grupo de radio. Esta es la razon por la que el controlador de eventos se basa en
una sentencia case:
p r o c e d u r e TForml.FormKeyPress(Sender: TObject; var Key: Char);
begin
c a s e RadioPreview.Item1ndex of

En el primer caso, si el valor del parametro Key es #13, valor que correspon-
de a la tecla Intro, desactivamos la operacion (definiendo Key como cero) y, a
continuacion, imitamos la activacion de la tecla Tab. Existen muchas formas de
hacer esto, per0 en este caso hemos escogido una bastante particular. Hemos
enviado el mensaje CM -DialogKey al formulario, pasando el codigo para la
tecla Tab (VK-TAB):
1: // I n t r o = T a b
i f Key = #13 t h e n
begin
Key : = #O;
Perform (CM-DialogKey, VK-TAB, 0 );
end;

documentado. Existen unos cuantos de estos memsajes, y resulta bastante


: interesante
- .- .
-- --- .-. - - - ..- ir componentes avanzados parra cllos y usarlos para rea-
.- constru
--

lizar una codificaicibn especial, pero Borland ja d s 10s ha descrito. Hay


que resaltar que este estilo exacto de codificaciclu u a ~ a GUa lucuaqsJca uu
esta disponible bqjo CLX.

Para escribir texto en el titulo del formulario mediante el teclado, el programa


aiiade sencillamente el caracter a1 Caption actual. Existen dos casos especiales
mas. Cuando se pulsa la tecla Retroceso, se elimina el ultimo caracter de la
cadena (a1 copiar al Caption todos 10s caracteres del Caption actual menos
el ultimo). Cuando se pulsa la tecla Intro, el programa detiene la operacion,
redefiniendo la propiedad ItemIndex del control del grupo de radio. Veamos el
codigo:
2 : // e s c r i b i r e n e l t i t u l o
begin
i f Key = # 8 t h e n // r e t r o c e s o : e l i m i n a r u l t i m o c a r a c t e r
Caption : = Copy (Caption, I, Length (Caption) - 1)
e l s e i f Key = #13 t h e n // i n t r o : d e t i e n e la o p e r a c i o n
RadioPreview.ItemIndex : = 0
e l s e // o t r a c o s a : a d a d e c a r a c t e r
Caption : = Caption + Key;
Key : = #0;
end:

Por ultimo, si sc selecciona el ultimo elemento de radio, el codigo verifica si el


caracter es una vocal (buscando su inclusion en un "conjunto de vocales" fijo). En
cste caso, el caracter se omitc:
3 : / / omite las vocales
if Key i n ['A', 'E', ' I t , 'O', 'U'] then
Key : = # O ;

Obtener una entrada de raton


Cuando un usuario hace clic con uno de 10s botones del raton sobre un formu-
lario (o sobre un componente), Windows envia a la aplicacion algunos mensajes.
Delphi define algunos eventos que sc pueden utilizar para escribir codigo que
responda a dichos mensajcs. Los dos cvcntos basicos son OnMouseDown,reci-
bid0 cuando se pulsa un boton del raton, y OnMouseUp, recibido cuando se
suelta cl boton. Otro mensaje fundamental del sistema csta relacionado con un
movimiento del raton. el evento es OnMouseMove.Aunque deberia ser sencillo
comprender cl significado de 10s tres mensajes (abajo. arriba y mover), podriamos
preguntarnos como se relacionan con el evento OnClick utilizado hasta ahora.
Hemos utilizado el evento OnClic k para 10s componentes, pero tambien esta
disponible para el formulario. Su significado general es que el boton izquierdo del
raton se ha pulsado y soltado en la misma ventana o componente. Sin embargo,
entre las dos acciones. el cursor podria haberse movido fuera de la zona de la
ventana o componente, mientras sc mantenia pulsado el boton izquierdo del raton.
Otra diferencia entre 10s eventos OnMouseXX y OnClick consiste en que el
ultimo se refiere so10 al boton izquierdo del raton. La mayoria de 10s ratones
conectados a un PC con Windows tienen dos botones dc raton y algunos incluso
tres. Normalmente se hace referencia a estos botones como boton izquierdo (el
que se suele utilizar para las selecciones), boton derecho (para acceder a 10s
menus contextuales) y boton central (quc se usa rara vez).
% .

Uso de Windows sin ratdn


Un usuario deberia se siempre capaz de utilizar cualquier aplicacion de
Windows sin el raton. No se trata de una opcion; es una regla de la progra-
maci6n para Windows. Por supuesto, una aplicacion podria resultar m b
ficil de Lsar con un raton, jambs deberia ser una obligaci6n. Algunos
usuarios puede que no tengan conectado un ratrjn, como la gente que viaja
.n..nhr, ,
IIIULUV w-
u, ..-
-a,..,SL.
wpqu~uv
-AAX+;I
yv~ulur ry. n31:1r1 a
r..srnr
paw Gs n r r A r , +rrL....:..Ar-a,
~ J ~ Wu
,
Va. u a j a u w ~
,altr-r,,
G~~ I
GIILVIIIVD

industrides y empleados de Lianca con muchos otros perifericos a su alre-


I dedor.
&
m- I
- - ~

oars sop&.a;-
el uso 6el teclado' usar if ratbn es& '
bien, per0 suele ser m b lento. Si se es habil con el teclado, no se querra
utilizar el raton para arrastrar una palabra de un texto; se utilizarb las
teclas de rn~toddabreviado para copiar y pegar el texto sin separar las
manos del teclado.
-Par estas razones, siempre deberia establecer un orden de tabulacion co-
rrecto para 10s componentes de un formulario. Hay que recordar aiiadir
teclas para 10s botones y para acceder a 10s elementos de menu mediante el
--
.^^l^l^&:I:-.... .^^I^- -1- -A*^*-
LGGI~UU, U I - w ~ a rL C G I ~ S UG IIIGLVUU
-L-.^-.:^l^ ---- --^:^-^-
~ U I G V I ~ U U para U~GIUIIGS
J^ ---- L -.
uc IIIG~U y w s a s
asi.

Los parametros de 10s eventos de raton


Todos 10s eventos del raton de bajo nivel poseen 10s mismos parametros: el
habitual parametro Sender, un parametro Butt o n que indica cual de 10s tres
botones de raton se ha pulsado (mbRight, m b L e f t o m b c e n t e r ) , el parametro
Shift que indica cual de las teclas relacionadas con el raton (Alt, Control y
Mayus, ademas de 10s tres botones) se encontraba pulsada cuando ocurrio el
evento y las coordenadas x e y de la posicion del raton, en las coordenadas de la
zona de clicnte de la ventana actual.
Utilizando dicha informacion, es muy sencillo dibujar un pequeiio circulo en la
posicion de un evento de pulsacion del boton izquierdo del raton:
.
procedure TForml FormMouseDown (Sender: TObj ect;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = &Left then
Canvas . E l l i p s e (X-10, Y-10, X+10, Y+10) ;
end;

NOTA: Para dibujar en el formulario, usamos una propiedad muy espe-


,+,I.
V'UI. bC111VC1a. "L I T ? obieto TCanvas tiene dos caracteristicas distintivas:

contiene una colec ion de herramientas de dibujo (como un lhpiz, una bro-
cha y una fuente) Y posee algunos metodos de dibujo, que usan las herra-
,-,..,I,, I A, f
:L
..
:, A, - ,,,
w u a l c a . El L I ~ U uo ~uulgu
,
a
,
,,
:
, b:,, A, -LA:-- A:,-+, ,:-,Ih
~ l u c u ~ ia
r s uc u v u j u UIIGGLU uc csw GJGIU~IU uu GS
correcto, porque la imagen en pantalla no es pennanente: a1 mover otra
ventana sobre la actual se eliminara su efecto.

Arrastrar y dibujar con el raton


Para demostrar algunas de las tecnicas de raton esplicadas hasta ahora, hemos
creado un ejemplo sencillo basado en un formulario sin ningun componente, lla-
mado MouseOne en la version VCL y QMouseOne en la version CLX. Muestra
en el titulo del formulario la posicion actual del raton:
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer);
begin
// r n u e s t r a l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x=%d, y = % d l , [ X , Y]) ;
end;

Se puede usar esta caracteristica del programa para entender mejor como fun-
ciona el raton. Se puede hacer esta prueba: se ejecuta el programa (esta version
simple o la version completa) y se ajusta el tamafio de las ventanas del escritorio
para que el formulario del programa MouseOne o QMouseOne quede detras de
otra ventana e inactivo per0 con el titulo visible. Si ahora se mueve el raton sobre
el formulario, se podra ver que las coordenadas cambian. Este comportamiento
significa que se envia el evento OnMouseMove a la aplicacion incluso aunque su
ventana no se encuentre activa, y demuestra lo ya comentado: 10s mensajes de
raton siempre se dirigen a la ventana que se encuentra bajo el cursor del raton. La
unica excepcion a esto es la operacion dc captura de raton que enseguida comen-
taremos .
Adcmas de mostrar la posicion en cl titulo de la ventana, el ejemplo Mouseonel
QMouseOne puede realizar un seguimiento de 10s movimientos del raton, pintan-
do pequeiios pixeles en el formulario si el usuario mantiene pulsada la tecla Mayus.
(De nuevo estc codigo de dibujo directo produce un resultado no permanente.)
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer);
begin
// rnuestra l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x = % d , y = % d l , EX, Y]) ;
if ssShift in Shift then
// m r c a p u n t o s e n a m a r i l l o
Canvas.Pixels [ X I Y] : = clYellow;
end ;

6 no incluye una matriz Pixels. En carnbio, se puede llarnar a1 mttodo


. - como hemos
Drawpoint tras haber fijado el color adecuado para el Iaviz.
becho en el ejemplo QMouseOne. Kylix 2 y Dellphi 7 vuelven a introducir
la propiedad de matriz Pixels.
-

Sin embargo, la caracteristica mas importante de este ejemplo es el soporte de


arrastre directo del raton. A1 contrario de lo que se podria pensar, Windows no
posee soporte del sistema para el arrastre, que esta implementado en la VCL
mediante 10s eventos y operaciones de raton de bajo nivel. En la VCL, 10s formu-
larios no pueden originar operaciones de arrastre, por lo que en este caso tendre-
mos que usar una tecnica de bajo nivel. El objetivo de este ejemplo consiste en
dibujar un rectangulo desde la posicion original de la operacion de arrastre a la
final, aportando a 10s usuarios indicaciones visuales sobre la operacion que estan
realizando. La idea que se encuentra tras la operacion de arrastre es sencilla. El
programa recibe una secuencia de mensajes sobre la pulsacion del boton, su mo-
vimiento y el soltado del boton. Cuando se pulsa el boton, comienza el arrastre,
aunque las acciones ocurren en realidad solo cuando el usuario mueve el raton
(sin soltar el boton del raton) y cuando termina el arrastre (cuando llega el mensa-
je de soltado del boton). El problema de esta tecnica basica es que no es fiable.
Una ventana normalmente recibe eventos solo cuando el raton esta sobre la zona
de cliente, por lo que si el usuario pulsa el boton del raton, mueve el raton sobre
otra ventana y, a continuacion, suelta el boton, la segunda ventana recibira el
mensaje de soltado del boton.
Existen dos soluciones a este problema. Una (poco usada) es el recorte del
raton. Utilizando una funcion de la API de Windows (Clipcursor), se puede
obligar a que el raton no abandone una cierta zona de la pantalla. Cuando intenta-
mos moverlo fuera de dicha zona, choca contra una barrera invisible. La segunda
solution, mas comun, consiste en capturar el raton. Cuando una ventana captura
el raton, todas las entradas de raton subsiguientes se envian a dicha ventana. Esta
es la tecnica que utilizaremos en el ejemplo MouseOne/QMouseOne.
El codigo del ejemplo se organiza en torno a tres metodos: FormMouseDown,
FormMouseMove y FormMouseUp. A1 pulsar con el boton izquierdo del ra-
ton sobre el formulario comienza el proceso, activando el campo booleano
fDrag g ing del formulario (que indica que el arrastre esta activo dentro de 10s
otros dos metodos). El metodo usa una variable TRect para realizar un segui-
miento de la posicion inicial y la actual de arrastre. Veamos el codigo:
procedure TMouseForm.FormMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
i f Button = mbLeft then
begin
£Dragging : = True;
Mouse.Capture : = Handle;
fRect.Left : = X;
fRect.Top : = Y;
fRect-BottomRight : = fRect.TopLeft;
dragstart : = fRect.TopLeft;
Canvas .DrawFocusRect (fRect);
end ;
end ;

Una accion importante de este metodo es la llamada a la funcion setcapture


de la API, obtenida a1 activar la propiedad Capture del objeto global Mouse.
Ahora, aunque el usuario mueva el raton fuera de la zona de cliente, el formulario
recibira igualmente todos 10s mensajes relacionados con el raton. Se puede com-
probar moviendo el raton hacia la esquina superior izquierda de la pantalla. El
programa muestra coordenadas negativas en el titulo.
-

TRUCO: El objeto global Mouse permite obtener infonnaci6n global so-


bre el rat6n, como su presencia, tipo y posicibn actudes, ademh & definir
algunas de sus caracteristicasglobales. Este objeto global oculta unas cuan-
tas funciones de la API, que simplifican el c6digo y lo hacen de miis fhcil
adaptation. En la VCL, la propiedad Capture es de tipo Handle, mien-
tras que en Iet'CLX es de tipo TControl (el objeto del componente que
captura d rat6n). Por eso, el c6digo de esta secci6n se convertirh en
Mouse.Capture := self, como se puede cornprobar en el ejemplo
QMoweOnc.

Cuando el arrastre esta activo y el usuario mueve el raton, el programa dibuja


un rectangulo con una linea de puntos que se corresponde a la posicion del raton.
En realidad, el programa llama a1 metodo D r a w F o c u s R e c t dos veces. La pri-
mera vez que se llama a este mktodo, borra la imagen actual, gracias a que dos
Ilamadas consecutivas a D r a w F o c u s Rec t recuperan la situacion original. Des-
puts de actualizar la posicion del rectangulo, el programa llama a1 metodo por
segunda vez:
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer) ;
begin
// rnuestra l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x = % d , y = % d ' , [X, Y]) ;
i f fDragging then
begin
// elirnina y d i b u j a d e n u e v o e l r e c t i n g u l o d e a r r a s t r e
Canvas.DrawFocusRect (fRect);
i f X > dragStart.X then
fRect.Right := X;
else
fRect.Left : = X;
i f Y > dragStart.Y then
fRect.Bottom : = Y;
else
fRect.Top : = Y;
Canvas.DrawFocusRect ( f R e c t ) ;
end
else
i f ssShift i n Shift then
// marca 1 0 s p u n t o s e n a m a r i l l o
Canvas .Pixels [ X , Y] : = clYellow;
end;
En Windows 2000 (y otras versiones), la funcion D r a w F o c u s R e c t no
dibuja rectangulos con un tamaiio ncgativo, asi que el codigo del programa se
ha preparado para comparar la posicion actual con la posicion inicial del
arrastre, guardada en el punto d r a g s t a r t . Cuando se suelta el boton del
raton, el programa termina la operacion de arrastre redefiniendo la propiedad
C a p t u r e del objeto M o u s e , que llama internamente a la funcion
R e l e a s e c a p t u r e de la API y definiendo el valor del campo fD r a g g i n g
como F a l s e :
procedure TMouseForm.F'ormMouseUp(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if fDragging then
begin
Mouse.Capture : = 0; / / l l a m a a R e l e a s e C a p t ~ i r e
fDragging : = False;
Invalidate;
end ;
end ;

La llamada final, I n v a l i d a t e , desencadena una operacion de pintado y


ejecuta el siguiente controlador de eventos O n p a i n t :
procedure TMouseF'orm.FormPaint(Sender: TObject);
begin
C a n v a s - R e c t a n g l e (fRect.Left, fRect.Top, f R e c t - R i g h t ,
fRect .Bottom) ;
end;

Esto transforma el resultado del formulario en permanente, aunque se oculte


tras otro formulario. En la figura 7.6 aparece la version anterior del rectangulo y
una operacion de arrastre en marcha.

Figura 7.6. El ejernplo MouseOne usa una linea de puntos para dibujar, durante la
operacion de arrastre, un rectangulo.
Pintar sobre formularios
~ Q u Ces lo que hace necesario controlar el evento onpaint para producir un
resultado correcto, y por que no se puede dibujar directamente sobre la superficie
del formulario? Depende del comportamiento predefinido de Windows. A1 pintar
sobre una ventana, Windows no almacena la imagen resultante. Cuando se cubre
la ventana, normalmente se pierde su contenido.
La razon de este comportamiento es sencilla: el almacenamiento en memoria.
Windows asume que resulta "mas barato" a largo plazo dibujar de nuevo la ven-
tana mediante codigo que dedicar la memoria de sistema a conservar el estado de
una ventana. Se trata de un compromiso clasico entre la memoria y 10s ciclos de
CPU. Un mapa de bits a color de una imagen a 600x800 en 256 colores necesita
unos 480 KB. A1 aumentar la calidad del color o el numero de pixeles, se pueden
alcanzar facilmente 10s 4 MB de memoria para una resolucion de l28Ox 1024 con
16 millones de colores.
En caso de que se quiera tener una salida coherente en las aplicaciones, se
pueden usar dos tecnicas. La solucion general consiste en almacenar suficientes
datos sobre la salida para poder reproducirla cuando el sistema realiza una solici-
tud para pintar. Una tkcnica alternativa consiste en guardar la salida del formula-
rio en un mapa de bits mientras se crea, colocando un componente Image sobre
el formulario y dibujando sobre el lienzo de este componente imagen.
La primera tecnica, pintar, es la tecnica comun para controlar la salida en la
mayoria de 10s sistemas de ventanas, a parte de 10s programas orientados a grafi-
cos especificos que almacenan la imagen del formulario completa en un mapa de
bits. La tecnica utilizada para implementar el pintado tiene un nombre muy des-
criptivo: almacenar y pintar. Cuando el usuario pulsa un boton del raton o realiza
alguna otra operacion, hay que almacenar la posicion y otros elementos. A conti-
nuacion, en el metodo de pintado, se usa esta informacion para pintar la imagen
correspondiente.
La idea de esta tecnica es permitir que la aplicacion pinte de nuevo su superfi-
cie completa en cualquier situacion posible. Si ofrecemos un metodo para dibujar
de nuevo el contenido del formulario y se llama a1 metodo automaticamente cuan-
do se ha ocultado una parte del formulario y es necesario pintarla de nuevo,
podremos volver a crear la salida de forma adecuada.
Como esta tecnica se realiza en dos etapas, debemos ejecutar esas dos opera-
ciones seguidas, solicitando a1 sistema que pinte de nuevo la ventana (sin esperar
a que el sistema lo pida). Se pueden usar diversos metodos para originar un nuevo
pintado: Invalidat e l Update, Repaint y Refresh. Los dos primeros
se corresponden a las funciones de la API de Windows, mientras que el ultimo lo
ha introducido Delphi:
El mttodo Invalidate: Informa a Windows de que hay que volver a pintar
la superficie total del formulario. Lo mas importante es que Invalidate
no desencadena una operacion de pintado de forma inmediata. Windows
almacena simplemente la solicitud y respondera solo despues de que se
haya ejecutado por completo el procedimiento actual (a no ser que se llame
a Application. ProcessMessages oUpdate) y e n cuanto no haya
otros eventos pendientes en el sistema. Windows retrasa deliberadamente
la operacion de pintado porque es una de las operaciones para las que mas
tiempo se necesita. A veces, con dicho retraso, solo es posible pintar el
formulario despues de que se hayan producido diversos cambios, lo cual
evita que haya muchas llamadas consecutivas a1 metodo (lento) de pintar.
El mCtodo Update: Solicita a Windows que actualice el contenido del
formulario, pintandolo de nuevo inmediatamente. Sin embargo, hay que
recordar que esta operacion se realizara solo si existe una zona no valida.
Esto ocurre si se acaba de llamar a1 metodo Invalidate o como resul-
tad0 de una operacion realizada por el usuario. Si no existe una zona no
valida, una llamada a Update no tiene ningun efecto. Por esa razon, es
frecuente ver una llamada a Update inmediatamente despues de una Ila-
mada a Invalidate.Como hacen exactamente 10s dos mktodos de Delphi:
Repaint y Refresh.
El mCtodo Repaint: Llama a Invalidate y, a continuacion, a Updat e.
Como consecuencia, activa el evento onpaint inmediatamente. Existe
una version ligeramente diferente de este metodo denominada Refresh.
El hecho de que existan dos metodos para la misma operacion se remonta a
10s dias de Delphi 1, cuando ambos eran sutilmente distintos entre si.
Cuando hay que pedir a1 formulario que se vuelva a pintar, normalmente debe-
riamos llamar a Invalidate,siguiendo el enfoque estandar de Windows. Esto
es importante sobre todo cuando hay que solicitar dicha operacion con bastante
frecuencia, porque si Windows emplea demasiado tiempo en actualizar la panta-
lla, las solicitudes de pintado se pueden acumular en una sencilla accion de pinta-
do. El mensaje wm Paint de Windows es un mensaje de baja prioridad; si hay
una solicitud pend&e, per0 hay mas mensajes esperando, 10s otros mensajes se
controlan antes de que el sistema lleve a cab0 la accion de pintado.
Por otra parte, si se llama varias veces a1 metodo Repaint, habra que volver
a pintar la pantalla cada vez antes de que Windows pueda procesar otros mensa-
jes y debido a que para las operaciones de pintado se realizan calculos de forma
exhaustiva, la capacidad de respuesta de la aplicacion sera menor. Sin embargo.
si queremos que la aplicacion pinte de nuevo una superficie lo antes posible hay
que llamar a Repaint .

NOTA: Otra consideracih imprtantq es que durante una.operacih de


pintado Windows vuelve a dibujs solo b denominada 'ked6riactudii-
dam,para acelerar dicha uperaeib. Pbr es& i$i;bn, si aiempre se inv562ida
m a part"eTE'~iniimil, jintara de nuevo dicha z o h . Para ello, se
pueden usar las funciones Inva:
En realidad, esta funci6n es un a m ae oome n ~ o a. s una recnlca muy
potente, que puede mejorar la velocidad y reducir el Imrpadeo causado por
operaciones de pintado frecuentes. Por otra parte, tam b i h puede originar
.. ,
una salida incorrecta. Un problema bastante comun consiste en que solo se
.
modifican en realidad algunas de las zonas afectadas pcIr las operaciones de
usuario, mientras que otras siguen como estaban aunql~eel sistema ejecute
el codino fuente aue su~uestamentedeberia actualizarl~.51
- ~- -
!.Ac: .
, ,
,.
,,
,a
,,
UIU vywawvu
.,
de pintho se da &era de la regi6n actualizada, el r:istermla ignora, como si
estuviera fuera de la zona visible de la ventana.

Tecnicas inusuales: Canal Alpha, Color Key y


la API Animate
Una de las caracteristicas mas recientes de Delphi relacionada con 10s formu-
larios es el soporte de algunas nuevas API de Windows que afectan a1 mod0 en
que se muestran 10s formularios (en Windows 2000/XP, per0 no disponibles en
QtICLX). En el caso dc un formulario, la tecnica de Canal Alpha permite
mezclar el contenido de un formulario con lo que se encuentra tras el en pantalla,
algo que rara vez resulta necesario, a1 menos en aplicaciones empresariales. La
tecnica es realmente mas interesante cuando se aplica a un mapa de bits (con las
nuevas funciones AlphaBlend y AlphaDIBBlend de la API) que en el caso
del propio formulario. En cualquier caso, a1 establecer la propiedad AlphaBlend
de un formulario como True y proporcionar a la propiedad ~1phaBlendValue
un valor inferior a 25 5 . se puede ver, en transparencia, lo que esta tras el formu-
lario. Cuanto menor sea el valor de AlphaBlendValue,mas transparente sera
el formulario. Se puede ver un ejemplo de la tecnica alpha blending en la figura
7.7. tomada a partir del ejemplo CkKeyHole.
Otra nueva caracteristica de Delphi es la propiedad booleana Transpa-
rentcolor,que permite indicar un color transparente, que sera sustituido por
el fondo, creando una especie de agujero en un formulario. El color transparente
lo indica la propiedad TransparentColorValue.De nuevo, se puede ver un
ejemplo de este efecto en la figura 7.7. Por ultimo, se puede usar una tecnica
originaria de Windows, la animacion de pantalla, que Delphi no soporta directa-
mente (mas alla de la presentacion de sugerencias). Por ejemplo, en lugar de
llamar a1 metodo show de un formulario, se puede escribir:
Form3.Hide;
Animatewindow (Form3.Handle, 2000, AW-BLEND);
Form3.Show;
Figura 7.7. El resultado de CkKeyHole, que rnuestra el efecto de las nuevas
propiedades Transparentcolor y AlphaBlend y de la API AnimateWindow.

Fijese en que hay que llamar a1 metodo show a1 final para que el comporta-
miento del formulario sea el adecuado. Tambien se puede conseguir un efecto de
animation similar al modificar la propiedad AlphaBlendValue en un bucle.
La API AnimateWindow se puede usar tambien para controlar el mod0 en que
se presenta el formulario: empezando desde el centro (con el indicador A w -
C E N T E R ) o desde uno de sus lados (AW H O R P O S I T I V E , AW -H O R -
NEGATIVE, AW V E R POSITIV, o AW VER NEGATIVE).
Esta misma fuhci6nie puede aplicar tambitin-a 10s controles de ventana para
darles un aspect0 transparente en lugar de su habitual apariencia directa. No
queda muy claro el gasto de ciclos de CPU que causan estas animaciones, pero si
se aplican correctamente y en el programa apropiado, pueden mejorar la interfaz
de usuario.

Posicion, tamaiio, desplazamiento y ajuste


de escala
En Delphi, tras haber diseiiado un formulario, se ejecuta el programa y se
espera que el formulario aparezca exactamente del mod0 en que se ha preparado.
Sin embargo, un usuario de la aplicacion podria usar una resolucion de pantalla
diferente o querer ajustar el tamaiio del formulario (si es posible, segun el estilo
del borde), lo cual afectaria en ultimo tkrmino a la interfaz de usuario. Ya hemos
comentado algunas tecnicas relacionadas con 10s controles, como la alineacion y
el anclaje.
Ademas de las diferencias en el sistema del usuario, existen muchas razones
para querer cambiar las configuraciones predefinidas de Delphi en este sentido.
Por ejemplo, tal vez queramos ejecutar dos copias del programa y evitar todos 10s
formularios aparezcan en el mismo lugar. De ello hablaremos a continuacion.
La posicion del formulario
Existen una serie de propiedades que se pueden usar para definir la posicion de
un formulario. La propiedad Position indica el mod0 en que Delphi determina
la posicion inicial del formulario. El valor predefinido poDes igned indica que
el formulario aparecera en el lugar en el que se diseiia y en el que se usan las
propiedades de posicion (Left y Top) y de tamaiio (Width y Height) del
formulario. Otras opciones (poDefault, poDef ault PosOnly y poDe-
fault Sizeon1y) dependen de una caracteristica del sistema operativo: usan-
do un indicador especifico, Windows puede establecer la posicion y/o tamaiio de
nuevas ventanas utilizando una disposicion en cascada. De este modo, las propie-
dades de posicion y tamaiio que definamos en tiempo de diseiio seran ignoradas,
per0 si se ejecuta la aplicacion dos veces, las ventanas no se solaparan. Cuando el
formulario tiene un estilo de borde de dialogo, se ignoran las posiciones
predefinidas. Por ultimo, con el valor po Screence nter,el formulario apare-
cera en el centro de la pantalla, con el tamaiio que definamos en tiempo de diseiio.
Se trata de una configuracion muy comun para cuadros de dialogo y otros formu-
larios secundarios. Otra propiedad que afecta a1 tamaiio y posicion iniciales de
una ventana es su estado. Se puede usar la propiedad Windowstate en tiempo
de diseiio para que una ventana aparezca maximizada o minimizada a1 iniciar.
Esta propiedad solo puede tener tres valores: wsNormal , wsMinimized y
wsMaximi ze d. Si se define un estado de ventana minimizada, a1 arrancar el
formulario aparecera en la barra de tareas de Windows. En el caso del formulario
principal de una aplicacion, esta propiedad se puede definir automaticamente a1
especificar 10s atributos correspondientes en un metodo abreviado que se refiera a
la aplicacion. Por supuesto, tambien se puede maximizar o minimizar una venta-
na en tiempo de ejecucion. Simplemente cambiando el valor de la propiedad
Windowstate a wsMaximized o wsNormal se produce el efecto esperado.
Sin embargo, definir la propiedad como wsMinimized crea una ventana mini-
mizada que se coloca sobre la barra de tareas, no en su interior. Esta no es la
accion esperada en el caso de un formulario principal, per0 si en el caso de uno
secundario. Una solucion sencilla a este problema consiste en llamar a1 metodo
Minimize del objeto Application.Tambien existe un metodo Restore en
la clase TApplication que se puede usar cuando es necesario restaurar el
tamaiio de un formulario, aunque normalmente el usuario realizara esta operacion
mediante la orden Restore del menu de sistema.

Ajuste a la ventana (en Delphi 7)


Los formularios tienen dos nuevas propiedades en Delphi 7:
La propiedad booleana Screensnap: Determina si el formulario deberia
ajustarse (como atraido por un iman) a1 area de representacion de la panta-
lla cuando se encuentre cerca de uno de sus bordes.
La propiedad entera SnapBuffer: Determina la distancia con respecto a
10s bordes que se considera cercana. Aunque no es una caracteristica par-
ticularmente vistosa, es practica para permitir que 10s usuarios ajusten 10s
formularios en un lateral de la pantalla y aprovechen toda la superficie de
la pantalla; resulta particularmente practico para aplicaciones con multi-
ples formularios visibles a1 mismo tiempo. Pero hay que tener cuidado y no
usar un valor demasiado grande para la propiedad SnapBuff e r (algo
tan alto como toda la pantalla) o se confundira a1 sistema.

El tamafio de un formulario y su zona de cliente


En tiempo de diseiio, existen dos formas de definir el tamaiio de un formulario:
dcfiniendo el valor de las propiedades W i d t h y H e i g h t o arrastrando sus bor-
des. En tiempo de ejecucion, si el formulario posee un borde con un tamaiio
ajustable, el usuario puede modificarlo (originando el evento OnResize. en el
que se pueden realizar acciones personalizadas para adaptar la interfaz de usua-
rio a1 nuevo tamaiio del formulario).
Sin embargo, si miramos a las propiedades de un formulario en el codigo
fuente o en la ayuda electronica, se puede ver que hay dos propiedades que se
refieren a su ancho y otras dos que se refieren a la altura. H e i g h t y W i d t h se
refieren a1 tamaiio del formulario, incluidos sus bordes. C l i e n t H e i g h t y
C l i e n t W i d t h hacen referencia al tamaiio de la zona interna del formulario,
excluyendo bordes, titulo, barras de desplazamiento (si las hay) y barra de menu.
La zona de cliente del formulario es la superficie que se puede usar para colocar
coinponentcs en el formulario, para crear la salida y para recibir entradas de
usuario. Hay que resaltar que en CLX, incluso H e i g h t y Width se refieren a1
tamaiio del area interna del formulario.
Dado que podria interesarnos tener un espacio dado disponible para 10s com-
ponentes. normalmente es mejor definir el tamaiio de cliente de un formulario en
lugar de su tamaiio global. Hacer esto es sencillo, ya que cuando se fija una de las
dos propiedades del area de cliente, la propiedad correspondicnte para el formula-
rio se modifica de manera correcta.

TRUCO: En Windows, tambikn se puede producir resultados y capturar


entradas desde el Area no de cliente del formulario (es decir, de su borde).
Pintar sobre el borde y recibir una entrada cuando se hace clic sobre el son
asuntos complejos. Si interesa, se puede buscar en el archivo de ayuda la
descripcion de mensajes de Windows como w m N C P a i n t , w m
N C C a l c S i z e y wm N C H i t T e s t , y la serie de mekajes no de clienG
relacionados con la gntrada de raton, corno wrn NCLButtonDown. La
dificultad de esta ttcnica reside en combinar el cgdigo propio con el com-
portarniento predefinido de Windows.
Restricciones del formulario
Cuando se escoge un borde de tamaiio ajustable para un formulario, 10s usua-
rios generalmente pueden ajustar el tamaiio del formulario como deseen y tambien
maximizarlo para que ocupe la pantalla completa. Windows informa de que el
tamaiio del formulario ha cambiado con el mensaje wm-Size, que genera el
evento OnRe si ze. OnRe si ze tiene lugar despues de que el tamaiio del formu-
lario ya haya cambiado. Modificar de nuevo el tamaiio dentro de este evento (si el
usuario ha reducido o aumentado el formulario en exceso) resultaria poco practi-
co. Un enfoque preventivo se adapta mejor a este problema.
Delphi proporciona una propiedad especifica para 10s formularios y tambien
para todos 10s controles: la propiedad c o n s t r a i n t s . A1 definir las
subpropiedades de esta propiedad con 10s adecuados valores maximos y minimos
se genera un formulario que no se puede cambiar de tamaiio mas alla de 10s
limites establecidos. Veamos un ejemplo:
object Form1 : TForml
Constraints.MaxHeight = 300
C~nstraints~MaxWidth = 300
Constraints-MinHeight = 150
Constraints.MinWidth = 150
end

Conviene tener en cuenta que el efecto de la propiedad constraints,des-


pues de haberla definido, es inmediato incluso en tiempo de diseiio, cambiando el
tamaiio del formulario si esta fuera de la zona permitida.
Delphi utiliza tambien las restricciones maximas para las ventanas maximizadas,
produciendo un efecto algo extraiio. Por este motivo, generalmente deberia
inhabilitarse el boton de maximizar de una ventana que tenga un tamaiio maximo.
En algunos casos las ventanas maximizadas con un tamaiio limite pueden tener
sentido (este es el comportamiento de la ventana principal de Delphi). Si se nece-
sita modificar las restricciones en tiempo de ejecucion, tambien se puede conside-
rar usar dos eventos especificos, OnCanRes ize y OnConstrainedRes ize.
El primer0 de 10s dos tambien puede usarse para inhabilitar el ajuste de tamaiio de
un formulario o control bajo determinadas circunstancias.

Desplazar un formulario
Cuando se crea una aplicacion simple, un solo formulario podria albergar
todos 10s componentes necesarios. Sin embargo, a medida que crece la aplicacion,
tal vez haya que reducir el espacio para 10s componentes y juntarlos mas, aumen-
tar el tamaiio del formulario o aiiadir formularios nuevos. Si se juntan mas 10s
componentes, se podria aiiadir la capacidad de modificar su tamaiio en tiempo de
ejecucion, posiblemente dividiendo el formulario en dos zonas diferentes. Si deci-
dimos aumentar el tamaiio del formulario, podriamos usar las barras de desplaza-
miento para permitir que el usuario se mueva por un formulario que sera mas
grande que la pantalla (o a1 menos mas grande que su zona visible en la pantalla).
Aiiadir una barra de desplazamiento a un formulario es sencillo. De hecho, no
hay que hacer nada. Si se colocan varios componentes dentro de un gran formula-
rio y se reduce su tamaiio, automaticamente se aiiadira una barra de desplaza-
miento a1 formulario, siempre que no se haya cambiado el valor de la propiedad
AutoScroll predefinida como True.
Junto con A u t o s c r o 1 1 , 10s formularios tienen dos propiedades,
HorzScrollBar y VertScrollBar, que se pueden usar para definir diver-
sas propiedades de 10s dos objetos T FormScro 11Bar asociados con el formu-
lario.
La propiedad Visible indica si esta presente la barra de desplazamiento, la
propiedad Posit ion determina el estado inicial del control de desplazamiento y
la propiedad Increment determina el efecto que se obtiene a1 hacer clic sobre
una de las flechas situadas en 10s extremos de la barra de desplazamiento. Sin
embargo, la propiedad mas importante es Range.
La propiedad Range de una barra de desplazamiento establece el tamaiio
virtual del formulario, no el rango real de valores de la barra de desplazamiento.
Supongamos que se necesita un formulario que aloje diversos componentes y que
por tanto necesite ser de 1000 pixeles de ancho. Podemos usar este valor para
definir el "rango virtual" del formulario, cambiando el Range de la barra de
desplazamiento horizontal.
La propiedad Position de la barra de desplazamiento variara entre 0 y
1000 menos el tamaiio actual de la zona de cliente. Por ejemplo, si la zona de
cliente de un formulario tiene 300 pixeles de ancho, podemos desplazarnos 700
pixeles para ver el extremo mas alejado del formulario (el pixel milesimo).

Un ejemplo de prueba de desplazamiento


Para demostrar el caso concreto que acabamos de exponer, hemos construido
el ejemplo Scroll, que tiene un formulario virtual de 1000 pixeles de ancho. Para
ello, hemos definido el rango de la barra de desplazamiento horizontal como 1000:
object Form1 : TForml
HorzScrollBar.Range = 1000
VertScrollBar-Range = 305
AutoScroll = False
OnResize = FormResize

En el formulario del ejemplo se han colocado dos cuadros de lista sin ninguna
funcion y se podria haber obtenido el mismo rango de barra de desplazamiento
colocando el cuadro de lista fijo a la derecha de tal manera para que su posicion
(Left) mas su tamaiio (Width) fuese igual a 1000.
La parte interesante del ejemplo es la presencia de una ventana de cuadro de
herramientas que muestra el estado del formulario y de su barra de desplazamien-
to horizontal. Este segundo formulario tiene cuatro etiquetas, dos con texto fijo y
dos con la salida. Ademas de eso, el formulario secundario (Ilamado st at us)
tiene un estilo de borde bsToolWindow y es una ventana que siempre estara
por encima. Tambien deberiamos definir su propiedad V i s ible como True,
para que su ventana se muestre automaticamente a1 arrancar:
object Status: TStatus
BorderIcons = [biSystemMenu]
Borderstyle = bsToolWindow
Formstyle = fsStayOnTop
Visible = True
object Labell: T L a b e l ...
...
El unico objetivo de este programa es actualizar 10s valores del cuadro de
herramientas cada vez que se modifica el tamaiio del formulario o que este se
desplaza (como muestra la figura 7.8). La primera parte es muy sencilla. Se
puede controlar el evento O n R e s i z e del formulario y copiar simplemente un par
de valores en ambas etiquetas. Estas etiquetas forman parte de otro formulario,
por lo que sera necesario aiiadirles como prefijo el nombre de la instancia del
formulario, S t a t us :
procedure TForml. FormResize (Sender: TObject) ;
begin
Status.Label3.Caption : = IntToStr(C1ientWidth);
Status.Label4.Caption : = IntToStr(HorzScrollBar.Position);
end;

Form Size @):


Scroll Position M: 0
450 I

r1 I -
b

Figura 7.8. El resultado del ejemplo Scrolll.

Si queremos cambiar el resultado cada vez que el usuario desplace el conteni-


do del formulario, no podemos usar un controlador de eventos de Delphi, porque
no esiste un evento O n S c r o l l para formularios (aunque 10s componentes
ScrollBar independientes tengan uno). Omitir este evento time sentido, porque
10s formularios de Delphi gestionan las barras de desplazamiento de un mod0
muy potente. En Windows, por contraste, las barras de desplazamiento son ele-
mentos de un nivel muy bajo, lo que requiere mucha codificacion. Manejar el
evento de desplazamiento solo tiem sentido en casos especiales, como cuando se
desea seguir la pista con precision de las operaciones de desplazamiento realiza-
das por un usuario.
Veamos el codigo que hay que escribir. Primero, aiiadimos una declaracion de
metodo a la clase y la asociamos con el mensaje de desplazamiento horizontal de
Windows (wm-H S c r o l l ) . A continuacion, escribimos el codigo de este procedi-
miento, que es casi el mismo que el del metodo F o r m R e s i z e visto antes:
public
procedure FormScroll (var ScrollData: TWMScroll) ; message
wm_HScroll;

procedure TForml.FormScroll (var ScrollData: TWMScroll);


begin
inherited;
Status.Label3.Caption : = IntToStr(C1ientWidth);
Status.Label4.Caption : = IntToStr(HorzScrollBar.Position);
end;

Es importante aiiadir la llamada a i n h e r i t e d , que activa el metodo relacio-


nado con el mismo mensaje en el formulario de clase basica. La palabra clave
i n h e r i t e d en 10s controladores de mensajes de Windows llama a1 metodo de la
clase basica que estamos sobrescribiendo, que se encuentra asociado con el co-
rrespondiente mensaje de Windows (incluso aunque el nombre del procedimiento
sea distinto). Sin esta llamada, el formulario no se desplazara en absoluto.

NOTA:Debido a que en CLX no podems mtrolar 10s rnensajes de des-


p l a z ~ e n t oa bajo nivel, no puece que haya un modo fslcil de crear un
program similar a Scroll 1. En las aplimiones del mundo real, esto no
resulta extremadamente importante, puem que el siseema de desplazamimta
es a u t d t i c o y probablernente se pwde realizar conectando la biblioteca
CLX a un nivel inferior.

Desplazamiento automatic0
La propiedad R a n g e de la barra de desplazamiento puede parecer extraiia
hasta que se comienza a usar continuamente. Entonces, se empieza a pensar en las
ventajas de la tecnica del "rango virtual". En primer lugar, la barra de desplaza-
miento se elimina automaticamente del formulario cuando la zona de cliente del
formulario es lo suficientemente amplia como para acomodar el tamaiio virtual y
cuando se reduce el tamaiio del formulario, la barra de herramientas vuelve a
aparecer.
Esta caracteristica resulta sobre todo interesante cuando la propiedad
A u t o s c r o l l del formulario se establece como T r u e . En este caso, las posicio-
nes extremas de 10s controles situados mas a la derecha y mas abajo se copian
automaticamente en las propiedades Range de las dos barras de desplazamiento
del formulario. El desplazamiento automatic0 funciona bien en Delphi. En el
ejemplo anterior, el tamaiio virtual del formulario se estableceria en el borde
derecho del ultimo cuadro de lista. Esto se definia con 10s siguientes atributos:
object ListBox6: TListBox
Left = 832
Width = 145
end

Por lo tanto, el tamaiio virtual horizontal del formulario seria de 9 7 7 (la suma
de 10s dos valores anteriores). Este numero se copia automaticamente en el campo
Range de la propiedad Horz S c r o l l B a r del formulario, a menos que se cam-
bie manualmente para conseguir un formulario mas grande (corno en el ejemplo
Scroll 1, en el que se usaban un valor de 1 0 0 0 para que hubiera algo de espacio
entre el ultimo cuadro de lista y el borde del formulario). Podemos ver dicho valor
en el Object Inspector o realizar la siguiente prueba: ejecutar el programa,
establecer el tamaiio deseado para el formulario y mover el control de desplaza-
miento hasta el extremo derecho. Cuando aiiadimos el tamaiio del formulario y la
posicion del control, siempre obtendremos 1 0 0 0, la coordenada virtual del pixel
situado mas a la derecha, sea cual sea su tamaiio.

Desplazamiento y coordenadas del formulario


Ya hemos visto que 10s formularios pueden desplazar automaticamente sus
componentes. Lo que sucede si se pinta directamente sobre la superficie del for-
mulario es que surgen ciertos problemas, aunque con una solucion sencilla. Su-
pongamos que queremos dibujar algunas lineas en la superficie virtual de un
formulario, como muestra la figura 7.9. ya que probablemente no se disponga de
un monitor capaz de mostrar 2000 pixeles a lo largo de cada eje, se puede crear un
formulario mas pequeiio, aiiadir dos barras de desplazamiento y definir la propie-
dad Range, como en el ejemplo Scroll2.
Si simplemente dibujamos las lineas usando las coordenadas virtuales del
formulario, la imagen no aparecera de forma adecuada. En el metodo de respuesta
o n p a i n t , es necesario que calculemos nosotros mismos las coordenadas virtuales.
Afortunadamente, esto es facil, puesto que sabemos que las coordenadas virtuales
x l e Y 1 de la esquina superior izquierda de la zona de cliente corresponden a las
posiciones actuales de las dos barras de desplazamiento:
procedure TForml.FormPaint(Sender: TObject);
var
XI, Y1: Integer;
begin
X1 : = HorzScrollBar.Position;
Y1 : = VertScrollBar.Position;

// d i b u j a una l i n e d a m a r i l l a
Canvas.Pen.Width : = 30;
Canvas.Pen.Color : = clYellow;
Canvas .MoveTo (30-XI, 30-Y1) ;
Canvas .LineTo (1970-XI, 1970-Y1) ;

// y a s i s u c e s i v a m e n t e ...
2000 Pixeles

2000
'ixeles

500 Pixeles I
Figura 7.9. Las lineas a dibujar sobre la superficie virtual del formulario

Como una mejor alternativa, en lugar de calcular la coordenada correcta para


cada operacion de salida, podemos llamar a SetWindowOrg Ex de la API para
desplazar el origen de las coordenadas de la propia Canvas. De este modo,
nuestro codigo de dibujo se referira directamente a las coordenadas virtuales per0
las lineas se mostraran correctamente:
procedure TForm2.FormPaint(Sender: TObject);
begin
SetWindowOrgEx (Canvas.Handle, HorzScrollbar.Position,
VertScrollbar.Position, nil);

// d i b u j a una l i n e a a m a r i l l a
Canvas.Pen.Width : = 30;
Canvas.Pen.Color : = clYellow;
Canvas .MoveTo (30, 3 0 ) ;
C a n v a s .LineTo ( 1 9 7 0 , 1970) ;

// y a s i s u c e s i v a m e n t e ...
Esta es la version del programa que encontrara en el codigo fuente del libro. Se
puede probar el programa y comentar la llamada a SetWindowOrgEx para ver
lo que sucede si no se usan las coordenadas virtuales. Se puede ver que el resulta-
do del programa no es correct0 (no se desplazara, y siempre permanecera la
misma imagen en la misma posicion, sin importar las operaciones de desplaza-
miento). Observe tambien que en la version QtICLX del programa, denominada
QScroll2, no se usan las coordenadas virtuales sino que simplemente se restan las
posiciones de desplazamiento a cada coordenada codificada manualmente.

Escalado de formularios
Cuando sc crea un formulario con multiples componentes, se puede escoger un
borde de tamaiio fijo o permitir que el usuario ajuste el tamaiio del formulario y se
aiiadan automaticamente barras de desplazamiento para poder acceder a 10s com-
poncntes que se encuentren fuera de la parte visible del formulario, como ya se ha
visto. Tambien podria suceder esto porque un usuario de la aplicacion utilizara
un controlador de pantalla con un numero de pixeles mucho menor que en el
desarrollo. En lugar de reducir el tamaiio del formulario y desplazar el contenido,
podria quercrse reducir el tamaiio de cada uno de 10s componentes al unisono.
Esto ocurre automaticamente si el usuario tiene una fuente de sistema con una
tasa dc piselcs por pulgada distinta que la usada para el desarrollo. Para enfren-
tarse a estos problemas, Delphi disponc de unas apreciables caracteristicas de
escalado, per0 no son completamente intuitivas.
El metodo S c a l e B y de formulario permite ajustar la cscala del formulario y
de cada uno dc sus componentes. Las propiedadcs P i x e l s P e r I n c h y S c a l e d
permiten que Delphi modifique el tamaiio de una aplicacion de forma automatica,
cuando esta se ejecuta con un tamaiio de fuente de sistema diferente, normalmente
debido a una resolution de pantalla distinta. En ambos casos, para que el formu-
lario establezca la escala de su ventana, hay que asegurarse de definir tambien la
propiedad A u t o s c r o l l como F a l s e . De otro modo, el contenido del formula-
rio se ajustara a la escala, pero el borde de dicho formulario no.

NOTA: El ajuste a escala del formulario se cafcula segun la diferencia


entre la altura de la fuente en tiempo de ejecucion y la altura de la fuente en
tiempo de disefio. La escala garantiza que 10s controles de edicion y otros
controles sean lo suficientemente grandes como para mostrar su texto, uti-
lizando las preferencias del usuario sobre fuentes sin recortar el texto. La
- - - - 1- uel
escala 2-1 r 1-2-
rvrrnu~ar~o --
A--L:L-
rarnulen se auapra, ----
peru 1-
->--A- - z - : ----
lo -mas ---A- - - que 10s
1rnponanLe es
controles de edicion y otros controles resulten legibles.
Escalado manual del formulario
Siempre que se quiere ajustar la escala de un formulario, asi como sus compo-
nentes, se puede usar el metodo S c a l e B y , que tiene dos parametros enteros, un
dividend0 y un divisor (es una fraccion). Por ejemplo, con esta sentencia el tama-
fio del formulario actual se reduce a tres cuartas partes de su tamaiio original:
ScaleBy (3, 4);

Normalmente, resulta mas sencillo usar porcentajes. Se obtiene el mismo efec-


to usando:
ScaleBy (75, 1 0 0 ) ;

Cuando se ajusta la escala de un formulario, se mantienen todas las proporcio-


nes, per0 si se superan ciertos limites minimos o maximos, las cadenas de texto
pueden modificar ligeramente todas sus proporciones. El problema es que en
Windows, 10s componentes se pueden colocar y se puede ajustar su tamafio solo
en pixeles enteros, mientras que el ajuste de la escala casi siempre implica una
multiplication por numeros fraccionarios. Por lo tanto, cualquier parte fraccionaria
del origen o tamaiio de componente se vera truncada.
Hemos creado un sencillo ejemplo, Scale (o QScale), para mostrar como se
puede ajustar manualmente la escala de un formulario, respondiendo a una solici-
tud realizada por el usuario. El formulario de esta aplicacion tiene dos botones,
una etiqueta, un cuadro de edicion y un control UpDown conectados a el (median-
te la propiedad A s s o c i a t e ) . Con esta configuration, un usuario puede escribir
numeros en el cuadro de edicion o pinchar sobre las dos pequeiias flechas para
aumentar o disminuir el valor (en la cantidad indicada por la propiedad
I n c r e m e n t ) . Para extraer el valor de entrada, se puede usar la propiedad T e x t
del cuadro de edicion o la propiedad P o s i t i o n del control UpDown. Cuando se
hace clic sobre el boton Do Scale, el valor de la entrada actual se utiliza para
determinar el porcentaje de escalado del formulario:
procedure TForml.ScaleButtonC1ick(Sender: TObject);
begin
AmountScaled : = UpDownl.Position;
ScaleBy (AmountScaled, 100) ;
UpDownl.Height : = Editl.Height;
ScaleButton. Enabled : = False;
RestoreButton.Enab1ed : = True;
end;

Este metodo almacena el valor de entrada actual en el campo privado


A m o u n t s c a l e d del formulario y activa el boton Restore, desactivando el que
estaba pulsado. A continuacion, cuando el usuario pulsa el boton Restore, se
ajustara la escala a1 contrario. A1 tener que restaurar el formulario antes de reali-
zar otra operacion de ajuste de escala, evitamos una acumulacion de errores de
redondeo. Tambien hemos aiiadido una linea para definir la altura del componente
UpDown como igual a la del cuadro de cdicion a1 que se encuentra conectado.
Esto cvita pequcfias difercncias entrc ambos. debido a problemas dc escalado del
control UpDown.
- -- - -
NOTA: Si queremos ajustar la escala del texto del formulario correcta-
mente, y tambien de 10s titulos de 10s componentes, 10s elementos de 10s
cuadros de lista, etc.. ., deberiamos utilizar exclusivamente fhentes TrueType.
La fuente de sistema (MS Sans Serif) no se ajusta a la escala correctamen-
te. El problema de la fhente resulta importante porque el tamaiio de muchos
componentes depende de la altura del texto de sus titulos y, si el titulo no se
ajusta bien a la escala, el componente podria no fun~ionar~correctamente.
Por esa razon, en el ejemplo Scale hemos usado una fuente Arial.

Esta misma tecnica de ajuste de escala funciona tambien en CLX, como se


pucde ver al ejecutar cl ejemplo QScale. La imica diferencia real es que hemos
sustituido el componente UpDown (y cl cuadro de edicion relacionado) por un
control SpinEdit, puesto quc el primer0 no cxiste en Qt.

Ajuste automatico de la escala del formulario


En lugar dc trabajar con cl metodo ScaleBy,podemos pedirle a Delphi que
se cncargue del trabajo. Cuando se inicia Delphi, pide al sistema la configuracion
de la pantalla y guarda el valor en la propiedad Pixels PerInch dcl objeto
Screen,un objeto especial de la VCL, que esta disponible en cualquier aplica-
cion.
Pixels Per Inch sucna como si tuviese algo que ver con la resolucion cn
piscles dc la pantalla (que en realidad se encuentra en Screen.Height y
Screen.Width). per0 desafortunadamente no es asi. Si cambiamos la resolu-
cion de la pantalla de 6 4 0 ~ 4 8 0a 800x60, o 1 0 2 4 ~ 7 6 8o incluso a 1600x1280,
vercmos que Windows indica el mismo valor PixelsPerInch en todos 10s
casos, a menos quc cambiemos la fuente del sistema. PixelsPerInch se refie-
re en realidad a la resolucion en pixeles dc la pantalla para la que se diseiio
originalmentc la fuente de sistema instalada actualmentc. Cuando el usuario cam-
bia la escala de la fuente de sistema, normalmente para que 10s menus y otros
testos Sean mas faciles de lecr. se espera que todas las aplicaciones respeten csa
configuracion. Una aplicacion que no refleja las preferencias de apariencia de la
pantalla del usuario parecera estar fuera de lugar y. en casos muy estremos.
resultara completarnente inutil para 10s usuarios con problemas visuales a 10s que
les resultan muy practicas las fuentes muy grandes y 10s esquemas de color de
gran contraste.
Los valores PixelPer Inch mas comunes son 96 (fuentes pequeiias) y 120
(fuentes grandes), per0 son posibles otros. Las versiones mas recientes de Windows
permiten incluso que el usuario defina el tamaiio de la fuente de sistema segun una
escala arbitraria. En tiempo de diseiio, el valor P i x e l s P e r I n c h de la pantalla,
que es una propiedad so10 de lectura, se copia en cada formulario de la aplicacion.
Delphi usa entonces el valor de P i x e l s P e r I n c h , si la propiedad S c a l e d esta
definida como T r u e , para ajustar el tamaiio del formulario cuando se inicia la
aplicacion .
Como hemos dicho, tanto el ajuste automatico de la escala como el realizado
por el metodo S c a l e B y modifican el tamaiio de la fuente de 10s componentes. El
tamaiio de cada control, en realidad, depende de la fuente que se use. Con el ajuste
de escala automatico, el valor de la propiedad P i x e l s P e r I n c h del formulario
(el valor en tiempo de diseiio) se compara con el valor del sistema en ese momento
(indicado por la propiedad correspondiente del objeto S c r e e n ) y se usa ese
resultado para modificar la fuente de 10s componentes del formulario. Para mejo-
rar la precision de este codigo, la altura final del texto se compara con la altura
del texto en tiempo de diseiio y se ajusta su tamaiio si ambas alturas no se corres-
ponden.
Gracias a1 soporte automatico de Delphi, una misma aplicacion ejecutada en
un sistema con un tamaiio de fuente de sistema distinto ajustara su escala de
forma automatica, sin ningun codigo especifico. Los controles de edicion de la
aplicacion tendran el tamaiio adecuado para mostrar su texto en el tamafio de
fuente preferido por el usuario y el formulario tendra el tamaiio adecuado para
alojar dichos controles. Aunque el ajuste automatico de escala tiene problemas en
algunos casos especiales, si se respetan las siguientes normas, 10s resultados de-
berian ser 10s correctos:
Definir la propiedad S c a l e d de 10s formularios como T r u e . (Es el valor
predefinido.)
Usar solo fuentes TrueType
Usar fuentes pequeiias de Windows (96 dpi) en el ordenador que se use
para desarrollar 10s formularios.
Definir la propiedad A u t o s c r o l l como F a l s e si se quiere ajustar a
escala del formulario y no solo sus controles. ( A u t o S c r o l l esta
predefinida como T r u e , por lo que conviene recordar este paso.)
Definir la posicion del formulario o bien proxima a la esquina superior
izquierda o en el centro de la pantalla (con el valor p o s c r e e n c e n t e ~ )
para evitar tener un formulario fuera de la pantalla.

Crear y cerrar formularios


Hasta ahora no hemos hablado de la cuestion de la creacion de un formulario.
Sabemos que cuando se crea el formulario, se recibe el evento o n c r e a t e y
podemos cambiar o comprobar algunas de las propiedades o campos iniciales del
formulario. La sentencia responsable de la creacion del formulario esta en el
archivo fuente del proyecto:
begin
Application-Initialize;
Application. CreateForm(TForm1, Forml) ;
Application.Run;
end.

Para saltarse la creacion automatica del formulario, se puede modificar este


codigo o utilizar la ficha Forms del cuadro de dialogo Project Options (vCase
figura 7.10). En este cuadro de dialogo, se puede decidir si el formulario deberia
crearse de manera automatica. Si desactivamos la creacion automatica, el codigo
de inicializacion del proyecto se transforma en el siguiente:
begin
Applications.Initialize;
Application.Run;
end.

r --Man
- -

-
fam l~olrn~

Figura 7.10. La ficha Forms del cuadro de dialogo Project Options de Delphi.

Ahora, si ejecutamos este programa, no pasara nada. Finaliza de forma inme-


diata porque no se crea ninguna ventana principal. El efecto de la llamada a1
metodo C r e a t e F o r m de la aplicacion crea una nueva instancia de la clase de
formulario que se pasa como primer parametro y la asigna a la variable pasada
como segundo parametro.
En el ambito interno sucede algo mas. Cuando se llama a C r e a t e F o r m , si en
ese momento no hay un formulario principal, se asigna el formulario actual a la
propiedad M a i n F o r m de la aplicacion. Por esa razon, el formulario indicado
como Main Form en el cuadro de dialog0 que aparece en la figura 7.10 se corres-
ponde con la primera llamada a1 metodo CreateForm de la aplicacion (es decir,
cuando se crean diversos formularios a1 arrancar).
A1 cerrar la aplicacion, ocurre lo mismo. Si se cierra el formulario principal,
finaliza la aplicacion, sin tener en cuenta 10s otros formularios. Para realizar esta
operacion desde el codigo del programa, sencillamente hay que llamar a1 metodo
close del formulario principal, como hemos hecho en diversas ocasiones en 10s
ejemplos anteriores.

Eventos de creacion de formularios


Sea cual sea el tipo de creacion de formularios (manual o automatica), cuando
se crea uno; hay muchos eventos que podemos interceptar. Los eventos de crea-
cion de formulario se dan en el siguiente orden:
1. Oncreate indica que se esta creando el formulario.
2. OnShow indica que se esta mostrando el formulario. Ademas de 10s for-
mularios principales, este evento tiene lugar despues de que se define como
True la propiedad Visible del formulario o se llama a 10s metodos
Show o ShowModal. Este evento ocurre de nuevo si el formulario se
oculta y aparece de nuevo.
3. OnActivate indica que el formulario se transforma en el formulario
activo de la aplicacion. Este evento ticne lugar cada vez quc nos movemos
desde otro formulario de la aplicacion a1 actual.
4. Otros eventos, como OnRes i ze y On Paint,indican operaciones reali-
zadas siempre a1 arrancar pero repetidas, a continuacion, varias veces.
, - - - - - -
- - . - -

NOTA: En Qt, el evento OnRes i ze no se lanza como en Windows cuan-


do se crea el formulario. Para que el &go resulte rnh adaptable de Delphi
a Kylix, CLX simula este evento, aunque tendria mas sentido retocar la
VLC para evitar que se diera este e x t r s o comportamiento (un comentario
en el c ~ d i g ofuente de CLX comenta esta situacion).

Como se puede ver, cada evento tiene una funcion especifica ademas de la
inicializacion del formulario, a escepcion del evento Oncreate,a1 que se llama
solo una vez de manera garantizada cuando se crea el formulario.
Sin embargo, existe un enfoque alternativo para aiiadir codigo de inicializacion
a un formulario: sobrescribir el constructor. Esto se hacc del siguiente modo:
constructor TForml.Create(A0wner: TComponent);
begin
inherited Create (AOwner);
// c o d i g o d e i n i c i a c i o n a d i c i o n a l
end;

Antes de la llamada a1 m e t o d o c r e a t e de la clase basica, las propiedades del


formulario no se han cargado todavia y 10s componentes internos no estan dispo-
nibles. Por esa razon, la tecnica estandar consiste en llamar a1 constructor de la
clase basica primcro y, a continuacion, hacer las operaciones personalizadas.
- -- - - .- - - - .

NOTA: Hasta la version 3, Delphi usaba un orden de creacion distinto, que


ha llevado a la propiedad de compatibilidad OldCreateOrder de la
VCL. Cuando se fija esta propiedad a1 valor predeteminado de False,
todo el codigo de un constructor de fomulario se ejecuta antes que el codi-
- -. - ". \ .. .. .
go del controlador del evento oncreate (que lanza el metodo especial
. . ....
A E tercons truct ion). 31se naml1t.a el antlguo oraen ae creaclon, la
. I . I .
llamada inherited del constructor lleva a la llamada a1 controlador del
evento Oncreate. Se puede examinar el comportamiento del ejemplo
CreateOrd utilizando 10s dos valores para la propiedad OldCreateOrder.

Cerrar un formulario
Cuando cerramos un formulario utilizando el metodo c l o s e o mediante el
tipico mdtodo (Ah-F4, el mcnu de sistcma o el boton Close), llamamos a1 evento
O n C l o s e Q u e r y . En este caso, se puede pedir a1 usuario que confirme la ac-
cion, sobrc todo si hay datos sin guardar en el formulario. Veamos la sencilla
cstructura dcl codigo que podemos escribir:
procedure TForml.FormCloseQuery(Sender: TObject; var CanClose:
Boolean) ;
begin
if MessageDlg ( ' A r e y o u s u r e y o u w a n t t o e x i t ? ' ,
mtconfirmation, [&Yes, &No] , 0 ) = idNo then
CanClose : = False;
end:

Si O n C l o s e Q u e r y indica quc el formulario deberia cerrarse, se efectua una


llamada a1 evento o n c l o s e . El tercer paso consiste en llamar a1 evento
OnDes t r o y , que es el contrario del evento OnCrea t e y se usa por lo general
para eliminar objetos relacionados con el formulario y liberar la memoria corres-
pondientc.
-- . - - - ..- -- - -- - -- - - . -- -

NOTA: Para ser m b precisos, el metodo Be foreDestruct ion genera


un evento OnDestroy antes de llamar a1 destructor Destroy.Es deck,
-
a -Ai-- L
m m o ~que nayamos J-P--:>-
aerln~ao 1-
la ---- l-2-J
propieaaa A. -.-_.- _L--.._.___
u1aLrear;euraer como -_-a-

True,en cuyo caso Delphi usa una secuencia de cierre distinta.


El uso dcl evento intermedio onclose esta en que nos ofrece otra oportuni-
dad para no cerrar la aplicacion o podemos especificar "acciones de cierrc" altcr-
nativas. De hecho, el metodo ticnc un parametro Action que se pasa mcdiante
rcferencia. Podemos asignar 10s siguientcs valorcs a dicho parametro:
caNone: No sc pcrmitc a1 formulario que se cicrrc. Corrcsponde a la con-
figuration del paramctro Canclose del metodo OnCloseQuery como
False.
caHide: No se cierra cl formulario, solo se oculta. Esto solo sc dcbe haccr
si hay otros formularios cn la aplicacion, si no, el programa finaliza. En cl
caso de 10s formularios secundarios es el comportamiento predefinido y
csa es la razon de quc hapamos tenido que controlar el evento onclose
en el ejemplo antcrior para poder cerrar 10s formularios secundarios.
caFree: Se cierra el forn~ulario~ sc libcra su memoria y por ultimo finaliza
la aplicacion si ese era el formulario principal. Esta es la accion predefinida
cn el caso del formulario principal y la accion que deberiamos usar cuando
sc crean varios formularios dc forma dinamica (si se desea eliminar las
ventanas y destruir cl corrcspondiente objeto Delphi cuando sc cicrrc cl
formulario).
cahlinimize: No se cierra el formulario, solo se minimiza. Esta es la ac-
cion predefinida en el caso de formularios hijo MDI.
- -

NOTA: C u a n d o un usuario cierra Windows. s e activa el evento


7

OnCloseQuery que puede usar un programa para detener el proceso de


c i e r r e . En ese c a s o , n o se llama a1 evento O ~ l C
- -. 3."
uncloseyuery aerina
A , . . canclose
1
el pararnerro - como 'Lrue.
- 3
o s e aunque
-

Cuadros de dialogo y otros formularios


secundarios
Cuando escribimos un programa, no existe una gran diferencia entre un cua-
dro de dialogo y otro formulario secundario, a parte del borde, 10s iconos del
borde y elementos similares de la interfaz de usuario que se pueden personalizar.
Los que 10s usuarios asocian con un cuadro de dialogo es el concept0 de una
ventana modal (una ventana que obtiene el foco y habra de cerrarse antes de que
el usuario pueda volver a la ventana principal). Esto ocurre en el caso de 10s
cuadros de mensaje y normalmente tambien en el de 10s cuadros de dialogo. Sin
embargo. tambien podemos tener cuadros de dialogo no modales. Entonces, si
piensa en 10s cuadros de dialogo como simplemente en formularios modales, esta
en el buen camino, aunque la descripcion no es precisa. En Delphi (igual que en
Windows), podemos tener tambien cuadros de dialogo no modales y formularios
modales. Tenemos que considerar dos elementos diferentes: el borde del formula-
rio y su interfaz de usuario establecen si su apariencia es la de un cuadro de
dialogo; y el uso de dos metodos diferentes (Show o ShowModal) para mostrar
el formulario secundario determina su comportamiento (no modal o modal).

Afiadir un formulario secundario a un programa


Para aiiadir un formulario secundario a una aplicacion, simplemente pulsamos
el boton New Form de la barra de herramientas de Delphi o la orden del menu
File>New Form. Como alternativa podemos seleccionar File>New, ir a la ficha
Forms o Dialogs y seleccionar una de las plantillas de formulario o asistentes
para formularios disponibles.
Si tenemos dos formularios en un proyecto, podemos usar el boton View Form
o View Unit de la barra de herramientas de Delphi para movernos por ellos en
tiempo de diseiio. Tambien podemos seleccionar quC formulario es el principal y
cuales debcrian de crearse automaticamente a1 arrancar, utilizando la ficha Forms
del cuadro de dialogo Project Options. Esta informacion se refleja en el codigo
fuente del archivo de proyecto.

TRUCO: Los formularios secundarios se crean automaticamente en el ar-


chive de codigo fuente del proyecto dependiendo del estado del cuadro de
comprobacidn Auto Create Forms de la phgina Designer del cuadro de
dialogo Environment Options. Aunque la creation automatica es la tec-
nica mas sencilla y mas fiable para 10s desarrolladores noveles y proyectos
sin perfeccionar, conviene desactivar esta casilla de verification en todos
aquellos proyectos de desarrolIo importantes. Si la aplicaci6n contiene cientos
de formularios, no se deberian crear todos a1 iniciar la aplicacion. Lo mejor
es crear las instancias de fonnularios secundarios cuando y donde Sean
necesarios y liberarlos cuando no se necesiten.

Cuando hayamos preparado el formulario secundario, simplemente podemos


definir su propiedad V i s i b l e como T r u e y ambos formularios apareceran a1
arrancar el programa. En general, 10s formularios secundarios de una aplicacion
se dejan "invisibles" y, mas tarde, se muestran llamando a1 metodo Show (o
definiendo la propicdad V i s i b l e en tiempo de ejecucion). Si utilizamos la fun-
cion Show, el segundo formulario aparecera como no modal, de mod0 que pode-
mos movernos de nuevo a1 primer0 mientras el segundo esta visible. Para cerrar el
segundo formulario, podriamos usar su menu de sistema o pulsar el boton o ele-
mento del menu que llama a1 metodo C l o s e . Tal y como acabamos de ver, la
accion de cierre predefinida (vease el evento O n c l o s e ) en el caso de un formu-
lario secundario consiste sencillamente en ocultarlo, por lo que el formulario
secundario no se destruye cuando se cierra, sino que se mantiene en memoria (no
es el mejor enfoque) y esta disponible si queremos volver a mostrarlo.

Crear formularios secundarios en tiempo de


ejecucion
A menos que creemos todos lo formularios a1 arrancar el programa, sera nece-
sario verificar si existe un formulario y, si es necesario, crearlo. El caso m b
sencillo es aquel en el que queremos crear diversas copias del mismo formulario
en tiempo de ejecucion. En el ejemplo MultiWin/QMultiWin, lo hemos hecho asi
con el siguiente codigo:
with TForm3.C r e a t e ( A p p l i c a t i o n ) do
Show;

Cada vez que hacemos clic sobre el boton, se crea una nueva copia del formu-
lario. Hay que darse cuenta de que no utilizamos la variable global F o r m 3 por-
que no tiene mucho sentido asignar a esta variable un nuevo valor cada vez que se
crea un nuevo objeto de formulario. Sin embargo, lo importante es no referirse a1
objeto global F o r m 3 en el codigo del formulario o en otras partes de la aplica-
cion. La variable F o r m 3 sera invariablemente un punter0 a n i l . Lo mas reco-
mendable es que en un caso como este se elimine de la unidad para evitar cualquier
confusion posible.
-- -.. -- - . -

TRUCO:En el c M g o de un formulario que puede tener mmiltiples instan-


cias, nunca se deben'a hacer referencia explicita al formulario utihzando la
variable global que Delphi define para 61. Por ejemplo, supongarnos que en
.
el codigo de T Form3 hacemos referencia a Form3 C a p t i o n . Si crea-
mos un segundo objeto del mismo tipo (la clase TForm3), la expresion
.
Form3 C a p t i o n se referira siempre a1 titulo del objeto de formulario al
que hace referencia la variable Form3, que podria no ser el objeto actual
que ejecuta el codigo. Para evitar dicho problerna, hay que referirse a la
propiedad C a p t i o n en el m6todo del formulario para indicar el titulo del
objeto de formulario actual y usar la palabra clave self cuando se necesi-
- .- - -. - - - - . -
te hacer reterencla especifica a1 0bjet0 del formulario en uso. Para evltar
cualquier problema al crear diversas copias de un formulario, se puede
elirninar el objeto global formulario de la parte de interfaz de la unidad que
declara el formulario.

Cuando se crean varias copias de un formulario de manera dinamica, hay que


recordar destruir cada objeto de formulario cuando se cierra, controlando el even-
to correspondiente:
procedure TForm3.FormClose(Sender: TObject; var Action:
TCloseAction) ;
begin
Action : = caFree;
end :

No hacer esto conllevara un gran consumo de memoria, ya que todos 10s for-
mularios que se creen (tanto las ventanas como 10s objetos Delphi) se mantendran
en memoria y se ocultaran.

Crear un unica instancia de formularios secundarios


Centremonos ahora en la creacion dinamica de un formulario, en un programa
que cuenta solo con una copia del formulario cada vez. Crear un formulario
modal es bastante sencillo, porque se puede destruir el cuadro de dialogo cuando
se cierra, con un codigo como este:
var
Modal: TForm4;
begin
Modal : = TForm4 .Create (Application);
try
Modal.ShowModa1;
finally
Modal.Free;
end ;

Como la llamada a ShowModal puede producir una excepcion, deberiamos


escribirla dentro de un bloque try seguido de un bloque finally para asegu-
rarnos de que se libera la memoria asignada para el mismo. Normalmente dicho
bloque incluye tambien codigo que inicializa el cuadro de dialogo antes de mos-
trarlo y codigo que extrae 10s valores definidos por el usuario antes de destruir el
formulario. Los valores finales son de solo lectura si el resultado de la funcion
ShowModal es mrOK.
La situacion es algo mas compleja cuando queremos mostrar una sola copia de
un formulario no modal. Tenemos que crear el formulario, si no existe todavia, y
despues mostrarlo:
if not Assigned (Form2) then
Form2 : = TForm2 .Create (Application);
Form2.Show;

Con este codigo, se crea el formulario la primera vez que se necesita y despues
se guarda en memoria, visible en pantalla u oculto. Para evitar consumir memoria
y recursos del sistema de forma innecesaria, debemos destruir el formulario se-
cundario cuando se cierre. Para ello podemos escribir un controlador del evento
OnClose:
procedure TForm2.FormClose(Sender: TObject; var Action:
TCloseAction) ;
begin
Action : = caFree;
/ / i m p o r t a n t e : d e f i n i r p u n t e r 0 como n i l
Form2 : = n i l ;
end ;

Fijese en que despues de destruir el formulario, la variable global Form2 se


define como n i l . Sin este codigo, a1 cerrar el formulario se destruiria su objeto,
per0 la variable Form2 remitiria aun asi a la posicion de memoria original. En
cste punto, si se intenta mostrar el formulario una vez mas con el metodo
b t n S i n g l e C l i c k , funcionara la prueba i f n o t A s s i g n e d ( ) , puesto que
sencillamente verifica si la variable Form2 es n i l . El codigo no crea un objeto
nuevo y el metodo show (invocado sobre un objeto inexistente) producira un
error de memoria del sistema.
Como esperimento, se puede generar este error eliminando la ultima linea del
listado anterior. Como se ha visto, la solucion es asignar n i l a1 objeto Form2
antes de destruir el objeto, de manera que un codigo escrito correctamente sera
capaz de "ver" que se debe crear un nuevo formulario antes de utilizarlo. Una vez
mas. esperimentar con el ejemplo Mu1t i W i n / Q M u l t i W i n puede ser muy util
para comprobar distintas situaciones.

NOTA: Conviene definir la variable del fonnulario como nil (y funciona)


si solo va a haber una instancia del fonnulario presente en un momento
dado. Si queremos crear diversas copias de un formulario, tendremos que
Ae lac m i c m a c AAp-
11tilivar n t r a c t b ~ n i r a cn a r a m a n t e n e r wn c o r n ~ i m i e n t n
-
mas, hay que tener en cuenta que en este caso, no podemos usar el procedi-
miento FreeAndNil, porque no podemos llamar a Free sobre Form2.

I
1,A
,, a
, ,
.
a " ,
A
*,",, A,"+,.:, ,I C,-..l..,, ,
,
+a
, ,
,A,
,:
,a+
,..
, A,
~a I ~ L U I GJ
I ~ U uu
G ~ J U G I I I V J U G J C I U I ~GI 1 ~ 1 1 1 1 ~ 1 4 CULCGJ
1 1 ~ UG ~ U CGIIIIIUGII
G UG

ejecutarse sus controladores de eventos.

Creacion de un cuadro de dialogo


Ya hemos dicho que un cuadro de dialogo no es muy distinto de otros formula-
rios. Para crear un cuadro de dialogo en lugar de un formulario, solo hay que
selcccionar el valor b s D i a l o g para la propiedad B o r d e r s t y l e . Con este
sencillo cambio, la interfaz del formulario se convierte en la de un cuadro de
dialogo, sin icono de sistema, ni casillas para ininimizar o maximizar. Por su-
puesto, dicho formulario posee el borde p e s o tipico de cuadro de dialogo, que
no podremos modificar de tamaiio.
Cuando hayamos creado el formulario de cuadro de dialogo, podemos mos-
trarlo como una ventana modal o no modal que use 10s dos metodos habituales
para aparecer (Show y ShowModal). Sin embargo, 10s cuadros de dialogo mo-
dales son mas habituales que 10s no modales. Es justo a1 contrario que con 10s
formularios: generalmente deberian evitarse 10s formularios modales, porque no
es lo que espera un usuario.

El cuadro de dialogo del ejemplo RefList


En el ejemplo RefList2 (y en su homologo CLX QRefLsit2), hemos aiiadido a
la version basica del ejemplo (el ejemplo RefListIQRefList del capitulo sobre
controles visuales, que usaba un control ListView para mostrar referencias a
libros, revistas y cosas asi) un cuadro de dialogo que se utiliza en dos circunstan-
cias distintas: aiiadir elementos nuevos a la lista y editar elementos existentes.

ADVERTENCIA: El componente ListView de la CLX tiene un problema.


En caso de que se activen 10s cuadros de verification y despues se inhabiliten,
desaparecerhnlas imiygenes. Este es el comportamientodel ejemplo QRefList
-._ J _ n n - m '-&A L
-..-__ _-_>:A- - I > : _ _ - _ - -.-I
m e n m o . P-
__--_-A-
ya w I-
c n ra $1-
verslon ynerusu nemos anaaao coalgo para vower
a asignar la propiedad ImageIndex de cada elemento como solucion de
este fallo.

La unica caracteristica interesante de este formulario en el ejemplo VCL es el


uso del componente ComboBoxEx,que esta conectado a la misma ImageList
utilizada por el control ListView para controlar el formulario principal. Los ele-
mentos de la lista desplegable, usados para escoger un tip0 de referencia, incluyen
tanto una descripcion textual como la imagen correspondiente.
Como ya se ha comentado, este cuadro de dialogo se usa en dos casos distin-
tos. El primer0 ocurre cuando el usuario selecciona File>Add Items en el menu:
procedure TForml.AddItemslClick(Sender: TObject);
var
NewItem: TList Item;
begin
FormItem.Caption : = 'New Item';
FormItem.Clear;
if FormItem.ShowModal = mrOK then
begin
NewItem : = ListViewl.1tems.Add;
NewItem.Caption : = FormItem.EditReference.Text;
NewItem.ImageIndex : = FormItem.ComboType.ItemIndex;
NewItem.SubItems.Add (Form1tem.EditAuthor.Text);
NewItem.SubItems.Add (Form1tem.EditCountry.Text);
end ;
end ;

Ademas de definir el titulo correct0 para el formulario, este procedimiento


necesita iniciar el cuadro de dialogo porque introducimos un nuevo valor. Sin
embargo, si el usuario hace clic sobre OK, el programa aiiade un nuevo elemento
a la lista y define todos sus valores. Para vaciar 10s cuadros de edicion del dialo-
go, el programa llama a1 metodo personalizado c l e a r , que reinicia el texto de
cada cuadro de edicion:
procedure TFormItem.Clear;
var
I: Integer;
begin
// b o r r a c a d a c u a d r o d e e d i c i o n
for I : = 0 to Controlcount - 1 do
if Controls [I] is TEdit then
TEdit (Controls[I]) .Text : = '';
end;

Para editar un elemento ya existente, es necesaria una tecnica ligeramente


distinta. En primer lugar, 10s valores actuales se llevan tambien a1 cuadro de
dialog0 antes de que aparezca. En segundo lugar, si el usuario hace clic sobre
OK, el programa modifica la lista actual en lugar de crear una nueva. Veamos el
codigo:
procedure TForml.ListViewlDblClick(Sender: TObject);
begin
if ListViewl.Selected <> nil then
begin
// i n i c i o d e l d i d l o g o
FormItem.Caption : = ' E d i t I t e m ' ;
FormItem.EditReference.Text : =
ListViewl.Se1ected.Caption;
FormItem.ComboType.ItemIndex : =
ListViewl.Selected.Image1ndex;
Form1tem.EditAuthor.Text : = ListViewl.Se1ected.SubItems [O];
Form1tem.EditCountry.Text : = ListViewl.Selected.SubItems [I];

// l o r n u e s t r a
if FormItem.ShowModa1 = mrOK then
begin
// l e e 1 0 s v a l o r e s n u e v o s
ListViewl.Se1ected.Caption : =
FormItem.EditReference.Text;
ListViewl.Selected.ImageIndex : =
FormItem.ComboType.ItemIndex;
ListViewl.Selected.Sub1tems [O] : =
Form1tem.EditAuthor.Text;
ListViewl.Selected.SubItems [I] : =
Form1tem.EditCountry.Text;
end ;
end ;
end;

Se puede ver el efecto de este codigo en la figura 7.11. Observe que el codigo
utilizado para leer el valor de un elemento nuevo o modificado es similar. En
general, hay que evitar este tip0 de codigo duplicado y colocar las sentencias de
codigo compartidas en un metodo aiiadido a1 cuadro de dialogo. En este caso; el
metodo podria recibir como parametro un objeto TList I t e m y copiar en el 10s
valores adecuados.

Marca Canlu

Eapaiia

Figura 7.11. El cuadro de dialogo del ejernplo RefList2 utilizado en modo edicion.

- . .- - - ... . - . - .. - - .

NOTA: Lo que sucede internamente cuando el usuario hace clic sobre el


boton OK o Cancel del cuadro de dialogo es que un cuadro de dialogo
modal se cierra estableciendo su propiedad ModalResul t, y devolviendo
el valor de esta propiedad. Se puede indicar el valor de retorno usando la
propiedad ModalResul t del boton. Cuando el usuario haga clic sobre el
boton, su valor Modal Resul t se copiarh a1 formulario, que lo que cierra
el formulario y devuelve el valor como el resultado de la funcion
ShowModal.

Un cuadro de dialogo no modal


El segundo ejemplo de cuadros de dialogo muestra un cuadro de dialogo modal
mas complejo que utiliza el enfoque estandar, a1 igual que un cuadro de dialogo
no modal. El formulario principal del ejemplo DlgApply (y del ejemplo identico
basado en CLX, QDlgApply) tiene cinco etiquetas con nombres, como muestra la
figura 7.12 y un analisis del codigo fuente del ejemplo.
Si el usuario hace clic sobre un nombre, su color se vuelve rojo. Si hace doble
clic sobre el, el programa muestra un cuadro de dialogo modal con una lista de
nombres de entre la que escoger. Si el usuario hace clic sobre el boton Style,
aparece un cuadro de dialogo no modal, el cual permite que el usuario modifique
el estilo de la fuente de las etiquetas del formulario principal. Las cinco etiquetas
del formulario principal estan conectadas con dos metodos, uno para el evento
OnClic k y otro para el evento OnDoubleClic k. El primero hace que la
etiqueta pulsada por el usuario se vuelva roja y reinicia todas las demas (que
tienen una propiedad Tag configurada como 1, como una especie de indice de
grupo) en negro. Fijese en que se asocia el mismo metodo con todas las demas
etiquetas:
procedure TForml.LabelClick(Sender: TObject);
var
I: Integer;
begin
for I : = 0 to Componentcount - 1 do
if (Components[I] is TLabel) and (Components[I] .Tag = 1)
then
TLabel (Components[I]) .Font.Color : = clBlack;
/ / define el color de la etiqueta pulsada como rojo
[Sender as TLabel) .Font.Color := clRed;
end ;

.Bob
C njy Name
Jane
Jelf Name
John
J iha
Ma~k Name
Martha
Mn,,
Name
Name

I Sample label

Figura 7.12. Los tres forrnularios del ejernplo DlgApply en tiempo de ejecucion (un
formulario principal y dos cuadros de dialogo).

El segundo metodo comun a todas las etiquetas es el controlador del evento


OnDoubleClic k . El mitodo LabelDoubleClick selecciona el titulo de la
etiqueta en uso (indicada por el parametro Sender) en el cuadro de lista del
dialogo y despues muestra el cuadro de dialogo modal. Si el usuario cierra el
cuadro de dialogo haciendo clic sobre OK y hay seleccionado un elemento de la
lista, este se copia de nuevo en el titulo de la etiqueta:
procedure TForml.LabelDoubleClick(Sender: TObject);
begin
with ListDial .Listbox1 do
begin
/ / selecciona el nombre actual del cuadro de lista
ItemIndex := Items.IndexOf (Sender as TLabel) .Caption);
/ / muestra el cuadro de didlogo modal, verificando el
valor de retorno
if (ListDial.ShowModa1 = mrOk) and (ItemIndex >= 0 ) then
// copia el elemento seleccionado en la etiqueta
(Sender as TLabel) .Caption : = Items [ItemIndex];
end ;
end;
I TRUCO: Fijese en que todo el clidigo usad~parapgsoqlalizar el.cundro:de,
dialog0 modal se encuentra en el m6todo ~ a b e l ~ o & l iec k~ del
l for-
mula& principal. El formulario de este cuadro de diilogo no tiene cbdigo
aiiadido.

Por el contrario, el cuadro de dialogo no modal tiene mucho codigo en su


interior. El formulario principal simplemente muestra el cuadro de dialogo cuan-
do se pulsa el boton Style (fijese en que el titulo de este boton termina con tres
puntos para indicar que lleva a un cuadro de dialogo), llamando a su metodo
s h o w . El cuadro de dialogo aparece en la anterior figura 7.12.
Existen dos botones, Apply y Close, que sustituyen a 10s botones OK y Can-
cel en un cuadro de dialogo no modal. (El mod0 mas rapido de obtener dichos
botones es seleccionar el valor bkOK o b k C a n c e l para la propiedad K i n d y
despues editar la propiedad C a p t i o n . ) De vez en cuando, puede que vea un
boton Cancel que funciona como un boton Close, pero el boton OK no suele tener
significado en un cuadro de dialogo no modal. En su lugar, uno o mas botones
podrian realizar acciones especificas sobre la ventana principal, como Apply,
Change Style, Replace, Delete y muchos mas.
Si el usuario hace clic sobre una de las casillas de verificacion del cuadro de
dialogo no modal, el estilo del texto de la etiqueta de muestra que esta en la parte
inferior cambia en funcion de la eleccion. Para ello, se aiiade o elimina el indica-
dor especifico que muestra el estilo, como en el siguiente controlador del evento
O n C l i c k:
procedure TStyleDial.ItalicCheckBoxClick(Sender: TObject);
begin
if 1talicCheckBox.Checked then
LabelSample.Font.Style : = LabelSample.Font.Style +
[fsItalic]
else
LabelSarnple.Font.Sty1e : = LabelSarnple.Font.Sty1e -
[ f s I t a l i c ];
end;

Cuando el usuario hace clic sobre el boton Apply, el programa copia el estilo
de la etiqueta de muestra en cada una de las etiquetas del formulario, en lugar de
tener en cuenta 10s valores de las casillas de verificacion:
procedure TStyleDial.ApplyBitBtnClick(Sender: TObject);
begin
Forml.Labell.Font.Style : = LabelSarnple.Font.Sty1e;
Forrnl.Label2.Font.Style : = LabelSample.Font.Style;

Como alternativa, en lugar de hacer referencia directamente a cada etiqueta, se


puede buscar llamando a1 metodo F i n d c o m p o n e n t del formulario, pasando el
nombre de la etiqueta como parametro y convirtiendo despues el resultado al tipo
TLabel.
La ventaja de este enfoque es que se pueden crear 10s nombres de las diversas
etiquetas dentro de un bucle f o r :
p r o c e d u r e TStyleDial.ApplyBitBtnClick(Sender: TObject),
var
I: Integer;
begin
for I : = 1 to 5 d o
(Forml.Findcomponent ( 'Label' + IntToStr (I)) as
TLabel) .Font.Style : = LabelSample.Font.Style;
end;

TRUCOi El metmdb.Appf.yBit~tn~lick tambih jodria haberse es-


crito para.que.aa&ase .fa matriz Controls en un b :le, como en otros

Esta segunda version del codigo es realmente mas lenta, porque tiene que
realizar mas operaciones, per0 la diferencia no se podra percibir porque sigue
siendo muy rapido.
Por supuesto, este enfoque tambien es mas flesible: si se aiiade una nueva
etiqueta, solo se necesita cambiar el limite superior para el bucle f o r , siempre
que todas las etiquetas tengan numeros consecutivos.
Hay que darse cuenta de que cuando el usuario hace clic sobre el boton Apply,
no se cierra el cuadro de dialogo (solo el boton Close tiene este efecto). Hay que
considerar tambih que este cuadro de dialogo no necesita codigo de inicializacion
porque el formulario no se destruye, y sus componentes mantienen su estado cada
vez que se muestra el cuadro de dialogo. Sin embargo, en la version CLX del
programa, QDlgApply, el dialogo es modal, incluso aunque se llame con el meto-
do Show.

Cuadros de dialogo predefinidos


Ademas de crear cuadros de dialogo propios, Delphi permite usar cuadros de
dialogo predefinidos de varios tipos. Algunos estan predefinidos por Windows,
otros son cuadros de dialogo sencillos (corno un cuadro de mensaje) que muestra
una rutina de Delphi.
La Component Palette de Delphi contiene una ficha de componentes de
cuadro de dialogo. Cada uno de esos cuadros de dialogo, conocidos como dialo-
gos comunes de Windows, se define en la biblioteca de sistema ComDlg32.
DLL.
Dialogos comunes de Windows
Ya hemos utilizado algunos de estos cuadros de dialogo en varios ejemplos de
10s capitulos anteriores; asi que probablemente le resulten familiares. Basicamen-
te, es necesario poner el componente correspondiente en un formulario, definir
algunas de sus propiedades, ejecutar el cuadro de dialogo (mediante el metodo
E x e c u t e , que devuelve un valor booleano) y recuperar las propiedades que se
hayan definido mientras se ejecutaba. Vamos a experimentar con estos cuadros de
dialogo gracias a1 ejemplo CommDlgTest. Veamos algunas de las caracteristicas
claves y no obvias de 10s cuadros de dialogo comunes:
El componente OpenDialog: Se puede personalizar definiendo filtros de
diferentes extensiones de archivo, usando la propiedad F i l t e r , que tiene
un editor muy practico y se puede asignar directamente con una cadena
como T e x t F i l e ( * . t x t ) 1 * . t x t . Otra funcionmuy util es l a d e
permitir que el dialogo verifique si la extension del archivo seleccionado se
corresponde con la extension predefinida, utilizando el indicador
o f E x t e n s i o n D i f f e r e n t de la propiedad O p t i o n s tras ejecutar el
dialogo. Por ultimo, este dialogo permite la seleccion multiple, definiendo
su opcion o f A 1 lowMul t i s e l e c t . En este caso se puede obtener la
lista de archivos seleccionados fijandose en la propiedad de lista de cadena
Files.
El componente SaveDialog: Se usa de un mod0 similar y tiene propieda-
des tambien similares, aunque no se pueden seleccionar diversos archivos.
Los componentes OpenPictureDialog y SavePictureDialog: Ofrecen fun-
ciones similares per0 tienen un formulario personalizado, que muestra una
vista previa de una imagen. Por supuesto, solo tiene sentido utilizarlos
para trabajar con archivos graficos o para guardarlos.
El componente FontDialog: se puede usar para mostrar y seleccionar
todos 10s tipos de fuentes, fuentes que se pueden usar tanto en la pantalla
como en la impresora seleccionada (WYSIWYG), o solo fuentes TrueType.
Se puede mostrar u ocultar la parte relacionada con 10s efectos especiales
y obtener diferentes versiones definiendo su propiedad o p t i o n s . Tam-
bien se puede activar un boton Apply ofreciendo sencillamente un contro-
lador de eventos para su evento OnApp l y y usando la opcion
f dApplyBut t o n . Un cuadro de dialogo de fuentes con un boton Apply
(vease la figura 7.13) se comporta casi como un cuadro de dialogo no
modal (pero no lo es).
El componente ColorDialog: Se usa con distintas opciones para mostrar
el dialogo totalmente abierto a1 principio o evitar que se abra por comple-
to. Dichas configuraciones se corresponden a 10s valores c d ~ ul l~ p e n ' o
c d P r e v e n t F u l l O p e n de la propiedad O p t i o n s .
0 Tfehuchet US
0 Tunga
0 Verdana A IE
16

A
Elector - - I r Eismph - I

Figura 7.13. El cuadro de dialogo de selection de fuentes con un boton Apply


(Aplicar).

Los cuadros d e dialogo Find y Replace: Son verdaderos cuadros de dia-


logo no modales, per0 tenemos que implementar la funcionalidad de bus-
queda y reemplazo nosotros mismos, como se ha hecho en parte en el
ejemplo CommDlgTest. El codigo personalizado esta conectado a 10s boto-
nes de 10s dos cuadros de dialogo proporcionando 10s eventos O n F i n d y

--- --
NOTA: Qt ofiece un conjunto similar de cuadros de d i h g o predeibiidos,
pero el conjunto de opciones suele ser mis limitado. La versih Q C o d g
del ejemplo pemite experimentar con estas configuracianaa,El programa
CLX tiene menos elementos de mmu, ya que algunas de opeiones no se
encuentran disponibles. Adern&, hay otros cambioa miaim08 en el M g o
fuente.
- ---- ~ - ~- -~ -

Un desfile de cuadros de mensaje


Los cuadros de mensaje de Delphi jJ 10s cuadros de entrada son otro conjunto
de cuadros de dialogo predefinidos. Se pueden usar muchos procedimientos y
funciones Delphi para mostrar cuadros de dialogo simples:
L a funci6n MessageDlg: Muestra un cuadro de mensaje personalizado,
con uno o mas botones y normalmente un mapa de bits. La funcion
M e s s a g e D l g P o s es similar a la funcion M e s s a g e D l g , per0 el cuadro
de mensaje aparece en una posicion dada, no en el centro de la pantalla (a
no ser que se use la posicion -1,-1 para hacer que aparezca en el centro de
la pantalla).
El procedimiento ShowMessage: Muestra un cuadro de mensaje mas sen-
cillo, con el nombre de la aplicacion como titulo y solo un boton OK. El
procedimiento ShowMessagePos hace lo mismo, per0 tambien se puede
indicar la posicion del cuadro de mensaje. El procedimiento ShowMessa-
ge Fmt es una variation de ShowMes sage, que tiene 10s mismos
parametros que la funcion Format.Corresponde a una llamada a Format
dentro de una llamada a ShowMessage.
El mCtodo MessageBox del objeto Application: Permite especificar el
mensaje y el titulo. Tambien ofrece varios botones y funciones. Esto es un
encapsulado direct0 y sencillo de la funcion de la API de Windows
MessageBox,que pasa como parametro de ventana principal el contro-
lador del objeto Appl i cat ion.Este se necesita para que el cuadro de
mensaje se comporte como una ventana modal.
La funci6n InputBox: Pide a1 usuario que escriba una cadena. Tenemos
que proporcionar un titulo, una consulta y una cadena predeterminada. La
funcion InputQuery tambien pide al usuario que escriba una cadena.
La unica diferencia entre ambas funciones estriba en su sintaxis. La fun-
cion InputQuery tiene un valor de retorno booleano que indica si el
usuario ha hecho clic sobre OK o sobre Cancel.
Para mostrar algunos de 10s cuadros de mensaje disponibles en Delphi, hemos
escrito otro programa de muestra, con un enfoque similar a1 anterior ejemplo
CommDlgTest.
En el ejemplo MBParade, existe una gran cantidad de opciones (botones de
radio, casillas de verificacion, cuadros de edicion y controles de edicion e incre-
mento) para definir antes de hacer clic sobre uno de 10s botones que muestra un
cuadro de mensaje. El ejemplo QMbParade solo carece del boton de ayuda, que
no esta disponible en 10s cuadros de mensaje de CLX.

Cuadros "Acerca de" y pantallas iniciales


Las aplicaciones suelen tener un cuadro "Acerca de", en el que mostramos
informacion, como por ejemplo la version del producto, el copyright, etc. El mod0
mas sencillo de construir uno de estos cuadros es usar la funcion MessageDlg.
Con este metodo, se puede mostrar solo una cantidad limitada de texto y ningun
grafico especial.
Por eso, el metodo habitual para crear un cuadro "Acerca de" es usar un
cuadro de dialogo, como el generado con una de las plantillas predefinidas de
Delphi. En este cuadro "Acerca dew,podria afiadirse algo de codigo para mostrar
informacion sobre el sistema, como la version de Windows o la cantidad de me-
moria libre, u otra informacion de usuario, como el nombre del usuario regis-
trado.
Creacion de una pantalla inicial
Otra tecnica tipica en aplicaciones es mostrar una pantalla inicial antes de que
aparezca el formulario principal. A1 hacer esto se consigue que la aplicacion
parezca responder mejor, porque se muestra algo al usuario mientras que se carga
el programa, y tambien es un efecto visual bastante agradable. En ocasiones esta
misma ventana se muestra como el cuadro "Acerca dewde la aplicacion. Como
ejemplo; de una pantalla inicial especialmente util, hemos creado un programa
que muestra un cuadro de lista con ni~merosprimos.
Los numeros primos se calculan a1 arrancar el programa para que aparezcan
desde el momento en que el formulario se hace visible, mediante un bucle for
que va de 1 a 3 0 - 0 0 0. Como hemos usado (a proposito) una funcion lenta para
calcular numeros primos, este codigo de inicializacion requiere algo de tiempo.
Los numeros se aiiaden a1 cuadro de lista que cubre toda la zona de cliente del
formulario y permite mostrar diversas columnas, como muestra la figura 7.14.

Figura 7.14. El formulario principal del ejemplo Splash, con la pantalla inicial (se
trata de la version Splash2).

Existen tres versiones de este programa (ademas de las tres versiones corres-
pondientes para CLX). A1 ejecutar SplashO, el problema es que para la operacion
inicial, que se realiza en el metodo FormCreate, se emplea mucho tiempo.
Cuando arrancamos el programa, tarda varios segundos en mostrar el formulario
principal. Si el ordenador es muy rapido o muy lento, podemos cambiar el limite
superior del bucle for del metodo FormCreate para que sea mas rapido o mas
lento. Este programa tiene un cuadro de dialogo sencillo con un componente de
imagen, un titulo y un boton de mapa de bits, todo colocado en un panel que ocupa
toda la superficie del cuadro "Acerca de". Este formulario aparece cuando selec-
cionamos la opcion del menu Help>About. Pero lo que queremos en realidad es
mostrar el cuadro "Acerca den mientras arranca el programa. Podemos ver este
efecto a1 ejecutar Splashl y Splash2, que muestran una pantalla inicial mediante
dos tecnicas distintas.
En primer lugar, hemos afiadido un metodo a la clase TAboutBox. Este
metodo, llamado Make S p l a s h, cambia algunas propiedades del formulario para
que la pantalla inicial encaje como formulario de pantalla inicial. Basicamente
elimina el borde y el titulo, oculta el boton OK, hace que el borde del panel sea
grueso (para sustituir el borde del formulario) y, a continuacion, muestra el for-
mulario y lo pinta inmediatamente:
procedure TAboutBox.MakeSplash;
begin
Borderstyle : = bsNone;
BitBtnl-Visible : = False;
Panell.BorderWidth : = 3;
Show;
Update;
end;

A este metodo se llama tras haber creado el formulario en el archivo de pro-


yecto del ejemplo Splashl. Este codigo se ejecuta antes de crear 10s otros formu-
larios (en este caso solo el formulario principal) y entonces se elimina la pantalla
inicial antes de ejecutar la aplicacion. Dichas operaciones suceden en un bloque
t r y/fi na 11y. Veamos el codigo del bloque principal del archivo de proyecto
del ejemplo Splash2:
var
SplashAbout: TAboutBox;

begin
Application.Initialize;

// c r e a y m u e s t r a e l f o r m u l a r i o i n i c i a l
SplashAbout := TAboutBox .Create (Application);
try
SplashAbout.MakeSp1ash;
// c o d i g o e s t d n d a r ...
Application.CreateForm(TForml, Forml);
// e l i m i n a e l f o r m u l a r i o i n i c i a l
SplashAbout.Close;
finally
SplashAbout.Free;
end;

Application.Run;
end.

Esta tecnica solo tiene sentido solo si se tarda en crear el formulario principal
de la aplicacion, para ejecutar su codigo de arranque (como en este caso) o para
abrir tablas de bases de datos. Fijese en que la pantalla inicial es el primer formu-
lario que se crea, per0 como el programa no usa el metodo CreateForm del
objeto Application, este no se transforma en el formulario principal de la
aplicacion. En ese caso: cerrar la pantalla inicial finalizaria el programa.
Un enfoque alternativo es mantener el formulario inicial en pantalla algo mas
de tiempo y utilizar un temporizador para librarse de el. Esta tecnica se utiliza en
el ejemplo Splash2. Este ejemplo tambien utiliza un enfoque distinto para crear el
formulario inicial: en lugar de crear el formulario inicial en el codigo fuente del
proyecto, lo crea a1 comienzo del metodo FormCreate del formulario principal.
procedure TForml. FormCreate ( S e n d e r : T O b j e c t ) ;
var
I: Integer;
SplashAbout: TAboutBox;
begin
// c r e a y m u e s t r a e l f o r m u l a r i o i n i c i a l
SplashAbout : = TAboutBox.Create ( A p p l i c a t i o n ) ;
SplashAbout.MakeSp1ash;
// c o d i g o l e n t o ( o m i t i d o ) . . .
// e l i m i n a e l f o r m u l a r i o i n i c i a l , d e s p u e s d e u n t i e m p o
SplashAbout.Timerl.Enab1ed : = True;
end ;

El temporizador se activa justo antes de finalizar el metodo. Despues de que su


intervalo de tiempo haya pasado (en el ejemplo, tres segundos) se activa el evento
OnTimer y el formulario inicial lo controla cerrandose y destruytndose, me-
diante Close y despues Release.
- - . - -.. ---
- - - - -- .-
- .----~-. ~

NOTA: El mitodo Release de un formulario es similar a1 m M o Free


de 10s objetos, pero la destruction del formulario se retrasa hasta que todos
10s cont*olado;es de eventos haya completado su ejecuci6n. US& Free
dentro
. -. .
de un formularia
-. podria
. -
provocar una violaci6n de acceso,
- - - - .
ya que el
- .
codigo rnterno que disparo el controlador de eventos podria referirse de
nuevo a1 objeto fomulario.

Hay algo mas que solucionar. El formulario principal aparecera mas tarde y
delante del formulario inicial, a menos que lo convirtamos en un formulario fijo
por encima de la pantalla. Por esa razon, hemos aiiadido una linea a1 metodo
Makesplash del cuadro "Acerca de" en el ejemplo Splash2:
Parte II
Arquitecturas
orientadas
a objetos
en Delphi
La arauitectura de
las aplicaciones
Delphi

A pesar de que se han presentado ejemplos de programas Delphi desde el


principio de este libro, estos no han estado centrados en la estructura y arquitec-
tura de las-aplicaciones desarrolladas con las bibliotecas de clases de Delphi. Por
ejemplo, no se ha entrado en profundidad a explicar el objeto global
A p p l i c a t i o n , las tecnicas para realizar el seguimiento del desarrollo de 10s
formularios creados, el flujo de mensajes del sistema, ni otros elementos afines.
En un capitulo anterior se ha descrito como crear aplicaciones con multiples
formularios y cuadros de dialogo, per0 no como se pueden relacionar esos formu-
larios entre si, como se pueden compartir caracteristicas comunes entre formula-
rios, o como trabajar con varios formularios similares de una manera consistente.
El objetivo del presente capitulo es explicar todos estos conceptos, cubriendo
tanto tecnicas basicas como avanzadas, incluyendo la herencia de formularios
visuales, el uso de marcos, y el desarrollo MDI, asi como el uso de interfaces para
construir jerarquias complejas de clases de formularios.
Este capitulo trata 10s siguientes temas:
LosobjetosglobalesApplicationy Screen.
Mensajes y multitarea en Windows.
Procesamiento en segundo plano y multihilo,
Busqueda de las instancias previas de una aplicacion.
Aplicaciones MDI.
Herencia de formularios visuales
Marcos.
Formularios base e interfaces.

El objeto Application
Se ha mencionado el objeto global Application en multiples ocasiones,
per0 dado que este capitulo se centra en la estructura de las aplicaciones Delphi,
pasemos a describir en detalle este objeto global y su clase correspondiente.
App 1icat ion es un objeto global de la clase TApp 1icat ion,definido en la
unidad Forms y creado en la unidad Controls. La clase TApplication es un
componente, per0 no se puede utilizar en tiempo de diseiio. Algunas de sus pro-
piedades pueden definirse directamente en la ficha Application del cuadro de
dialog0 Project Options, otras deben asignarse en el codigo.
Para controlar estos eventos, en cambio, Delphi incluye un componente muy
comodo, App 1icat ionEvent s. Ademas de permitir asignar controladores en
tiempo de diseiio, la ventaja de este componente es que permite usar multiples
controladores. Si situamos una instancia del componente App 1icationEvents
en dos formularios diferentes, cada uno de ellos podra controlar el mismo evento
y se ejecutaran ambos controladores. En otras palabras, multiples componentes
App 1icat io nEve nt s pueden encadenar sus controladores.
Algunos eventos que afectan a toda la aplicacion, como OnAct ivat e,
OnDeactivate, OnMinimize y OnRestore, permiten realizar un segui-
miento del estado de la misma. En el caso de otros eventos, 10s controles que 10s
reciben 10s reenvian a la aplicacion, como en el caso de OnActionExecute,
OnActionUpdate,OnHelp,OnHint,OnShortCut y OnShowHint.Por
ultimo, existe un controlador global de excepciones, 0nEx ception,el evento
O n ~ d l eutilizado para ejecutar procesos en segundo plano y el evento
OnMessage, que se activa siempre que se envia directamente un mensaje a
cualquier ventana o control de ventana de la aplicacion.
Aunque su clase hereda directamente de T C o m p o n e n t , el objeto
Application tiene asociada una ventana. La ventana de la aplicacion esta
oculta per0 aparece en la barra de tareas. Por este motivo Delphi llama Form1 a
la ventana y Pro j e ct 1 a1 icono correspondiente en la barra de tareas.
La ventana relacionada con el objeto Application ( la ventana de la apli-
cacion) sirve para mantener todas las ventanas de una aplicacion juntas. El hecho
de que todos 10s formularios de alto nivel de un programa tengan invisible esta
ventana propietaria resulta fundamental, por ejemplo, cuando se activa la aplica-
cion. De hecho. cuando las ventanas de un programa estan detras de las de otros
programas. a1 hacer clic sobre una ventana de la aplicacion todas las ventanas de
la aplicacion apareceran en primer termino. Es decir, la ventana invisible se utili-
za para conectar 10s diferentes formularios de la aplicacion. En realidad, la venta-
na de la aplicacion no esta oculta; porque eso afectaria a su comportamiento, solo
tiene una altura y anchura nulas y, por lo tanto, no se ve.
-- --, -
TRUCO: En Windows, las operaciones de minimizar y maximizar e s t h
asociadas Dor defect0 con sonidos del sistema y con un efecto visual anima-
producen el sonido y muestran el

Cuando se crea una nueva aplicacion en blanco, Delphi genera un codigo para
el archivo de proyecto. que incluye lo siguiente:
begin
Application.Initialize;
Application.CreateForm(TForml, Forml);
Application.Run;
end.

Como se lfe en este codigo estandar, el objeto Application puede crear


formularios, definiendo el primero como MainForm (una de las propicdades de
Application), y cerrar toda la aplicacion cuando se destruye dicho formula-
rio principal. La ejecucion del programa esta incluida en el mCtodo Run, que
contiene el bucle del sistema que procesa 10s mensajes del sistema. Este bucle
continua hasta que la ventana principal (la ventana creada en primer lugar) esta
cerrada.
-
TRUCO: El formulario principal no es necesariarnente el que se crea en
primer lugar, per0 es el primero que se crea con la 1lamadaApplicat i o n .
C r e a t e Form.

El bucle de mensaje de Windows contenido en el metodo Run envia 10s mensa-


jes del sistema a la ventana de aplicacion adecuada. Cualquier aplicacion Windows
necesita un bucle de mensaje, per0 en Delphi no tenemos que escribirlo porque el
objeto Application ofrece uno predefinido.
Aunque Qta es la funcion principal del objeto Application, tambien controla
otras funciones interesantes:
Las sugerencias.
El sistema de ayuda, que incluye la capacidad de definir el tipo de visor de
ayuda.
La activacion de la aplicacion, su minirnizacion y restauracion.
Un controlador de escepciones global.
Information general sobre la aplicacion, como MainForm. el nombre del
archivo ejecutable y su ruta (ExeName), el icono y el titulo que aparecen
en la barra de tareas de Windows y cuando buscamos en las aplicaciones
en ejecucion con las teclas AIt-Tab.

TRUCO: Para evitar discrepancias entre 10s dos titulos, se puede cambiar
el titulo de la aplicacion en tiempo de disefio. En caso de que cambie en
tiempo de ejecucion, se puede copiar el titulo del formulario a1 de la aplica-
cion con el siguiente codigo: Application.Title := Forml.
Caption.

En la mayoria de las aplicaciones, no tenemos en cuenta la ventana de la


aplicacion, aparte de fijar su titulo e icono y controlar algunos eventos. Sin em-
bargo, podemos realizar operaciones sencillas. Si definimos la propiedad
ShowMainForm como False en el codigo fuente del proyecto, indicamos que
el formulario principal no deberia aparecer a1 arrancar. Dentro de un programa,
en cambio, se puede usar la propiedad MainForm del objeto Application
para accedcr a1 formulario principal.

Mostrar la ventana de la aplicacion


No hay mejor prueba de que esiste una ventana para el objeto Application
que mostrarla, como en el ejeinplo ShowApp. En realidad, no hay que.mostrarla,
sino, simplemente, ajustar su tamaiio y definir una serie de atributos de la venta-
na, como el titulo y el borde. Para ello podemos utilizar las funciones de la API de
Windows en la ventana que indica l a propiedad H a n d l e del objeto
Application:
procedure TForml.ButtonlClick(Sender: TObject);
var
OldStyle: Integer;
begin
// a d a d e un b o r d e y u n t i t u l o a l a v e n t a n a d e l a a p l i c a c i o n
OldStyle : = GetWindowLong (Application.Handle, gwl-Style);
SetWindowLong (Application.Handle, gwl-Style,
OldStyle o r ws-ThickFrame o r ws-Caption);
// d e f i n e e l t a m a d o d e l a v e n t a n a d e l a a p l i c a c i o n
SetWindowPos (Application.Handle, 0, 0, 0, 200, 100,
swp-NoMove o r swp-NoZOrder ) ;
end;

Las dos funciones GetWindowLong y SetWindowLong de la API acce-


den a la informacion del sistema relacionada con la ventana. En este caso, utiliza-
mos el parametro gwl Style para leer o escribir el estilo de la ventana, lo que
incluye su borde, tituloTmenfi de sistema, iconos del borde, etc. El c6digo anterior
obtiene 10s estilos actuales y aiiade (usando una sentencia or) un borde estandar
y un titulo a1 formulario. Generalmente no es necesario implementar algo como
esto en 10s programas. Pero saber que el objeto aplicacion tiene una ventana
conectada a el y que se puede modificar es un aspect0 importante para compren-
der la estructura por defect0 de las aplicaciones Delphi.

Activacion de aplicaciones y formularios


Para mostrar el mod0 en que se activan 10s formularios y las aplicaciones,
hemos creado un ejemplo autoexplicatorio sencillo, llamado ActivApp. Este ejemplo
tiene dos formularios. Cada formulario tiene un componente etiqueta
(Labe1 Form) utilizado para mostrar el estado del formulario. El programa usa
para ello texto y color, como demuestran 10s controladores de 10s eventos
OnAct ivate y OnDeact ivate del primer formulario:
p r o c e d u r e TForml.ForrnActivate(Sender: TObject);
begin
LabelForm.Caption : = 'Form2 Activo ';
LabelForm.Color : = clRed;
end;

p r o c e d u r e TForml.FormDeactivate(Sender: TObject);
begin
LabelForm.Caption : = 'Form2 N o Activo';
LabelForm.Color : = clBtnFace;
end;

El segundo formulario tiene una etiqueta y codigo similares. El formulario


principal tambien muestra el estado de toda la aplicacion. Utiliza un componente
A p p l i c a t i o n E v e n t s para controlar 10s eventos O n A c t i v a t e y
OnDeact ivate del objeto Appl icat ion.Existen dos controladores de eventos
similares a 10s dos anteriores, con la unica diferencia de que modifican el texto y
color de una segunda etiqueta del formulario y que uno de ellos emite un sonido.
A1 ejecutar este programa, veremos si la aplicacion esta activa y, de ser asi,
cual de sus formularios es el activo. Si nos fijamos en el resultado (vease la figura
8.1) y escuchamos el sonido, entenderemos como se desencadena cada uno de 10s
eventos de activacion en Delphi.

Seguimiento de formularios con el objeto Screen


El objeto Screen, cuya clase basica es T S cr ee n,nos ofrece cierta informa-
cion global de interes sobre una aplicacion. Este objeto guarda informacion sobre
la configuracion del sistema (el tamaiio de la pantalla y las fuentes de la pantalla)
y tambien sobre el conjunto actual de formularios de una aplicacion en ejecucion.
Por ejemplo, podemos mostrar el tamaiio de la pantalla y la lista de fuentes si
escribimos:
Label1 .Caption := IntToStr (Screen-Width) + ' x ' + IntToStr
( S c r e e n - H e i g h t );
ListBoxl.Items : = Screen. Fonts;

Figura 8.1. El ejemplo ActivApp muestra si la aplicacion esta activa y cual de sus
formularios esta activo.

Tscreen informa tambien sobre el numero y resolucion de 10s monitores de


un sistema de varios monitores. Sin embargo, nos centraremos en la lista de for-
mularios almacenada por la propiedad Forms del objeto screen,el formulario
superior que indica la propiedad ActiveForm y el evento relacionado
OnActiveFormChange. Observe que 10s forrnularios a 10s que se refiere el
objeto screen son 10s formularios de la aplicacion y no 10s del sistema.
Estas funciones se demuestran mediante el ejemplo Screen, que mantiene una
lista de 10s formularios actuales en un cuadro de lista. Esta deberia actualizarse
cada vez que se crea un nuevo formulario, se destruye un formulario existente o
cambia el formulario activo del programa. Para ver su funcionamiento, se pueden
crear formularios secundarios haciendo clic sobre el boton New:
procedure TMainForm.NewButtonClick(Sender: TObject);
var
NewForm: TSecondForm;
begin
// crea un forrnulario nuevo, define su titulo y lo ejecuta
NewForm := TSecondForm-Create (Self);
Inc (nForms);
NewForm-Caption : = 'Second ' + IntToStr (nForms);
NewForm-Show;
end ;

Hay que tener en cuenta que debe desactivarse la creacion del formulario se-
cundario mediante la pagina Forms del cuadro de dialog0 Project Options.
Una de las partes clave del programa es el controlador del evento oncreate del
formulario, que rellena la lista por primera vez y, a continuacion. conecta un
controlador a1 evento OnAc tiveFormchange:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FillFormsList (Self);
/ / define el contador de formularios secundarios a 0
nForms : = 0;
/ / define un controlador de eventos para el objeto en
pantalla
Screen.0nActiveFormChange : = FillFormsList;
end;

El codigo usado para cubrir el cuadro de lista Forms esta en un segundo


procedimiento, FillFormsList,que tambien esta instalado como controlador
de eventos para el evento OnAct iveFormChange del objeto Screen:
procedure TMainForm. FillFormsList (Sender: TObject) ;
var
I: Integer;
begin
/ / ornite codigo en la fase de destruccidn
if Assigned (FormsListBox) then
begin
FormsLabel.Caption : = 'Forms: ' + IntToStr
(Screen.Formcount) ;
FormsListBox.Clear;
// escribe un nombre de clase y un titulo de forrnulario en
el c~ladrode lista
for I : = 0 to Screen.FormCount - 1 do
FormsListBox. Items .Add (Screen.Forms [I] .ClassName + ' - ' +
Screen.Forms [I] .Caption) ;
ActiveLabel.Caption : = 'Active Form : ' +
Screen.ActiveForm.Caption;
end;
end;

ADVERTENCIA:Resulta muy importante no ejecutar este codigo mien-


tras se destruye el formulario principal. Como alternativa a comprobar que
el cuadro de lista no este definido como nil,tambitn podemos probar el
Component state del formulario para el indicador csDestroying.
Otra tkcnica seria destruir el controlador de eventos OnAc tiveForrn-
Change antes de salir de la aplicacion, es deck, controlar el evento
.
u -
nc; o -
7
s e oer Prormulario
3 - . .1 J . -
principal y asignar n. 1 1 a- s; c_ r
.-
-
n.
e e .-
-

OnActiveFormChange.

El metodo Fill FormsList rellena el cuadro de lista y define un valor para


las dos etiquetas que estan sobre 61 para mostrar el numero de formularios y el
nombre del activo. Cuando hacemos clic sobre el boton New, el programa crea
una instancia del formulario secundario, le aiiade un nuevo titulo y lo muestra. El
cuadro de lista Forms se actualiza automaticamente debido a1 controlador del
evento OnAc t i v e Fo r m C ha nge instalado. En la figura 8.2, aparece el resulta-
do de este programa cuando se han creado diversos formularios secundarios.

Aclive Farn : S e c d 3
MI
Farns: 4
TSecondForm . Second 3
TSecondForm - Second 2
TSecondForrn .Second 1
TMa~nForrn- Sc~eenInfo

I
Figura 8.2. El resultado del ejemplo Screen con algunos formularios secundarios.

Cada uno de 10s formularios secundarios tiene un boton Close que podemos
pulsar para eliminarlos. El programa controla el evento onclose,definiendo el
parametro Action como caFree,de mod0 que en realidad el formulario se
destruye cuando lo cerramos. Este codigo cierra el formulario, per0 no actualiza
la lista de ventanas como es debido. El sistema desplaza primer0 el foco a otra
ventana, activando el evento que actualiza la lista y destruye el antiguo formula-
rio solo despues de dicha operacion.
Una priinera aprosimacion puede plantear que para actualizar la lista de ven-
tanas adecuadamente puede introducirse un retraso, enviando un mensaje de
Windows definido por el usuario. Pero debido a que el mensaje enviado es encola-
do y no es tratado inmediatamente, aunque el envio se realice a1 final de la esis-
tencia del formulario secundario, el formulario principal lo recibira cuando el
otro formulario sea destruido. El truco esta en poder enviar el mensaje con el
controlador del evento OnDestroy del formulario secundario. Para ello, es ne-
cesario referirse al objeto MainForm,aiiadiendo una sentencia uses en la parte
de implernentacion de esta unidad. Hemos enviado un mensaje wm User,contro-
lado por un mCtodo message especifico del formulario como pode-
mos ver a continuacion:
public
procedure Childclosed (var Message: TMessage) ; message
-User;

procedure TMainForm.ChildC1osed (var Message: TMessage);


begin
FillFormsList ( S e l f );
end:

El problema es que si cerramos la ventana principal antes de cerrar 10s formu-


larios secundarios; el formulario principal sigue existiendo, per0 su codigo ya no
se puede ejecutar. Para evitar otro error del sistema, solo se debe enviar el mensa-
je si el formulario principal no se esta cerrando. Para poder determinar si se esta
cerrando se puede aiiadir un indicador a la clase TMainForm y modificar su
valor cuando se cierra el formulario principal, para asi poder probar el indicador
desde el codigo de la ventana secundaria. Esta es una buena solucion, tan buena
que la VCL ya proporciona una funcionalidad similar con la propiedad
Componentstate y su indicador csDestroying,como ya se ha menciona-
do anteriormente. Por tanto, podemos utilizar el siguiente codigo:
procedure T S e c o n d F o r m . F o r r n D e s t r o y (Sender: TObject);
begin
i f not (csDestroying i n MainForm.ComponentState) then
PostMessage (MainForm.Handle, -User, 0, 0) ;
end ;

Con este codigo, el cuadro de lista siempre muestra todos 10s formularios de la
aplicacion. ,

Existe otra alternativa, una solucion mas orientada a Delphi. El truco esta en
considcrar que cada vez que un componente es destruido, avisa a su propietario
acerca del evento llamando a1 metodo Notification definido en la clase
TComponent.Dado que 10s forn~ulariossecundarios son propiedad del formu-
lario principal, como se ha mostrado en el codigo del metodo NewButtonClick,
puede sobrecargarse este metodo y simplificarse el codigo (vease el directorio
Screen2 de 10s ejemplos adjunto para ver el codigo de esta version):
procedure TMainForm.Notification (AComponent: TComponent;
Operation: Toperation) ;
begin
i n h e r i t e d Not if ication (AComponent, Operation) ;
i f (Operation = opRemove) and Showing and (AComponent i s
TForm) then
FillFormList ;
end ;

hacer que 10s formularios secundarios avisaran a1 principal cuando hiran


destruidos. FreeNot i f i c a t i o n tecibe como parametro el componente
del que tiene que notificar que ha sido destruido. Este metodo es utitizado
------l---L-
generalmeme --- I~- - u uesarrwauores
pur -1- ----- 11--1----
s -1-
ue ---- cunecmr curn-
curnpunenies para
ponentes de diferentes formularios o modulos de datos de forma segura.
El ultimo cambio aiiadido a ambas versiones del programa es sencillo: cuando
se hace clic en un elemento del cuadro de dialogo, el formulario correspondiente
se activa mediante el metodo B r i n g T o F r o n t . Pero esta version tiene un peque-
iio fallo, si se hace clic en el cuadro de dialogo cuando el formulario principal no
esta activo, primer0 se activa este y despues se reordena el cuadro de dialogo; de
este mod0 puede ocurrir que seleccionemos un formulario distinto del esperado.
Este error del programa es un ejemplo de 10s riesgos de actualizar informacion
dinamicamente y permitir que el usuario trabaje con ella a1 mismo tiempo.

De eventos a hilos
Para comprender como funcionan las aplicaciones de Windows internamente,
dedicaremos unos momentos a explicar como soporta este entorno la multitarea.
Es necesario comprender tambien, el papel de 10s temporizadores (y el componen-
te T i m e r ) y 10s calculos en segundo plano (o en espera), asi como el metodo
P r o c e s s M e s s a g e s del objeto global A p p l i c a t i o n .
Resumiendo, debemos sumergirnos mas en la estructura orientada a eventos de
Windows y su soporte a la multitarea. Dado que este libro esta dedicado a la
programacion con Delphi no entraremos en detalles sobre este tema, per0 dare-
mos una vision global para aquellos lectores que tengan poca experiencia en
programacion con la API de Windows.

Programacion guiada por eventos


La idea fundamental de la programacion guiada por eventos consiste en que
hay eventos concretos que establecen el flujo de control de la aplicacion. Un
programa emplea la mayor parte del tiempo en esperar a que ocurran dichos
eventos, para ofrecer el codigo que corresponde a cada uno de ellos. Por ejemplo,
cuando un usuario pulsa uno de 10s botones del raton, produce un evento. Enton-
ces, se envia un mensaje que describe el evento a la ventana que en ese momento
esta bajo el cursor del raton. El codigo del programa que responde a 10s eventos
de dicha ventana recibira el evento, lo procesara y respondera segun corresponda.
Cuando el programa ha terminado de responder a1 evento, vuelve a un estado de
espera u "ocioso".
Los eventos, por lo tanto, funcionan en serie: se controla cada evento solo
cuando se ha terminado con el anterior. Cuando una aplicacion esta ejecutando un
codigo de control de eventos (es decir, cuando no esta esperando un evento), 10s
demas eventos de la aplicacion han de esperar en una cola de mensajes reservada
para dicha aplicacion (a menos que la aplicacion utilice varios threads o hilos).
Cuando la aplicacion ha respondido a un mensaje y ha vuelto a su estado de
espera, pasa a1 ultimo lugar en la lista de programas en espera para controlar
mensajes adicionales. En cada version de Win32 ( 9 x , NT, Me y 2000), despues de
que haya pasado un tiempo establecido, el sistema interrumpe la aplicacion actual
e inmediatamente pasa el control a la siguiente en la lista. El primer programa se
reanudara solo despues de que haya pasado el turno de cada aplicacion. A esto se
le llama multitarea no cooperativa.
Asi, una aplicacion que realice una operacion para la que emplee mucho tiem-
po en un controlador de eventos no evita que el sistema funcione correctamente
(porque otros procesos tienen su porcion de tiempo de procesador), per0 normal-
mente no puede pintar ni siquiera de nuevo sus propias ventanas correctamente, lo
cual causa un efecto horroroso. Si no se ha esperimentado este problema, pode-
mos hacer la siguiente prueba: Escribimos un bucle consumidor de tiempo que se
ejecute al hacer clic en un boton; intentarnos mover el formulario o mover otra
ventana encima de el. El efecto es realmente molesto. Si aiiadimos la llamada
Application. ProcessMessages dentro del bucle veremos que la opcra-
cion se vuelve mucho mas lenta, pero el formulario se refresca inmediatamentc.
Como cjemplo del uso de Application. ProcessMessages dentro de
un bucle consumidor de tiempo, podeinos acudir a1 ejemplo BackTask. Este es el
codigo que utiliza esta aproximacion:
procedure T F o r m l . B u t t o n 2 C l i c k ( S e n d e r : TObject);
var
I, Tot: Integer;
begin
Tot : = 0;
f o r I : = 1 t o Max do
begin
i f IsPrime ( I ) then
Tot : = Tot + I;
ProgressBarl.Position : = I * 100 div Max;
App1ication.ProcessMessages;
end;
ShowMessage (IntToStr (Tot)) ;
end;

TRUCO: Existe otra alternativa a la llamada a ProcessMess ages: la


funcion HandleMessaae. Hav dos diferencias: HandleMessaqe pro-
>

cesa un solo mensaje cada vez que es llamada, mientras ProcessMlessa-


ges sigue procesando 10s mensajes de la cola; y HandleMessaqe t ambien
-1 - ------_
_ - ! - -.n-
acrlva el riernpo ocloso
_-A!_.- L!
oe proceso.

Si una aplicacion ha respondido a sus eventos y esta esperando su turno para


procesar mensajes, no tiene oportunidad de recuperar el control hasta que recibe
otro mensaje (a no ser que utilice multiples hilos). Esta es una razon para el uso
de un temporizador, un componente del sistema que envia un mensaje a la aplica-
cion siempre que se cumple un interval0 de tiempo dado. La utilizacion de un
temporizador es la unica manera de hacer que una aplicacion realice operaciones
automaticamente de forma regular, incluso cuando el usuario esta ausente o no
esta usando el programa (y, por lo tanto, no esta procesando ningun evento).
Cuando hablamos de eventos, hay que recordar que 10s eventos de entrada
(generados mediante el raton o el teclado) suponen solo un pequefio porcentaje del
total del flujo de mensajes de una aplicacion Windows. La mayoria de mensajes
son 10s internos del sistema o 10s intercambiados entre diferentes controles y
ventanas. Incluso, una operacion de entrada tan familiar como un clic de raton
puede derivar en un gran numero de mensajes, la mayoria de 10s cuales son men-
sajes internos de Windows. Esto puede comprobarse utilizando la utilidad
W inS ight incluida en Delphi. En Wins ight,elegiremos ver las trazas de 10s
mensajes ( M e s s a g e Trace) y seleccionaremos 10s mensajes de todas las venta-
nas. Haremos clic sobre el boton S t a r t y, despues, realizaremos algunas opera-
ciones con el raton. Se mostraran cientos de mensajes en pocos segundos.

Entrega de mensajes Windows


Antes de ver 10s ejemplos, debemos tener en cuenta otro elemento clave del
control de mensajes. En Windows, hay dos modos de enviar un mensaje a una
ventana:
L a funcion A P I PostMessage: Se usa para colocar un mensaje en la cola
de mensajes de la aplicacion. El mensaje sera controlado solo cuando la
aplicacion tenga oportunidad de acceder a su cola de mensajes (es decir,
cuando reciba el control desde el sistema) y solo despues de que se hayan
procesado 10s mensajes anteriores. Esta llamada es asincrona, dado que no
sabemos cuando se recibe en realidad el mensaje.
L a funci6n API SendMessage: Se usa para ejecutar inmediatamente codi-
go de control de mensajes. SendMessage pasa por alto la cola de men-
sajes de la aplicacion y envia el mensaje directamente a una ventana o
control de destino. Esta llamada es sincrona. Esta funcion tiene incluso un
valor de retorno, que vuelve a pasar mediante el codigo de control de men-
sajes. Llamar a SendMessage no es diferente de llamar directamente
otro metodo o funcion del programa.
La diferencia entre estas dos formas de enviar mensajes es similar a la que
existe entre enviar una carta por correo, que tarde o temprano llega a su destino?
y enviar un fax, que llega inmediatamente a su destinatario. A pesar de que estas
funciones de bajo nivel se utilizaran poco en Delphi, esta descripcion nos ayudara
a determinar cual usar en caso de tener que escribir este tipo de codigo.

Proceso secundario y multitarea


Supongamos que es necesario implementar un algoritmo que emplee un tiempo
considerable. Si escribimos el algoritmo como respuesta a un evento? la aplica-
cion se detendra por completo durante el tiempo que emplee para procesar dicho
algoritmo. Para que el usuario sepa que se esta procesando algo, podemos usar el
cursor en forma de reloj de arena o una barra de progreso, per0 esta solucion no
sera la mejor para el usuario. Win32 permite que otros programas sigan ejecutan-
dose, per0 el programa en cuestion se congelara, ni siquiera actualizara su propia
interfaz de usuario si se solicita que se vuelva a pintar. De hecho, mientras el
algoritmo se esta ejecutando, la aplicacion no podra recibir ni procesar ningun
otro mensaje, como 10s mensajes de representacion.
La solucion mas sencilla a este problema consiste en llamar a 10s metodos
ProcessMessages y HandleMessage descritos anteriormente. El proble-
ma de hacerlo asi esta en que el usuario podria pulsar de nuevo el boton o las
teclas que iniciaron dicho algoritmo. Para solucionarlo, se pueden desactivar 10s
botones y ordenes que no queramos que el usuario seleccione y mostrar el cursor
en forma de reloj de arena (que tecnicamente no evita que tenga lugar un evento de
pulsado del raton, pero si sugiere a1 usuario que deberia esperar antes de realizar
otra operacion).
Para realizar algunos procesos secundarios de baja prioridad, tambien pode-
mos dividir el algoritmo en trozos mas pequeiios para ejecutar cada trozo de uno
en uno, dejando a la aplicacion que responda a todos 10s mensajes pendientes
mientras 10s procesa. Podemos usar un temporizador para hacer que el sistema
nos notifique cuando se ha consumido un interval0 de tiempo. Aunque podemos
usar temporizadores para implementar alguna forma de procesamiento secunda-
rio, esta no es una buena solucion. Seria mejor ejecutar cada paso del programa
cuando el objeto Application recibe el evento OnIdle.La diferencia entre
llamar a ProcessMes sages y usar el evento OnIdle esta en que a1 llamar a
ProcessMessages daremos a1 codigo mas tiempo de procesado que con la
tecnica OnIdle.Llamar a ProcessMessages es un buen mod0 de dejar que
el sistema realice otras operaciones mientras el programa esta procesando. Utili-
zar el evento OnIdle es una forma de dejar que la aplicacion realice las tareas
secundarias cuando no hay solicitudes del usuario pendientes.

Multihilo en Delphi
Cuando es necesario realizar operaciones en segundo plano, o cualquier proce-
so no estrictamente relacionado con la interfaz de usuario, se puede utilizar la
aproximacion mas correcta desde el punto de vista tecnico: crear un hilo de ejecu-
cion separado dentro del propio proceso. La programacion multihilo puede pare-
cer un tema complejo, pero, realmente, no es tan complicado, aunque tenga que
ser considerado cuidadosamente. Es conveniente conocer a1 menos 10s fundamen-
tos de la programacion multihilo porque, en el mundo de 10s sockets y la progra-
macion para Internet, hay pocas cosas que se puedan hacer sin hilos.
La biblioteca RTL de Delphi contiene una clase TThread que permite crear y
controlar hilos. La clase TThread no se utiliza nunca directamente dado que es
una clase abstracta (una clase con un metodo abstracto virtual). Para usar hilos,
sc hereda de T T h r e a d y se utilizan las caracteristicas de esta clase base.
La clase T T h r e a d tiene un constructor con un unico parametro
( C r e a t e s u s p e n d e d ) que permite elegir entre arrancar el hilo inmediatamente
o dejarlo en espera hasta mas tarde. Cuando el objeto hilo arranca automaticamente,
o cuando se reanuda su ejecucion, mantiene el metodo E x e c u t e en funciona-
miento hasta el final. La clase proporciona una interfaz protegida que incluye dos
mctodos basicos para 10s hilos:
procedure Execute:virtual; abstract;
p r o c e d u r e Synchronize(Method: TThreadMethod);

El metodo E x e c u t e declarado como un procedimiento abstracto virtual, debe


ser redefinido en cada hilo. ~ s t contiene
e el c6digo principal del hilo. es decir. el
codigo que habitualmente se utiliza en una funcion de hilo a1 usar las funciones
del sistema.
El metodo S y n c h r o n i z e se utiliza para evitar el acceso concurrente a com-
ponentes VCL. El codigo VCL sc cjecuta dentro del hilo principal del programa,
por ello. es necesario sincronizar el acceso a 10s componentes VCL para cvitar 10s
problemas de reentrada (errores por la reentrada de una funcion antes de comple-
tar una ejecucion previa) y el acceso concurrente a recursos compartidos. El
unico parametro de S y n c h r o n i z e es un metodo sin parametros, normalmente
un mctodo de la misma clase hilo. Dado que no se le pueden pasar parametros a
este metodo, habitualmente se guardan algunos valores entre 10s datos del objeto
hilo en el metodo E x e c u t e y se usan esos valores en 10s metodos sincronizados.

NOTA: Delphi 7 incluye dos nuevas versiones de S y n c h r o n i z e que


permiten sincronizar un metodo con el hi10 principal sin necesidad de lla-
marlo desde el objeto hilo. Am'bos metodos sobrecargados, s y n c h r o n i z e
y S t a t i c s y n c h r o n i z e son metodos de 1 a c l a s e T T h r e a d y requieren
un hilo como parametro.

Otro mod0 de evitar conflictos es utilizar las tecnicas de sincronizacion que


ofrccc cl sistema operativo. La unidad S y n c O b j s define unas pocas clases VCL
para algunos de cstos objetos de sincronizacioi~de bajo nivel, tales como eventos
(con las clases T E v e n t y T S i n g l e E v e n t ) y secciones criticas (con la clase
T C r i t i c a l s e c t i o n ) . (Los eventos de sincronizacion no deben de confundir-
se con 10s cucntos dc Dclphi. dado que ambos conceptos no estan relacionados.)

Un ejemplo con hilos


Para ver un ejemplo de un hilo, puede estudiarse nuevamente el ejemplo
Backstack. Este ejemplo produce un hilo secundario para calcular la suma de 10s
numeros primos. La clase hilo tiene el tipico metodo E x e c u t e , un valor inicial
pasado mediante una propiedad publica (Max), y dos valores internos (FTo t a l
y FPo s i t i o n ) usados para sincronizar la salida de 10s metodos S ho wTo t a 1y
U p d a t e P r o g r e s s . Esta es la declaracion completa para el objeto hilo:

tYPe
TPrimeAdder = c l a s s (TThread)
private
FMax, FTotal, FPosition: Integer;
protected
procedure Execute; override;
procedure ShowTotal;
procedure UpdateProgress;
public
property Max: Integer read FMax write FMax;
end ;

El metodo E x e c u t e es muy similar a1 codigo usado para 10s botones en el


ejemplo Backstack presentado anteriormente. La unica diferencia esta en la lla-
mada final a S y n c h r o n i z e , como puede comprobarse en el siguiente frag-
mento:
procedure TPrimeAdder.Execute;
var
I, Tot: Integer;
begin
Tot : = 0;
f o r I : = 1 t o FMax do
begin
i f IsPrime (I) then
Tot : = Tot + I;
i f I mod (£Max d i v 100) = 0 then
begin
FPosition : = I * 100 d i v £Max;
Synchronize (UpdateProgress) ;
end ;
FTotal : = Tot;
Synchronize (ShowTotal);
end ;

procedure TPrimeAdder.ShowTota1;
begin
ShowMessage ( ' Thread: ' + IntToStr ( FTotal) ) ;
end :

procedure TPrimeAdder-Updateprogress;
begin
Forml.ProgressBar1.Position : = £Position;
end :

El objeto hilo se crea a1 hacer clic sobre un boton y es automaticamente des-


truido cuando termina el metodo E x e c u t e :
procedure TForml.Button3Click(Sender: TObject);
var
AdderThread: TPrimeAdder;
begin
AdderThread : = TPrimeAdder .Create (True);
AdderThread.Max : = Max;
AdderThread.Free0nTerminate : = True;
AdderThread.Resume;
end;

En lugar de fijar el numero maximo utilizando una propiedad, hubiera sido


mejor pasar este valor como un parametro adicional de un constructor hecho a
medida; esto se ha evitado a fin de centrar el ejemplo en el uso del hilo. Se
estudiaran mas ejemplos de hilos en capitulos posteriores.

Verificando si existe una instancia previa


de una aplicacion
Una forma de multitarea consiste en ejecutar dos o mas instancias de la misma
aplicacion. Por lo general, cualquier aplicacion puede ser ejecutada por un usua-
rio en mas de una instancia y es necesario que pueda verificar si ya se ha ejecuta-
do una instancia anterior, para desactivar el comportamiento predefinido y permitir
a lo sumo una instancia. Veremos diversos modos de implementar dicha verifica-
cion.

Buscando una copia de la ventana principal


Para encontrar una copia de la ventana principal de una instancia previa, hay
que usar la funcion API FindWindow y pasarle el nombre de la clase de ventana
(el nombre utilizado para registrar el tipo de ventana del formulario, o WNDCLASS,
en el sistema) y el titulo de la ventana que buscamos. En una aplicacion Delphi, el
nombre de la clase de ventana WNDCLASS es el mismo que el nombre en Pascal
orientado a objetos para la clase del formulario (por ejemplo, TForml). El resul-
tad0 de la funcion FindWindow es o bien un manejador de la ventana o cero (si
no se encuentra ninguna correspondencia).
El codigo principal de la aplicacion Delphi deberia escribirse de tal mod0 que
se ejecute solo si el resultado de FindWindow es cero:
var
Hwnd : THandle ;
begin
Hwnd : = FindWindow ( ' TForml ', nil ) ;
if Hwnd = 0 t h e n
begin
Application.Initialize;
Application.CreateForm(TForm1, Forml);
Application.Run;
end
else
SetForegroundWindow (Hwnd)
end.

Para activar la ventana de la instancia anterior de la aplicacion, se puede usar


la funcion Set ForegroundWindow,que funciona en el caso de ventanas que
poseen otros procesos.
Esta llamada produce su efecto solo si se ha minimizado la ventana pasada
como parametro. De hecho, cuando se minimiza el formulario principal de una
aplicacion Delphi, se oculta y, por esa razon, el codigo de activacion no tiene
efecto.
Desafortunadamente, si se ejecuta un programa que utilice la llamada
Findwindow que acaba de aparecer en el IDE de Delphi, puede que ya exista
una ventana con ese titulo y clase: el formulario en tiempo de diseiio. Por lo tanto,
el programa no arrancara ni siquiera una vez.
Sin embargo, se ejecutara si cerramos el formulario y su archivo de codigo
fuente correspondiente (en realidad, cerrar el formulario oculta sencillamente la
ventana) o si cerramos el proyecto y ejecutamos el programa con el Explorador de
Windows. Tambien debemos considerar que tener un formulario llamado Form 1
puede, probablemente, hacer que no funcione como se espera ya que muchas
aplicaciones Delphi pueden tener un formulario con el mismo nombre. Esto se
corregira en las proximas versiones del codigo.

Uso de un mutex
Un mutex, u objeto de exclusion mutua, es una tecnica totalmente distinta. Es
una tecnica comun de Win32, utilizada normalmente para sincronizar hilos. Aqui,
utilizamos un mutex para sincronizar dos aplicaciones diferentes o, para ser mas
precisos, dos instancias de la misma aplicacion.
Cuando una aplicacion ha creado un mutex con un nombre determinado, puede
probar si el objeto ya lo posee otra aplicacion, llamando a la funcion de la API de
Windows Wait ForSingleOb jec t.De no ser asi, la aplicacion que llama a
esta funcion se transforma en la aplicacion propietaria. Pero si el mutex ya tiene
un propietario, la aplicacion espera hasta que se consume el tiempo (el segundo
parametro de la funcion). Entonces devuelve un codigo de error.
Para implementar esta tecnica, podemos usar el siguiente codigo fuente del
proyecto:
var
hMutex: THandle;
begin
hMutex : = CreateMutex (nil, False, 'OneCopyMutex ' ) ;
if WaitForSingleObject (hMutex, 0 ) <> wait-Timeout then
begin
Application.Initialize;
Application.CreateForm(TForm1, Forml);
Application.Run;
end;
end.

Si ejecutamos dos veces este ejemplo, veremos que se crea una nueva copia
temporal de la aplicacion (aparece el icono en la barra de tareas) y despues se
destruye cuando se ha consumido el tiempo Esta tecnica es realmente mas robusta
que la anterior, per0 tiene un pequeiio problema que esta en activar la instancia
existente de la aplicacion y encontrar su formulario, para lo que podemos emplear
una tecnica mejor.

Buscar en una lista de ventanas


Para buscar una ventana principal concreta del sistema, podemos usar las
funciones Enurnwindows de la API. Las funciones de enumeracion son bastante
peculiares en Windows, porque suelen requerir otra funcion como parametro.
Estas funciones de enumeracion necesitan un punter0 a una funcion (descrita
normalmente como funcion de retrollamada o callback). La idea consiste en que
esta funcion se aplica a cada elemento de la lista (en este caso, la lista de ventanas
principales), hasta que la lista termina o la funcion devuelve F a l s e .
Veamos la funcion de enumeracion del ejemplo Onecopy:
function EnumWndProc (hwnd: THandle; Param: Cardinal): Bool; .
stdcall;
var
ClassName, WinModuleName: string;
WinInstance: THandle;
begin
Result : = True;
SetLength (ClassName, 100) ;
GetClassName (hwnd, PChar (ClassName), Length (ClassName)) ;
ClassName : = PChar (ClassName);
if ClassName = TForml .ClassName then
begin
// o b t i e n e el n o m b r e d e m o d u l o d e la ventana d e d e s t i n o
SetLength (WinModuleName, 200) ;
WinInstance : = GetwindowLong (hwnd, GWL-HINSTANCE) ;
GetModuleFileName (WinInstance,
PChar (WinModuleName), Length (WinModuleName)) ;
WinModuleName := PChar (WinModuleName); / / a j us ta la
1 o n g i t ud
// c o m p a r a 10s nornbres d e m o d u l o
if WinModuleName = ModuleName then
begin
FoundWnd : = Hwnd;
Result : = False; / / d e t i e n e la enumeracion
end;
end;
end;

Esta funcion, a la que se llama para cada ventana no hijo del sistema, verifica
el nombre de cada clase de ventana, buscando el nombre de la clase TForml.
Cuando encuentra una ventana con esta cadena en su nombre de clase, usa
GetModule Fi lename para extraer el nombre del archivo ejecutable de la
aplicacion que pertenece a1 formulario correspondiente. Si el nombre del modulo
corresponde a1 del programa en uso (que se extrajo anteriormente con un codigo
similar), podemos estar casi seguros de que encontraremos una instancia anterior
del mismo programa. Veamos el mod0 en que podemos llamar a la funcion enu-
merada:
var
FoundWnd: THandle;
ModuleName: string;
begin
i f Wait ForSingleObj ect (hMutex, 0) <> wait-Timeout then
- ..
else
begin
/ / o b t i e n e e l n o m b r e d e l m o d u l o en u s o
SetLength (ModuleName, 200) ;
GetModuleFileName (HInstance, PChar (ModuleName), Length
(ModuleName)) ;
ModuleName : = PChar (ModuleName); // a j u s t a l a l o n g i t u d
// b u s c a una v e n t a n a d e una i n s t a n c i a p r e v i a
EnumWindows (@EnumWndProc, 0) ;

Controlar mensajes de ventana definidos


por el usuario
Antes hemos mencionado que la llamada a Set Fo regroundwindow no
funciona si se ha minimizado el formulario principal del programa. Para resolver
este problema, podemos pedir a1 formulario de otra aplicacion, la instancia previa
del mismo programa en este caso, que restaure su formulario principal enviandole
un mensaje de ventana definido por el usuario. A continuacion, se puede compro-
bar si el formulario esta minimizado y enviar un mensaje definido por el usuario a
la ventana anterior.
Veamos el codigo en el programa OneCopy. Esta es una continuacion del
parrafo del apartado anterior:
i f FoundWnd <> 0 then
begin
// rnuestra f i n a l r n e n t e l a v e n t a n a
i f n o t IsWindowVisible (FoundWnd) then
PostMessage (FoundWnd, %User, 0, 0) ;
SetForegroundWindow (FoundWnd);
end:

La funcion P o s tMessage de la API envia un mensaje a la cola de mensajes


dc la aplicacion que posee la ventana de destino. En el codigo del formulario, se
puede aiiadir una funcion especial para controlar dicho mensaje:
public
p r o c e d u r e W M A p p ( v a r msg : TMessage) ;
message C p p ;
p r o c e d u r e TForml .WMApp ( v a r msg : TMessage) ;
begin
Application.Restore;
end;

NOTA: El programa hace uso del mensaje wrn App en lugar de wm U s e r ;


algunas ventanas del sistema usan wm ~ s e r , p o lo
r que no hay gar&ia de
que otras aplicaciones o el sistema no enviarhn este mensaje. Esta es la
razon por la que Microsofi introdujo wm ~ p para p 10s mensajes que estim
limitados a la interpretacibn de la ap1icaci6n.

Creacion de aplicaciones MDI


Una tecnica comun en la estructura de una aplicacion es MDI (Interfaz de
Documento Multiple). Una aplicacion MDI esta compuesta por diversos formula-
rios que aparecen dentro de un unico formulario. Si usamos el bloc de notas de
Windows, solo podemos abrir un documento de texto, porque el bloc de notas no
es una aplicacion MDI. Sin embargo, con el procesador de textos, probablemente
se puedan abrir distintos documentos, cada uno con su propia ventana hijo. por-
que son aplicaciones MDI. Todas estas ventanas de documento se guardan nor-
malmente en una ventana de marco o aplicacion.

NOTA: Microsofi se aleja cada vez mas del modelo MDI utilizado en 10s
dias de Windows 3. Incluso las versiones recientes de Office tienden a
utilizar ventanas principales especificas para cada documento, la tCcnica
clasica SDI (Interfaz de Documento ~ n i c o ) En
. cualquier caso, MDI no
esta muerto y, a veces, puede ser una estructura util.

MDI en Windows: resumen tecnico


La estructura MDI ofrece a 10s programadores varias ventajas automaticas.
Por ejemplo, Windows controla una lista de ventanas hijo en uno de 10s menus de
lista desplegables de una aplicacion MDI y existen metodos especificos de Delphi
que activan la funcionalidad MDI correspondiente, para organizar las ventanas
hijo en forma de mosaic0 o cascada. A continuacion presentamos la estructura
tecnica de una aplicacion MDI en Windows:
La ventana principal de la aplicacion actua como marco o contenedor
Una ventana especial, conocida como el "cliente MDI", cubre toda la zona
de la ventana marco, Este cliente MDI es uno de 10s controles predefinidos
Windows, a1 igual que un cuadro de edicion o un cuadro de lista. La venta-
na cliente MDI carece de cualquier elemento especifico de interfaz de usua-
rio, per0 esta visible. De hecho, se puede cambiar el color de sistema
estandar de la zona de trabajo MDI (denominadaApplication Background)
en la ficha Apariencia del cuadro de dialogo Propiedades de pantalla
de Windows.
Existen diversas ventanas hijo, del mismo tipo o de distintos tipos. Estas
ventanas hijo no se colocan en la ventana marco directamente, sin0 que
cada una se define como un hijo de la ventana cliente MDI, que a su vez es
hijo de la ventana marco.

Ventanas marco y ventanas hijo en Delphi


Delphi facilita el desarrollo de aplicaciones MDI, aunque no se use la plantilla
de aplicacion MDI disponible en Delphi (vease la ficha Applications del
cuadro de dialogo File>New>Other). Solo es necesario crear como minimo dos
formularies, uno con la propiedad Formstyle definida como fsMDIForm y el
otro con la misma propiedad definida como fsMDIC h i1d.Hay que definir estas
dos propiedades en un programa sencillo y ejecutarlo y veremos 10s dos formula-
rios anidados en el tipico estilo MDI.
Sin embargo, por lo general, el formulario hijo no se crea a1 arrancar y sera
necesario ofrecer un mod0 de crear una o mas ventanas hijo. Esto se puede hacer
aiiadiendo un menu con un elemento New y escribiendo el siguiente codigo:
var
ChildForm: TChildForm;
begin
ChildForm : = TChildForm. Create (Application);
ChildForm. Show:

Otra caracteristica importante consiste en aiiadir un menu desplegable Window


y utilizarlo como el valor de la propiedad WindowMenu del formulario. Este
menu desplegable lista automaticamente todas las ventanas hijas disponibles. Po-
demos escoger, por supuesto, cualquier otro nombre para el menu desplegable,
per0 Window es el estandar.
Para que el programa funcione correctamente, podemos aiiadir un numero a1
titulo de cualquier ventana hijo cuando se crea:
procedure TMainForm.NewlClick(Sender: TObject);
var
ChildForm: TChildForm;
begin
WindowMenu := Window1 ;
Inc (Counter);
ChildForm := TChildForm.Create (Self);
ChildForm-Caption : = ChildForm.Caption + ' ' + IntToStr
(Counter);
ChildForm-Show;
end;

Tambien podemos abrir ventanas hijo, minimizar o maximizar cada una de las
mismas, cerrarlas y usar el menu desplegable Window para movernos de unas a
otras. Supongamos ahora que queremos cerrar alguna de estas ventanas hijo, para
despejar el area cliente del programa. Si hacemos clic en las cajas Close de
alguna de estas ventanas, esta se minimiza, en lugar de ocultarse como ocurria
hasta ahora. Los formularios cerrados en Delphi siguen existiendo aunque no
Sean visibles.
En el caso de las ventanas hijo, ocultarlas no funcionara porque el menu MDI
Window y la lista de ventanas continuara mostrando las ventanas hijo existentes,
aunque esten ocultas. Por esta razon, Delphi minimiza las ventanas MDI hijo
cuando intentamos cerrarlas. Para resolverlo, debemos borrarlas cuando son ce-
rradas dando el valor ca Free a1 p a r h e t r o de referencia Act ion del evento
OnClose.

Crear un menu Window completo


Nuestra primera tarea consiste en definir una estructura de menu mejor para el
ejemplo. Normalmente, el menu desplegable Window posee a1 menos tres elemen-
tos, Cascade, Tile y Arrange Icons. Para controlar las ordenes de menu, podemos
usar algunos de 10s metodos predefinidos de T Form que pueden ser usados solo
para marcos MDI:
El metodo Cascade: Organiza en forma de cascada las ventanas hijo MDI
abiertas. Las ventanas se solapan entre si. Tambien se organizan las venta-
nas hijo representadas por iconos (vease Arrange I cons).
El metodo Tile: Organiza en forma de mosaic0 las ventanas hijo MDI
abiertas. Los formularios hijo se organizan de tal mod0 que no se solapen.
El comportamiento predefinido es una organizacion horizontal, aunque si
tenemos muchas ventanas hijo, se organizaran en varias columnas. Esta
configuracion predefinida se puede modificar utilizando la propiedad
TileMode (dandole un valor tbHorizonta1 o tbvertical).
El procedimiento ArrangeIcons: Organiza todas las ventanas hijo reprc-
sentadas por iconos. Los formularios que estan abiertos no se mueven.
Como una alternativa mejor a la llamada a estos metodos, sc puede colocar un
A c t i o n L i s t en el formulario y aiiadirlo a una serie de acciones MDI
predefinidas. Las clases relacionadas son: TWi ndowArrange, TWi ndowCas -
cade, TWindowClose,TWindowTileHorizonta1,TWindowTile-
Vertical y TWindowMinimizeAll. Los elementos del menu conectados
realizaran las acciones correspondientes y se desactivaran si no existe ninguna
ventana hijo disponible. El ejemplo MdiDemo, que veremos a continuacion, de-
muestra el uso de acciones MDI, entre otras cosas.
Tambien existen otros metodos y propiedades interesantes relacionadas estric-
tamente con MDI en Delphi:
ActiveMDIChild: Es una propiedad solo de lectura en tiempo dc ejecu-
cion del formulario marco MDI y aloja a la ventana hijo activa. El usuario
puede cambiar este valor seleccionando una nueva ventana hijo o el pro-
grama puede cambiarlo usando 10s procedimientos Next y Previous, que
activan la ventana hijo siguiente o anterior a la activa en ese momento.
La propiedad CIientHandle: Aloja el controlador Windows de la ventana
cliente MDI, que cubre la zona de cliente del formulario principal.
La propiedad MDIChildren: Es una matriz de ventanas hijo. Podemos
usar esta y la propiedad MDIChildCount para movernos por todas las
ventanas hijo. Esto puede resultar util para encontrar una ventana hijo
concreta o trabajar en una dc ellas.
Fijese en que el orden interno de las ventanas hijo es el inverso a1 ordcn de
activacion. Esto significa que la ultima ventana seleccionada es la ventana activa
(la primera en la lista interna), la penultima ventana hijo seleccionada es la segun-
da y la primera ventana hijo seleccionada es la ultima. Este orden establece el
mod0 en que se organizan las ventanas en pantalla. La primera de la lista esta
encima del resto, mientras la ultima esta deba-jo de todas y, probablemente, ocul-
ta. Podemos imaginarlo como un eje (el eje z ) saliendo de la pantalla hacia noso-
tros.
La ventana activa tiene un valor mayor para la coordenada z y, por tanto,
cubrc cl resto de ventanas. Por ello, a1 esquema de ordenacion de Windows se le
conoce como z-order.
- -- ---
NOTA: El menu Window puede manejarse con ActionManager y con el
control de menu ActionMainMenuBar, a partir de Delphi 7. Este control
tiene un propiedad especifica, ~ i n d o w ~ e n que
u , debemos usar para es-
pecificar el menG que listara las ventanas hijo MDI. ,:
El ejemplo MdiDemo
Hemos creado un primer ejemplo para demostrar la mayoria de las funciones
de una aplicacion sencilla MDI. MdiDemo es en realidad un editor de testos MDI
completo, porque cada ventana hijo aloja un componente Memo y puede abrir y
guardar archivos de testo. El formulario hijo tiene una propiedad Modified
utilizada para indicar si el texto del memo ha cambiado (esta definido como True
en el controlador del evento OnChange del memo). Modified esta definida
como False en 10s metodos personalizados Save y Load y se verifica cuando
se cierra el formulario (sugiriendo que se guarde el archivo).
Como ya hemos dicho, el formulario principal del ejemplo esta basado en un
componente ActionList. Las acciones del ejemplo estan disponibles mediante al-
gunos elementos de menu y en una barra de herramientas, como muestra la figura
8 . 3 . Para conocer 10s detalles del ActionList podemos estudiar el codigo fuente
del ejemplo; aqui nos centraremos en el codigo de las acciones.

o lnleresanle
lgo Inlelesante
lgo Intelesante
lgo lntelesanle
lyo ~nteleranle
h o ~nlererante

Figura 8.3. El prograrna MdiDemo hace uso de una serie de acciones Delphi
predefinidas conectadas a un menu y una barra de herrarnientas.

Una de las acciones mas sencillas es el objeto ActionFont, que tiene un


controlador OnExecute, que usa un componente FontDialog, y un controla-
dor Onupdate, que desactiva la accion (y; por lo tanto, el elemento de menu
asociado y boton de la barra de herramientas) cuando no hay formularios hijo:
procedure TMainForm.ActionFontExecute(Sender: TObject);
begin
if FontDialog1.Execute then
(ActiveMDIChild as TChildForm) .Memol. Font :=
FontDialogl.Font;
end;
procedure TMainForm.ActionFontUpdate(Sender: TObject);
begin
ActionFont.Enabled : = MDIChildCount > 0;
end;

La accion denominada New crea el formulario hijo y define un nombre de


archivo predefinido. La accion Open llama a1 metodo Act ionNewExcecute
antes de cargar el archivo:
procedure TMainForm.ActionNewExecute(Sender: TObject);
var
ChildForm: TChildForm;
begin
Inc (Counter);
ChildForm : = TChildForm.Create (Self);
ChildForm.Caption : =
Lowercase (ExtractFilePath (App1ication.Exename)) +
'texto' +
IntToStr (Counter) + ' . t x t ';
ChildForm.Show;
end;

procedure TMainForm.ActionOpenExecute(Sender: TObject);


begin
if 0penDialogl.Execute then
begin
ActionNewExecute (Self);
(ActiveMDIChild as TChildForm).Load (OpenDialogl-FileName);
end;
end ;

En realidad, el archivo se carga mediante el metodo Load del formulario. De


igual modo, el metodo save del formulario hijo lo usan las acciones save y
Save As.Fijese en que el controlador OnUpdate de la accion Save activa la
accion solo si el usuario ha cambiado el texto del memo:
procedure TMainForm.ActionSaveAsExecute(Sender: TObject);
begin
// s u g i e r e e l nombre d e l a r c h i v o a c t u a l
SaveDialogl.FileName : = ActiveMDIChild.Caption;
i f SaveDialogl.Execute then
begin
// m o d i f i c a e l n o m b r e d e l a r c h i v o y g u a r d a
ActiveMD1Child.Caption : = SaveDialogl.Fi1eName;
(ActiveMDIChild as TChildForm) .Save;
end;
end;

procedure TMainForm.ActionSaveUpdate(Sender: TObject);


begin
Actionsave. Enabled := (MDIChildCount > 0) and
(ActiveMDIChild as TChildForm) .Modified;
end;
procedure TMainForm.ActionSaveExecute(Sender: TObject);
begin
(ActiveMDIChild as TChildForm) .Save;
end;

Aplicaciones MDI con distintas ventanas hijo


En las aplicaciones MDI complejas es frecuente incluir ventanas hijo de dife-
rentes tipos (es decir, basadas en diferentes formularios hijo). Hemos creado un
nuevo ejemplo, llamado MdiMulti, para resaltar algunos problemas de dicha tec-
nica. En el cjemplo, hay dos tipos de formularios, el primer0 aloja un circulo
dibujado en la posicion dcl ultimo clic del raton, mientras que el segundo conten-
dra un gran cuadrado. TambiCn hemos aiiadido a1 formulario principal un fondo
pcrsonalizado, obtenido a1 pintar una imagen en forma de mosaic0 sobre el.

Formularios hijo y mezcla de menus


El primer tipo de formulario hijo muestra un circulo en la posicion en la que el
usuario ha hecho clic en uno de 10s botones el raton. La figura 8.4 muestra un
cjemplo dcl rcsultado del programa MdiMulti. El programa incluye un menu Circle,
que permite a1 usuario cambiar el color de la superficie del circulo y el color y
tamaiio de su borde. En este caso, resulta interesante que para programar el for-
mulario hijo, no sea necesario considerar la existencia de otros formularios ni de
la ventana marco. Simplemente, escribimos el codigo del formulario y ya esta.
Solo se nccesita tener especial cuidado con 10s menus de 10s dos formularios.

Figura 8.4. El resultado del ejemplo MdiMulti, con una ventana hijo que rnuestra
10s circulos.
Si preparamos un menu principal para el formulario hijo, sustituira a1 menu
principal de la ventana marco cuando se active el formulario hijo. De hecho, una
ventana MDI hijo no puede tener un menu propio. Pero el hecho de que una
ventana hijo no pueda tener menus no deberia preocuparnos porque este es el
comportamiento estandar de las aplicaciones MDI. Podemos usar la barra de
menu de la ventana marco para mostrar 10s menus de la ventana hijo. Una tecnica
mejor es mezclar la barra de menu de la ventana marco y la del formulario hijo.
Por ejemplo, en este programa, el menu del formulario hijo puede colocarse entre
el marco de 10s menus desplegables File y Window de la ventana. Para ello se
usan 10s siguientes valores GroupIndex:
Menu desplegable File, formulario principal: 1.
Menu desplegable Circle, formulario hijo: 2.
Menu desplegable Window, formulario principal: 3 .
A1 usar estas definiciones para 10s indices de grupo del menu, la barra de menu
de la ventana marco tendra dos o tres menus desplegables. A1 arrancar, la barra
de menu tiene dos menus. Desde el momento en que creamos una ventana hijo,
existen tres menus y cuando se cierra la ultima ventana hijo, el menu desplegable
Circle desaparece.
El segundo tip0 de formulario hijo muestra una imagen en movimiento. El
cuadrado, un componente Shape,se mueve por la zona de cliente del formulario
a intervalos fijos de tiempo, utilizando un componente Timer y rebota a1 chocar
contra 10s extremos del formulario, cambiando su direccion. Este proceso de cam-
bio utiliza un complejo algoritmo que no examinaremos aqui, ya que el objetivo
principal del ejemplo es mostrar como se comporta la combinacion de menus
cuando tenemos un marco MDI con formularios hijo de diferentes tipos. (Queda
en manos del lector examinar el codigo fuente para ver como funciona).

El formulario principal
Tenemos que integrar ahora 10s dos formularios hijo en una aplicacion MDI.
El menu desplegable File tiene dos elementos de menu New aparte, que se usan
para crear una ventana hijo de cualquier tipo. El codigo usa un solo contador de
ventanas hijo. Como alternativa, podriamos usar dos tipos diferentes de contado-
res para 10s dos tipos de ventanas hijo. El menu Window usa las acciones MDI
predefinidas. Desde el momento en que un formulario de este tip0 aparece en
pantalla, su barra de menu se mezcla automaticamente con la barra de menu
principal. Cuando seleccionamos un formulario hijo de uno de 10s dos tipos, la
barra de menu cambia de acuerdo con ello. Cuando se hayan cerrado todas las
ventanas hijo, se configura de nuevo la barra de menu original del formulario
principal. A1 usar indices de menu adecuados, permitimos que Delphi lo haga
todo de forma automatica, como muestra la figura 8.5.
Figura 8.5. La barra de menu de la aplicacion MdiMulti cambia de forrna autornatica
para reflejar la ventana hijo seleccionada, corno puede verse cornparando la barra de
menu con la de la figura 8.4.

Se han aiiadido unos pocos elementos a1 menu del formulario principal para
cerrar todas las ventanas hijo y mostrar algunas estadisticas sobre ellas. El meto-
do relacionado con el comando C o u n t analiza la propiedad MDIChildren
para contar el numero de ventanas hijo de cada tipo (usando el operador RTTI
is):
for I : = 0 t o MDIChildCount - 1 do
if MDIChildren is TBounceChildForm then
Inc (NBounce);
else
Inc (NCircle) ;

Subclasificacion de la ventana MdiClient


El programa de ejemplo incluye tambi6n soporte para una imagen de fondo en
forma de mosaico. El mapa de bits se toma de un componente Image y deberia de
pintarse en el formulario con el controlador de mensajes de Windows
wm E r a s e B k g n d . El problema es que no podemos conectar sencillamente el
c6digo a1 formulario principal, porque una ventana aparte, MdiClient, cubre su
superficie.
No tenemos un formulario Delphi correspondiente a esta ventana, por lo que
para controlar sus mensajes, debemos recurrir a una tecnica de programacion de
ba.jo nivel conocida corno subclasificacion (aparte de por el nombre, no tiene nada
que ver con la herencia de la programacion orientada a objetos). La idea basica es
que podemos sustituir el procedimiento de ventana, que recibe todos 10s mensajes
de la ventana, por otro. Esto se realiza llamando a la funcion SetwindowLong
de la API y ofreciendo la direccion de memoria del procedimiento, el punter0 de
funcion.
NOTA: Un procedimiento de ventana es una funcion que recibe todos 10s
mensajes de una ventana. Cada mensaje habrh de tener un procedimiento de
ventana, solo uno. hcluso 10s formularios Delphi tienen un procedimiento
de ventanas, aunque estA oculto en el sistema. ~ s t llama
e a la funcion vir-
tual WndProc, que podemos usar. Pero la VCL tiene un mod0 de control
.-la ,
:
,
,, ,,A,C,:.-l, ,.., ,
, .:.,, . I,, ,&+,A, A, ....-...+-,l A, ,.,.
UG I l l G l l D t l J G 3 ~ l G U G l L l l l U U ,r j U G 36 I G G I I V l 4 4 I V 3 IILGLUUUD U G b V U L I U I U G l l L G l l D 4 '

jes de un formulario despuks de cierto procesado previo. Con todo este


soporte, es necesario controlar 10s procedimientos de ventana explicitamen-
te solo cuando se trabaje con ventanas que no s e a de Delphi, como en este
caso.

A mcnos que tengamos una razon para cambiar el comportamiento que tiene
por defecto esta ventana de sistema, podemos guardar cl procedimiento original y
llamarlo para obtener el procesamiento por defecto. Los dos procedimientos (vie-
jo y nuevo) a 10s que se refieren 10s dos punteros de funcion se guardan en dos
campos locales del formulario:
private
OldWinProc, NewWinProc: Pointer;
procedure NewWinProcedure (var Msg: TMessage) ;

El formulario tiene tambien un metodo que usaremos como un nuevo procedi-


miento de ventana, con el codigo real usado para pintar en el fondo de la ventana.
Dado que este es un metodo y no un procedimiento de ventana normal, el progra-
ma ha de llamar a1 metodo MakeOb j ectInstance para aiiadir un prefijo al
metodo y dejar que el sistema lo use como si fuera una funcion. Toda esta descrip-
cion se resume en dos sentencias complejas:
procedure TMainForm. Formcreate (Sender: TObject) ;
begin
NewWinProc : = MakeObjectInstance (NewWinProcedure);
OldWinProc : = Pointer (SetwindowLong (ClientHandle,
gwl-WndProc, Cardinal
(NewWinProc)) ) ;
Outcanvas := TCanvas .Create;
end:

El procedimiento de ventana que instalamos llama al procedimiento predefinido.


A continuacion, si el mensaje es wm-EraseBkgnd y la imagen no esta en blan-
co, la dibujamos en pantalla varias veces usando el metodo D r a w de un lienzo
temporal. Dicho objeto lienzo se crea cuando el programa arranca (vease el codi-
go anterior) y se conecta al controlador que el mensaje pasa como parametro
wParam. Con esta tecnica, no tenemos que crear un nuevo objeto T C a n v a s
para cada operacion de pintado del fondo solicitada, asi se ahorra un cierto tiem-
po en esta frecuente operacion. Veamos el codigo, que produce la salida que
aparece en la figura 8.5:
p r o c e d u r e TMainForm.NewWinProcedure (var Msg: TMessage);
var
BmpWidth, BmpHeight: Integer;
I, J: Integer;
begin
/ / p r o c e s a d o predefinido p r i m e r 0
Msg.Result : = CallWindowProc (OldWinProc, ClientHandle,
Msg.Msg, Msg.wParam,
M s g - l P a r a m );

/ / controla el pintado d e fondo


i f Msg.Msg = ~ E r a s e B k g n dt h e n
begin
BmpWidth : = MainForm.1magel.Width;
BmpHeight : = MainForm.Image1.Height;
i f (BmpWidth <> 0 ) a n d (BmpHeiyht <> 0 ) then
begin
0utCanvas.Handle : = Msg.wParam;
f o r I : = 0 t o MainForm.ClientWidth d i v BmpWidth d o
f o r J : = 0 t o MainForm.ClientHeight d i v BmpHeight d o
0utCanvas.Draw (I * BmpWidth, J * BmpHeight,
MainForm.Imagel.Pictu~e.Graphic);
end;
end;
end;

Herencia de formularios visuales


Cuando sea necesario crear dos o mas formularios similares, tal vez con dife-
rentes controladores de eventos, se pueden usar tecnicas dinamicas, ocultar o
crear componentes nuevos en tiempo de ejecucion, cambiar controladores de eventos
y utilizar sentencias i f o c a s e . Tambien se pueden aplicar tecnicas orientadas a
objetos, gracias a la herencia de formularios visuales. Asi, en vez de crear un
formulario basado en T F o r m , podemos heredar un formulario de uno existente,
aiiadiendole nuevo componentes o cambiando las propiedades de 10s existentes.
La ventaja real de esta tecnica de herencia de formularios visuales depende en
gran medida del tip0 de aplicacion que se Cree. Si tiene diversos formularios,
algunos de ellos muy similares'o solo con elementos comunes, se pueden colocar
componentes comunes y controladores de eventos comunes en el formulario base
y aiiadir el comportamiento especifico y 10s componentes a las subclases. Por
ejemplo, si preparamos un formulario padre estandar con una barra de herramien-
tas, un logotipo, codigo predefinido de ajuste del tamaiio y de cierre y 10s
controladores de algunos mensajes Windows, podemos usarlo como clase padre
de cada uno de 10s formularios de la aplicacion.
Tambien se puede usar herencia de formularios visuales para personalizar una
aplicacion para clientes diferentes, sin duplicar el codigo fuente ni el codigo de
definicion del formulario (se hereda la version especifica para un cliente de 10s
formularios estandar). La principal ventaja de la herencia visual es que mas ade-
lante puede modificarse el formulario original y actualizar automaticamente to-
dos 10s formularios derivados. Esta es una de las ventajas de la herencia en 10s
lenguajes de programacion orientados a objctos. Pero existe un efecto colateral
muy beneficioso: el polimorfismo. Podemos aiiadir un metodo virtual en un for-
mulario base y sobrecargarlo en un formulario heredado para despucs referirnos a
ambos formularios y poder llamar a ese metodo en cada uno de cllos.
- i

NOTA: Delphi incluye otra funcion, 10s marcos, que imita la herencia de
formularios visuales. En arnbos casos, se puede trabajar en tiempo de dise-
iio en las dos versiones de un formulario/marco. Sin embargo, en la heren-
cia de formularios visuales, definimos dos clases distintas (padre y derivada),
mientras que con 10s marcos, trabajamos en una clase y en una instancia.
Los marcos se trataran mas adelante en este capitulo.

Herencia de un formulario base


Las normas sobre herencia de formularios visuales son bastante sencillas, si se
tiene claro lo que es la herencia. Fundamentalmente, un formulario subclase tiene
10s mismos componentes quc el formulario padre asi como otros componentes
nuevos. No podemos eliminar un componente de la clasc basica, aunque podemos
haccrlo invisible (si es un control visual). Lo importante es que podemos cambiar
facilmcntc las propiedades de 10s componentes h,eredados.
Fijese cn quc si cambiamos una propiedad de un componcnte en un formulario
heredado, cualquier modificacion dc la misma propiedad cn cl formulario padre
no tendra cfccto alguno. Si cambiamos otl-as propiedades del componente afecta-
ran tambicn a las versiones heredadas. Podemos sincronizar de nuevo 10s dos
valores de la propiedad utilizando la orden Revert t o I n h e r i t e d del mcnu
local del Object Inspector. Al definir las dos propicdades con el mismo valor y
compilar de nuevo el codigo se consigue lo mismo. Tras modificar diversas pro-
picdades, podemos sincronizarlas de nuevo con la version basica aplicando la
orden R e v e r t t o I n h e r i t e d del menu local del componente.
Ademas de heredar componentes, el formulario nuevo hereda todos 10s meto-
dos del formulario base, incluyendo 10s controladores de eventos. Podemos aiiadir
controladores nuevos en el formulario heredados y sobrescribir tambicn 10s cxis-
tentes.
Para describir el mod0 en que funciona esta tecnica, hemos crcado un cjemplo
llamado VFI. Para crcarlo, primero, hay quc iniciar un nuevo proyecto y aiiadir
cuatro botones a su formulario principal. A continuacion, seleccionar
File>New>Other y escoger la pagina con el nombre del proyccto en el cuadro de
dialog0 New Items (vease la figura 8.6).
I I I
Data Modules InIraweb WtbSs~v~ces8-ss IWebsnap ) Web Docurnenlo (
I I I I I 1
New

a a Acb&

Form2
Mukltler Vfl F m Dialogs Rolccts

r 6 !nhrit r

Figura 8.6. El cuadro de dialogo New Items permite crear un formulario heredado

En cl dialogo New Items, se puede escoger el formulario del que se quiere


heredar. El nucvo formulario tiene 10s mismos cuatro botones. Veamos la des-
cripcion textual inicial del nuevo formulario:
inherited Form2: TForm2
Caption = 'Form2 '
end

Esta es la declaration dc clase inicial, en la que vemos que la clase basica no


es la habitual T F o r m sino el formulario de clase basica actual:
type
TForm2 = class (TForml)
private
{ Declaraciones privadas }
public
{ D e c l a r a c i o n e s pLiblicas )
end;

Fijese en la presencia de la palabra clave inherited en la descripcion tex-


tual y tambien en que el formulario tiene algunos componentes, aunque estan
definidos en el formulario de clase basico. Si movemos el formulario y aAadimos
el titulo de uno de 10s botones, la descripcion textual cambia de acuerdo con
dichos cambios:
inherited Form2 : TForm2
Left = 313
Top = 202
Caption = 'Form2'
inherited Button2: TButton
Caption = ' B e e p . . . '
end
end
Solo se listan las propiedades con un valor diferente (por lo que si quitamos
estas propiedades de la descripcion textual del formulario heredado podemos de-
jarlas con 10s valores del formulario base). Como muestra la figura 8.7, hemos
cambiado el titulo de la mayoria de 10s botones.

Figura 8.7. Los dos formularios del ejemplo VFI en tiempo d e ejecucion

Cada uno de 10s botones del primer formulario tiene un controlador OnClic k .
El primer boton muestra el formulario heredado llamando a su metodo Show,el
segundo y tercer boton llaman a1 procedimiento Beep y el ultimo boton muestra
un mensaje sencillo.
En el formulario heredado, primer0 deberiamos eliminar el boton Show, por-
que el formulario secundario ya esta visible. Sin embargo; no podemos borrar un
componente de un formulario heredado. Podemos dejar el componente, per0 defi-
nir su propiedad Visible como False (el boton seguira estando ahi per0 no
sera visible). Los otros tres botones estaran visibles per0 con distintos
controladores. Esto es sencillo de conseguir. Si seleccionamos el evento OnClic k
de un boton en el formulario heredado (haciendo doble clic en el), obtendremos un
metodo ligeramente diferente a1 predefinido; porque incluye la palabra clave
inherited.Esta palabra clave representa m a llamada a1 controlador de even-
tos correspondiente. Esta palabra clave siempre la aiiade Delphi, incluso si el
controlador no se define en la clase padre o si el componente no esta presente en la
clase. Es sencillo ejecutar el codigo del formulario base y realizar algunas opera-
ciones mas:
procedure TForm2.Button2Click(Sender: TObject);
begin
inherited;
ShowMessage ( 'Hi' ) ;
end;

Esta no es la unica posibilidad. Tambien podemos crear un nuevo controlador


de eventos y no ejecutar el codigo de la clase base, como hemos hecho con el
tercer boton del ejemplo VFI: para ello, solo debemos quitar la palabra clave
inherited.
Otra posibilidad consiste en llamar a un metodo de una clase base despues de
ejecutar algun codigo a medida, llamandolo cuando se cumple una condicion o
llamando a1 controlador de otro evento de la clase base, como hemos hecho con el
cuarto boton:
procedure TForm2.ButtonrlClick(Sender: TObject);
begin
inherited Button3Click ( S e n d e r ) ;
inherited;
end;

No es habitual heredar de un controlador diferente, per0 debemos tener en


cucnta quc cs posible. Podemos. por supuesto, considerar cada metodo del formu-
lario basc como un metodo de nuestro formulario, para asi llamarlo libremente.
Este ejcmplo nos permite explorar algunas caracteristicas de la herencia de for-
mularios visuales, pero para comprobar su valia debemos pensar en ejemplos del
mundo real, que son mas complejos de lo que este libro puede tratar. Ahora;
cstudiaremos cl polimorfismo de formularios visuales.
- -

NOTA: La herencia de formularios visuales no funciona muy bien con


colecciones. n n nnrlemnc extender iinn nrnnierlnrl rle cnlecciirn rle Iin cnm-
ponente en I
uso de serie!
les de lista con detalles. Estos componentes pueden usarse tanto en el tor-
mulario padre como en el heredado, por supuesto, per0 no podemos extender
10s elementos que contienen, ya que estan almacenados en una coleccion.
TT-,.,
A
:
,
.I
, ,
c, ,
I
L,
- ,,&A ,, ,.:,., ,A,:
I,,,,:,, A, ,
.c
, ,:,,,I,..
I I ~ cu C V I F ~ Ila a a q p a w u u uc csrira GU~CGGIU-
ulla SUIUGIUIIa CSLC ~ I U U I C I CSW
nes en tiempo de diseiio para, en su lugar, hacerlo en tiempo de ejecucion.
Seguiriamos usando la herencia de formularios per0 perderiamos la parte
visual de esta. Si intentamos usar el componente ActionManager, des-
cubriremos que ni siquiera podemos heredar de un formulario que lo con-
tenga. Borland deshabilito esta caracteristica porque podria causar
demasiados problemas.

Formularios polimorficos
Si quercmos afiadir un controlador de eventos a1 formulario y despues conver-
tirlo en formulario heredado, no hay forma de referirse a 10s dos metodos que
usan una variable comun de la clase basica; porque 10s controladores de eventos
usan por defect0 enlace estatico.
Veamos un ejemplo: queremos crear un formulario visor de mapas de bits y un
visor de texto en el mismo programa. Los dos formularios tienen elementos simi-
larcs, una barra de herramientas similar, un menii similar, un componente
OpenDialog y componentes diferentes para ver 10s datos. Por lo tanto, decidimos
crear un formulario de clase basica que contenga 10s elementos comunes y herede
10s dos formularios de el. En la figura 8.8, podemos ver 10s tres formularios en
tiempo de diseiio.
Figura 8.8. El forrnulario d e clase basica y 10s dos forrnularios heredados del
ejernplo PoliForrn.

El formulario principal contiene un panel de barra de herramicntas con algu-


nos botones (las barras de herramientas reales tienen problemas con la hcrcncia
de formularios visuales); un menu y un componente dc dialog0 abierto. Los dos
formularios heredados tienen solo diferencias mcnores, per0 presentan un nuevo
componente, o un visor de imageries ( T I m a g e ) o un visor de testo (TMemo).
Tambien modifican la configuration del componente O p e n D i a l o g , para refe-
rirse a diferentes tipos dc archivos.
El formulario principal incluye algo de codigo comun. El boton Close y la
orden File,>Close llaman a1 mdtodo c l o s e del formulario. La orden Help>About
mucstra un cuadro de mensa.je sencillo. El boton Load del formulario base tiene
unicamente una llamada a S h o w M e s s a g e para mostrar un mensa.je de error. La
ordcn File>Load en cambio llama a otro mttodo:
procedure TViewerForm.LoadlClick(Sender: T O b j e c t ) ;
begin
LoadFile;
end;

Este metodo se define en la clase T V i e w e r Form como un metodo abstracto


(de forma que la clase del formulario base es en realidad una clase abstracts).
Dado que este es un metodo abstracto, sera necesario redefinirlo (y sobrescribirlo)
en 10s formularios heredados.
El codigo de este metodo L o a d F i l e utiliza sencillamente el componentc
O p e n D i a l o g l para pedir a1 usuario que seleccione un archivo de entrada y lo
cargue en el componente de imagen:
procedure TImageViewerForm.LoadFi1e;
begin
if 0penDialogl.Execute then
1magel.Picture.LoadFromFile (0penDialogl.Filename);
end;

La otra clase heredada tiene un codigo similar, que carga el texto en el compo-
nente memo. El proyecto tiene un formulario mas, un formulario principal con
dos botones, utilizados para cargar de nuevo 10s archivos en cada uno de 10s
formularios de visor. El formulario principal es el unico formulario creado por el
proyecto a1 iniciar. El formulario de visor generic0 nunca se crea: solo es una
clase basica generica, que contiene codigo comun y componentes de dos subclases.
Los formularios de las dos subclases se crean en el controlador de eventos
oncre at e del formulario principal:
p r o c e d u r e TMainForm.FormCreate(Sender: T O b j e c t ) ;
var
I: Integer;
begin
FormList [ l ] : = TTextViewerForm.Create (Application);
FormList [2] : = T1mageViewerForm.Create (Application);
for I := 1 to 2 do
FormList [I] .Show;
end ;

Fo rmL i s t es una matriz polimorfica de objetos TVi ewe r Fo rm genericos,


declarada en la clase T M a i n Fo rm.Para hacer esta declaracion en la clase debe-
mos aiiadir la unidad Viewer (pero no 10s formularios especificos) en la clausu-
la uses de la parte de interfaz del formulario principal. La matriz de formularios
se usa para cargar un archivo nuevo en cada formulario de visor cuando hacemos
clic en alguno de 10s dos botones. Los controladores del evento oncl ic k de
cada uno de 10s botones funcionan de diferente manera:
//ReloadButtonlClick
for I := 1 to 2 do
FormList [I] .ButtonLoadClick (Self);

//ReloadButtonZClick
for I := 1 to 2 do
FormList [I] .LoadFile;

El boton secundario llama a un metodo virtual, funcionando sin problemas. El


boton primario llama a un controlador de eventos y siempre llega a la clase gene-
rica TFormView (mostrando el mensaje de error de su metodo ButtonLoad-
Click). Esto ocurre porque el metodo es estatico en lugar de virtual.
Para hacer que esto funcione, podemos declarar el metodo But tonLoadClic k
de la clase T FormVie w como virtual y sobrecargarlo en cada una de las clases
formulario heredadas, como hacemos con cualquier otro metodo virtual:
type
TViewerForm = class (TForm)
p r o c e d u r e ButtonLoadClick (Sender: TObject); virtual;
public
procedure LoadFile; virtual; abstract;
end;

type
TImageViewerForm = class (TViewerForm)
procedure ButtonLoadClick (Sender: TOb ject) ; override;
public
procedure LoadFile; override;
end;

Este truco funciona aunque no se mencione en la documentacion de Delphi.


Esta capacidad para usar controladores de eventos virtuales es lo que llamamos
polimorfismo de formularios visuales. En otras palabras, podemos asignar un
metodo virtual a una propiedad de evento, que capturara la direccion del metodo
en funcion de la instancia disponible en tiempo de ejecucion.

Entender 10s marcos


Como hemos visto en capitulos previos, con Delphi podemos crear un nuevo
marco, situar componentes en el, desarrollar controladores de eventos para el
componente y, despues, aiiadir el marco a un formulario. 0, dicho de otra manera,
un marco es similar a un formulario, per0 define solo una parte de una ventana,
no la ventana completa. El elemento totalmente nuevo de 10s marcos es que se
pueden crear diversas instancias de un marco en tiempo de diseiio y se pueden
modificar la clase y la instancia a1 mismo tiempo. Por ello, 10s marcos son una
herramienta muy practica para crear controles compuestos moldeables en tiempo
de diseiio, algo parecido a una herramienta de desarrollo de componentes visua-
les.
En la herencia de formularios visuales, podemos trabajar en tiempo de diseiio
tanto con un formulario base como con un formulario derivado, asi, cualquier
cambio que hagamos en el formulario base se extiende a1 heredado (a no ser que
sobrecarguemos una propiedad o un evento). En el caso de 10s marcos, trabaja-
mos en una clase, per0 tambien se puede personalizar una o mas instancias de la
clase en tiempo de diseiio. Cuando trabajamos con un formulario, no podemos
cambiar una propiedad de la clase T F o r m l para el objeto F o r m 1 en tiempo de
diseiio per0 con 10s marcos si.
Una vez que asumamos que estamos trabajando con una clase y una o mas de
sus instancias en tiempo de diseiio no tendremos que comprender nada mas acerca
de 10s marcos. En la practica, 10s marcos son utiles cuando queremos usar el
mismo grupo de componentes en varios formularios en una aplicacion. De hecho,
en este caso, podemos personalizar las instancias en tiempo de diseiio. Esto ya se
podia hacer con plantillas de componentes, per0 estaban basadas en el concept0
de copiar y pegar componentes y su codigo. No podiamos cambiar la definicion
original de la plantilla y ver el efecto en todos 10s sitios donde la usabamos. Con
marcos (y, de otra manera, con la herencia de formularios visuales), 10s cambios
de la version original (la clase) se reflejan en la copia (las instancias).
Podemos ver algunos elementos mas sobre 10s marcos con el ejemplo Frames2.
Este programa tiene un marco con un cuadro de lista, un cuadro de edicion y tres
botones con codigo sencillo que opera en 10s componentes. El marco tiene tam-
bien un boton Bevel alineado con su zona de cliente, porque 10s marcos no tienen
borde. El marco tiene tambien una clase correspondiente que parece una clase de
formulario:
type
TFrameList = class (TFrame)
ListBox: TListBox;
Edit: TEdit;
btnAdd: TButton;
btnRemove: TButton;
btnclear: TButton;
Bevel: TBevel;
procedure btnAddClick (Sender: TObj ect) ;
procedure btnRemoveClick(Sender: TObject);
procedure btnClearClick (Sender: TObject) ;
private
{ Declaraciones privadas )
public
{ Declaraciones publicas
end ;

L a diferencia con respecto a u11 formulario esta en que podemos aiiadir el


marco a un formulario. Hemos usado dos instancias del marco en el ejemplo
(como muestra la figura 8.9) y modificado el comportamiento ligeramente. La
primera instancia del marco tiene 10s eleme~itosdel cuadro de lista organizados.
A1 cambiar una propiedad de un componente de un marco, el archivo DFM del
formulario anfitrion listara las diferencias, como hace con la herencia de formula-
rios visuales:
object FormFrames : TFormFrames
Caption = ' F r a m e s P '
inline FrameListl: TFrameList
Left = 8
Top = 8
inherited ListBox: TListBox
Sorted = True
end
end
inline FrameList2: TFrameList
Left = 232
Top = 8
inherited btnclear: TButton
OnClick = FrameList2btnClearClick
end
end
end

......
&d(~I-l Du I , .: .; .: .; .; .:
meted , ... ... ... ... ... ...
I
Some !ex(

Figura 8.9. Un rnarco y dos instancias del rnisrno en tiempo de disetio, en el ejemplo
Frarnes2.

Como se ve en el listado, el archivo DFM de un formulario que tiene marcos


usa una nueva palabra clave DFM, inline.Las referencias a 10s componentes
modificados del marco, en cambio, utilizan la palabra clave inherited,aun-
que este termino se usa con un significado ampliado: inherited aqui no se
refiere a la clase basica de la que se hereda, sin0 a la clase de la que estamos
sacando una instancia de un objeto (o heredando). Hemos utilizado una caracte-
ristica existente en la herencia de formularios visuales para aplicarla a este nuevo
contexto. Asi, en realidad, se puede usar la orden Revert to Inherited del
Object Inspector o del formulario para cancelar 10s cambios y volver a1 valor
predefinido de las propiedades.
Los componentes de la clase marco que no han sido modificados tampoco son
mostrados en el archivo DFM del formulario que utiliza el marco, y aunque 10s
componentes de ambos formularios tengan el mismo nombre 10s dos marcos tie-
nen nombres diferentes. Estos componentes no son propiedad del formulario sino
del marco. Esto implica que el formulario debe referencia 10s componentes a
traves del marco. como puede verse en el codigo de 10s botones que copian ele-
mentos de un cuadro de lista a otro:
procedure TFormFrames.btnLeftClick (Sender: TObject);
begin
FrameListl.ListBox.Items.AddStrings
(FrameList2.ListBox.Items);
end ;

Ademas de modificar las propiedades de cualquier instancia de un marco,


tambien podemos cambiar el codigo de cualquiera de sus controladores de even-
tos. Si hacemos doble clic en uno de 10s botones del marco mientras trabajamos en
cl formulario (no en el marco independiente), Delphi generara este codigo por
nosotros:
procedure TFormFrames.FrameList2btnClearClick (Sender:
TObject) ;
begin
FrameList2.btnClearClick (Sender);
end;

La linea de codigo aiiadida automaticamente por Delphi corresponde a una


llamada a1 controlador de eventos hcrcdado dc la clase base mediante herencia de
forrnularios visuales. En este caso, para que se de el comportamiento original dcl
marco, debemos llamar a un controlador de eventos y aplicarlo a una instancia
especifica, el propio objeto marco. El marco actual no incluye este controlador ni
sabe nada de dl. Tanto si decidimos dcjar esta llamada como si la quitamos depen-
dera del efecto que busquemos.
- ... ~ -

TRUCO:Debemos tener en cuenta que, dado que el controlador de eventos


tiene codigo, dejarlo como lo ha generado Delphi y guardar el formulario
no lo eliminara como es habitual, ya que no esta vacio. Si queremos omitir
el codigo por defect0 para un evento, tenemos que aiiadirle, a1 menos, un
comentario para evitar que el sistema lo borre automaticamente.

Marcos y fichas
Cuando tenemos un cuadro de dialogo con muchas fichas llenas de controlesj
el codigo subyacente a1 formulario se vuelve muy complejo, porque todos 10s
controlcs y metodos se declaran en un ilnico formulario. Ademas, a1 crear todos
estos componentes (e iniciarlos) podriamos originar un retraso en la aparicion del
cuadro de dialogo. En realidad, 10s marcos no reducen el tiempo de construccion
e inicializacion dc 10s formularios cargados de forina equivalente. A1 contrario, es
mas complicado cargar 10s marcos para el sistema de slrenrning que cargar com-
ponentes simples. Sin embargo, a1 utilizar marcos se puedan cargar solo las fi-
chas visibles de un cuadro de dialogo, reduciendo el tiempo de carga inicial, que
es el que percibe cl usuario.
Los marcos pueden resolver estos dos temas. En primer lugar, se puede dividir
facilmente el codigo de un formulario unico complejo en un marco por ficha. El
formulario albergara sencillamente todos 10s marcos en un PageControl. Esto
ayuda realmente a tener unidades mas sencillas y mas centradas, y hace que
resulte mas sencillo reutilizar una ficha concreta en un cuadro de dialogo diferen-
te o una aplicacion. Reutilizar una unica ficha de un PageControl sin usar un
marco o un formulario incrustado es muy complicado (para conocer una tecnica
alternativa vease el apartado "Formularies en fichas").
Para ilustrar esto, hemos creado el e.jemplo FramePag,que tiene algunos mar-
cos colocados en el interior de las tres fichas de un Pagecontrol, como muestra la
figura 8.10. Todos 10s marcos estan alineados con la zona del cliente, utilizando
toda la superficie de la hoja de solapa (la ficha) en la que se encuentran. En reali-
dad, dos de las fichas tienen el mismo marco, per0 dos de las instancias del inarco
tienen algunas diferencias en tiempo de diseiio. El marco, llamado Frame3 en el
ejemplo, tiene un cuadro de lista que contiene un archivo de texto al arrancar y tiene
botones para modificar 10s elementos de la lista y guardarlos en un archivo. El
nombre de archivo se coloca en una etiqueta, para poder seleccionar con facilidad
un archivo en tiempo de diseiio cambiando el titulo de la etiqueta.

Figura 8.10. Cada ficha del ejemplo FramePag contiene un marco, separando el codigo
de este formulario complejo en trozos mas rnanejables.

Poder utilizar diversas instancias de un marco es una de las razones para la


introduccion de esta tecnica y personalization del marco en tiempo de diseiio
resulta incluso mas importante. Dcbido a que aiiadir propiedades a un marco y
hacer que estCn disponibles en tiempo de diseiio requiere cierto codigo complejo y
personalizado, esta bien poder usar un componente que contenga estos valores
personalizados.
Tambien esiste la opcion de ocultar estos componentes (como la ctiqueta dcl
ejcmplo) si no pertenecen a la interfaz de usuario.
En el ejemplo, es neccsario cargar el archivo cuando se crea la instancia del
marco. Como 10s marcos no tienen un evento Oncreate. la mejor opcion es
probablemente sobrescribir el metodo CreateWnd.Crear un constructor a me-
dida no funciona, porque es ejecutado demasiado pronto, antcs de que la etiqueta
de testo especifica este disponible. En el metodo CreateWnd, sencillamente
cargamos el contenido del cuadro de lista desde un archivo.
NOTA: La creacion de la ventana marco (como ocurre con la mayoria de
controles) se retrasa por razones de eficiencia. Causa mas problemas la
utilization de herencia entre formularios que contienen marcos, por lo que,
para evitar este problema, se desactivo el controlador de eventos oncreate
para marcos (asi lo programadores pueden escribir el codigo que conside-
ren razonable).

Formularios en fichas
A pesar de que podemos utilizar marcos para definir las fichas de un
PageControl en tiempo de diseiio, tambien podemos usar otros formula-
rios en tiempo de ejecucion. Esta tecnica nos da la flexibilidad de tener las
fichas definidas en unidades y ficheros DFM separados y, al mismo tiempo,
permite utilizar esos formularios como ventanas independientes.
Cuando tenemos un formulario principal con un control de fichas y uno o
mas formularios secundarios para mostrar en el, todo lo que tenemos que
hacer es escribir este codigo para crear 10s formularios secundarios y si-
tuarlos en las fichas:
var
Form: TForm;
Sheet: TTabSheet;
begin
// c r e a r una h o j a d e t a b u l a c i o n e n e l c o n t r o l f i c h a
Sheet : = TTabSheet.Create(PageContr011);
Sheet.PageContro1 : = PageControll;
// c r e a r e l f o r m u l a r i o y s i t u a r l o e n l a h o j a d e t a b u l a c i o n
Form := TForm2 .Create (Application);
Form.BorderStyle := bsNone;
Form.Align : = alclient;
Form. Parent := Sheet;
Form. Visible := True;
// a c t i v a r l o y p o n e r l e t i t u l o
PageContro1l.ActivePage : = Sheet;
Sheet.Caption : = Form.Caption;
end;

Se puede encontrar este texto en el ejemplo Formpage, per0 el programa no


hace nada mas. Para ver una aplicacion, puede consultarse el programa de
demostracion RWBlocks, en el capitulo 14.

Varios marcos sin fichas


Otra tecnica consiste en no crear todas las fichas con el formulario que las
contiene. Esto se puede realizar dejando el PageControl en blanco y creando 10s
marcos solo cuando aparece una ficha. Si tenemos marcos en varias fichas de un
Pagecontrol, las ventanas para 10s marcos se crean solo cuando se muestran por
primera vez, como podemos comprobar si colocamos un punto de parada en cl
codigo de creacion de ejemplo anterior.
Como tecnica mas drastica; podemos eliminar 10s controles de ficha y utilizar
TabControl. De este modo, la solapa no esta conectada a ninguna hoja (o ficha)
sino que simplemente muestra un conjunto de informacion cada vez. Por dicha
razon, sera necesario crear el marco actual y destruir el anterior o sencillamente
ocultarlo definiendo su propiedad V i s i b l e como F a l s e o llamando a
BringTo Front del nuevo marco. A pesar de que esto puede parecer muy tra-
bajoso, en una aplicacion grandc esta tecnica puede merecer la pena por el redu-
cido uso de recursos y memoria que se obtiene.
Para demostrar esta tecnica, hemos creado el ejemplo FrameTab, similar a1
antcrior, basado csta vez en un TabControl y hemos creado marcos de forma
dinamica. El formulario principal, visible en tiempo de ejecucion en la figura
8.1 1; solo tiene un TabControl con una ficha para cada marco:
o b j e c t Forml: TForml
Caption = ' F i c h a s d e M a r c o '
OnCreate = Formcreate
o b j e c t Buttonl: TButton...
o b j e c t Button2: TButton...
o b j e c t Tab: TTabControl
Anchors = [akLeft, akTop, akRight , akBottom]
Tabs .Strings = ( ' M a r c o 2 ' ' M a r c o 3 ' )
OnChange = Tabchange
end
end

Figura 8.11. La primera ficha del ejemplo FrameTab en tiernpo de ejecucion. El


marco dentro de la solapa es creado en tiempo de ejecucion.
Hemos dado un titulo para cada solapa que corresponde a1 nombre del marco,
porque vamos a usar esta informacion para crear nuevas fichas. Cuando creamos
el formulario y siempre que el usuario cambia la solapa activa, el programa
obtiene el titulo actual de la solapa y lo pasa a1 metodo Show Frame.El codigo
de este metodo, que aparece a continuacion, verifica si el marco solicitado ya
existe (10s nombres de 10s marcos de este ejemplo siguen el estandar de Delphi de
llevar un numero agregado a1 nombre de la clase) y, despues, hace que aparezca
en la parte de delante. Si el marco no existe, usa el nombre del marco para encon-
trar la clase de marco relacionada, crea un objeto de dicha clase y le asigna
algunas propiedades. El codigo utiliza de forma ampliada las referencias de clase
y las tecnicas de creacion dinamica:
type
TFrameClass = class of TFrame;

p r o c e d u r e TForml.ShowFrarne(FrameNarne: string);
var
Frame: TFrame;
FrameClass: TFrameClass;
begin
Frame : = Findcomponent (FrameName + '1 ' ) a s TFrame;
i f n o t Assigned (Frame) t h e n
begin
FrameClass : = TFrameClass ( Findclass ( 'T' + FrameName) ) ;
Frame : = FrameClass .Create (Self);
Frame.Parent : = Tab;
Frame.Visible : = True;
Frame.Name : = FrameName + '1';
end;
Frame.BringToFront;
end ;

Para que el codigo funcione, debemos recordar afiadir una llamada a


Reg i s t e rC 1a s s en la parte de inicializacion de cada una de las unidades que
definen el marco.

Formularios base e interfaces


Hemos visto que cuando necesitamos dos formularios similares en una aplica-
cion, podemos usar la herencia de formularios visuales para heredar uno de otro o
ambos de un ascendiente comun. La ventaja de la herencia de formularios visua-
les esta en que podemos usarla para heredar la definicion visual, la DFM. Sin
embargo, esto no siempre se solicita.
Para que varios formularios muestren un comportamiento comun o respondan
a las mismas ordenes, sin tener ningun componente compartido ni elementos de la
interfaz de usuario, en lugar de utilizar la herencia de formularios visuales con un
formulario base que no tiene componentes adicionales podemos utilizar otra tec-
nica. Es preferible utilizar una clase de formulario personalizada, heredada de
T F o r m y, a continuacion, editar manualmente las declaraciones de clase del for-
mulario para heredar de esa clase de formulario base personalizada en lugar de
heredar de la estandar. Si todo lo que hay que hacer es definir algunos metodos
compartidos o sobrescribir 10s metodos virtuales T F o r m de forma continuada,
puede ser buena idea definir clases de formulario personalizadas.

Uso de una clase de formulario base


En el ejemplo FormIntf, podemos ver el uso de dicha tecnica, en la que se
muestra tambien el uso de interfaces para formularios. En una nueva unidad,
llamada SaveStatusForm, hemos creado la siguiente clase de formulario (sin nin-
gun archivo relacionado DFM). En lugar de usar la orden N e w F o r m , creamos
una nueva unidad y escribimos el codigo en la misma:
type
T S a v e S t a t u s F o r m = c l a s s (TForm)
protected
p r o c e d u r e Docreate; override;
p r o c e d u r e DoDestroy; override;
end ;

Los dos metodos sobrecargados son llamados a la vez que el controlador de


eventos por lo que podemos agregarle codigo extra (que nos permita definir el
controlador de eventos como hacemos usualmente). Dentro de 10s dos metodos
cargamos y guardamos la posicion del formulario en un archivo IN1 de la aplica-
cion, en una seccion marcada con el nombre del formulario. Este es el codigo de
ambos metodos:
p r o c e d u r e TSaveStatusForm.DoCreate;
var
Ini: TIniFile;
begin
inherited;
Ini : = TIniFile.Create (ExtractFileName
( A p p 1 i c a t i o n . E x e N a m e )) ;
Left : = Ini .ReadInteger (Caption, 'Izquierda ', Left) ;
T o p : = Ini .ReadInteger (Caption, 'Arriba ', T o p ) ;
W i d t h := Ini. ReadInteger (Caption, 'Anchura ', W i d t h ) ;
Height : = Ini .ReadInteger (Caption, 'Altura ', H e i g h t ) ;
Ini. Free;
end ;

p r o c e d u r e TSaveStatusForm.DoDestroy;
var
Ini: TIniFile;
begin
Ini : = T I n i F i l e - C r e a t e (ExtractFileName (Application.ExeName));
Ini .WriteInteger (Caption, ' I z q u l e r d a ', Left) ;
Ini .WriteInteger (Caption, ' A r r i b a ', Top) ;
Ini .WriteInteger (Caption, ' A n c h u r a ', Width) ;
1ni.WriteInteger (Caption, ' A l t u r a ', Height) ;
Ini. Free;
inherited;
end ;

Estc cs un ejemplo muy sencillo per0 podemos definir una clase compleja en su
interior. Para utilizar esta como clase base para 10s formularios que construya-
mos, debemos dejar que Delphi c:ee 10s formularios como siempre (sin herencia)
y, despues, actualizaremos la declaracion a algo parecido a este:
type
TFormBitmap = class (TSaveStatusForm)
Imagel: TImage;
OpenPictureDialogl: TOpenPictureDialog;

Aunque es tan sencilla como parece, esta tecnica es muy potente, porque lo
unico que tenemos que hacer es cambiar la definicibn de 10s formularios de nues-
tra aplicacion para referirnos a esta clase base. Incluso si este paso nos resulta
muy tedioso porque, quiza, queramos en algun momento cambiar esta clase en
nuestro programa, podemos usar un truco extra: las clases de interposicion
- - - - -- - - -- - -

Archivos IN1y el Registro en Delphi


Si queremos guardar cierta infarmblcibn sobre el estadq de una aplicacion
para restaurarla la prbxima vez que se ejecute el pragwm, podemos usar
explicitamente el soporte que ofrece windows para almacenar este tip de
informacion. Los archivos MI son una vez nuis la forma preferible de guar-
dar datos de la aplicacibn. La alternativa es el Registro. Delphi ofiece
cIases Iistas para usar para trabajar con ambas t h i c a s .
La clase TIniFile: En el caso de 10s archivos INI, Delphi tiene una
clase TIniFile. Cuando hemos c r e d o un objeto de dicha clase y lo
hemos conectado a un archivo, podemos leer y escribir informacibn en
61. Para crear el objeto, es necesario llamar a1 constructor, pasando un
nombre de archivo, como en el siguiente c6digo:
var
IniFile: TIniFile;
begin
IniFile := TIniFile .Create ( 'myprogrsm. ini ') ;
Existen dos posibilidades para colocar un archivo MI. El &%go que
acabamos de listar guarda el archivo en el directorio Windows o en una
carpeta de usuario con las configuraciones en Windows 2000. Para
guardar'los datos de forma locd en la itpii~&i&n(en oposicibn a bacer-
lo de forma local para el usuario actual), deberiamos ofiecer una ruta
completa a1 constructor.
Los archivos IN1 se dividen en secciones, cada una de ellas indicada
mediante un nombre entre corchetes. Cada apartado puede contener
rlivarcnc alam~ntncAP tree tinnc nncihlac- rarlenac antarnc n hnnlaannc
. .

La clase T I n i F i l e tiene tres mCtodos Read, uno para cada tipo de


datos: R e a d B o o l , R e a d I n t e g e r y R e a d s t r i n g . TambiCn hay
. . .. .. . .

c s p c c ~ ~ ~UIIc a
valor
~ p~cu~lmu
para
u uiuuar SI lir c o ~ ~ c s p o u u r c rcn-
~u
trada no existe en un archivo INI.
Debemos tener en cuenta que Delphi utiliza 10s ficheros IN1 muy a
menudo, per0 con nombres diferentes. Por ejemplo, 10s archivos de es-
critorio (.dsk) y de opciones (.dof) e s t h estructurados como archivos
INI.
Las clases TRegistry y TRegIniFile: El Registro es una base de datos
jerarquica de inforrnacion sobre el ordenador, la configuraci6n de soft-
ware y las preferencias del usuario. Windows tiene un conjunto de fun-
ciones API para interactuar con el Registro. Basicamente abrimos una
clave (o carpeta) y, a continuacion, trabajamos con subclaves (o
subcarpetas) y con valores (o elementos), pero debemos ser conscientes
de la estructura y 10s datos del Registro.
Delphi ofrece basicamente dos tkcnicas a1 uso del Registro: la clase
T R e g i s t r y un encapsulado del Registro API, mientras que Ia clase
T R e g I n i F i l e la interfaz de la clase T I n i F i l e per0 guardando 10s
datos en el Registro. Esta clase es la opcion natural para conseguir el
intercambio entre la informacibn basada en IN1 y las veniones basadas
en Registro de un mismo programa. Cuando creamos un objeto
TReg I n i F i l e , nuestros datos termina.n en la infonaacb5n de usuario
actual, por lo que nomlmente usamos un ~wstructorcmm:
IniFile := TRegIniFile.Create
( 'Software\MyCompany \MyProgramp)
;

A1 usar las clases TJniFile y TRegistryhiFile que nos &ece la VCL,


podemos desplazamos de un modelo de almacenamiento local y por
usuario a otro. A pesar de todo esto, no deberiaanos usar el registro
demasiado porque tener un repositorio centralizado para la configma-
cion de cada aplicaci6n fue un error de diseilo de la arquitectura. Inclu-
so Microsoft reconoce este becho (sin admitir realmente el error) a1
I sugerir, en 10s requisites de compatibilidad con windows 2000, que
deberia evitarse usar el Registro para almacenar la configuracih de las
I
aplicaciones y, en su lugar, volver a utilizar 10s archivos IN1 dentro de
la carpeta de documentos del usuario actual (algo de lo que muchos
programadores no han oido hablar).

Un truco adicional: clases de interposicion


En oposicion a 10s componentes VCL de Delphi, que deben tener nombres
unicos, las clases de Delphi han de ser generalmente unicas solo dentro de su
unidad. Asi, podemos tener dos unidades diferentes que definan una clase con el
mismo nombre. Esto parece extraiio a primera vista, per0 puede ser util. Por
ejemplo, Borland esta usando esta tecnica para ofrecer compatibilidad entre las
clases VCL y VisualCLX. Ambas tienen una clase TForm,una definida en la
unidad Forms y otra en la unidad QForms.

NOTA: Esta tecnica es mucho mas antigua que CLXNCL. Por ejemplo,
las unidades de servicio y panel de control definen su propio objetaI
TApplication. que no tiene nada que ver con el TApplication usa-
.. . . . -.-- . - . . .. . -
do por las apllcaciones Vlsuales V L L y dekmldo en la unidad k-oms.

Existe una tecnica que hemos visto mencionada con el nombre de "clases de
interposicion", que sugeria que se sustituyesen 10s nombres de clase estandar de
Delphi por versiones propias, que tuviesen el mismo nombre de clase. Asi, pode-
mos usar el diseiiador Delphi que se refiere a componentes estandar de Delphi en
tiempo de diseiio, per0 usando nuestras propias clases en tiempo de ejecucion.
La idea es sencilla. En la unidad SaveStatusForm, podriamos definir la nueva
clase de formulario asi:
type
TForm = class ( F o r m s . TForm)
protected
procedure D o c r e a t e ; override;
procedure D o D e s t r o y ; override;
end;

Esta clase se llama TForm y hereda de TForm de la unidad Forms (esta


ultima referencia es obligatoria para evitar una especie de definicion recursiva).
En el resto del programa, no hay que cambiar la definicion de clase del formula-
rio, sino sencillamente aiiadir la unidad que define la clase de interposicion (la
unidad SaveStatusForm en este caso) en la sentencia u s e s despues de la unidad
que define la clase Delphi. El orden de las unidades en la sentencia u s e s es
importante, y es la razon por la que algunas personas critican esta tecnica, ya que
es dificil saber lo que ocurre. Y tienen razon: las clases de interposicion son
practicas a veces (mas para componentes que para formularios), per0 su uso hace
10s programas menos legibles y? en algunas ocasiones, mas dificil de depurar.

Uso de interfaces
Otra tecnica, que es ligeramente mas compleja per0 mas potente que la defini-
cion de una clase de formulario comun consiste en tener formularios que
implementes interfaces especificas. Asi, podemos tener formularios que
implementen una o mas interfaces, consulten cada formulario para saber que
interfaz implementa y llamen a 10s metodos soportados.
Como ejemplo, hemos definido una interfaz simple para cargar y almacenar:
type
IFormOperations = i n t e r f a c e
[ ' {DAC,FDB76-0703-4A40-A951-1OD1 40B4AZAO) '1
p r o c e d u r e Load;
p r o c e d u r e Save;
end;

Cada formulario puede implementar opcionalmente esta interfaz, como la si-


guiente clase T FormBitmap:
type
TFormBitmap = c l a s s (TForm, IFormOperations)
Imagel: T I m a g e ;
OpenPictureDialogl: TOpenPictureDialog;
SavePictureDialogl: TSavePictureDialog;
public
p r o c e d u r e Load;
p r o c e d u r e Save;
end;

El codigo de ejemplo incluye 10s metodos Load y Save, que utilizan 10s
cuadros de dialog0 estandar para cargar o guardar la imagen (en el ejemplo, el
formulario hereda tambien de la clase TSaveStatus Form). Cuando una apli-
cacion tiene uno o mas formularios que implementan interfaces, podemos aplicar
un metodo de interfaz concreto a todos 10s formularios que lo soportan, con codi-
go como este (extraido del formulario principal del ejemplo FormIntf):
p r o c e d u r e TFormMain.btnLoadClick (Sender: T O b j e c t ) ;
var
i: Integer;
iFormOp: IFormOperations;
begin
f o r i : = 0 t o Screen.FormCount - 1 d o
i f Supports (Screen.Forms [i],' IFormOperations, iFormOp) t h e n
iFormOp-Load;
end ;
Tengamos en cuenta que en una aplicacion empresarial podemos sincronizar
todos 10s formularios con 10s datos de una empresa especifica o un evento empre-
sarial especifico. Ademas, a diferencia de la herencia, podemos tener varios for-
mularios que implementen cada uno varias interfaces, con combinaciones
ilimitadas. Esta es la razon por lo que utilizar una arquitectura como esta puede
mejorar mucho una compleja aplicacion Delphi y hacerla mucho mas flexible y
mas sencilla de adaptar para la implernentacion de cambios.

El gestor de memoria de Delphi


Terminaremos este capitulo dedicado a la estructura de aplicaciones Delphi
con una seccion sobre la gestion de memoria. Este tema es muy complejo y,
posiblemente, merece dedicarle un capitulo entero, aunque aqui solamente le da-
remos un breve repaso y daremos algunas indicaciones para pruebas posteriores.
Para un analisis mas detallado de la memoria podemos acudir a las multiples
herramientas dedicadas a la verificacion y el control de la memoria con Delphi,
tales comoMemCheck, MemProof, MemorySleuth, Code Watch y AQTime.
Delphi tiene un gestor de memoria, accesible usando las funciones
GetMemoryManager y SetMemoryManager de la unidad System. Estas
funciones permiten acceder a1 registro del gestor de memoria original o modifi-
carlo con nuestro propio gestor personalizado. Un registro de gestor de memoria
es un conjunto de tres funciones usadas para asignar, liberar y reasignar memo-
ria:
type
TMemoryManager = record
GetMem: function (Size: Integer) : Pointer;
FreeMem: function (P: Pointer) : Integer;
ReallocMem: function (P: Pointer; Size: Integer) : Pointer;
end;

Es importante conocer como estas funciones son llamadas a1 crear un objeto


ya que podemos insertarlas en dos pasos. Como llamamos a un constructor, Delphi
invoca la funcion de clase virtual NewInstance, definida en TObject.Esta
funcion es virtual por lo que podemos modificar el gestor de memoria para una
clase especifica sobrecargandola. Para llevar a cab0 la asignacion de memoria
NewIns tance, normalmente, termina llamando a la funcion GetMem del ges-
tor de memoria activa, lo que nos da una segunda oportunidad para modificar el
comportamiento esthdar .
A no ser que tengamos necesidades especiales, generalmente no necesitaremos
intervenir en el gestor de memoria para modificar el funcionamiento de la asigna-
cion. De todas maneras, resulta practico hacerlo para determinar si este funciona-
miento es correcto, es decir, para asegurarnos de que el programa no tiene agujeros
de memoria. Por ejemplo, podemos sobrecargar 10s metodos New I ns t ance y
F r e e I n s t a n c e de una clase para llevar la cuenta del numero de objetos que se
crean y destruyen para verificar si el total es cero.
Una tecnica mas simple es hacer el mismo analisis sobre el total de objetos
asignados por el gestor de memoria. En las primeras versiones de Delphi, hacer
esto requeria codigo extra, per0 el gestor de memoria proporciona dos variables
(A1 1ocMemCount y A 1 1ocMemSize) que pueden ayudarnos a saber que esta
ocurriendo en el sistema.
El mod0 mas simple de determinar si nuestro programa esta tratando la memo-
ria adecuadamente es comprobar si A 1 1ocMemCount vuelve a cero. El proble-
ma es decidir cuando hacer esa comprobacion. Un programa empieza ejecutando
la seccion de inicializacion de sus unidades, que normalmente asigna memoria
liberada por las respectivas secciones de finalizacion. Para garantizar que nuestro
codigo se ejecuta a1 final, debemos escribirlo en la seccion de finalizacion de una
unidad y colocarlo a1 principio de la lista de unidades en el archivo de codigo
fuente del proyecto. Podemos ver una unidad como esta en el listado 8.1. Esta es
la unidad SimpleMemTest del ejemplo ObjsLeft, que tiene un formulario con un
boton para mostrar el contador de asignaciones actual y un boton para crear un
agujero de memoria (que es capturado a1 terminar el programa).

Listado 8.1. Una unidad para cornprobar agujeros de rnernoria, del ejemplo ObjsLeft.

unit SimpleMemTest;

interface

implementation

uses
Windows ;

var
msg: string;

initialization
finalization
if AllocMemCount > 0 then
begin
Str (AllocMemCount, msg) ;
Msg : = msg + ' b l o q u e s r e s t a n t e s e n l a p i l a ' ;
MessageBox ( 0 , PChar (msg), ' a g u j e r o d e memoria ' , MB-OK) ;
end ;
end.

Este programa es practico, per0 realmente no ayuda a comprender que ha ido


mal. Para conocer esto, existen herramientas muy potentes (algunas de las que
tienen versiones de prueba gratuitas), o podemos utilizar el gestor de memoria
descrito es este libro, que analiza las asignaciones de memoria.
Creacion
de componentes
Delphi

La mayoria de 10s programadores en Delphi estaran, probablemente, acostum-


brados a usarlos pero, a veces, puede resultar practico crear nuestros propios
componentes o personalizar 10s ya existentes. Uno de 10s aspectos mas interesan-
tes de Delphi es que crear componentes no es mucho mas dificil que crear progra-
mas. Por esta razon, aunque este libro este dirigido a programadores de aplicaciones
Delphi en lugar de a creadores de herramientas, este capitulo tratara sobre la
creacion de componentes y presentara 10s afiadidos de Delphi, tales como 10s
componentes y editores de propiedades.
Este capitulo ofrece una introduccion a la creacion de componentes Delphi y
presenta algunos ejemplos. No hay espacio suficiente para presentar componentes
muy complejos per0 las ideas que hemos incluido cubren todos 10s fundamentos y
nos ofreceran un punto de partida.
Este capitulo trata estos temas:
Ampliacion de la biblioteca de Delphi.
Creacion de paquetes.
Componentes compuestos.
Uso de propiedades de interfaz.
Definicion de eventos personalizados.
paquete solo de disefio, suele estar enlazado estaticamente al archivo eje-
cutable, utilizando el codigo de 10s archivos DCU (Delphi Compiled Unit)
correspondientes. Sin embargo, hay que tener en cuenta que tambiln es
tecnicamente posible utilizar un paquete solo de diseiio como paquete de
tiempo de ejecucion.
Las aplicaciones de Delphi utilizan 10s paquetes de componentes solo de
cjccucion en tiempo de ejecucion. No sc pueden instalar en cl entorno Delphi,
per0 se afiaden autonxiticamente a la lista de paquetcs en tiempo de ejecu-
cion cuando 10s necesita un paquetc solo de diseiio quc instalamos. Los
paquetes solo de ejecucion contienen normalmente el codigo de las clases
de componentes, per0 no poseen soporte en tiempo de diseiio (asi se mini-
miza el tamaiio de las bibliotecas de componentes incluidas con el archivo
ejecutablc). Los paquetes solo de ejecucion son importantes porque se pue-
den distribuir libremente junto con las aplicaciones, per0 no se pueden
instalar en cl entorno para crear programas nuevos.
Los paquetes normales de componentes (10s que no tienen ni la opcion de
solo disefio ni la dc solo ejecucion) no se pueden instalar y no se afiadiran
a la lista de paquctes en tiempo de ejecucion de forma automatica. Pueden
verse en paquetes de utilidades usados por otros paquetes, per0 no suelen
ser habituales.
Los paquetes que tengan ambos indicadores pueden instalarse y se afiaden
automaticamente a la lista de paquctcs en tiempo de ejecucion. Normal-
mente, dichos paquctes contienen componentes que necesitan poco o nin-
gun soporte en tiempo de disefio (aparte del reducido codigo de registro del
componente)
-- --
I - . --- - -

r
~-
1
. - - - .-
TRUCO: Lon nombres de archivide 10s oaouetes s61o de diseiio de Delobi
comienzan con las letras DCL (por ejemplo, DCLSTDGO BPI,). Los nom- .
? -

r
-

bres de archivo de 10s paquetes solo de ejecuci6n comienzan con las letras
VCL (por ejemplo, VCL60 .BPL). Podemos, si queremos, utilizar la mis-
/ . .
ma teenlea en nuestros paquetes.

Anteriormente, hemos tratado el efecto de 10s paquetes en el tamaiio del archi-


vo ejecutable del programa. Ahora nos centraremos en la creacion de 10s paque-
tes, porque este es un paso necesario para la creacion o instalacion de componentes
en Delphi.
A1 compilar un paquetc de tiempo de ejecucion, se produce una biblioteca de
enlace dinamico con el codigo compilado (el archivo BPL) y un archivo solo con
una informacion de simbolo (un archivo DCP), que incluye el codigo maquina no
compilado. El ultimo archivo lo usa el compilador Delphi para reunir informacion
de simbolo sobre las unidades quc forman park del paquete sin tener acceso a 10s
archivos de la unidad (DCU), que contienen tanto la informacion de simbolo
como el codigo maquina compilado. Esto reduce el tiempo de compilacion y per-
mite distribuir solo 10s paquetes sin 10s archivos de unidad previamente compila-
dos. Las unidades precompiladas son necesarias para enlazar de forma estatica
10s componentes en una aplicacion. La distribucion de archivos DCU precompilados
(o codigo fuente) puede ser util dependiendo del tipo de componentes que desarro-
llemos.
Veremos como crear un paquete despues de presentar algunas recomendacio-
nes generales y crear un componente.
...

I NOTA: Las DLL son archivos ejecutables que contienen colecciones de


hnciones y clases, que pueden ser usadas por una aplicacion u otra DLL en
tiempo de ejecucion. La ventaja comun es que si muchas aplicaciones usan
la misma DLL, por lo que solo es necesario que haya una copia en el disco
- . .. ..
o cargada en memona y el tamaflo de cada archlvo ejecutable sera mucho
menor. Eso es lo que ocurre tambien en 10s paquetes Delphi.

Normas para escribir componentes


Esisten ciertas normas generales sobre la escritura de componentes. A conti-
nuacion, esponemos un resumen de las mismas:
Estudiar el lenguaje Delphi con detenimiento. La herencia, la sobrescritura
y la sobrecarga de metodos, la diferencia entre partes publicas y publica-
das de una clase y la definition de eventos y propiedades son conceptos
especialmente importantes. Para estudiar estos conceptos basicos puede
consultar la primera parte del libro.
Estudiar la estructura de la jerarquia de clases de la VCL y tener a mano
un esquema de dichas clases (corno el que se incluye con Delphi).
Seguir las convenciones estandar de denominacion de Delphi. Hay algunas
normas para componentes y si se siguen, sera mas facil para otros progra-
madores interactuar con sus componentes y seguir ampliandolos.
Crear componentes sencillos, imitar a otros componentes y evitar depen-
dencias. Estas tres normas, significan basicamente que un programador
que utiliza 10s componentes deberia poder usarlos de un mod0 tan sencillo
como 10s componentes previamente instalados de Delphi. Usar, siempre
que sea posible, nombres de propiedades, metodos y eventos similares. Si
10s usuarios no necesitan aprender normas complejas sobre el uso de nues-
tros componentes (es decir, si las dependencias entre metodos o propieda-
des son reducidas) y pueden acceder sencillamente a propiedades con
nombres sin significado, 10s mantendremos satisfechos.
Usar excepciones. Cuando algo falla, el componente deberia crear una
excepcion. Cuando se asignan recursos de algun tipo, debemos protegerlos
con bloques t r y / f i n a l l y y llamadas a1 destructor.
Para completar un componente, hay que aiiadirle un mapa de bits, para que
lo utilice la Component Palette de Delphi. Si queremos que nuestro
componente lo use mucha gente, debemos considerar tambien la idea de
aiiadirlc un archivo de ayuda.
Prepararnos para cscribir codigo rcal y olvidar 10s aspectos visuales de
Dclphi. Por lo gcncral. cscribir componcntes significa escribir codigo
sin soporte visual (aunque la f u n c i o n c l a s s C o m p l e t i o n puede acele-
rar bastantc la codificacion de clases normales). La excepcion a esta
norma es que podemos m a r marcos para escribir componentes de forma
visual.

NOTA: Tambien podemos usar herramientas de escritura de componentes


de terceros para crear nuestros componentes o acelerar su desarrollo. La
herramienta mas potente de terceros para crear componentes Delphi que
conocemos es Component Development Kit (CDK) de Eagle Software
(www .eagle-software.com), per0 hay muchas mas.

Las clases basicas de componentes


Para crcar un nuevo componente, normalmente partimos de uno ya existente o
dc una de las clases basicas de la VCL. En ambos casos, el componente esta en
una de las tres grandes categorias de componentes, definidas por las tres clases
basicas de la jerarquia de componentes:
T w i n c o n t r o l : Es la clase padre de cualquier componente basado en una
ventana. Los componentes que descienden de esta clase pueden recibir el
foco de entrada y obtener mensajes Windows del sistema. Tambien pode-
mos usar su manejador de ventana a1 llamar a funciones API. Cuando
creamos un control de ventana nuevo, por lo general se hereda de la clase
derivada T C u s t o m C o n t r o l , que posee una serie de utiles caracteristi-
cas adicionales (sobre todo soporte para pintar el control).
TGraphicControl: Es la clase padre de 10s componentes visibles que no
tienen manejador de Windows (que guarda algunos recursos Windows).
Estos componentes no pueden recibir el foco de entrada ni responder a
mensajes Windows directamente. A1 crear un control grafico, se hereda
directamente de esta clase (que posee un conjunto de funciones muy simila-
res a T C u s t o m C o n t r o l ) .
TComponent: Es la clase padre de todos 10s componentes (incluidos 10s
controles) y se pueden usar como clase padre directa de componentes no
visuales.
En el resto del capitulo, crearemos algunos componentes usando varias clases
padre y analizaremos todas las diferencias entre ellas. Empezaremos con compo-
nentes que heredan de componentes o clases existentes a un bajo nivel de la jerar-
quia. Despues trataremos ejemplos de clases que heredan directamente de las
clases precedentes que acabamos de mencionar.

Creacion de nuestro primer componente


Crear componentes es una actividad importante para 10s programadores en
Delphi. La idea bisica es que cada vez que necesitemos el mismo comportamiento
en dos lugares distintos de una aplicacion o en dos aplicaciones diferentes, poda-
mos colocar el codigo compartido en una clase (0, mejor, en un componente).
En esta seccion, presentaremos un par de componentes para hacernos una idea
de 10s pasos que hay que seguir para construir uno. Tambikn mostraremos las
diferentes cosas que podemos hacer para personalizar un componente existente
con una cantidad limitada de codigo.

El cuadro combinado Fonts


Muchas aplicaciones tienen una barra de herramientas con un cuadro combi-
nado que podemos usar para seleccionar una fuente. Si utilizamos con frecuencia
un cuadro combinado personalizado como ese, podriamos convertirlo en un com-
ponente, lo que nos Ilevaria, probablemente, menos de un minuto.
Para empezar, hay que cerrar todos 10s proyectos activos en el entorno Delphi
y arrancar el asistente para componentes, seleccionando Component>New
Component o File>New para abrir el Object Repository y, a continuacion,
escoger el componente en la ficha New. Como vemos, el asistente para compo-
nentes necesita la siguiente informacion:

&kUePapa hid A
~dk c+.nm 1ud~0n1~0mbo
pas
J
El nombre del tip0 ascendente: la clase de componente de la que se quiere
heredar. En este caso podemos usar TComboBox.
El nombre de la clase del nuevo componente que vamos a crear. Podemos
usar TMdFontCombo.
La ficha de la Component Palette en la que queremos que aparezca el
componente, que puede ser una ficha nueva o una que ya exista. Podemos
crear una nueva ficha, llamada Md.
El nombre del archivo de la unidad en la que queremos que Delphi coloque
el codigo fuente del nuevo componente. Podemos escribir MdFont Box.
La ruta de busqueda actual (que deberia aparecer de forma automatica).
Hacemos clic sobre el boton OK y el asistente para componentes generara el
archivo fuente mostrado en el listado 9.1 con la estructura de nuestro componen-
te. El boton Install se puede usar para instalar el componente en un paquete de
forma inmediata. Veamos el codigo primer0 y despues trataremos la instalacion.

Listado 9.1. Codigo del TMdFontCombo, producido por el asistente para componentes.

u n i t MdFontBox;

interface

uses
windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
TMdFontCombo = c l a s s (TComboBox)
private
{ Private declarations 1
protected
{ Protected declarations 1
public
{ Public declarations 1
published
{ Published declarations ]
end;

p r o c e d u r e Register;

implementation

p r o c e d u r e Register;
begin
Registercomponents ( ' M d ' , [TMdFontCombo] ) ;
end;

end.
Uno de 10s elementos clave de este listado es la definicion de clase, que co-
mienza indicando la clase padre. La otra unica parte importante es el procedi-
miento R e g i s t e r . Como podemos ver, el asistente para componentes hace muy
poco trabajo.

ADVERTENC1A:El procedimiento Register debe escribirse con R ma-


yliscula. Este requisito se impuso por razones de compatibilidad con C++
Builder (10s identificadores en C++ hacen distincion entre maylisculas y
minusculas).

r
r
r~ornvresue clase u~sr~rl~os.
ror esa razo11,la mayorla ue los uesarrollauores
de componentes en Delphi han escogido aiiadir un prefijo de dos o tres
letras a 10s nombres de nuestros componentes. En este libro, hemos escogi-
d o Md para identificar 10s componentes escritos en este. La ventaja de esta
tecnica esta en que podemos instalar un componente TMd F o n t C o m b o ,
aunque ya hayamos instalado un componente denominado T F o n t Combo.
Observe que 10s nombres de unidad han de ser unicos para todos 10s com-
ponentes instalados en el sistema, por lo que hemos aplicado el mismo
prefijo a los nombres de unidad.

Esto es todo lo que hay que hacer para crear un componente. Este codigo, por
supuesto, no incluye demasiado codigo. Ahora, solo hay que copiar todas las
fuentes del sistema en la propiedad I t e m s del cuadro combinado a1 arrancar.
Para ello, podemos intentar sobrescribir el metodo c r e a t e en la declaracion de
clase, afiadiendo la sentencia I t e m s : = S c r e e n . F o n t s . Sin embargo, esta
no es la tecnica adecuada. El problema esta en que no podemos acceder a la
propiedad I t e m s del cuadro combinado, antes de que el manejador de ventana
del componente este disponible. El componente no puede tener un manejador de
ventana hasta que se defina su propiedad P a r e n t y esa propiedad no se define en
el constructor, sin0 mas adelante.
Por esa razon, en lugar de asignar las nuevas cadenas en el constructor Create.
debemos realizar esta operacion en el procedimiento C r e a t e W n d , a1 que se
llama para crear el control ventana despues de que se construya el componente, se
defina su propiedad P a r e n t y su manejador de ventana este disponible. De
nuevo, ejecutamos el comportamiento predefinido y, a continuacion, podemos
escribir nuestro codigo personalizado. Podiamos habernos saltado el constructor
C r e a t e escribiendo todo el codigo en C r e a t e W n d , per0 hemos usado ambos
metodos iniciales para mostrar las diferencias entre ellos. Veamos la declaracion
de la clase del componente:
type
TMdFontCombo = class (TComboBox)
private
FChangeFormFont: Boolean;
procedure SetChangeFormFont(c0nst Value: Boolean);
public
constructor Create (AOwner: TComponent); override;
procedure CreateWnd; override;
procedure Change; override;
published
property Style default csDropDownList;
property Items stored False ;
property ChangeFormFont: Boolean
read FChangeFormFont write SetChangeFormFont default True;
end;

Este es el codigo fuente de 10s dos mktodos ejecutados a1 arrancar:


constructor TMdFontCombo.Create (AOwner: TComponent);
begin
inherited Create (AOwner);
Style := csDropDownList;
FChangeFormFont : = True;
end;

procedure TMdFontCombo.CreateWnd;
begin
inherited CreateWnd;
Items .Assign (Screen.Fonts) ;
/ / obtiene la fuente predefinida del forrnulario propietario
if FChangeFormFont and Assigned (Owner) and (Owner is TForm)
then
ItemIndex := Items. IndexOf ( (Owner as TForm) .Font .Name) ;
end:

Fijese en que ademas de dar un nuevo valor a la propiedad S t y l e del compo-


nente, en el metodo c r e a t e , hemos definido de nuevo dicha propiedad definien-
do un valor con la palabra clave default. Tenemos que realizar ambas
operaciones, porque aiiadir la palabra clave default a una declaracion de pro-
piedad no tiene un efecto direct0 en el valor inicial de dicha propiedad. Debemos
definir un valor predefinido porque las propiedades que tienen un valor igual a1
predefinido no se agrupan en el mismo stream que la definicion del formulario (y
no aparecen en la descripcion textual del formulario, el archivo DFM). La pala-
bra clave d e f a u l t , informa a1 codigo de streaming, que el codigo de inicio del
componente definira el valor de dicha propiedad.

TRUCO: Es importante especificar un valor predefinido de la propiedad


publicada, para reducir el t a m d o de los archivos DFM y, en ultimo tCrmi-
no, el tarnaiio de 10s archivos ejecutables (que incluyen 10s archivos DFM).
La otra propiedad que hemos vuelto a definir, I t e m s , se define como una
propiedad que no deberia guardarse en el archivo DFM, sea cual sea el valor real.
Esto se consigue con la directiva s t o r e d seguida del valor F a l s e . El compo-
nente y su ventana se van a crear de nuevo cuando el programa arranca, por lo que
no tiene sentido guardar
- en el archivo DFM informacion que mas tarde se va a
dcscartar (para ser sustituida por la nueva lista de fuentes).

NOTA: Podriamos haber escrito el codigo del metodo CreateWnd para


rnniar lac
VY'JIU.I U Y
fiientpc
L U W Y C W "
en
V..
I.VU
n c elpmentnc
V.W..LVY.V"
rlel VUUU."
UW.
riigrlrn Yrnmh;narln
V..L"I..UUV
en
V..
timnnn
*."..y,V AP
UV

ejecucion. Esto se puede hacer utilizando sentencias como i f not


(csDesigning in ComponentState). Pero en el caso de este
..-;...a- n~...nn..a..** -..a P"+~-A" ,...a..-rlfi a1 ma..- af ,.;a..*a -a-n YX" ma..,.;
~ 1 1 1 1 1 ~
~U U~U G G ~ L ~ I I ~
1 IILVUIIGIIL I U ~ ~ U U kU1 I I I G I I U ~ ~ L I ~ I G I I C~G
I G I 1 1V
1aa S G I I ~ I -

110 metodo que hemos d e s c30


~ ofrece un enfoque mas claro del procedi-
miento basico.

La tercera propiedad, C h a n g e F o r m F o n t , no se hcreda sino que es introdu-


cida por el componente. Se usa para establecer si la seleccion de la fuente actual
del cuadro combinado, deberia especificar la fuente del formulario en el que se
incluye el componente. De nuevo, esta propiedad se declara con un valor
predefinido, declarado en el constructor. Se usa la propiedad ChangeFormFont
en el codigo del mdtodo C r e a t e W n d , que aparecia anteriormente, para estable-
cer la seleccion inicial del cuadro combinado que depende de la fuente dcl formu-
lario que ale-ja a1 componente. Estc es normalmente el propietario del componente,
aunque podiamos habernos desplazado por el arb01 P a r e n t en busca de un
componente formulario. Este codigo no es perfecto, per0 las verificaciones
A s s i g n e d e i s ofrecen una seguridad adicional.
La propiedad C h a n g e F o r m F o n t y la misma prueba i f tienen una funcion
clave en el metodo C h a n g e d , que en la clase basica desencadena el evento
O n C h a n g e . A1 sobrescribir este mCtodo, ofrecemos un comportamiento
predefinido, que sc puede desactivar cambiando el valor de la propiedad, pero
tambien permite la ejecucion del evento OnChange, para que 10s usuarios de
dicha clase puedan personalizar por completo su comportamiento. El ultimo me-
todo; S e t C h a n g e F o r m F o n t , se ha modificado para refrescar la fuente del
formulario en caso de quc se estd activando la propiedad. Este es el codigo com-
pleto:
procedure TMdFontCombo.Change;
begin
// asigna la fuente a 1 formulario propietario
if FChangeFormFont and Assigned (Owner)
and (Owner is TForm) then
TForm (Owner).Font.Name : = Text;
inherited;
end ;
procedure TMdFontCombo.SetChangeFormFont(const Value: Boolean);
begin
FChangeFormFont : = Value;
// refresca l a fuente
i f FChangeFormFont then
Change;
end ;

Creacion de un paquete
Ahora, tenemos que instalar el componente en el entorno, usando un paquctc.
Para este ejemplo; podemos crcar un nucvo paquctc o utilizar uno cxistcntc, como
el paquete predefinido del usuario.
En cada caso, hay que selcccionar la orden dcl menu Component>lnstall
Component. El cuadro dc dialog0 rcsultantc ticnc una ficha para instalar cl
componentc en un paquetc cxistentc y una ficha para crcar un nucvo paquctc. En
este ultimo caso? simplemente tecleainos un nombre dc archivo y una dcscripcion
dcl paquctc. A1 haccr clic sobrc cl boton OK sc abrc el Package Editor (veasc
la figura 9. l ) , que tiene dos partcs:
La lista Contains: lndica 10s componentes incluidos en el paquete (0, para
scr mas exactos, las unidades que definen esos componentes).
La lista Requires: Indica 10s paquctcs necesarios para dicho paqucte.
Norinalinentc, nucstro paquete necesitara 10s paquetes rtl y vcl (el paquetc
de la bibliotcca cn ticmpo dc cjccucion y el paquete principal VCL), pero
podria neccsitar tambicn cl paqucte vcldb (que contiene la mayoria de las
clases relacionadas con bases dc datos) si 10s componentes del nuevo pa-
quctc rcalizan alguna operacion relacionada con bases de datos.

22 2 s T
I
Compk Add Remove Opl~ons

Figura 9.1. El Package Editor.

I ..
NOTA: Los nombres de Daauetes desde Debhi 6 va no son es~ecificosde I
la version, aunque 10s paquetes compilados tengan todavia el numero de la
version en el nombre del archivo. Para conocer mas detalles acerca de como
- - . ..
I se logra. esro recnlcamenre
1 * . *
poaemos1.
acuair 3. 1 *
mas aaelanre,_ J 1 I
a la seccron que (
I trata 10s cambios en 10s nombres de proyecto y biblioteca. I
Si afiadimos el componente a1 nuevo paquete que acabamos de definir y, a
continuacion, sencillamente compilamos el paquete y lo instalamos (usando 10s
dos botones correspondientes de la barra de herramientas del Package Editor),
veremos aparecer inmediatamente el nuevo componente en la ficha Md de la
Component Palette. El procedimiento Register del archivo de la unidad del
componente inform6 a Delphi sobre donde instalar el nuevo componente. Por
defecto, el mapa de bits utilizado sera el mismo que el de la clase padre, porque no
hemos ofrecido un mapa de bits personalizado (haremos esto en ejemplos poste-
riores). Fijese en que si movemos el raton sobre el nuevo componente, Delphi
mostrara en forma de sugerencia el nombre de la clase sin la letra inicial T.

~ Q u hay
e detras de un paquete?
El Package Editor produce basicamente el codigo fuente del proyecto de
paquete: un tipo especial de DLL creada en Delphi. El proyecto de paquete se
guarda en un archivo con la extension DPK (de Delphi PacKage), mostrado si
pulsamos la tecla F12 en el editor de paquetes. Un proyecto de paquete normal es
como el siguiente:
package M d P a c k ;

...
{ $ D E S C R I P T I O N 'Mastering D e l p h i Package I }

{ $ IMPLICITBUILD ON)

requires
vcl ;

contains
MdFontBox in 'MdFontBox.pa s ';
end.

Como se puede ver, Delphi usa palabras clave del lenguaje especificas para
paquetes: la primera es la palabra clave package (similar a la palabra clave
library que se tratara mas adelante), que introduce un nuevo proyecto de
paquete.
A continuacion, hay una lista con todas las opciones del compilador, algunas
de las cuales han sido omitidas. Normalmente, las opciones de un proyecto Delphi
se guardan en un archivo a parte. Por el contrario, 10s paquetes incluyen todas las
opciones del compilador directamente en su codigo fuente. Entre las opciones de
compilador, esta la directiva de cornpilacion DESCRIPTION,utilizada para que
la descripcion del paquete este disponible en el entorno Delphi. De hecho, despues
de haber instalado un nuevo paquete, su descripcion aparecera en la ficha
Packages del cuadro de dialog0 Project Options, una ficha que se puede
activar tambidn seleccionando el elemento del menu Component>lnstall
Packages. Este cuadro de dialogo aparece en la figura 9.2.

..
Desciition I Campier I Cornplerbtessages 1 Luiker
DrectarmlConddimls Version Info Packages

, Design packages

'KI Eorlard BDE DE Components


14Barlard CLX Database Compmerls

Figura 9.2. Las opciones de proyecto de 10s paquetes

Ademas de dircctivas comunes como D E S C R I P T I O N ; hay otras directivas


especificas para paquetes. Se puede acceder facilmente a las opciones mas comu-
ncs mediante el boton Options del Package Editor. Despues de esta lista de
opciones, estan las palabras clave requires y contains,que listan 10s ele-
mentos que aparecen visualmente en las dos fichas del Package Editor. De
nuevo. la primera es la lista de paquetes que neccsita el actual y la segunda una
lista de las unidades que instala dicho paquete.
Veamos 10s efectos a nivel tecnico de la creacion de paquetes. Ademas del
archivo DPK con el codigo fuente, Delphi genera un archivo BPL con la version
de enlace dinamico del paquete y un archivo DCP con la informacion de simbolos.
En la practica, este archivo DCP es la suma de la informacion de simbolo de 10s
archivos DCU de las unidades contenidas en el paquete.
En tiempo de diseiio, Delphi necesita tanto 10s archivos BPL como DCP, por-
que el primero tiene el codigo real de 10s componentes creados en el formulario de
diseiio y la informacion de simbolos necesaria para la tecnologia Code Insight. Si
enlazamos el paquete de forma dinamica (usandolo como un paquete en tiempo de
ejecucion), el archivo DCP se utilizara tambien para el editor de enlaces y el
archivo BPL se enviara con el archivo ejecutable principal de la aplicacion. En
cambio, si enlazamos el paquete de forma estatica, el editor de enlaces hara refe-
rencia a 10s archivos DCU y solo sera necesario distribuir el archivo ejecutable
final.
Por ese motivo, como diseiiadores de componentes, deberiamos distribuir nor-
malmente a1 menos el archivo BPL, el archivo DCP y 10s archivos DCU de las
Uso del cuadro combinado Fonts
Ahora crearemos un nuevo programa en Delphi para probar el cuadro combi-
nado Font. Vamos a la Component Palette, seleccionamos el nuevo componen-
te y lo aiiadimos a un nuevo formulario. Aparecera un cuadro combinado de
apariencia tradicional. Sin embargoj si abrimos el editor de la propiedad 1terns,
veremos una lista de fuentes instaladas en nuestro ordenador. Para crear un ejem-
plo sencillo, hemos aiiadido un componente Memo a1 formulario con un texto en
el interior. A1 dejar activa la propiedad C h a n g e F o r m F o n t ; no es necesario
escribir ningun otro codigo en el programa, como veremos en el ejemplo. Como
alternativa, podriamos haber desactivado la propiedad y controlado el cvento
OnChange del componente. con un codigo como el siguiente:

El proposito de este programa es solo probar el comportamiento del compo-


nente que acabamos dc crear. Aun asi, el componente no resulta muy util (podia-
mos haber aiiadido unas pocas lineas de codigo a un formulario para conseguir el
mismo efccto), per0 ver algunos componentes sencillos deberia servir de ayuda
para que nos hagamos una idea de lo que implica la construccion de un compo-
nente.

Los mapas de bits de la Component Palette


Antes de instalar este segundo componente. podemos realizar un paso adicio-
nal: definir un mapa de bits para la Component Palette. Si no lo hacemos, la
paleta utiliza el mapa de bits de la clase padre o un mapa de bits de un objeto
predefinido si la clase padre no es un componente instalado. Definir un nuevo
mapa de bits para el componente es facil, una vez que conocemos las reglas.
Podemos crear un mapa de bits con el Image Editor, comenzando un proyecto
nue\ro y seleccionando el tipo de proyecto Delphi Component Resozrrce (DCR,
Recursos de componente de Delphi).
-

TRUCO:Los archivos DCR son sencillamente archivos RES esthdar con


una extension distinta. Si lo preferimos, se pueden crear con cualquier edi-
tor de recursos, como el Borland Resource Workshop, que es realmente
una herramienta mas potente que el ed itor Delphi Image. A1 finalizar la
creaci6n del archivo de recurs;, s61o hlay que dar otro nombre a1 archivo
. .-a
r . .. -n"
m3 para w a r una extension ULK.

Ahora podemos aiiadir un nuevo mapa de bits a1 recurso, seleccionando un


tamaiio de 24x24 piseles y podemos dibujar el mapa de bits. Las otras normas
importantes hacen referencia a la denominacion. En este caso, la norma de deno-
minacion no es solo una convencion, es un requisito para que el IDE pueda encon-
trar la imagen de una clase de componentes dada:
El nombre del recurso de mapa de bits habra de corresponder a1 nombre del
componente, incluida la letra inicial T. En este caso, el nombre del recurso
del mapa de bits deberia scr TMDFONTCOMBO. El nombre del recurso
de mapa dc bits habra de estar en mayuscula (esto es obligatorio).
Si queremos que el Package Editor reconozca e incluya el archivo de recur-
so, el nombre del archivo DCR habra de corresponder a1 nombre dc la
unidad compilada que define el componente. En este caso, el nombre de
archivo deberia ser M d C l o c k . DCR. Si incluimos el archivo de recurso
manualmente, mediante la directiva SR, podemos darle cl nombre que que-
ramos asi como utilizar una extension RES y afiadir multiples mapas de
bits en dl.
Cuando esta listo cl mapa de bits para el componentc, podemos instalar el
componcnte en Dclphi. utilizando el boton InstaII Package de la barra de herra-
mientas dcl Package Editor. Tras csta operacion, la scccion contains del
editor dcberia listar tanto el archivo PAS del componente como el correspondien-
tc archivo DCR. En la figura 9.3 podcmos ver todos 10s archivos (tambien 10s
archivos DCR) de la vcrsion final del paquete MdPack.Si la instalacion DCR no
funciona correctamentc, se puede aiiadir manualmente la sentencia { $ R
unitname. d c r } en el codigo fuente del paqucte.

I. _ .. ... .- - -- . ....-. _ -
. _ .-.
: J Contains
3
Md4ctiveBtn pas D:\md7code\OS\Mdpack
3
Md4rrow.dcr D:\md7code\OS\Mdpack
k
J Md4rrow.pas D:\md7code\OS\Mdpack
MdClock.dc1 D:\md7code\OS\Mdpack
a
MdClock.pas D:\rnd7code\OS\Mdpack
E 9
MdClockFfarns D:\rnd7code\OS\Mdpack
MdCollect.pas D:\md7code\OS\Mdpack
MdfontCombo.pas D:\md7code\OS\Mdpack
9
MQntfTest.pas D:\md7code\OS\M&ack
MdLifl4ct pas D.\rnd7de\OS\Md~zck
K 3
MdLiflDial D:\rnd7code\OS\Mdpack
9 MdListDddcr D:\rnd7code\OS\Mdpack
b D:\md7codeU!S\Mdpack
MdNumEd pas D:\md7code\OS\Mdpack
MdPersonalData... D:\md?code\OS\Mdpack
MdSounB dcr D:\rnd7code\OS\Mdpack
MdSounB.pas D:\rnd7code\OS\Mdpack
3 0 Requires
~N.dcp
vcl.dcp

Figura 9.3. La seccion Contains del Package Editor rnuestra tanto las unidades
incluidas en el paquete como 10s ficheros de recursos de componente.
Creacion de componentes compuestos
Los componentes no existen de un mod0 aislado. Los programadores usan a
menudo 10s componentes en conjuncion con otros. codificando la relacion en uno
o mas controladores de eventos. Una tecnica alternativa es crear componentes
compuestos, lo que puede encapsular esta relacion y facilitar su manejo. Hay dos
tipos diferentes de componentes compuestos:
Componentes Internos: Son creados y gestionados por el componentc
principal, que puede mostrar algunas de sus propiedades y eventos.
Componentes Externos: Se conectan usando propiedadcs. Automatizan
la interaccion entre componentes separados, que pueden estar en el mismo
formulario o diseiiador o en uno diferente.
En ambos casos, el desarrollo sigue algunas reglas estandar. Una tercera alter-
nativa, menos esplorada, implica el desarrollo de contenedores de componentes,
que pueden interactuar con 10s controles hijo. Este es un tema mas avanzado por
lo que no se tratara aqui.

Componentes internos
El componente en el que nos centraremos ahora es un reloj digital. Este ejem-
plo tiene algunas caracteristicas muy interesantes. Primero, tiene un componente
dentro de otro componente (un Temporizador). Segundo, muestra la tecnica de
datos en vivo: tendremos la posibilidad de ver un comportamiento dinamico (la
actualizacion del reloj) incluso en tiempo de diseiio, como ocurre, por ejemplo,
con componentes relacionados con datos.

NOTA: La primera caracteristica se ha convertido en algo mis relevante


desde Delphi 6, puesto que el Object Inspector de esta ultima version,
permite exponer propiedades de subcomponentes directamente.

Dado que el rcloj digital ofrecera una salida con un cierto texto, hemos consi-
derado el heredar de la clase TLabel.Sin embargo, esto permitiria que el usua-
rio cambiase el titulo de la etiqueta (es decir, el testo del reloj). Para evitar este
problema, sencillamente hemos utilizado el componente TCustomLabel como
clase padre.
Un objeto TCustomLabel tiene las mimas capacidades que un objeto
TLabel,per0 pocas propiedades publicadas. En otras palabras, una clase que
hereda de TCUS tomLabel puede decidir que propiedades deberian estar dispo-
nibles y cuales deberian permanecer ocultas.
-- _I

NOTA: L a mayoria de 10s componentes Delphi, sobrc todo 10s basados en


Windows, tienen una clase basica TCustomXxx, que implementa toda la
funcionalidad pero expone solo un conjunto limitado de propiedades. Here-
dar de estas clases basicas es la forma estandar de exponer solo algunas de
las propiedades de un componente en una version personalizada. De hecho,
no se pueden ocultar las propiedades publicas ni publicadas de una clase
b h i c a , a no ser que las ocultemos definiendo una nueva propiedad con el
mismo nombre en la clase heredada.

Con versiones previas de Dclphi, el componentc debia dcfinir una propiedad


nucva. A c t i v e , envolvicndo la propiedad E n a b l e d del Tcmporizador. Quc
una propicdad sca envolvcr~lesignifica que sus mctodos set y gel lccn y cscribcn
el valor de la propiedad er?vuelto, que pcrtcnece a un componente intcrno (gcnc-
ralmcnte, una propicdad envolvcnte no contiene datos locales). Veamos cl codigo
de cstc caso cspccifico:
f u n c t i o n T M d C l o c k - G e t A c t i v e : Boolean;
begin
Result : = FTimer.Enabled;
end;

p r o c e d u r e TMdClock.SetActive (Value: Boolean);


begin
FTlmer . Enabled : = Value;
end;

Publicacion de subcomponentes
Ya desdc Delphi 6. podemos exponer simplemcnte el componente completo (cl
tcmporizador) en una sola propiedad, quc ampliara norn~allnenteel Object Ins-
pector y pcrmitira a1 usuario definir cada una dc sus subpropiedades e incluso
controlar sus evcntos.
Vcamos la dcclaracion de tipo completa del componente T M d C l o c k , con cl
subconiponentc dcclarado en 10s datos privados y espucsto como una propiedad
publicada (en la ultima linea):
type
TMdClock = c l a s s (TCustomLabel)
private
FTimer : TTimer ;
protected
p r o c e d u r e Updateclock (Sender: TOblect);
public
constructor Create (AOwner: TComponent); override;
published
property Align;
property Alignment;
property Color;
property Font;
property Parentcolor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint ;
property Transparent;
property Visible;
property Timer: TTimer read FTimer;
end;

La propiedad T i m e r es solo de lectura, puesto que no queremos que 10s usua-


rios seleccionen otro valor para cste componente en el Object Inspector (ni que
desvinculen el componentc eliminando el valor de esta propiedad). Dcsarrollar
conjuntos de subcomponentes quc pueden ser usados alternativamente es posible,
per0 afiadir soporte de escritura para esta propiedad de un mod0 seguro no es
scncillo (considerando quc 10s usuarios de nuestros componentes puedcn no ser
programadores cspertos en Delphi). Por ello, es conveniente limitarse a propieda-
dcs de solo lectura para subcomponentes.
Para crcar cl tcmporizador. tenemos que sobrescribir el constructor del com-
p o n e n t ~rc10.j. El metodo c r e a t e llama a1 metodo correspondiente de la clase
basica y crea el objeto temporizador, instalando un controlador para su evento
OnTimer:
constructor T M d C l o c k - C r e a t e (AOwner: TComponent);
begin
i n h e r i t e d C r e a t e (AOwner);
// c r e a e l o b j e t o t e m p o r i z a d o r i n t e r n o
FTimer := TTimer .Create ( S e l f ) ;

FTimer .Name := ' C l o c k T i m e r ';


FTimer.OnTimer : = Updateclock;
FTimer. Enabled : = True;
FTimer . SetSubComponent ( T r u e );
end:

El codigo da un nombre a1 componente, para mostrarlo en el Object Inspec-


tor (vease la figura 9.4) y llama a1 metodo especifico S e t S u b C o m p o n e n t . No
necesitamos un destructor, sencillamente porque el objeto F T i m e r t i m e el
T M ~ ~ l o ccomo k propietario (como indica el parametro de su constructor
C r e a t e ) , por lo tanto se destruira automaticamente cuando se destruya el com-
ponente reloj.

NOTA: El efecto real de la llamada a1 m&odo SetSubCom~onenten el


codigo anterior es que define un i dicador interno, guardado en el conjunto
de propiedades Components t l e . El indicador (csSubComponent)
L.

afecta a! ; i i E Z Z s t r e a m i n g , que permite-que d's u b c e n t e ~ G u i


propiedades se guarden en el archivo DFM. De hecho, el sistema de streaming
ignora por defecto 10s componentes que no posee el formulario.

I
Pfoyr~ks Events 1- - -

I
.. -

1 ~elc 120 A
Name Mdnockl
ParelllCobr True

Figura 9.4. El Object Inspector puede ampliar automaticamente 10s subcomponentes,


mostrando sus propiedades, como en el caso de la propiedad Timer del componente
TMdClock.

La parte claw del codigo del componente cs el proccdimiento U p d a t e c l o c k ,


que consiste en una sola sentencia:
procedure TMdLabelClock.UpdateC1ock (Sender: TObject);
begin
/ / d e f i n e l a h o r a a c t u a l como t i t u l o
Caption : = TimeToStr ( T i m e ) ;
end;

Estc metodo usa C a p t i o n , que es una propiedad no publicada, para quc un


usuario del componentc no pueda modificarlo en el Object Inspector. El resul-
tad0 de la scntencia es mostrar la hora actual. Esto ocurre continuamente, porque
el mctodo esta conectado a1 evento O n T i m e r del temporizador.

NOTA: Pueden editarse 10s eventos de 10s subcomponentes en el Object


Inspector, de mod0 que un usuario pueda manejarlos. Si manejamos el
. .- . - --

evento lnternamente, como hemos hecho en T M d L a b e l C o c k, un usuarlo


podra sobrecargar el comportamiento controlando el evento, en este caso
O n T i m e r . Generalmente, la solucion esta en definir una clase derivada
para el componente interno, sobrecargando sus mktodos virtuales, como el
mCtodo T i m e r de la clase T T i m e r . Pero en este caso, esta tecnica no
funcionara porque Delphi activa el temporizador s61o si tiene un controla-
aor unlao. 31soorecargarnos el meroao vlrrual s. ~- proveer
J ! 0 I I *
n ~ el
I .
r .I ~ ~
conrrolaaor ae-I- . ~ - * - - l - J - - 3 -

eventos (lo correct0 en un subcomponente) el temporizador no funcionara.


Componentes externos
Cuando un componente referencia un componentes externo, no crea este com-
ponente por si mismo (que es la razon por la que se le llama externo). Es el
programador que usa 10s componentes quien crea ambos separadamente (arras-
trandolos desde la Paleta de Componentes a un formulario, por ejemplo) y
conecta 10s dos componentes usando una de sus propiedades. Por ello, podemos
decir que una propiedad de un componentes referencia a un componente enlazado
externamente. Esta propiedad debe ser de un tipo de clase que hereda de
TComponent.
Para mostrarlo, hemos creado un componente no visual que puede mostrar
informacion acerca de una persona en una etiqueta y actualizarla automaticamente.
El componente publica estas propiedades:
type
TMdPersonalData = class (TComponent)

published
property FirstName: s t r i n g r e a d F F i r s t N a m e w r i t e SetFirstName;
property LastName: s t r i n g r e a d FLastName w r i t e SetLastName;
property Age: Integer r e a d FAge w r i t e SetAge;
property Description: string r e a d GetDescription;
property OutLabel: TLabel r e a d FLabel w r i t e SetLabel;
end ;

Hay cierta informacion basica y una propiedad de solo lectura D e s c r i p t i o n


que devuelve toda la informacion de una vez. La propiedad O u t L a b e l esta
conectada con un campo local privado llamado F L a b e l . En el codigo del com-
ponente, hemos usado esta etiqueta externa por medio de la referencia interna
F L a b e l , como aqui:

p r o c e d u r e TMdPersonalData.UpdateLabe1;
begin
i f Assigned (FLabel) t h e n
FLabel.Caption : = Description;
end;

Este metodo U p d a t e L a b e l es ejecutado cada vez que una de las otras pro-
piedades cambia (como puede verse en tiempo de diseiio en la figura 9 . 5 ) , como
podemos ver aqui:
p r o c e d u r e TMdPersona1Data.SetFirstName (const Value: string);
begin
i f FFirstName <> Value t h e n
begin
FFirstName : = Value;
UpdateLabel;
end ;
end;
if FLabel <> Value then
begin
FLabel : = Value;
if FLabel < > n i l then
begin
UpdateLabel;
FLabel-FreeNotification ( S e l f ) ;
end;
end ;
end ;

s usar la notification opuesta (op I n s e r t)para


icamente a1 ailadirles a1 mismo formulario o
aisenaaor. A pesar ae que Csta es una tknica util en muchas situaciones
comunes, no se usa habitualmente. Esto puede deberse a que es mas 16gico
crear editores de propiedades y componentes especificos para dar soporte a
overaciones de tiempo de diseiio, que a empotrar codigo dentro de 10scorn-
ponentes.

Referencias a componentes mediante interfaces


A1 referirnos a componentes externos, hemos estado tradicionalmente limita-
dos en la jerarquia a un nivel por debajo del actual. Por ejemplo, el componente
que hemos creado en la seccion anterior, puede referirse so10 a objetos de la clase
TLabel o de las clases que heredan de ella, a pesar de que seria logico poder
publicar la inforrnacion a otros componentes. Delphi 6 aiiadio soporte para una
caracteristica interesante que tiene el potencial para revolucionar algunas areas
del VCL: referencias a componentes de tip0 interfaz.

NOTA: Esta caracteristica se usa raramente en Delphi 7. Dado que es


probablemente dernasiado tarde para actualizar la arquitectura de compo-
nentes relacionados con datos que utilizan interfaces, todo lo que podemos
esperar es que sera utilizada para expresar fbturas relaciones complejas
dentro de la biblioteca.

Si tenemos componentes que ofrecen una interfaz concreto (aunque no sean


parte de la misma subjerarquia), podemos declarar una propiedad de tipo interfaz
y asignarle cualquiera de esos componentes. Por ejemplo, supongamos que tene-
mos un componente no visual asociado a un control para mostrar su resultado,
algo parecido a lo que vimos en la seccion anterior. Habiamos usado una tecnica
tradicional, uniendo el componente a una etiqueta, per0 ahora podemos definir
una interfaz asi:
tYPe
IMdViewer = interface
['{97668600-8E4A-4254-9843-59B98FEE6C54}']
procedure View (const str: string);
end ;

Un componente puede usar su interfaz viewer para mostrar su resultado a otro


control (de cualquier tipo). El listado 9.2 muestra como declarar un componente
que usa esta interfaz para referirse a un componente externo.

Listado 9.2. Un cornponente que referencia un cornponente externo usando una


interfaz.

tYPe
TMdIntfTest = class (TComponent)
private
FViewer: IViewer;
FText: string;
procedure SetViewer (const Value: IViewer);
procedure SetText (const Value: string);
protected
procedure Notification (AComponent: TComponent;
Operation: TOperation) ; override;
pub1i shed
property Viewer: IViewer read FViewer write SetViewer;
property Text: string read FText write SetText;
end;

{ TMdIntfTest }

procedure TMdIntfTest.Notification (AComponent: TComponent;


Operation: TOperation) ;
var
int f : IMdViewer;
begin
inherited;
if (Operation = opRemove) and
(Supports (AComponent, IMdViewer, intf) ) and (intf =
FViewer ) then
begin
FViewer : = nil;
end;
end;

procedure TMdIntfTest.SetText(const Value: string);


begin
FText : = Value;
i f Assigned (FViewer) then
FViewer .View (FText);
end;

procedure TMdIntfTest.SetViewer(const Value: IMdViewer);


var
icomp: I I n t e r f a c e C o r n p o n e n t R e f e r e n c e ;
begin
if FViewer <> Value t h e n
begin
FViewer : = Value;
FViewer.View(FText);
i f Supports (FViewer, IInterfaceComponentReference,
iComp) t h e n
iComp.GetComponent.FreeNotification(Se1f);
end;
end;

El uso de una interfaz implica dos diferencias relevantesj comparado con el


traditional uso de un tip0 clase para referenciar un componente esterno. Primero,
en el metodo Notification. debemos estraer la interfaz del componente pa-
sado como parametro y compararlo con la interfaz que ya tenemos. Segundo, para
llamar a1 metodo FreeNotification,debemos ver si el objeto que pasamos
como parametro soporta la interfaz I Inter facecomponentReference.
Esto se declara en la clase TComponent y ofrece un mod0 de volvernos a referir
a1 componente (Getcomponent)y llamar a sus metodos. Sin esta a y d a hubie-
ramos tenido que aiiadir un metodo similar a nuestra interfaz personalizada, por-
que a1 extraer una interfaz de un ob-jeto no hay forma automatica de referirse de
nucvo a1 objeto.
Ahora que tenemos un componente con una propiedad interfaz podemos asig-
narlo a cualquier componente (de cualquier parte de la jerarquia VCL) aiiadiln-
dolc la interfaz Iviewer e implemcntando el mttodo View.Veamos un ejemplo:
type
TViewerLabel = c l a s s (TLabel, IViewer)
public
p r o c e d u r e View(str: S t r i n g ) ;
end:

p r o c e d u r e TViewerLabel.View(const str: String);


begin
Caption : = str;
end:

'I -
Creaci6n de componentes compuestos con marcos
En lugar de crear un componente compuesto utilizando esta tecnica, po-
r l r i n m n c h a h p r i i c a r l n iin m a r r n I .nc m a r m c h d~ mmnn-
a r ~ rn l ~rll ~ c a r r n l l n

aiiadlendolo a1 KepOsltory o creando una plantllla usando la orden A d d


t o Palette del menu de mCtodo abreviado del marco.
Como alternativa, podemos querer compartir el marco situandolo en un
paquete y registrandolo como un componente. Tecnicamente, esto no es
complicado: aiiadimos un procedimiento Register a la unidad del mar-
co, aiiadimos la unidad a un paquete y lo escribimos. El nuevo componente/
marco aparece en la Component Palette igual que cualquier otro compo-
nente. Cuando colocamos este componente/marco en un formulario vemos
sus subcomponentes. No podemos seleccionar estos subcomponentes con
un clic de raton en el Form Designer, per0 si en la Object Tree View.
A pesar de todo, cualquier cambio en estos componentes en tiempo de dise-
fio se perdera a1 arrancar el programa o a1 guardar y recargar el formulario,
porque 10s cambios en esos subcomponentes no se conservan (a diferencia
de lo que ocurre con 10s marcos estiindar colocados en forrnularios).
Veremos como aplicar una tecnica bastante sencilla para utilizar marcos en
paquetes, demostrada mediante el componente MdFr amedC l o c k. La idea
consiste en convertir 10s componentes que posee el formulario en
subcomponentes, llamando a1 metodo Set SubComponent . Tarnbien he-
mos expuesto 10s componentes internos con propiedades, aunque no sea
obligatorio (pueden seleccionarse la Object Tree View). Esta es la decla-
ration del componente y el codigo de sus mttodos:
type
TMdFramedClock = class (TFrame)
Lahell: TLabel;
Timerl: TTimer;
Bevell: TBevel;
procedure TimerlTimer(Sender: TObject);
publia
constructor Create (AOnwer: TComponent ) ; override;
pub1 i shed
property SubLabel: TLabel read Labell:
property SubTimer: TTimer read Timerl;
end;
constructor TMdFramedClock.Create(A0nwer: TComponent);
begin
inherited;
Timer1.SetSubComponent (true);
Label1.SetSubComponent (true);
end;
procedure TMdFramedClock.TimerlTimer(Sender: TObject):
begin
Labell. Caption := TimeToStr (Time);
end;

En este caso. en oposicibn a1 caso del reloj, no es necesario d e f i r las


propiedades del temporizador ni conectar el evento temporizador a su fun-
cion controlador manualmente, puesto que eso se realiza visualmente y se
guarda en el archivo DFM del marco. Fijese tambikn en que no hemos
Component en el), por 10 que podemos intentar editarlo en tiempo de
disefio y ver que todos 10s cambios se pierden, como hernos meecionado
antes.
Tras haber i~lstaladoeste marco/componente, pademos usarlo en d q u i e r
aplicacion. En este caso concreto, &sde el momento en quc tiqjm.105 el
marco en el formulario, el temporizador comamra a iwttdbm k etiqueta
con la hora actual. Sin embargo, todavia se puede controh el evento
OnTimer y el IDE de Delphi (que reconoce que el cornpone& BstB en el
marco) definlra un m h d o con este c6digo predefinido:
procedure TForml.MdFramedCLocklTimerlTimer(Sender: TObject);
bagi n
MdFramedClockl.TimerlTimer(Sender);
end;

Desde el momento en que se conecta el temponzador, &haq en tiempo de


diseilo, el reloj en vivo se detendrfi, porque se descollecta su contrdador de
eventos 0rigma.I. Sin embargo, despub de compib-g cjerc,.latarel pr~grama,
se restaurara el comportamiento ori& fa1d m , ari nahrraniodIa linea
anterior) y tambib se ejecutara c6digo persod* adic;ional, Este corn-
portmiento es cxa&mente lo qqe de la9 marcos. Podernos en-
contra una demostracih c o r n p l e t a z m marMlcomponente en el
ejemplo FrameCbck.
Como conclusi6e, podemos afinpar que @mica no cs'en absolutb Bt
neal. Es mucho mejor que en v a r h w s a&d~res & Delphi, .en la que 10s
marcos dentro de 10s paquetes w se p d i a n util izar. per0 pfobablernente no
merezca la pena el esfuerm. En el caso de pcquefias ~qanbacioneso gru-
pos de trabajo es mejor usar mmeos sinipl;es dnmcenados en el Repostbiy.
En orgapiswkmes nub pp'a9des spa disbljbuk los,marcos a una audien-
cia mayor, mkha g&te preferid Great mi eomponentes de rnodo tradicio-
d, 9jIE maqiw. ETT palabaras,esperamoq que Borland ofrezca un
sopork mis,(ieqpple<o para 4 desanollo vjsual de mrnponentes paquete
basado m a&tk

Un componente grafico complejo


Vamos a construir un componente grafico de flecha. Se puede usar dicho com-
ponente para representar un flujo de informacion o una accion. Este componente
es bastante complejo, por lo que presentaremos su creacion paso a paso en lugar
de ir directamente a1 codigo fuente completo. El componente que hemos aiiadido
a1 paquete MdPack es la version final de este proceso, que demuestra muchos
conceptos importantes:
La definicion de nuevas propiedades enumeradas, basadas en tipos de da-
tos enumerados.
La implementacion del metodo P a i n t del componente, que ofrece su
interfaz de usuario y deberia ser lo suficientemente generic0 como para
acomodar todos 10s valores posibles de las diversas propiedades, como su
W i d t h y H e i g h t . El metodo P a i n t tiene una funcion importante en
este componente grafico.
El uso de propiedades de clases derivadas de TPer s i s t e n t , como T Pen
y TBrush, y 10s temas relacionados con su creation, destruccion y con-
trol de sus eventos OnChange de forma interna en nuestro componente.
La definicion de un controlador de eventos personalizado para el compo-
nente, que responda a la entrada del usuario (en este caso, hacer doble clic
en la punta de la flecha). Para esto, sera necesario controlar directamente
10s mensajes de Windows y el uso de la API de Windows para partes
graficas.
El registro de propiedades en categorias del Object Inspector y la defini-
cion de una categoria personalizada.

Definicion de una propiedad enumerada


Tras haber creado el nuevo componente con el asistente de componentes y
seleccionar TGr a p h i c C o n t r o 1como la clase padre, podemos comenzar a per-
sonalizar el componente. La flecha puede apuntar en cualquiera de las cuatro
direcciones: arriba, abajo, izquierda o derecha. Un tip0 enumerado expresa estas
opciones:
'=me
TMdArrowDir = (adup, adRight, adDown, adleft);

Este tipo enumerado define un miembro de datos privado del componente, un


parametro del procedimiento utilizado para cambiarlo y el tip0 de la propiedad
correspondiente.
La propiedad A r r o w H e i g h t establece el tamaiio y F i l l e d si se rellena la
punta de la flecha:
'=me
TMdArrow = c l a s s (TGraphicControl)
private
FDirection: TMdArrowDir;
FArrowHeight: Integer;
FFilled: Boolean;
procedure SetDirection (Value: TMd4ArrowDir);
procedure SetArrowHeight (Value: Integer) ;
procedure SetFilled (Value: Boolean) ;
published
property W i d t h d e f a u l t 50;
property Height d e f a u l t 20;
property Direction: TMd4ArrowDir
read FDirection w r i t e SetDirection d e f a u l t adRight;
property ArrowHeight: Integer
read FArrowHeight w r i t e SetArrowHeight d e f a u l t 10;
property Filled: Boolean r e a d FFilled w r i t e SetFilled
d e f a u l t False;

cuando se coloca en un formulario, su t a m d o sera un h i c o pixel. Por esa


r d n , es importante aiiadir un valor predefinido para las propiedades Width
-. -.-I ---- J- ---- :-2-J--
y n e l g-nLrL; -.
7.-J d-C-:-
y aennlr I-- 2-
los ~iunposUG -I---
CMSG C u r n o v a 1 w E ; s ue PIVPIGU~SIGS
predefinidas en el constructor de la clase.

Las tres propiedades personalizadas se leen directamente del campo corres-


pondiente y se escriben usando tres metodos Set, que tienen todos la misma es-
tructura estandar:
procedure TMdArrow.SetDirection (Value: TMdArrowDir);
begin
i f FDirection <> Value then
begin
FDirection : = Value;
ComputePoints;
Invalidate;
end;
end;

Observe que pedimos a1 sistema que pinte de nuevo el componente (llamando a


Invalidate) solo si la propiedad cambia su valor y despues de llamar a1
metodo ComputePoints, que calcula el triangulo que delimita la punta de
flecha. De no ser asi, se pasa el codigo por alto y el metodo finaliza de forma
inmediata. Esta estructura de codigo resulta muy comun y la usaremos en el caso
de la mayoria de 10s procedimientos Set de propiedades.
Debemos recordar definir 10s valores predefinidos de las propiedades en el
constructor de componentes:
c o n s t r u c t o r T M d A r r o w - C r e a t e (AOwner: TComponent);
begin
/ / llama a 1 c o n s t r u c t o r padre
i n h e r i t e d Create ( A O w n e r );
// d e f i n e 10s valores predefinidos
FDirection : = adRight;
W i d t h : = 50;
Height : = 20;
FArrowHeight : = 10;
FFilled : = False;
Como hemos dicho anteriormente, el valor predefinido especificado en la de-
claracion de propiedad se usa solo para decidir si se guarda el valor de la propie-
dad en el disco.
El constructor create se define en la parte publica de la definicion de tip0
del nuevo componente y se indica mediante la palabra clave override,ya que
sustituye el constructor virtual Create de TComponent.Es fundamental re-
cordar esta palabra clave, puesto que de no ser asi, cuando Delphi crea un compo-
nente nuevo de esta clase, llamara a1 constructor de la clase basica; en lugar de a1
que hemos escrito para la clase derivada.

Convenciones de denominacl6n de propiedades


En la d&ici&n del componente Arrow, fijese en el uso de diversas con-
venciones de denomination de propiedades, m6todos de acceso y campos.
Veamos un resumen:
Los nombres de propiedades s e r h legibles y tendrh significado.
Cuando se use un campo de datos privado para contener un valor, el
campo deberia denominarse con una F (defield) maytiscula a1 princi-
pio, seguida del nombre de la propiedad correspondiente.
Cuando se use una hncion para cambiar el valor de la propiedad, dicha
funci6n deberia llevar a1 principio la paIabra Set, seguida del nombre
de la propiedad correspondiente.
Una funcidn correspondente utillzada para leer la propledad deberia
t principio, de nuevo seguida por el nombre de la
llevar la palabra ~ e a1
propiedad.
Estas directrices bque 10s programas resulten m h fhiles de leer. El
cornpilador no obliga a ello. Estas reglas son las seguidas por el mecanismo
que utiliza Delphi para completar 10s nombres de las clases.

Escritura del metodo Paint


Para dibujar la flecha en distintas direcciones y con diferentes formatos, sera
necesario usar algo mas de codigo. Para realizar el pintado personalizado,
sobrescribimos el metodo Paint y utilizamos la propiedad protegida canvas.
En lugar de calcular la posicion de 10s puntos que delimitan la punta de la
flecha en el codigo de dibujo que 10s ejecutara, hemos escrito una funcion a parte
para calcular la zona de la punta de flecha y guardarla en una matriz de puntos
definidos entre 10s campos privados del componente como:
FArrowPoints: array [ 0 . . 3 ] o f TPoint;
Estos puntos 10s establece el metodo privado Cornput e p o i n t s, llamado
cada vez que cambia alguna de las propiedades del componente. Veamos un ex-
tracto de su codigo:
procedure TMdArrow.ComputePoints;
var
XCenter, YCenter: Integer;
begin
// c a l c u l a 10s p u n t o s d e l a p u n t a d e f l e c h a
YCenter : = (Height - 1) div 2;
XCenter : = (Width - 1) div 2;
case FDirection of
adup: begin
FArrowPoints [0] := Point (0, FArrowHeight) ;
FArrowPoints [I] : = Point (XCenter, 0) ;
FArrowPoints [2] := Point (Width-1, FArrowHeight) ;
end;
// y a s i sucesivamente para o t r a s d i r e c c i o n e s

El codigo calcula el centro de la zona del componente (dividiendo sencillamen-


te las propiedades H e i g h t y W i d t h entre dos) y, a continuation, lo usa para
establecer la posicion de la punta de flecha. Ademas de cambiar la direccion u
otras propiedades, es necesario refrescar la posicion de la punta de flecha cuando
cambia el tamaiio del componente. Lo que podemos hacer es sobrescribir el meto-
do S e t B o u n d s del componente, a1 que llama la VCL cada vez que cambian las
propiedades L e f t , Top, W i d t h y H e i g h t del componente:
procedure TMdArrow.SetBounds(ALeft, ATop, AWidth, AHeight:
Integer) ;
begin
inherited SetBounds (ALeft, ATop, AWidth, AHeight);
ComputePoints;
end;

Cuando el componente sabe la posicion de la punta de flecha, su codigo de


pintado resultara mas sencillo. Veamos un extract0 del codigo del metodo P a i n t :
procedure TMdArrow-Paint;
var
XCenter, YCenter: Integer;
begin
// c a l c u l a e l c e n t r o
YCenter : = (Height - 1) div 2;
XCenter : = (Width - 1) div 2;
/ / d i b u j a l a lined d e l a f l e c h a
case FDirection of
adup: begin
Canvas .MoveTo (XCenter, Height-1) ;
Canvas-LineTo (XCenter, FArrowHeight);
end;
/ / y a s i s u c e s i v a m e n t e para l a s demds d i r e c c i o n e s
end ;

// d i b u j a l a punta d e flecha y, e n u l t i m o caso, l a p i n t a


if FFilled then
Canvas.Polygon (FArrowPoints)
else
Canvas.PolyLine (FArrowPoints);
end;

Podemos ver el resultado de este componente en la figura 9.6.

Figura 9.6. El aspect0 del componente Arrow.

Adicion de las propiedades TPersistent


Para que el resultado del componente sea mas flexible, le hemos aiiadido dos
propiedades nuevas, definidas con un tipo de clase (sobre todo, un tip0 de datos
T P e r s i s t e n t , que define objetos con 10s que Delphi pueda realizar streamrng
facilmente). Dichas propiedades son un poco mas complejas, porque el compo-
nente tiene que crear y destruir estos objetos internos. En esta ocasion, exportare-
mos tambien 10s objetos internos utilizando algunas propiedades, para que 10s
usuarios puedan cambiarlas directamente desde el Object Inspector. Para ac-
tualizar el componente cuando cambien estos subobjetos, tambien necesitaremos
su propiedad interna O n c h a n g e . Veamos la definicion de las dos propiedades
nuevas del tipo T P e r s i s t e n t y 10s otros cambios de la definicion del compo-
nente de clase:
type
TMdArrow = c l a s s (TGraphicControl)
private
FPen: TPen;
...
procedure SetPen (Value: TPen) ;
procedure RepaintRequest (Sender: TObject);
published
property Pen: TPen read FPen write SetPen;
end;
Lo primer0 es crear 10s objetos en el constructor y definir su controlador de
eventos OnChange:
constructor TMdArrow.Create (AOwner: TCornponent);
begin
...
/ / crea e l l d p i z y e l pincel
FPen : = TPen.Create;
/ / d e f i n e u n c o n t r o l a d o r p a r a e l e v e n t o OnChange
FPen.OnChange : = RepaintRequest;
end;

Estos eventos OnChange se producen cuando una de las propiedades del


lapiz cambia, todo lo que hay que hacer es pedir a1 sistema que pinte de nuevo
nuestro componente:
procedure TMdArrow.RepaintRequest (Sender: TObject);
begin
Invalidate;
end;

Tambien debemos aiiadir un destructor a1 componente, para eliminar el objeto


grafico de la memoria (y liberar sus recursos de sistema). Todo lo que tiene que
hacer el destructor es llamar a1 metodo F r e e del objeto P e n .
Las propiedades relacionadas con estos dos componentes requieren cierto con-
trol especial: en lugar de copiar el punter0 a 10s objetos, deberiamos copiar 10s
datos internos del objeto pasado como parametro. La operacion estandar := co-
pia 10s punteros, por lo que en este caso tenemos que usar en cambio el metodo
Assign.

procedure TMdArrow.SetPen (Value: TPen) ;


begin
FPen.Assign (Value);
Invalidate;
end:

Muchas clases TPe r s i s t e n t tienen un metodo A s s i g n que deberia utili-


zarse cuando hay que actualizar 10s datos de dichos objetos. Ahora, para utilizar
realmente el lapiz para dibujar, tenemos que modificar el metodo P a i n t , confi-
gurando las propiedades del componente c a n v a s como el valor de 10s objetos
internos antes de dibujar alguna linea (podemos ver un ejemplo del nuevo resulta-
do del componente en la figura 9.7):
procedure TMdArrow.Paint;
begin
// usa e l l d p i z a c t u a l
Canvas.Pen : = FPen;

Como C a n v a s usa una rutina de asignacion para el objeto lapiz, no solo


estamos almacenando una referencia a1 lapiz en un campo del C a n v a s , sin0 que
estamos copiando toda su informacion. Esto significa que podemos destruir el
objeto lapiz local (FPen) sin problemas y que modificar F P e n no afectara al
lienzo hasta que P a i n t sea llamado y el codigo anterior se ejecute otra vez.
.
I

-
Figura 9.7. El resultado del cornponente Arrow con un lapiz grueso y una trarna
especial.

Definicion de un nuevo evento personalizado


Para completar el desarrollo del componente Arrow, podemos aiiadir un even-
to personalizado. Generalmente, 10s componentes nuevos utilizan 10s eventos de
sus clases padre. Por ejemplo, en este componente, hemos creado algunos eventos
estandar, declarandolos de nuevo en la parte publicada de la clase:
type
TMdArrow = class (TGraphicControl)
pub1 i shed
property OnClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;

Gracias a esta declaration, 10s eventos anteriores (declarados originalmente en


una clase padre) estaran ahora en el Object Inspector cuando se instale el
componente. Sin embargo, a veces es necesario un evento personalizado. Para
definir un evento nuevo, primer0 hay que asegurarse de que existe un tip0 de
puntero de metodo valido para el evento; si no, debemos definir un tipo de evento
nuevo. Este tip0 es un tipo de puntero de metodo. En ambos casos, tenemos que
aiiadir a la clase un campo del tipo del evento. Veamos la definicion aiiadida en la
parte privada de la clase TMdArrow:

En este caso, hemos usado el tipo TNo t i fy E v e n t , que solo tiene un parametro
S e n d e r y Delphi lo utiliza con muchos eventos, comoOnClick y O n D b l C l i c k .
Usando este campo hemos definido una propiedad publicada muy simple, con
acceso direct0 al campo:
property OnArrowDblClick: TNotifyEvent
read FArrowDblClick write FArrowDblClick;

(Una vez mas utilizamos la convencion estandar al dar a1 evento un nombre


que empieza con On). El puntcro de metodo FArrowDblCl i c k se activa (eje-
cutando la funcion correspondiente) dentro del metodo dinamico especifico
ArrowDblClick. Esto ocurre solo si se ha especificado un controlador de
eventos en el programa que usa el componente:
procedure TMdArrow.ArrowDb1Click;
begin
if Assigned (FArrowDblClick) then
FArrowDblClick (Self);
end;

TRUCO: El uso de Self como ~a&nit& dkla inmcacibn dcl mttodo del :

controhdor de eventos gmm&a$uq;4& b a d 0 cl rnttodo, su parihctro


S e n d e r ae referira a1 objeto que 8cti~lSfdevento, @neralmcntc, un usua- ,.
rio d d agqmnentes.

Uso de llamadas de bajo nivel a la API de Windows


El metodo fArrowDblClic k se define en la parte protegida de la definition
de tipo para permitir que las subclases futuras lo llamen y lo modifiquen. Basica-
mente, el controlador del mensaje de Windows wm LButtonDblCl k llama a1
metodo ArrowDblClick, pero s61o si se hizo doble clic dentro de la punta de
flecha. Para probar csta caracteristica, podemos usar alguna de las funciones de
la zona Windows API.
. - . -
NOTA: Ona z- en eoestc contextoes una paflc &la pantalla mdeada por '
algGn tip0 de forma. Por ejdplo. pademos clear m a zona poligonal que
utilice 10s tres vtrtices del trislt$lo & la&qMil i(c flgcha. El rinico probb-
ma es que para rellenar la superficie correctamegte, ,debemos definir una
'

matriz de T P o i n t s en Ia dirtccih deli. w,asde ~ l q[ iv k e la descijp ;


cion d e C r e a t e P o l y g o n a l R g n enli+ayud.azJcla q ~ ~ & x ~ i n & w s ~ a r 3
conocer 10s detalles de esta tknica). E h o es b gne hiein-bs en el m d t d o
ComputePoints.

Una vez definida la zona, podemos probar si el punto en cl que se hizo doble
clic csta dentro de la zona, utilizando la llamada Pt InRegion de la API. Pode-
mos utilizar el codigo fuente completo de este procedimiento del siguiente listado:
procedure TMdArrow.WMLButtonDb1Clk (
var M s g : TWMLButtonDblClk) ; // mensa je wm-LBut tonDblClk;
var
HRegion: HRgn;
begin
// r e a l i z a e l c o n t r o l p r e d e f i n i d o
inherited;

// c a l c u l a l a zona d e l a p u n t a d e f l e c h a
HRegion : = CreatePolygonRgn (fArrowPoints, 3, WINDING);
try / / v e r i f i c a s i e l c l i c s e r e a l i z o e n l a z o n a
i f PtInRegion (HRegion, Msg.XPos, Msg.YPos) then
ArrowDblClick;
finally
DeleteOb ject (HRegion);
end ;
end :

La version CLX: Llamadas a funciones Qt nativas


El codigo previo no se podra portar a Linux y no tendria sentido en la version
CLXIQt del componente. Si queremos crear un componente similar para la biblio-
teca de clases CLX, podemos sustituir las llamadas a la API de Win32 con llama-
das directas (de bajo nivel) a la capa Qt, creando un objeto de la clase Q R e g i o n ,
como en el siguiente listado:
procedure TMdArrow.DblClick;
var
HRegion: QRegionH;
MousePoint: TPoint;
begin
// r e a l i z a e l c o n t r o l p r e d e f i n i d o
inherited;
// c a l c u l a l a z o n a d e l a p u n t a d e f l e c h a
HRegion := QRegion-create (PPointArray(FArrowPoints), True) ;
try
// o b t i e n e l a p o s i c i o n a c t u a l d e l r a t o n
GetCursorPos (MousePoint);
MousePoint : = ScreenToClient (MousePoint);
// v e r i f i c a s i e l c l i c se r e a l i z o e n l a zona
if QRegion-contains(HRegion, PPoint(@MousePoint)) then
ArrowDblClick;
finally
QRegion-destroy(HRegi0n);
end ;
end;

Registro de las categorias de propiedades


Hemos aiiadido a este componente algunas propiedades personalizadas y un
nuevo evento. Si organizamos las propiedades en el Object Inspector por cate-
gorias, todos 10s elementos apareceran en la categoria generica Miscellaneous.
S c s g o de S r tantas categorias como propie-
dades.

Nuestro codigo registra la propiedad Filled en dos categorias diferentes.


Esto no es un problema, porque la misma propiedad se puede mostrar varias
veces en el Object Inspector en diferentes grupos, como puede verse en la
figura 9.8. Para probar el componente flecha, hemos creado un programa ejemplo
muy sencillo, ArrowDemo, que permite modificar la mayoria de sus propiedades
en tiempo de ejecucion. Este tip0 de prueba, despues de haber escrito un compo-
nente o mientras lo creamos resulta muy importante.

Figura 9.8. El componente Arrow define una categoria de propiedades personalizada,


Arrow, como puede verse en el Object l n s p e c t o r . ~ a spropiedades pueden verse en
multiples secciones, como la propiedad Filled en este caso.

m - X " ~ acategoria dc propieda&s Loca 1izable posee una funcion


especial, relacionada con el uso del ITE (Entorno de Traduccion I n t e
do). Cuando una propiedad forma park de dicha categoria. su valor apz&
cera fstado en el ITE como una propiedad que se puede traducir a o k o
lenguaje.

Personalizacion de 10s controles de Windows


Una de las formas mas comunes de personalizar 10s componentes existentes
consiste en afiadir un comportamiento predefinido a sus controladores de eventos.
Cada vez que es necesario unir el mismo controlador de eventos a componentes de
formas distintas, deberiamos considerar aiiadir el codigo del evento directamente
a una subclase del componente. Un ejemplo claro es el de 10s cuadros de edicion
que aceptan solo una entrada numerica. En lugar de unir a cada uno de ellos un
controlador de eventos comun O n C h a r , podemos definir un nuevo componente
simple.
Sin embargo, dicho componente no controlara el evento, 10s eventos son solo
para 10s usuarios de 10s componentes. En cambio, el componente puede o bien
controlar el mensaje Windows directamente o sobrescribir un metodo, llamado
con frecuencia manejador de mensajes de segundo nivel. La tecnica anterior se
utilizaba habitualmente en el pasado pero hacia a 10s componentes ser especificos
para la plataforma Windows. Para crear un componente portable a CLX y Linux
(y, en el futuro, a la arquitectura .NET) deberiamos evitar 10s mensajes de segun-
do nivel Windows y, en su lugar, sobrescribir 10s metodos virtuales del compo-
nente basico y las clases de control.

NOTA: Cuando la mayoria de componentes VCL manejs. u.mensaje


Windows, llaman a un rnanejador de mensajes de s e g h nivel (normal-
mente, un mitodo dhhmico), en lugar de ejecutar c6digo directammte con
el metodo de respuesta a mensajes. Esta tecnica facilita personalizar el
componente en una clase derivada. Habitualmente, un rnanejador de segun-
do nivel hara su propio trabajo y llamara despds a cudqukr controlador
de eventos que el usuario del componente haya adtipado. Por tauto, siempre
deberiamos llamar a inherited para dejar a1compoaente actiw d wento
como se espera.

Ademas de la portabilidad, no hay razon por la que sobrescribir 10s manejadores


de segundo nivel esistentes, sea una tecnica mejor que manejar directamente 10s
mensajes Windows. Primero, esta tecnica es mas cercana a la perspectiva orienta-
da a objetos.
En lugar de duplicar el codigo de respuesta a mensajes de la clase b k i c a para
despues personalizarlo, estamos sobrescribiendo una llamada a un metodo vir-
tual, que 10s diseiiadores de VCL planearon para ser sobrescrita. Segundo, si
alguien necesita derivar otra clase de una de nuestras clases de componentes,
deberiamos facilitarle la posibilidad de personalizarla, y es muy facil que
sobrescribir 10s manejadores de segundo nivel induzca a errores. Por ejemplo,
podiamos haber creado este control de cuadro de edicion numeric0 manejando el
mensaje de sistema wm-C h a r :
type
TMdNumEdit = class (TCustomEdit)
public
procedure WmChar (var Msg: TWmChar); message wm-Char;

De todas maneras, el codigo es mas portable si sobrescribimos el metodo


Keypress, como hemos hecho en el codigo del siguiente componente. En un
ejemplo posterior tendremos que manejar mensajes Windows personalizados por-
que no hay un metodo correspondiente que sobrescribir.

El cuadro de edicion numeric0


Para personalizar un componente cuadro de edicion que restrinja la entrada
que se va a aceptar, todo lo que hay que hacer es sobrescribir su metodo
Ke yPre ss,que es llamado cuando el componente recibe el mensaje Windows
wm-Char.A continuation, aparece el codigo de la clase TMdNumEdit:
type
TMdNumEdit = class (TCustomEdit)
private
FInputError: TNotifyEvent;
protected
function GetValue: Integer;
procedure SetValue (Value: Integer) ;
procedure Keypress (var Key: Char) ; override;
public
constructor Create (Owner: TComponent) ; override;
published
property OnInputError: TNotifyEvent read FInputError
write FInputError;
property Value: Integer read GetValue write SetValue
default 0;
property Autoselect;
property Autosize;
.
// y a s i s u c e s i v a m e n t e . .

Este componente hereda de TCustomEdit en lugar de hacerlo de Tedit,


por lo que puede ocultar la propiedad Text y exteriorizar en cambio la propiedad
entera Value. Fijese en que no hemos creado un nuevo campo para alojar este
valor, porque podemos utilizar la propiedad Text existente (pero ahora no publi-
cada) .
Para ello, sencillamente convertiremos el valor numeric0 en una cadena de
texto y viceversa. La clase TCustomEdit (0, en realidad, el control de Windows
que envuelve) dibuja automaticamente la informacion de la propiedad Text en la
superficie del componente:
function TMdNumEdit-Getvalue: Integer;
begin
// d e f i n i d a como 0 en caso de e r r o r
Result : = StrToIntDef (Text, 0) ;
end ;

.
procedure TMdNumEdit SetValue (Value: Integer) ;
begin
Text : = IntToStr (Value);
end;
El metodo mas importante es el metodo redefinido Keypress, que filtra to-
dos 10s caracteres no numericos y crea un evento especifico en caso de error:
p r o c e d u r e TMdNumEdit .WmChar ( v a r Msg : TWmChar) ;
begin
i f n o t (Key i n [ ' O r . . ' 9 ' ] ) a n d n o t (Key = # 6 ) t h e n
begin
K e y : = #O; / / s i m u l a r q u e n o s e h a p u l s a d o n a d a
begin
i f Assigned ( F I n p u t E r r o r ) t h e n
FInputError ( S e l f );
end
else
inherited;
end;

Este metodo verifica cada caracter cuando el usuario lo introduce, compro-


bando 10s numeros y la tecla Retroceso (que tiene un valor ASCII 8). El usuario
dcberia poder utilizar la tecla Retroceso ademas de las teclas de sistema (las
tcclas del cursor y Supr), por lo que es necesario verificar dicho valor.
Ahora, si colocamos este componente en un formulario, podemos escribir algo
en el cuadro de edicion y observar su comportamiento. Tambien podemos asociar
un metodo a1 evento OnInputError para ofrecer respuesta a1 usuario cuando
se pulsa una tecla incorrecta.
Un editor numbrico con separador de millares
Como ampliacion del ejemplo, cuando el usuario escribe numeros largos (guar-
dados internamente como numeros de coma flotante que, comparados con 10s
enteros pueden ser mas grandes y tener digitos decimales) seria interesante que
aparecieran automaticamente separadores de millares y se actualizaran en fun-
cion de la entrada:

Podemos hacer esto sobrescribiendo el metodo interno Change y formateando


el numero correctamente. Solo hay un par de pequeiios problemas a tener en
cuenta. El primer0 es que para formatear el numero debemos tener una cadena
que contenga un numero, per0 el texto del cuadro de edicion no es una cadena
numerica que Delphi reconozca, ya que tiene separadores de millares, y no puede
ser convertido directamente a un numero. Hemos creado una version modificada
de la funcion StringToFloat llamada StringToFloatSkipping, para
realizar esta conversion.
El segundo pequeiio problema, es que si modificamos el texto del cuadro de
edicion, la posicion actual del cursor se perdera. Por eso, necesitamos salvar la
posicion original del cursor, reformatear el numero, y restaurar la posicion del
cursor (considerando que la posicion del cursor deberia cambiar en funcion de si
se ha aiiadido o quitado un separador).
Todas estas soluciones se contemplan en el siguiente codigo completo de la
clase TMdThousandEdit:
type
TMdThousandEdit = class (T~dNumEdit)
public
procedure Change; override;
end;

function StringToFloatSkipping (s: string) : Extended;


var
sl: string;
I: Integer;
begin
// q u i t a r c a r a c t e r e s no numericos
sl : = ' 1 ;
for i : = 1 to length (s) do
if s[i] i n [ ' 0 1 . . ' 9 ' ] then
sl : = sl + s [i];
Result : = StrToFloat (sl);
end;

procedure TMdThousandEdit.Change;
var
CursorPos, // p o s i c i o n o r i g i n a l d e l c u r s o r
LengthDiff: Integer; // n u m e r o d e n u e v o s s e p a r a d o r e s (+ o -)
begin
if Assigned (Parent) then
begin
CursorPos : = SelStart;
LengthDif f : = Length (Text);
Text : = FormatFloat ( I # , # # # ' ,
StringToFloatSkipping (Text)) ;
LengthDif f : = Length (Text) - LengthDif f ;
// mover e l c u r s o r a l a p o s i c i o n apropiada
SelStart : = CursorPos + LengthDiff;
end;
inherited;
end;

El boton Sound
Nuestro proximo componente, TMdSoundBut ton,emite un sonido cuando
pulsamos el boton y otro cuando lo soltamos. El usuario especifica cada sonido
modificando dos propiedades String que denominen 10s archivos WAV corres-
pondientes a 10s respectivos sonidos. Una vez mas, es necesario interceptar algu-
nos de 10s mensajes del sistema (wm LButtonDown y wm -LButtonUp), o
sobrescribir el controlador de segundonivel apropiado.
Veamos el codigo de la clase TMdSoundBut t o n , con 10s dos metodos prote-
gidos y las dos propiedades de cadena que identifican 10s archivos de sonido,
proyectados sobre campos privados porque no necesitamos hacer nada especial
cuando el usuario cambia esas propiedades:
type
TMdSoundButton = class (TButton)
private
FSoundUp, FSoundDown: string;
protected
procedure MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) ; override;
procedure MouseUp (Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) ; override;
published
property SoundUp: string read FSoundUp write FSoundUp;
property SoundDown : string read FSoundDown write FSoundDown;
end:

Veamos el codigo de dos controladores de segundo nivel:


uses
MMSystem;

procedure TMdSoundButton.MouseDown(Button: TMouseButton; Shift:


TShiftState;
X, Y: Integer) ;
begin
inherited MouseDown (Button, Shift, X, Y) ;
PlaySound (PChar (FSoundDown), 0, sndAsync) ;
end:

Hemos llamado a la version heredada de 10s metodos antes de hacer ninguna


otra cosa. En el caso de la mayor parte de 10s controladores de scgundo nivel, esta
es una buena costumbre, porque garantiza que ejecutamos el comportamiento
estandar antes que cualquier comportamiento personalizado. A continuacion, se
llama a la funcion de la API de Win32 P l a y S o u n d para que reproduzca el
sonido. Podemos usar dicha funcion, definida en la unidad MmS y s t e m , para
reproducir archivos WAV o sonidos del sistema, como demuestra el ejemplo
SoundB. Esta es la descripcion textual del formulario del programa de ejemplo
(del archivo DFM):
object MdSoundButtonl: TMdSoundButton
Caption = 'Press '
SoundUp = 'RestoreUp '
SoundDown = 'RestoreDown '
end
NOTA: Elegir un valor apropiado para estos sonidos no es nada simple.
Mas adelante en este capitulo, mostraremos cbmo aiIadir un editor de pro-
piedades a1 componente para simplificar la operacion.

Control de mensaje internos: El boton Active


La interfaz de Windows esta evolucionando hacia un nuevo estandar, que in-
cluye componentes que aparecen resaltados cuando el cursor se mueve sobre ellos.
Delphi ofrece un soporte similar en muchos de 10s componentes incorporados.
Imitar este comportarniento con un boton puede parecer una tarea compleja de
acometer, per0 no lo es. El desarrollo de un componente puede convertirse en algo
mucho mas sencillo una vez que sabemos que funcion virtual sobrescribir o a que
mensaje hay que cngancharlo.
El siguiente componentc, la clase TMdAct iveButton,demuestra esta tec-
nica, controlando algunos mensajes internos de Delphi para realizar esta tarea de
un mod0 muy sencillo. Para obtener mas informacion acerca de estos mensajcs
internos de Delphi podemos acudir a la seccion siguiente, "Mensajes de Compo-
nente y Notificaciones".
El componente ActiveButton controla 10s mcnsajes internos de Delphi
c m MouseEnter y cm MouseExi t,que se reciben cuando el cursor entra o
saledc la zona que corresionde al componente:

type
TMdActiveButton = class (TButton)
protected
procedure MouseEnter (var Msg: TMessage); message
cm-mouseEnter;
procedure MouseLeave (var Msg: TMessage); message

end;

El codigo que escribimos para estos dos metodos puede hacer lo que nosotros
queramos. Por ejemplo, hemos decidido que alternara el estilo negrita de la fuente
del propio boton. Podemos ver el efecto que se obtiene al mover el raton sobre uno
de esos componentes en la figura 9.9.
procedure TMdActiveButton.MouseEnter (var Msg: TMessage);
begin
Font.Style : = Font.Style + [fsBold];
end;

procedure TMdActiveButton.MouseLeave (var Msg: TMessage);


begin
Font .Style : = Font .Style - [fsBold];
end ;
Figura 9.9. Un ejemplo del uso de un componente ActiveButton.

Podemos aiiadir otros efectos, como agrandar el tip0 de letra, hacer el boton el
seleccionado por defect0 o cambiar el tamaiio del boton. Los meJores efectos
normalmente implican colores, pero debemos heredar de la clase T B i t B t n para
poder manipularlos (10s controles T B u t t on tienen un color predefinido).

Mensajes de componente y notificaciones


Para crear un componente A c t i v e B u t t on,hemos usado dos mensajes de
componentes Delphi, como lo indica su prefijo c m . Estos mensajes pueden ser
bastante interesantes, como subraya el ejemplo, pero no estan practicamente
documentados por Borland. Hay t a m b i h un segundo grupo de mensajes inter-
nos de Delphi, indicados como notificaciones de componentes y que se distin-
guen por su prefijo en. No tenemos espacio suficiente aqui para explicar cada
uno de ellos u ofrecer un analisis detallado; podemos estudiar el codigo fuente
VCL para saber mas.

ADVERTENCIA: Este es un tema bastante avanzado, por lo que aquellos


lectores que sean nuevos en la creaci6n de componentes Delphi pueden
saltarse esta seccion. Los mensajes de componente no estan documentados
en el archivo de ayuda de Delphi, por lo que se ha considerado importante
citarlos aqui.

Mensajes de componentes
Un componente de Delphi pasa mensajes de un componente a otros componen-
tes, para indicar cualquier cambio en su estado que podria afectar a dichos com-
ponentes. La mayoria de estos mensajes comienzan como mensajes Windows,
per0 algunos son mas complejos, traducciones de alto nivel y no simples
reproyecciones. Ademas, 10s componentes envian sus propios mensajes y reen-
vian aquellos recibidos de Windows. Por ejemplo, cambiar un valor de propiedad
o alguna otra caracteristica del componente puede requerir el informar a uno o
mas componentes sobre dicho cambio.
Podemos agrupar 10s mensajes en categorias:
Los mensajes de activacion y foco de entrada se envian a1 componente que
se activa o desactiva y que recibe o pierde el foco de entrada:
c m A c t i v a t e : Corresponde a1 evento OnActivate de formularios y
de la aplicaci6n.
c m-D e a c t i v a t e : Corresponde a OnDeactivate.
c m-E n t e r : Corresponde a OnEnter.
c m-E x i t : Corresponde a OnExit.
c m F o c u s C h a n g e d : Se envia siempre que cambia el foco entre 10s
componentes del mismo formulario (mas adelante, veremos un ejemplo
con este mensaje).
c m-~ oFO tc u s : Declarado per0 no se usa.
c m-L o s t F O C U S : Declarado per0 no se usa.

Los mensajes enviados a 10s componentes hijo cuando cambia una propie-
dad:
c m-BiDiModeChanged: c m-I c o n c h a n g e d
c m-B o r d e r C h a n g e d : c m-S h o w H i n t C h a n g e d
c m-C o l o r C h a n g e d : c m-S h o w i n g c h a n g e d
c m-C t l 3 D C h a n g e d : c m-S y s F o n t C h a n g e d
c m-C u r s o r C h a n g e d : c m-T a b s t o p c h a n g e d
c m-E n a b l e d C h a n g e d : cm-T e x t c h a n g e d
c m-F o n t C h a n g e d : c m-V i s i b l e C h a n g e d
Si se siguen estos mensajes, eso puede ayudarnos a mantener la pista de 10s
cambios de una propiedad. Podemos necesitar responder a estos mensajes
en un nuevo componente, per0 no es probable.
Los mensajes relacionados con las propiedades ParentXxx: c m -
P a r e n t F o n t C h a n g e d , c m P a r e n t C o l o r C h a n g e d , cm -
P a r e n t C t l 3 D C h a n g e d , c m ~ a r e n t B i D i M o d e C h a n g e dy
c m-P a r e n t ~ h o w ~ i n t ~ h a n g e d similares
Son a 10s mensajes del grupo
anterior.
Las notificaciones sobre 10s cambios en el sistema Windows: cm-
SysColorChange, cm-WinIniChange, cm-Timechange y
cm-Fontchange.Controlar estos mensajes resulta util en componentes
especiales que necesitan mantener un seguimiento de 10s colores o fuentes
del sistema.
Los mensajes del raton: cm-Drag se envia varias veces durante las opera-
ciones de arrastre. cm MouseEnter y cm MouseLeave se envian a1
control cuando el cursor entra o sale de su<uperficie, per0 10s envia el
objeto Ap p 1 i c a t i o n como mensajes de poca prioridad. c m
Mouse Whee 1 corresponde a las operaciones basadas en la rueda del ra:
ton.
Mensajes de la aplicacion:
cm AppKeyDown: Se envia a1 objeto Application para dejarlo que
decida si una tecla corresponde a un menu de metodo abreviado.
cm-AppSysComrnand:Corresponde a1 mensaje wm-SysCommand.
cm DialogHandle:Se envia en una DLL para recuperar el valor
de la propiedad DialogHandle (utilizada por algunos cuadros de dido-
go no creados en Delphi).
cm Invo keHelp: Lo envia el codigo en una DLL para llamar a1
m&do InvokeHelp .
cm WindowHook: S e e n v i a e n u n a D L L p a r a l l a m a r a l o s m e t o d o s
~ o i k ~ a i n ~ i n dy oUnhookMainWindow
w .
Es poco probable que necesitemos usar estos mensajes. Existe tambien un
mensaje cm-Hintshowpause, que nunca se maneja en VCL.
Mensajes internos de Delphi:
cm CancelMode:Termina operaciones especiales, como mostrar la
list; desplegable de un cuadro combinado.
cm Controlchange: Se envia a cada control antes de aiiadir o
elihinar un control hijo (controlado por controles comunes).
cm ControlLis tChange:Se envia a cada control antes de aiiadir
o eiminar un control hijo (controlado por el componente DBCtrlGrid).
cm DesignH itTest : Determina si una operacion de raton deberia
ir alcomponente o a1 diseiiador de formularios.
cm Hintshow:Se envia a un controljusto antes de mostrar su suge-
rencia (solo si la propiedad ShowHint est6 definida como rue).
cm Hit T est : Se envia a un control cuando un control padre intenta
loc&zar a un control hijo en una position de rat6n dada (si la hay).
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 tLines 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 t a -sin --- - - --
--- emharm- -- 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 M y N e w P r o p e r t y 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 R u w R e g i s t e r 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 .......
-1
el valor numerlco 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 ~ ~
la :L- --L--
rmrorrnauon -a - - - I - - - I -
swore GI ernpleauo en -- 1la- .raola
-LI- 1- -I:--&--
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 --- -- -"--
decde mercndns -- 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
tUnch€mgecb <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 informacion en forma de un archivo SCHEMA. IN1 situado
en el mismo directorio que 10s archivos de texto a 10s que se refiere. Este archivo
contiene informacion esquematica, tambien denominada metadatos, sobre cual-
quier archivo de texto en el mismo directorio (o todos ellos). Cada archivo de
texto recibe su propio apartado, identificado mediante el nombre del archivo de
texto, como [NightShift .T X T ] .
Por lo tanto, podemos especificar el formato del archivo: 10s nombres, tipos y
tamaiios de las columnas, cualquier conjunto de caracteres especiales que se va-
yan a usar y cualquier formato especial para las columnas (corno fecha y hora o
moneda). Supongamos que cambiamos el archivo NightShif t .TXT a1 siguiente
formato:
Ne o ICincinnati
Trinity l London
Morpheus l Milan

En este ejemplo, 10s nombres de columna no se incluyen en el archivo de texto


y el delimitador es una barra vertical. Un archivo SCHEMA. IN1 asociado podria
parecerse a1 siguiente:
[Nightshift-TXT]
Format=Delimited ( l )
ColNameHeader=False
Coll=CrewPerson Char Width 10
ColZ=HomeTown Char Width 30

Utilicemos o no un archivo SCHEMA. INI, encontraremos dos limitaciones


con el Text IISAM: no se pueden borrar ni editar las filas.
Importation y exportacion
El motor Jet es especialmente aficionado a la importacion y exportacion de
datos. El proceso de exportacion de datos es el mismo para cada formato de
exportacion y consisten en la ejecucion de una sentencia SELECT con una sin-
taxis especial. Comencemos con un ejemplo de exportacion de datos desde la
version de Access de la base de datos DBDemos de vuelta a una tabla Paradox.
Necesitaremos una ADOConnection activa, denominada ADOConnect ion1 en
el ejemplo JetlmportExport, que usa el motor Jet para abrir la base de datos. El
siguiente codigo exporta la tabla Customer a un archivo customers. db de
Paradox:
SELECT * INTO Customer IN "C: \tmp" "Paradox 7 .x;" FROM CUSTOMER

Fijemonos en las partes de esta sentencia SELECT.La clausula INTO especi-


fica la nueva tabla que creara la sentencia SELECT.Dicha tabla no puede existir
previamente. La clausula IN especifica la base de datos a la que se aiiadira la
nueva tabla; en Paradox, se trata de un directorio que ya existe. La clausula
inmediatamente siguiente a la base de datos es el nombre del controlador IISAM
que se utilizara para la exportacion. Es necesario incluir el punto y la coma
posterior a1 final del nombre del controlador. La clausula FROM es una parte
habitual de cualquier sentencia SELECT.En el programa de muestra, la opera-
cion se ejecuta mediante el componente ADOConnection y utiliza la carpeta del
programa en lugar de una fija:
ADOConnectionl .Execute ( ' S E L E C T * I N T O C u s t o m e r I N " ' +
CurrentFolder + ' " " P a r a d o x 7 . x ; " PROM CUSTOMER' ) ;

Todas las sentencias de exportacion siguen estas mismas normas basicas, aun-
que algunos controladores IISAM tienen distintas interpretaciones de lo que es
una base de datos. En este caso, exportaremos 10s mismos datos a Excel:
ADOConnectionl .Execute ( S E L E C T * I N T O C u s t o m e r I N " ' +
CurrentFolder + ' d b d e m o s . x l s " " E x c e l 8 . 0;" PROM CUSTOMER' ) ;

Se crea un nuevo archivo Excel llamado dbdemos . xls en el directorio ac-


tual de la aplicacion. Se aiiade un libro llamado Customer, que contiene todos 10s
datos de la tabla Customer de dbdemos .mdb.
Este ultimo ejemplo exporta 10s mismos datos a un archivo HTML:
ADOConnectionl .Execute ( ' S E L E C T * I N T O [ C u s t o m e r . h t m ] I N " ' +
CurrentFolder + ' " "HTML E x p o r t ; " PROM C U S T O M E R ' ) ;

En este caso, la base de datos es el directorio, como para Paradox aunque no


para Excel. El nombre de la tabla debe incluir la extension . htm y, por lo tanto,
debera ir entre corchetes. Observe que el nombre del controlador IISAM es HTML
Export, no solo HTML,ya que este controlador solo puede utilizarse para ex-
portar a HTML. El ultimo controlador IISAM que veremos en este analisis del
motor Jet es el hermano de HTML Export: HTML Import. Aiiadimos una
ADOTable a un formulario, definimos su c o n n e c t i o n s t r i n g para utilizar el
proveedor Jet 4.0 OLE DB y tambien definimos Extended Properties como HTML
Import. Establecemos el nombre de la base de datos del archivo HTML creado
mediante exportacion hace un momento, a saber, c u s t o m e r .h t m . Ahora defi-
nimos la propiedad T a b l e N a m e como C u s t o m e r . Abrimos la tabla e inmedia-
tamente habremos importado el archivo HTML. Conviene recordar, sin embargo,
que si intentamos actualizar 10s datos, recibiremos un error, puesto que este con-
trolador solo esta pensado para la importacion. Por ultimo, si creamos nuestros
archivos HTML que contengan tablas y queremos abrir dichas tablas utilizando
este controlador, hay que recordar que el nombre de la tabla es el valor de la
etiqueta c a p t i o n de la tabla (etiqueta t a b l e ) HTML.

Trabajo con cursores


Existen dos propiedades de 10s conjuntos de datos ADO que poseen un impac-
to fundamental sobre nuestra aplicacion y que estan fuertemente relacionados
entre si: C u r s o r L o c a t i o n y C u r s o r T y p e . Si se quiere comprender el com-
portamiento del conjunto de datos ADO, hay que comprender previamente estas
dos propiedades.

Ubicacion de cu.rsor
La propiedad c u r s o r l o c a t i o n permite especificar quien controla la recu-
peracion y actualizacion de datos. Existen dos opciones: el cliente ( c l u s e c l i e n t )
o el servidor ( c l u s e se r v e r ) . La decision tomada afectara a la funcionalidad
del conjunto de datos, asi como a su rendimiento y escalabilidad.
Un cursor de cliente lo administra el motor ADO Cursor Engine. Este motor es
un excelente ejemplo de un proveedor de servicio OLE DB: Presta servicio a otros
proveedores OLE DB. El motor ADO Cursor Engine administra 10s datos del
cliente de la aplicacion. Todos 10s datos del conjunto de resultado se consiguen
del servidor cuando se abre el conjunto de datos. Por tanto, 10s datos se mantienen
en memoria y el motor ADO Cursor Engine se encarga de las actualizaciones y la
manipulacion. es algo parecido a usar el componente ClientDataSet en una apli-
cacion dbExpress. Una ventaja de esta manipulacion de 10s datos, despues de la
recuperacion inicial, consiste en su considerable velocidad incrementada. Aun
mas, dado que la manipulacion se realiza en memoria, el motor ADO Cursor
Engine resulta mas versatil que la mayoria de 10s cursores de servidor y ofrece
servicios adicionales. Mas adelante analizaremos estas ventajas, a1 igual que otras
tecnologias de cursores de cliente (corno 10s conjuntos de registros desconectados
y permanentes).
Un cursor de servidor lo administra el RDBMS. En una arquitectura clientel
senidor fundarnentada en una base de datos como SQL Server, Oracle o InterBase,
esto significa que el cursor se administra fisicamente en el servidor. En una base
de datos de escritorio como Access o Paradox, la ubicacion del "servidor" es solo
una ubicacion logica, puesto que la base de datos se ejecuta en el escritorio. Los
cursores de servidor normalmente son mas rapidos de cargar que 10s cursores de
cliente porque no se transfieren todos 10s datos al cliente cuando se abre el con-
junto de datos. Gracias a esto, son mas apropiados para conjuntos de resultados
muy grandes en 10s que el cliente no tenga memoria suficiente para mantener todo
el conjunto de resultados en memoria. Con frecuencia, podemos decidir que tipos
de caracteristicas estaran disponibles con cada ubicacion de cursor al pensar en el
mod0 de funcionamiento del cursor. Un buen ejemplo del mod0 en que sus carac-
teristicas nos ayudan a decidir el tip0 de cursos es el cierre o bloqueo. Para
colocar un cierre sobre un registro hace falta un cursor de servidor, porque debe
haber una comunicacion entre la aplicacion y el RDBMS.
Otro aspect0 que afectara a la eleccion de la ubicacion del cursor es la
escalabilidad. Los cursores de servidor son administrados por el RDBMS. En una
base de datos cliente/senidor, este estara ubicado en el servidor. A medida que
aumente el numero de usuarios de la aplicacion, la carga del senidor aumenta con
cada cursor de senidor. Una mayor carga en el senidor significa que el RDBMS
se convierte en un cuello de botella cada vez mas rapido, por lo que la aplicacion
resulta menos escalable. Podemos mejorar la escalabilidad utilizando cursores de
cliente. El impact0 inicial a1 abrir el cursor es normalmente mas fuerte, porque
todos 10s datos se transfieren a1 cliente, per0 el mantenimiento del cursor abierto
puede ser menor. Como puede comprobarse, hay muchas cuestiones conflictivas
relacionadas con la eleccion de la ubicacion del cursor apropiada para 10s conjun-
tos de datos.

Tipo de cursor
La eleccion de la ubicacion del cursor afecta directamente a la eleccion dcl tip0
de cursor. Existen cuatro tipos de cursores que podemos usar para cualquier fin o
proposito, per0 hay un valor que no se puede usar, porque es un valor "sin espe-
cificar". Muchos valores en ADO implican un valor sin especificar, y 10s analiza-
remos y explicaremos porque no hay que utilizarlos. Existen en Delphi solo porque
existen en ADO. ADO se diseiio basicamente para programadores en Visual Basic
y C. En estos lenguajes, se pueden usar directamente objetos sin la ayuda que
proporciona dbGo. De ese modo, se pueden crear y abrir conjuntos de registros,
tal y como se llaman en terminos de ADO, sin tener que especificar cada valor
para cada propiedad. Las propiedades para las que no se ha especificado un
valor, tienen un valor no especificado. Sin embargo, en dbGo se utilizan compo-
nentes. Estos componentes tienen constructores, y esos constructores dan un va-
lor inicial a cada propiedad de 10s componentes. De ese modo, desde el momento
en que se crea un componente dbGo, generalmente se tendra un valor para cada
propiedad. como consecuencia, no hay mucha necesidad de tener valores sin espe-
cificar en muchos tipos enumerados.
Los tipos de cursor afectan al mod0 en que se leen y actualizan 10s datos.
Existen cuatro opciones: solo de avance, estatico, conjunto de claves y dinamico.
Antes de profundizar mas en todas las combinaciones de ubicaciones de cursor y
tipos de cursor, deberia quedar claro que solo hay disponible un tip0 de cursor
para 10s cursores de cliente: el cursor estatico. Todos 10s demas tipos de cursor
son unicamente para cursores de servidor. Hablaremos mas adelante sobre la
disponibilidad de 10s tipos de cursor despues de comentar 10s distintos tipos de
cursor, en orden creciente de coste:
Cursor s61o de avance: Este tipo de cursor es el menos costoso y, por lo
tanto, el tipo con el mejor rendimiento posible. Como su nombre indica,
permite desplazarse hacia delante. El cursor lee el numero de registros
especificados por Cachesi ze (predefinido como 1) y cada vez que se
queda sin registros, lee otro conjunto de CacheSi ze registros. Cualquier
intento de desplazarnos hacia atras por el conjunto de resultados mas alla
del numero de registros que hay en cache creara un error. Este comporta-
miento es parecido a1 de un conjunto de datos dbExpress. Un cursor solo
de avance no resulta apropiado para se utilizado en la interfaz de usuario
en la que el usuario puede controlar la direccion a traves del conjunto de
resultados. Sin embargo, si resulta apropiado para las operaciones por
lotes, 10s informes y las aplicaciones Web sin estado, porque dichas situa-
ciones comienzan por la parte superior del conjunto de resultados y fimcio-
nan de forma progresiva hasta el final, cerrando a continuacion el conjunto
de resultados.
Cursor estatico: Un cursor estatico funciona leyendo el conjunto de resul-
tad0 completo y ofreciendo una ventana de registros Caches i ze en el
conjunto de resultado. Dado el servidor ha recuperado el conjunto de resul-
tad0 completo, podemos desplazarnos adelante y atras a traves del conjun-
to de resultados. Sin embargo, a cambio de esta facilidad, 10s datos son
estaticos, es decir, las actualizaciones, inserciones y eliminaciones realiza-
das por otros usuarios no se pueden ver porque ya se han leido 10s datos del
cursor.
Cursor de conjunto de claves: Un cursor conjunto de claves se compren-
de mejor si separamos el termino en dos palabras: conjunto y clave. Clave,
en este contexto, se refiere a un identificador para cada fila. Normalmente
se tratara de una clave primaria. Por ello, un cursor de conjunto de claves
es un conjunto de claves. Cuando se abre el conjunto de resultados, se lee
la lista completa de claves para el conjunto de resultados. Si, por ejemplo,
el conjunto de datos fuese una consulta como SELECT * FROM CUSTOMER,
la lista de claves se crearia a partir de SELECT CUSTID FROM CUSTOMER.
Este conjunto de claves se mantiene hasta que se cierra el cursor. Cuando
la aplicacion solicita datos, el proveedor OLE DB lee las filas que utilizan
las claves del conjunto de claves. Como consecuencia, 10s datos siempre
estan actualizados. Si otro usuario cambia una fila del conjunto de resulta-
dos, entonces 10s cambios se veran cuando se lean de nuevo 10s datos. Sin
embargo, el conjunto de claves, por si mismo, es estatico. se lee solo cuan-
do el conjunto de resultados se abre a1 principio. Por tanto, si otro usuario
aiiade registros nuevos, dichas adiciones no se veran. Los registros elimi-
nados resultan inaccesibles y 10s cambios en las claves primarias (algo que
no deberia permitirse a 10s usuarios) tambien lo son.
Cursor dinamico: El ultimo tip0 de cursor y el mas costoso es el dinami-
co. Un cursor dinamico es casi identico a un cursor de conjunto de claves.
La unica diferencia es que el conjunto de claves se vuelve a leer cuando la
aplicacion solicita datos que no estan en cache. Ya que, de manera prede-
terminada TADOData Set .Caches ize es 1,dicha solicitud resulta muy
frecuente. Puede imaginarse la carga adicional que esto supone para el
RDBMS y la red, y es el motivo de que este sea el cursor mas costoso. Sin
embargo, el conjunto de resultados puede ver y responder a las adiciones y
eliminaciones realizadas por otros usuarios.

Pedir y no recibir
Tras haberlo explicado todo sobre las ubicaciones y tipos del cursor, debemos
de hacer una advertencia: no todas las combinaciones de ubicacion y tipo de
cursor son posibles. Normalmente, esta es una limitacion impuesta por el RDBMS
o el proveedor OLE DB como resultado de la funcionalidad y arquitectura de la
base de datos. Por ejemplo, 10s cursores de cliente siempre condicionan que el
tip0 de cursor sea estatico. Podemos comprobarlo por nosotros mismos. Aiiadi-
mos un componente ADODataSet a un formulario, definimos su propiedad
Connectionstring como cualquier base de datos, definimos ClientLoca-
tion como clUseCursor y CursorType como ctDynamic.Ahora defi-
nimos Active como True y vigilamos el CursorType:cambia a ctstatic.
A partir de este ejemplo, sacamos la siguiente conclusion:
lo que pedimos no es necesariamente lo mismo que recibimos. Hay que com-
probar siempre las propiedades despues de abrir un conjunto de datos para ver el
efecto real de las solicitudes. Cada proveedor OLE DB realizara distintos cam-
bios de acuerdo con distintas solicitudes y distintas circunstancias, per0 para
tener una idea de lo que podemos esperar, pondremos algunos ejemplos:
El proveedor Jet 4.0 OLE DB cambia la mayoria de 10s tipos de cursor a
conjunto de claves.
El proveedor SQL Server OLE DB cambia normalmente el conjunto de
claves y estatico a dinamico.
El proveedor Oracle OLE DB cambia todos 10s tipos de cursor a solo de
avance.
El proveedor ODBC OLE DB realiza diversos cambios segun el controla-
dor ODBC en uso.

Sin recuento de registros


Los conjuntos de datos de ADO a veces -1 como su RecordCount. Un
cursor solo de avance no puede saber el numero de registros que hay en el conjun-
to de resultados hasta que llega a1 final, por lo que devuelve -1 para
RecordCount.Un cursor estatico siempre sabe el numero de registros que hay
en el conjunto de resultados, porque lee el conjunto completo cuando se abre, por
lo que devuelve el numero de registros que hay en el conjunto. Un cursor de
conjunto de claves tambien conoce el numero de registros que hay en el conjunto
de resultados, ya que tiene que recuperar un conjunto fijo de claves cuando se
abre el conjunto de resultados, por lo que tambien devuelve un valor util para
RecordCount.
Un cursor dinamico no sabe con seguridad el numero de registros que hay en el
conjunto de resultados, porque vuelve a leer de manera regular el conjunto de
claves, por lo que devuelve -1. Por supuesto, podriamos no utilizar Record-
Count y ejecutar SELECT COUNT ( * ) FROM nombre de tabla, per0 el
resultado sera un reflejo precis0 del nGmero de registros Gue hay en la base de
datos, que no necesariamente sera igual a1 numero de registros que hay en el
conjunto de datos.

Indices de cliente
Una de las muchas ventajas de 10s cursores de cliente es la capacidad de crear
indices locales o de cliente. Para verlo, podemos suponer que tenemos un conjun-
to de datos de cliente ADO para la tabla Customer de DBDemos, que tiene una
cuadricula conectada, y definir la propiedad Index Fie ldNames del conjunto
de datos como CompanyName.La cuadricula mostrara inmediatamente que 10s
datos estan ordenados por nombre de empresa (CompanyName). Debemos acla-
rar algo importante: para indexar 10s datos, ADO no vuelve a leer 10s datos desde
su fuente. El indice se creo a partir de 10s datos en memoria. Esto significa que no
solo la creacion del indice es tan rapida como seria posible, sin0 que la red y el
RDBMS no se sobrecargan debido a la transferencia de 10s mismos datos una y
otra vez con distintas ordenaciones.
La propiedad IndexFieldNames posee mas potencial. Si la definimos como
Country; CompanyName, veremos 10s datos se ordenan primero por pais y
despues, dentro de cada pais, por nombre de empresa. Ahora, definimos
IndexFieldNames como CompanyName DESC.Debemos asegurarnos que
escribimos DESC en mayusculas y no "desc" ni "Desc". Seguro que no resulta
sorprendente que 10s datos se ordenen de manera descendente.
Esta caracteristica tan simple per0 potente permite resolver algunos de 10s
grandes problemas de 10s desarrolladores de bases de datos. Los usuarios podrian
pedir algo muy razonable e inevitable, si se podria hacer clic sobre las columnas
de la cuadricula para ordenar 10s datos. Las respuestas como sustituir las
cuadriculas por controles que no Sean sensibles a 10s datos como ListView que
tengan incluida la capacidad de ordenacion, o como capturar el evento
O n T i t l e C l i c k del componente DBGrid y rehacer la sentencia SQL SELECT
tras incluir una clausula ORDER BY apropiada no son nada satisfactorias.
Si se tienen 10s datos en la cache del cliente (corno ya se ha visto durante el uso
del componente ClientDataSet), se puede usar un indice de cliente calculado en
memoria. Podemos ariadir el siguiente evento OnT it leC1i c k a la cuadricula:
procedure TForml.DBGridlTitleClick(Column: TColumn);
begin
if ADODataSetl.IndexFieldNames = Column.Field.FieldName then
ADODataSet1.IndexFieldNames : = Column.Field.FieldName + '
DESC'
else
ADODataSetl.IndexFie1dNames : = Column.Field.Fie1dName
end;

Este sencillo evento comprueba si el indice actual se ha creado en el mismo


campo que la colurnna. De ser asi, se crea un nuevo indice en la colurnna pero en
orden descendente. De no ser asi, se crea un indice nuevo en la colurnna. Cuando
el usuario hace clic sobre la colurnna por primera vez, se organiza en orden
ascendente y cuando hace clic por segunda vez, se organiza en orden descendien-
te. Podriamos ampliar este comportamiento para permitir que el usuario manten-
ga pulsada la tecla Control y haga clic sobre varios titulos de columnas para
crear indices mas complejos.

NOTA: Todo esto se puede conseguir utilizando Cl i e n t Da t a s e t , per0


esa solucion no es tan elegante por dos razones. C l i e n t D a t a s e t no
soporta la palabra clave DESC,por lo que habra que crear un elemento de
conjunto de indices para conseguir un indice descendente, algo que requiere
rnis c d i g o . Ademb, cuando se cambia un indice ascendente por una ver-
sion desckdente, el C l i e n t D a t a S e t re:construye el indice, una opera-
cion innecesaria y posiblemente lent..

Replicacion
ADO tiene una gran cantidad de potentes caracteristicas. Se puede pensar que
eso supone un gran consumo de recursos, per0 tambien se traduce en aplicaciones
mas potentes y fiables. Una de estas caracteristicas es la replicacion o clonacion.
Un conjunto de registros clonado es un nuevo conjunto de registros que posee las
mismas propiedades que el original a partir del que se ha clonado. Vamos a ver en
primer lugar como crear un clon y despuks veremos porqud son tan utiles.
- +

NOTA: El ClientDataSet tambiCn soporta la replicacicin, aunque no se


comento en el capitulo 13.

Se puede clonar un conjunto de registros (0; en terminologia dbGo, un conjun-


to de datos) usando el metodo Clone. Podemos clonar cualquier conjunto de
datos ADO. per0 en este ejemplo utilizaremos ADOTable. El ejernplo DataClone
(vease figura 15.6) utiliza dos componentes ADOTable, uno conectado con la
basc de datos y otro vacio. Ambos conjuntos de datos estan conectados a un
Datasource y a una cuadricula. Una sola linea de codigo, ejecutada cuando el
usuario haga clic sobre el boton, replicara el conjunto de datos:

1354 Cayman Divers Wolld U4rnited


1356 Torn S a r y n D~vingCentre
13XJ BheJadt Aqua Ceder 1380 BlueJack Aqua Cenlu
1384 VIP Dive~sClub 1384 VlPDEverrClub
1510 O n m Para&
1513 FmtastiqueAqu&ia 1513 Fanlasltql~eAqud~w
1551 Marmd D k s Club 1551 Marmot Dtvers Club
1560 TheDeplhCha~c 1560 The Deplh Cha~ge
1563 BlueSpuls
1624 Makai SCUBA Cbb 1624 M&a SCUBA Chb
1645 AclmChb 1645 Actm Club

Figura 15.6. El forrnulario del ejernplo DataClone, con dos copias de un conjunto de
datos (el original y el clon).

Esta linea dona ADOTable 1 y asigna el don a A D O T a b l e 2 . En el progra-


ma veremos una segunda vista de 10s datos. Los dos conjuntos de datos poseen
sus propios punteros de registro y otra information de estado, por lo que el clon
no interfiere con su copia original. Este comportamiento hace que 10s clones
resulten ideales para la trabajar con un conjunto de datos sin afectar a 10s datos
originales. Otra caracteristica interesante es que se pueden tener varios registros
activos distintos, uno para cada uno de 10s clones, una prestacion que no puede
conseguirse en Delphi con un unico conjunto de datos.
TRUCO: Un conjunto de registros debe soportar marcadores para que
podamos clonarlo, de mod0 que no se pueden clonar cursores de solo avan-
ce y dinhmicos. Se puede determinar si un conjunto de registro soporta
marcadores usando el metodo s u p p o r t s (par ejemplo, como en
.
ADOTablel Supports ( [coBookMark]1). Uno de 10s utiles efec-
tos secundarios de 10s clones es que 10s marcadores creados por un clon 10s
pueden usar todos 10s demas clones.

Procesamiento de transacciones
Como ya vimos en el capitulo 14, el procesamiento de transacciones permite a
10s desarrolladorcs agrupar actualizaciones individuales de una base de datos en
una unidad logica de trabajo unica.
El soporte de procesamiento de transacciones de ADO se controla con el com-
ponente ADOConnection, empleando 10s metodos B e g i n T r a n s , C o r n r n i t T r a n s
y R o l l b a c k T r a n s , que tienen efectos parecidos a 10s de 10s nietodos BDE y
dbExpress correspondientes. Para investigar el soporte del procesamiento de tran-
sacciones de ADO. crearemos un programa de prueba sencillo, llamado
TransProcessing. El programa ticne un componente ADOConnection con su pro-
piedad c o n n e c t i o n s t r i n g configurada para el proveedor Jet 4.0 OLE DB y
el archivo dbdemos.mdb. Tiene un componente A D O T a b l e conectado a la tabla
Customer y un Datasource y una DBGrid para mostrar 10s datos. Por ultimo,
tiene tres botones para ejecutar cada una de las siguientes ordenes:

Con este programa, se pueden realizar cambios sobre la tabla de la base de


datos y despues descartarlos, y se dara marcha atras como es de esperar. Resalta-
mos este punto porque el soporte de transacciones varia segun la base de datos y
el proveedor OLE DB que se utilice. Por ejemplo, si se realiza una conesion con
Paradox empleando el proveedor ODBC OLE DB Driver; sencillamente se recibi-
ra un error que indica que la base de datos o el proveedor OLE DB no es capaz de
iniciar una transaccion. Podemos averiguar el nivel de soporte de procesamiento
de transacciones usando la propiedad dinamica T r a n s a c t i o n D D L de la co-
nexion:
if ADOConnectionl .Properties [ ' Transaction DDL' ] .Value >
DBPROPVAL-TC-NONE then
ADOConnection1.BeginTrans;

Si tratamos de acceder a 10s mismos datos de Paradox empleando el proveedor


Jet 4.0 OLE DB, no se vera el error, per0 tampoco se podran deshacer 10s cam-
bios, debido a limitaciones del proveedor OLE DB. Otra diferencia algo extraiia
resulta evidente cuando se trabaja con Access: si se usa el proveedor ODBC OLE
DB, se podran usar transacciones, per0 no transacciones anidadas. Abrir una
transaccion cuando otra esta activa producira un error. Sin embargo, mediante el
motor Jet, Access si soporta transacciones anidadas.

Transacciones anidadas
Mediante el programa TransProcessing vamos a hacer esta prueba:
1 . Iniciar una transaccion.
2. Cambiar el ContactName del registro Around The Horn de Thomas
Hardy a Dick Solomon.
3. Iniciar una transaccion anidada.
4. Cambiar el ContactName del registro Bottom-Dollar Markets de
Elizabeth Lincoln a Sally Solomon.
5. Deshacer la transaccion mas interna.
6. Confirmar la transaccion externa.
El efecto global es que so10 es permanente el cambio del registro Around The
Horn. Sin embargo, si la transaccion interna se habia confirmado y la transaccion
externa se deshace, el efecto global seria que ninguno de 10s cambios seria perma-
nente (ni siquiera 10s cambios de la transaccion interna). Esto es lo esperado,
siendo el unico limite que Access solo soporta cinco niveles de transacciones
anidadas.
ODBC ni siquiera soporta transacciones anidadas, el proveedor de Jet OLE
DB solo soporta hasta cinco niveles de transacciones anidadas y el proveedor
SQL Server OLE DB no soporta en absoluto la anidacion. Podriamos obtener un
resultado distinto segun la version de SQL Server o del controlador, per0 la docu-
mcntacion y nuestros experimentos con 10s servidores indican que asi sucede.
Aparentemente, solo la transaccion mas externa decide si el trabajo se confirma o
se deshace.

Atributos de ADOConnection
Existe otro tema que deberiamos considerar si estamos pensando en utilizar
transacciones anidadas. El componente ADOConnection posee una propiedad lla-
mada Attributes que determina el mod0 en que se deberia comportar una
transaccion cuando se confirma o se deshace. Se trata de un conjunto de
TXActAtt ributes que, por defecto, esta vacio. Solo hay dos valores en
TXActAttribuf:es:xaCommitRetainingyxaAbortRetaining(este
valor se suele escribir, incorrectamente, como xaRol lbac kRetaining por-
que este seria un nombre mas logico). Cuando se incluye xaComitRetaining
en Attributes y se confirma una transaccion, se inicia automaticamente una
nueva transaccion. Cuando se incluye xaAbortRe taining en Attributes
y se deshace una transaccion, se inicia automaticamente una nueva transaccion.
Esto significa que si incluimos estos valores en Attributes,siempre habra
una transaccion en marcha, porque cuando finalizamos una transaccion siempre
se inicia otra nueva.
La mayoria de 10s programadores prefiere tener un mayor control sobre sus
transacciones y no permitirles que se inicien automaticamente, por lo que estos
valores no suelen usarse. Sin embargo, poseen especial importancia en relacion
con las transacciones anidadas. Si anidamos una transaccion y definimos
Attributes como [xaComitRetaining, xaAbortRetaining1,la
transaccion externa nunca se puede finalizar. Veamos esta secuencia de eventos:
1. Se inicia una transaccion esterna.
2. Se inicia una transaccion interna.
3. La transaccion interna se confirma o deshace
4. Se inicia automaticamente una nueva transaccion interna como consecuen-
cia de la propiedad Attributes.
La transaccion externa no acaba nunca porque cuando una interna finaliza se
iniciara una nueva transaccion. Como conclusion, el uso de la propiedad
Attributes y el uso de transacciones anidadas deberian resultar mutuamente
exclusivos.

Tipos de bloqueo
ADO soporta cuatro tecnicas diferentes para bloquear 10s datos frente a actua-
lizaciones. Las cuatro tecnicas se pueden utilizar mediante la propiedad LockType
del conjunto de datos, con 10s valores: 1tReadOnl y, 1tPessimist ic,
1tOptimistic o ItBatchOptimistic. (Existe ademas una opcion
itunspecified,pero, como ya se comento, ignoraremos 10s valores no espe-
cificados.) En esta seccion ofreceremos una vision global de estos cuatro enfo-
ques. El valor 1tReadOnly especifica que 10s datos son solo de lectura y no
pueden actualizarse. Asi, no se necesita ningun control de bloqueo porque 10s
datos no se pueden actualizar.
Los valores 1tPessimistic y 1topt imistic ofrecen el mismo control
de bloqueo "pesimista" y "optimista" que ofrece el BDE. Una ventaja importante
que ofrece ADO en oposicion a BDE a este respecto es que la opcion del control
de bloqueo es nuestra, en lugar de del controlador BDE. Si se usa una base de
datos de escritorio como dBase o Paradox, el controlador BDE usara un bloqueo
pesimista; si se usa una base de datos clientelservidor como InterBase, SQL Server
u Oracle, el controlador usara el bloqueo optimista.
El bloqueo pesimista
Las palabras pesimista y optimista en este context0 se refieren a lo que espera
el desarrollador de cara a1 conflicto entre actualizaciones de usuario. El bloqueo
pesimista supone que existe una alta probabilidad de que 10s usuarios intenten
actualizar 10s mismos registros a1 mismo tiempo y por lo tanto el conflicto es
probable. Para evitar dicho conflicto, se bloquea el registro cuando comienza la
edicion. El bloqueo se mantiene hasta que se ha finalizado o cancelado la actuali-
zacion. Un segundo usuario que intente editar el mismo registro a1 mismo tiempo
no podra colocar su bloqueo de registro y recibira una excepcion "Could not
update; currently locked" ("No se puedo actualizar, en este momento esta cerra-
do"). Esta tecnica de bloqueo resultara familiar a 10s desarrolladores que hayan
trabajado con bases de datos como dBase y Paradox. La ventaja es que si el
usuario sabe que si puede comenzar a editar un registro, podra guardar su actua-
lizacion con exito. El inconveniente del bloqueo pesimista es que el usuario con-
trola cuando se coloca y se elimina el bloqueo. Si el usuario domina la aplicacion,
el bloqueo podria durar meros segundos. Sin embargo, de cara a una base de
datos, un par de segundos puede ser una eternidad. Por otra parte, el usuario
podria comenzar la edicion e irse a almorzar, y el registro permaneceria bloquea-
do todo ese tiempo, hasta su vuelta. Como consecuencia, la mayoria de 10s defen-
sores del bloqueo pesimista se protegen contra este caso usando un temporizador
u otro dispositivo para provocar la caducidad de 10s bloqueos despues de un
cierto plazo de inactividad a la entrada.
Otro problema del bloqueo pesimista es que necesita un cursor de servidor.
Antes mencionamos que las ubicaciones del cursor influian sobre la disponibili-
dad de 10s diferentes tipos de cursor. Ahora podemos ver que las ubicaciones del
cursor tambien influyen sobre 10s tipos de bloqueo. Mas adelante, analizaremos
las ventajas de 10s cursores de cliente, si se decide aprovechar las ventajas de este
tipo de cursores, no se podra utilizar el bloqueo pesimista.
El bloqueo pesimista es un area de dbGo que ha cambiado en Delphi 6 (respec-
to a Delphi 5). En este apartado se describe como funciona el bloqueo pesimista
de las versiones 6 y 7. Para remarcar este mod0 de funcionamiento, hemos creado
el ejemplo PessimisticLocking. Es parecido a otros ejemplos de este capitulo,
per0 la propiedad CursorLocation se configura como cluseserver y la
propiedad Lo ckType como 1tP e ssimistic.Para usarlo, hay que ejecutar
dos copias desde el Explorador de Windows y tratar de editar el mismo registro en
ambas instancias en ejecucion del programa: no se podra, porque el registro esta-
ra bloqueado por otro usuario.

Actualizacion de 10s datos


Una de las razones por las que se usa el componente ClientDataSet (o se
volvieron a usar las actualizaciones mediante cache en el BDE) es que asi se
puede hacer que una union SQL sea actualizable. Consideremos la siguiente equi-
union SQL:
SELECT * FROM Orders, Customer
WHERE Customer.CustNo=Orders.CustNo

Esta sentencia proporciona una lista de pedidos y 10s datos de 10s clientes que
han realizado dichos pedidos. El BDE considera que cualquier union SQL es de
solo lectura porque insertar, actualizar y eliminar filas en una union resulta ambi-
guo. Por ejemplo, podriamos plantearnos si la insercion de una fila en la union
anterior originaria un nuevo pedido y tambien un nuevo cliente o solo un nuevo
pedido. La arquitectura ClientDataSet/Provider permite especificar una tabla de
actualizacion principal (y otras caracteristicas avanzadas que no vamos a comen-
tar), y tambien personalizar el codigo SQL de las actualizaciones, como se vio en
parte en el capitulo 14 y se comentara aun mas en el capitulo 16.
ADO soporta un equivalente a las actualizaciones mediante cache, denomina-
do actualizaciones por lotes, que son muy similares a1 enfoque del BDE. En el
proximo apartado hablaremos sobre estas actualizaciones por lotes, lo que puede
ofrecer y por que son tan importantes. Sin embargo, en esta seccion no las necesi-
taremos para resolver el problema de la actualizacion de una union porque, en
ADO, las uniones son intrinsecamente actualizables.
El ejemplo JoinData se basa en un componente A D O D a t a S e t que usa la
union SQL anterior. Si se ejecuta, se puede editar uno de 10s campos y guardar 10s
cambios (saliendo del registro). No se produce ningun error, porque la actualiza-
cion se habra aplicado con exito. ADO, en comparacion con el BDE, ha adoptado
una tecnica mas practica para el problema. En una union ADO, cada objeto de
campo sabe a que tabla subyacente pertenece. Si actualizamos un campo de la
tabla Orders y enviamos el cambio, se crea entonces una sentencia SQL UPDATE
para actualizar el campo de la tabla O r d e r s . Si cambiamos un campo de la tabla
Orders y un campo de la tabla Customer, se crean dos sentencias SQL UPDATE,
una para cada tabla.
La insercion de una fila en una union sigue un comportamiento similar. Si
insertamos una fila e escribimos valores solo para la tabla O r d e r s , se crea una
sentencia SQL I N S E R T para la tabla O r d e r s . Si escribimos valores para am-
bas tablas, se crean dos sentencias SQL I N S E R T , una por tabla. El orden en el
que se ejecutan las sentencias es importante, porque el nuevo pedido podria estar
relacionado con el nuevo cliente, por lo que el nuevo cliente se inserta en primer
lugar .
El mayor problema de la solucion de ADO se puede ver cuando se elimina una
fila de una union. El intento de eliminacion parece no tener exito. El mensaje
exacto que veamos dependera de la version de ADO que estemos usando y de la
base de datos, per0 se nos comunicara que no podemos eliminar la fila porque
otros registros estan relacionados con ella. El mensaje de error puede resultar
confuso. En este caso, el mensaje de error implica que un pedido no puede elimi-
narse porque esisten registros que estan relacionados con el pedido, per0 el error
ocurre tanto si el pedido tiene asociados otros registros como si no. La explica-
cion puede obtenerse siguiendo la misma logica para las eliminaciones que para
las inserciones. Se crean dos sentencias SQL DELETE:una para la tabla Customer
y, a continuacion, otra para la tabla Orders. En contra de lo que pudiera parecer,
la sentencia DELETE de la tabla Orders tiene esito. Es la sentencia DELETE de
la tabla Customer la que no funciona, porque no se puede eliminar el cliente
mientras registros dependientes.

TRUCO: Si siente curiosidad sobre las sentencias SQL que se generan, y


usa SQL Server, se pueden ver estas sentencias mediante el SQL Server
Profiler.

A pesar de entender como funciona este proceso, una forma mejor de enfocar
el problema es desde la perspectiva del usuario. Desde su punto de vista, cuando
se elimina una fila de la cuadricula, casi seguro que el 99 por ciento de 10s
usuarios espera eliminar el pedido, no el pedido y el cliente. Afortunadamente,
podemos conseguir esactamente esto mediante otra propiedad dinamica, en este
caso, la propiedad dinamica Unique Table. Podemos especificar que las eli-
minaciones se refieren solo a la tabla Orders y no a Customer mediante el
siguiente codigo:
A D O Q u e r y l . P r o p e r t i e s [ ' U n i q u e T a b l e ' ] .Value := ' Products' ;

Puesto que este valor no se puede asignar en tiempo de diseiio, la siguiente


mejor alternativa consiste en colocar esta linea en el evento Oncreate del for-
mulario.

Actualizaciones por lotes


Cuando se usan actualizaciones por lotes, cualquier cambio sobre 10s registros
se realizara en memoria; mas adelante se puede enviar el lote completo de cam-
bios como una sola operacion. Este enfoque suele ofrecer algunas ventajas en
cuanto a prestacion, per0 hay mas razones practicas por las que esta tecnologia es
una necesidad: el usuario podria no estar conectado a la base de datos en el
momento en que realice sus actualizaciones. Este seria el caso de una aplicacion
de maletin (tema a1 que volveremos casi a1 final de este capitulo), per0 tambien el
caso de aplicaciones Web que usen otra tecnologia ADO, Remote Data Services
(RD S) .
Podemos activar las actualizaciones por lotes en cualquier conjunto de datos
ADO definiendo su propiedad Loc kT ype como lt Bat chOpt imis t ic antes
de abrirlo. Ademas, sera necesario definir C u r s o r L o c a t i o n como
cluseclient, puesto que las actualizaciones por lotes son gestionadas por el
motor de cursor de ADO. A partir de aqui, 10s cambios se realizan en un "delta"
(es decir, una lista incremental de cambios). El conjunto de datos se comporta
para todo proposito como si 10s datos hubieran cambiado, per0 10s cambios solo
se han realizado en memoria; no se han aplicado a la base de datos. Para que 10s
cambios Sean permanentes, utilizamos U p d a t e Bat c h (equivalente a
ApplyUpdat e s en actualizaciones mediante cache):

Para rechazar el lote completo de actualizaciones, usamos CancelBatch o


Cance lUpdate s. Existen muchas similitudes en 10s nombres de metodos y
propiedades entre las actualizaciones por lotes de ADO y las actualizaciones
mediante cache del BDE y ClientDataSet. Update Status, por ejemplo, se
puede utilizar exactamente del mismo mod0 que para las actualizaciones median-
te cache para identificar registros segun hayan sido insertados, actualizados, eli-
minados o no modificados. Esto resulta particularmente util para resaltar registros
con distintos colores en una cuadricula o mostrar su estado en una barra de
estado. Algunas diferencias entre las sintaxis son muy leves, como el cambio de
RevertRecord por CancelBatch (arcurrent). Otras son mas compli-
cadas.
Una caracteristica util de las actualizaciones mediante cache que no esta pre-
sente en las actualizaciones por lotes de ADO es la propiedad updatesPending
de un conjunto de datos. Dicha propiedad es verdadera si se han realizado cam-
bios per0 aun no se han aplicado. Esto resulta especialmente util en el evento
OnCloseQuery de un formulario:
procedure TForml.FormCloseQuery(
Sender: TObject; var CanClose: Boolean) ;
begin
CanClose := True;
i f ADODataSet1.UpdatesPending then
CanClose : = (MessageDlg ( ' U p d a t e s a r e s t i l l p e n d i n g ' $ 1 3 +
' C l o s e a n y w a y ? ' , mtconfirmation, [&Yes, &No] , 0 ) =
mrYes) ;
end;

Sin embargo, con un poco de conocimiento y un poco de ingenio podemos


implementar una funcion ADOUpda te sPending apropiada. El conocimiento
necesario es que 10s conjuntos de datos de ADO poseen una propiedad llamada
FilterGroup,que es una especie de filtro. A diferencia de la propiedad Filter
de un conjunto de datos, que filtra 10s datos basandose en una comparacion de 10s
datos con una condicion, Fi1terGroup filtra basandose en el estado del regis-
tro. Uno de esos estados es fgPendingRecords,que incluye todos 10s regis-
tros que hemos modificado per0 cuyos cambios no hemos aplicado aun. Por eso,
para permitir que el usuario vea todos 10s cambios que se han realizado hasta
ahora, solo hay que ejecutar dos lineas:
AD0DataSetl.FilterGroup : = fgPendingRecords;
ADODataSetl.Filtered : = True;

El conjunto de resultados incluira ahora los registros que se hayan eliminado.


El efecto es que se vera que 10s campos se dejan en blanco, lo que no es de mucha
ayuda porque no se puede saber que registros se han borrado. (El comportamiento
de la primera version de ADOExpress no era este, si no que se mostraban 10s
valores de 10s campos de 10s registros eliminados.) El ingenio necesario para
resolver el problema de Updatespending implica a de 10s clones, que mencio-
namos antes. La idea de la funcion ADOUpdate spending es que definira el
FilterGroup para restringir el conjunto de datos a solo aquellos cambios que
todavia no se hayan aplicado. Todo lo que tenemos que hacer es ver si existen
registros en el conjunto de datos despues de aplicar FilterGroup. Si 10s hay,
significa que hay actualizaciones pendientes. Sin embargo, si hacemos esto con el
conjunto de datos real, la definicion de FilterGroup desplazara el punter0 de
registro y se actualizara la interfaz de usuario. La mejor solucion consiste en
utilizar un clon:
function ADOUpdatesPending(AD0DataSet: TCustomADODataSet) :
boolean;
var
Clone: TADODataSet;
begin
Clone : = TADODataSet. Create (nil);
try
Clone.Clone(AD0DataSet);
Clone.FilterGroup : = fgPendingRecords;
Clone.Filtered : = True;
Result : = not (Clone.BOF a n d Clone. EOF) ;
Clone. Close;
finally
Clone. Free;
end;
end;

En esta funcion, clonamos el conjunto de datos original, configuramos


Fi 1terGroup y verificamos si el conjunto de datos se encuentra a1 principio
del archivo y tambien a1 final del mismo. De ser asi, no hay registros pendientes.

Bloqueo optimista
Ya hablamos anteriormente de la propiedad Loc kType y vimos como funcio-
na el bloqueo pesimista. A continuacion hablaremos del bloqueo optimista, no
solo por tratarse del tip0 de bloqueo preferido para transacciones con un trafico
medio o alto, sino tambien por ser el esquema de bloqueo empleado para las
actualizaciones por lotes.
El bloqueo optimista supone que existe una probabilidad muy reducida de que
10s usuarios intenten actualizar 10s mismos registros a1 mismo tiempo y, por ello,
no es probable que ocurra un conflicto. Asi, la actitud es que todos 10s usuarios
pueden editar cualquier registro en cualquier momento y tratamos con las conse-
cuencias de conflictos entre actualizaciones de diferentes usuarios sobre 10s mis-
mos registros cuando se guardan 10s cambios. De este modo, 10s conflictos se
consideran una excepcion a la norma. Esto significa que no existen controles que
eviten que dos usuarios editen el mismo registro a1 mismo tiempo. El primer
usuario en guardar 10s cambios tendra exito. El intento del segundo usuario de
actualizar el mismo registro puede que no lo tenga. Este comportamiento es esen-
cia1 para las aplicaciones de maletin y aplicaciones Web, en las que no existe una
conexion permanente con la base de datos y, por lo tanto, no hay forma de
implementar un bloqueo pesimista. En oposicion a1 bloqueo pesimista, el bloqueo
optimista posee la considerable ventaja adicional de que 10s recursos solo se
consumen momentaneamente y, por lo tanto, el uso medio de 10s recursos es
mucho menor, haciendo que la base de datos resulte mas escalable.
Vearnos un ejemplo. Supongamos que tenemos un ADODataSet conectado a la
tabla Customer de la base de datos dbdemos .mdb, con LockType definido
como 1tBat chOpt imi s t i c y que el contenido se muestra en una cuadricula.
Supongamos que tambien tenemos un boton para llamar a UpdateBat ch.Eje-
cutamos el programa dos veces (es el ejemplo BatchUpdates) y empezamos a
editar un registro en la primera copia del programa. Aunque por motivos de
sencillez, mostraremos un conflicto empleando un unico equipo, la situacion y
eventos subsecuentes no cambian cuando se usan varios equipos:
1. Escogemos la empresa Bottom-Dollar Markets de Canada y cambiamos el
nombre por Bottom-Franc Markets.
2. Guardamos el cambio, salimos del registro para enviarlo y hacemos clic
sobre el boton para actualizar el lote.
3. Ahora, en la segunda copia del programa, buscamos el mismo registro y
cambiamos el nombre de la empresa a Bottom-Pound Markets.
4. Salimos del registro y hacemos clic sobre el boton para actualizar el lote.
No funcionara.
A1 igual que con muchos otros mensajes de error de ADO, el mensaje exacto
que se reciba dependera no solo de la version de ADO sin0 tambien de la precision
con que se siga el ejemplo. En ADO 2.6, el mensaje de error es "Row cannot be
located for updating. Some values may have been changed since it was last
react' ("No se puede encontrar la fila para su actualizacion. Puede que algunos
valores hayan cambiado desde su ultima lectura"). Este es el comportamiento del
bloqueo optimista. La actualizacion del registro se realiza ejecutando la siguiente
sentencia SQL:
UPDATE CUSTOMER SET CompanyName="Bottom-Pound Markets"
WHERE CustomerID="BOTTM" AND CompanyName="Bottom-Dollar
Markets"
El numero de registros afectados por esta sentencia de actualizacion se espera
que sea uno, porque se busca el registro original utilizando la clave primaria y el
contenido del campo CompanyName tal como estaba cuando el registro se ley6
por primera vez. En este ejemplo, sin embargo, el numero de registros afectados
por la sentencia UPDATE es cero. Esto solo puede ocurrir si se ha eliminado el
registro, ha cambiado la clave primaria del registro o el campo que estamos modi-
ficando fue modificado por otra persona. Por lo tanto, la actualizacion no se
realiza.
Si nuestro "segundo usuario" hubiera cambiado el campo ContactName y
no el campo CompanyName,la sentencia UPDATE se habria parecido a esta:
UPDATE CUSTOMER SET ContactName="Liz Lincoln"
WHERE CustomerID="BOTTM" AND ContactName="Elizabeth Lincoln"

En nuestro caso, esta sentencia habria funcionado porque el otro usuario no


cambio la clave primaria ni el nombre de contacto. Este comportamiento es simi-
lar a1 comportamiento del BDE con el mod0 de actualizacion en caso de cambios.
La propiedad UpdateMode del BDE en ADO se sustituye por la propiedad
dinamica Update Criteria de un conjunto de datos. La siguiente tabla mues-
tra 10s valores posibles que se pueden asignar a esta propiedad dinamica:

adCriteriaKey Solo columnas de d a v e primaria.


adCriteriaAllCols Todas las columnas.
adCriteriaUpdCols Solo columnas de clave primaria y columnas
modificada.
adCriteriaTimeStamp Solo columnas de clave primaria y columna de
marca temporal de ljltima modificacion.

No hay que caer en el error de pensar que una de estas configuraciones es


mejor que otra para toda la aplicacion. En la practica, la eleccion dependera del
contenido de cada tabla. Digamos que la tabla Customer solo posee 10s campos
CustomerID,N a m e y City.
En este caso, la actualizacion de cualquiera de estos campos es logicamente no
mutuamente excluyente con respecto a la actualizacion de cualquier otro campo,
por lo que una buena eleccion seria adCriteriaUpdCols (la opcion
predefinida). Sin embargo, si la tabla Customer incluyera un campo
Post alCode,entonces la actualizacion de un campo Postalcode seria mu-
tuamente escluyente con la actualizacion del campo City por parte de otro usua-
rio (porque si cambia la ciudad, seguramente tambien deberia cambiar el codigo
postal y viceversa). En este caso, podriamos argumentar que adCr iteriaAl1-
Co 1s seria una solucion mas segura.
Otra cuestion que hay que tener en cuenta es el mod0 en que ADO trata con
errores durante la actualizacion de varios registros. Usando las actualizaciones
mediante cache del BDE y ClientDataSet, podemos usar el evento OnUpda-
teError para controlar cada error de actualizacion cuando sucede el error y
resolver el problema antes de pasar a1 registro siguiente. En ADO, no podemos
establecer dicho dialogo. Podemos realizar un seguimiento del progreso y del
exito o fracas0 de la actualizacion del lote usando OnWillChangeRecord y
OnRecordChangeComplet e del conjunto de datos, per0 no podemos revisar
el registro y reenviarlo durante dicho proceso como podemos con el BDE y
ClientDataSet. Ademas, si durante el proceso de actualizacion ocurre un error, la
actualizacion no se detiene, sin0 que continua hasta el final, hasta que se hayan
aplicado o hayan fallado todas las actualizaciones. Esto puede ocasionar un men-
saje de error incorrect0 e inutil. Si no se puede actualizar mas de un registro o el
unico registro que ha fallado es distinto del ultimo registro, el mensaje de error en
ADO 2.6 es "Multiple-step OLE DB operation generated errors. Check each
OLE DB status value, if available. No work was done" ("La operacion OLE DB
en varios pasos genero errores. Verifique cada valor de estado OLE DB, si estan
disponibles. No se ha realizado ninguna tarea"). El problema esta en la ultima
frase, declara que "No se ha realizado ninguna tarea", per0 eso no es correcto. Es
verdad que no se realizo ninguna tarea para el registro que fallo, per0 en 10s
demas registros se aplicaron correctamente las actualizacionesy Qtas se mantienen.

Resolucion de conflictos de actualizacion


Como consecuencia de la naturaleza de aplicar actualizaciones, la tecnica que
necesitamos adoptar para actualizar el lote consiste en actualizar el lote, permitir
fallos para 10s registros individuales y, a continuacion, tratar con 10s registros
que han fallado cuando ya ha finalizado el proceso. Podemos determinar que
registros han fallado definiendo la propiedad Filte rGroup del conjunto de
datos como fgConf 1ictingRecords:
ADODataSetl.Fi1terGroup : = fgConflictingRecords;
AD0DataSetl.Filtered : = True;

En el caso de cada registro que haya fallado, podemos informar a1 usuario de


tres elementos de informacion criticos sobre cada campo mediante las siguientes
propiedades T Fie 1d :

Newvalue El valor al que el usuario ha cambiado el registro.


I curvalue El nuevo valor obtenido de la base de datos.
II
I OldValue El valor cuando se ley6 por primera vez de la base
de datos.
Los usuarios del componente C 1i e n t Da t a Se t conoceran ya el practico
dialog0 T R e c o n c i l e E r r o r Form, que envuelve el proceso de mostrar a1 usuario
10s registros nuevos y 10s antiguos y les permite especificar que accion realizar.
Por desgracia, no existe un equivalente en ADO para este formulario y
T R e c o n c i l e E r r o r F o r m se ha escrito teniendo tan P r e s e n t e C l i e n t D a t a S e t
que es dificil usarlo para conjuntos ADO.
Hay que tener en cuenta una cosa mas acerca del uso de estas propiedades
T F i e l d : se toman directamente de 10s objetos Field de ADO subyacentes a 10s
que hacen referencia. Esto significa que se depende del soporte del proveedor
OLE DB para las caracteristicas que se espera utilizar. Todo suele hncionar bien
con la mayoria de 10s proveedores, per0 el proveedor Jet OLE DB devuelve el
mismo valor para C u r V a l u e y O l d v a l u e . En otras palabras, si utilizamos
Jet, no podemos determinar el valor a que cambio el campo el otro usuario a
menos que recurramos a medidas propias. Sin embargo, mediante el proveedor
OLE DB para SQL Server, se puede acceder a c u r v a l u e solo despues de lla-
mar a1 metodo R e s y n c del conjunto de datos con el parametro A f f e c t R e c o r d s
configurado como addAf f e c t G r o u p y R e s y n c v a l u e s como a d R e s y n c -
U n d e r 1y i ngVa l u e s ; como en el siguiente fragment0 de codigo:
adoCustomers.FilterGroup : = fgConflictingRecords;
adoCustomers.Filtered : = true;
adoCustomers.Recordset.Resync(adAffectGroup,
adResyncUnderlyingValues);

Conjuntos de registros desconectados


Todo este conocimiento de las actualizaciones por lotes nos permite aprove-
char la proxima caracteristica de ADO: 10s conjuntos de registro desconectados.
Un conjunto de registros desconectado es un conjunto de registros que se han
desconectado de su conexion. Lo que es chocante de esta hncion es que el usuario
no puede ver la diferencia entre un conjunto de rcgistro normal y uno desconecta-
do. Sus conjuntos de caracteristicas y comportamiento son casi identicos. Para
desconectar un conjunto de registros de su conexion, C u r s o r L o c a t i o n habra
de definirse como c l U s e C l i e n t y LockType como I t B a t c h O p t i m i s t i c .
A continuacion, podemos sencillamente decir a1 conjunto de datos que ya no tiene
una conexion:
ADODataSetl.Connection := nil;

A partir de ahi, el conjunto de registro seguira teniendo 10s mismos datos,


soportando las mismas hnciones de navegacion y permitira que se aiiadan regis-
tros, se editen y se eliminen. La unica diferencia relevante es que no podemos
actualizar el lote porque es necesario que este conectado a1 servidor para actuali-
zar el servidor.
Para volver a establecer la conexion (y usar UpdateBatch), podemos usar
este codigo:
ADODataSetl.Connection : = ADOConnectionl;

Esta funcion tambien esta disponible para el BDE y otras tecnologias de bases
de datos cambiando a1 uso de componentes Client Dataset, per0 la belleza
de la solucion de ADO esta en que podemos crear toda la aplicacion utilizando
componentes de conjuntos de datos dbGo y no percatarnos de 10s conjuntos de
registros desconectados. En el momento en que descubramos esta caracteristica y
deseemos beneficiarnos de ella, podemos continuar utilizando 10s mismos compo-
nentes que hemos usado siempre. Existen dos razones por las que podriamos
querer desconectar 10s conjuntos de registros:
Para que el numero total de conexiones sea reducido.
Para crear una aplicacion de maletin.
La mayoria de las aplicaciones empresariales clientetservidor abren tablas y
mantienen una conexion permanente con su base de datos mientras la tabla esta
abierta. Sin embargo, normalmente solo existen dos razones por las que queramos
estar conectados a la base de datos: recuperar datos y actualizarlos. Supongamos
que queremos cambiar la tipica aplicacion clientetservidor para que una vez que
se abra la tabla y se consigan 10s datos, se desconecte el conjunto de datos de la
conexion y se rompa esta. El usuario no tiene porque saberlo y la aplicacion no
necesitara mantener una conexion abierta con la base de datos. El siguiente codi-
go muestra 10s dos pasos:
ADODataSetl.Connection : = nil;
ADOConnection1.Connected : = False;

El unico momento en el que se necesita una conexion es aquel en el que es nece-


sario aplicar el lote de actualizaciones, por lo que el codigo de actualizacion seria:
ADOConnection1.Connected : = True;
AD0DataSetl.Connection : = ADOConnectionl;
t rY
ADODataSetl.UpdateBatch;
finally
AD0DataSetl.Connection : = nil;
ADOConnectionl.Connected : = False;
end;

Pooling de conexiones
Toda esta explicacion sobre el cierre de conexiones y su reapertura nos acerca
el tema delpooling de conexiones. Elpooling o reserva de conexiones, que no se
ha de confundir con el pooling de sesiones, permite que las conesiones a la base
de datos se reutilicen despues de que hayan finalizado. Esto se realiza de forma
automatica y, si nuestro proveedor OLE DB la soporta y esta activada, no es
necesario hacer nada para beneficiarnos del pooling de conesiones.
Existe una unica razon por la que querriamos utilizar esta tecnica para nues-
tras conesiones: el rendimiento. El problema de las conexiones a bases de datos
esta en que puede llevar cierto tiempo establecer una conexion. En una base de
datos de escritorio como Access, esto se reduce normalmente a un corto period0
de tiempo. En una base de datos clientelservidor como Oracle, que se utiliza en
una red, este tiempo podria medirse en segundos. Tiene sentido promover la
reutilizacion de este tipo de recurso tan costoso (en cuanto a rendimiento).
Si activamos la fusion de conexiones de ADO, 10s objetos Connection de ADO
se colocan en una cola cuando la aplicacion 10s "destruye". Los subsiguientes
intentos de crear una conesion ADO buscaran automaticamente en la cola de
conesion una conexion con la misma cadena de conesion. Si se encuentra una
conesion apropiada, se reutiliza. De no ser asi, se crea una nueva conexion. Las
propias conesiones permanecen en cola hasta que se reutilizan, se cierra la apli-
cacion o expiran. De manera predeterminada, las conesiones expiraran despues
de 60 segundos, pero a partir de MDAC 2.5 podemos configurar este tiempo
utilizando la clave de registro HKEY C L A S S E S R O O T \ C L S I D \ < Provi-
d e r C L S I D > \ S PT i r n e o u t . El procesi de poolingde conesion tiene lugar de
forma homogenea, sin la intervencion ni el conocimiento del desarrollador. Este
proceso es similar alpooling de bases de datos del BDE en Microsoft Transaction
Server (MTS) y COM+, con la importante escepcion de que ADO realiza su
propio pooling de conexiones sin la ayuda de MTS ni de COM+.
De manera predeterminada, elpooling de conexiones esta activado en 10s pro-
veedores MDAC OLE DB para bases de datos relacionales (como SQL Server y
Oracle), con la notable excepcion del proveedor Jet OLE DB. Si usamos ODBC,
deberiamos escoger entre el pooling de conexiones de ODBC y el de ADO, per0
no deberiamos utilizar ambos. A partir de MDAC 2.1, el pooling de conesiones
de ADO esta activado y el de ODBC esta desactivado.

NOTA: Elpooling de conexiones no se realiza en Windows 95 a pesar del


proveedor OLE DB.

Para que encontrarnos realmente comodos con el pooling de conesiones, sera


necesario ver como se realiza el pooling y como expiran. Lamentablemente, no
existen herramientas de analisis depooling de conesiones para ADO, per0 pode-
mos utilizar el Performance Monitor de SQL Server que puede espiar con preci-
sion las conexiones de bases de datos SQL Server.
Podemos activar o desactivar el pooling de conexion en el Registro o en la
cadena de conexion. La clave del Registro es OLEDB S E R V I C E S y puede en-
contrarse en HKEY -C L A S S E S R O O T \ C L S I D \ < P ~ & ~ ~ ~ ~ C L S I DEs> una
.
mascara de bits que permite desactivar varios servicios OLE DB, como elpooling
de conexion, el listado de transacciones y el motor de cursor. Para desactivar el
pooling de conexiones usando la cadena de conexion incluimos ";OLE DB
Services= - 2 " a1 final de la cadena de conexion. Para activar el pooling de
conexion para el proveedor Jet OLE DB, podemos incluir If;O L E D B
services=-1 " a1 final de la cadena de conexion, lo cual activa todos 10s
servicios OLE DB.

Conjuntos de registros permanentes


Una de las funciones mas utiles que contribuye a1 modelo de maletin son 10s
conjuntos de registro permanentes. Estos permiten guardar el contenido de cual-
quier conjunto de registro en un archivo local, que podemos cargar mas tarde.
Ademas de ayudar con el modelo de maletin, esta caracteristica permite a 10s
desarrolladores crear autenticas aplicaciones de una sola capa (se puede desple-
gar una aplicacion de base de datos sin tener que desplegar una base de datos).
Esto genera una impronta reducida en la maquina del cliente.
Podemos hacer que nuestros conjuntos de datos "permanezcan" utilizando el
metodo SaveToFile:
ADODataSetl. SaveToFile ( ' L o c a l . ADTG' ) ;

Este guarda 10s datos y su delta en un archivo del disco duro. Podemos volver
a cargar dicho archivo utilizando el metodo LoadFromFile,que acepta un
solo parametro que indica el archivo que se ha de cargar. El formato del archivo
es Advanced Data Table Gram (ADTG), que es un formato propietario de
Microsoft. Sin embargo, tiene la ventaja de ser muy eficiente. Si lo preferimos,
podemos guardar el archivo como XML pasando un segundo parametro a
SaveToFile:

Sin embargo, ADO no posee su propio analizador sintactico XML incorpora-


do (igual que ClientDataSet), por lo que debemos usar el analizador sintactico
MSXML. Nuestro usuario debe instalar Internet Explorer 5 o una version poste-
rior, o descargar el analizador sintactico MSXML de la propia pagina Web de
Microsoft. Si pretendemos que 10s archivos Sean permanentes de forma local en
formato XML, debemos tener en cuenta una serie de inconvenientes:
En primer lugar, es mas lento guardar y cargar archivos XML que guardar
y cargar archivos ADTG.
En segundo lugar, 10s archivos XML de ADO (y 10s archivos XML en
general) son bastante mas grandes que sus homologos ADTG (el tamaiio
de 10s archivos XML es normalmente el doble que el de sus homologos
ADTG).
En tercer lugar, el formato XML de ADO es especifico de Microsoft, a1
igual que la mayoria de las implementaciones XML de las empresas. Esto
significa que el XML producido en ADO, noes legible por el ClientDataSet
de Borland, y viceversa. Afortunadamente, este ultimo problema se puede
superar utilizando el nuevo componente XMLTransform de Delphi, que se
puede usar para traducir entre las diferentes estructuras XML.
Si queremos usar estas caracteristicas solamente para aplicaciones de una
capa y no como parte del modelo de maletin, podemos ahorrar esfuerzos utilizan-
do un componente ADODataSet y definiendo su propiedad ComrnandType como
c m d F i l e y su C o r n m a n d T e x t como el nombre del archivo. Esto nos ahorrara
el esfuerzo de tener que llamar a L o a d F r o m F i l e de forma manual. Sin embar-
go, tendremos que llamar a S a v e T o F i l e . En una aplicacion de maletin, esta
tecnica es demasiado restrictiva, puesto que el conjunto de datos se puede usar de
dos formas distintas.

El modelo de maletin
Nuestro recientemente adquirido conocimiento sobre las actualizaciones por
lotes, conjuntos de registros desconectados y conjuntos de registros permanentes
nos permiten sacar partido del "modelo de maletin". La idea que se esconde detras
este modelo es que 10s usuarios quieren ser capaces de usar nuestra aplicacion
mientras e s t h de viaje, quieren llevarse la misma aplicacion que utilizan en 10s
escritorios de su oficina y utilizarla en sus portatiles mientras estan en las instala-
ciones de sus clientes. El problema de dichas circunstancias es que normalmente
cuando nuestros usuarios se encuentran en las instalaciones de sus clientes, no
estan conectados a su servidor de base de datos, porque el servidor de base de
datos esta en funcionamiento en la red interna de su propia oficina. En consecuen-
cia, no hay datos en el portatil y de todas formas no se pueden actualizar 10s
datos.
Es aqui donde entra en juego lo aprendido. Supongamos que la aplicacion ya
esta escrita. El usuario ha solicitado esta nueva ampliacion de maletin y tenemos
que adaptar la aplicacion existente. Es necesario afiadir una nueva opcion para
permitir que 10s usuarios que se "preparen" para la aplicacion de maletin ejecu-
tando sencillamente S a v e T o F i l e para cada tabla de la base de datos. El resul-
tad0 es una coleccion de archivos ADTG o XML en la que se refleja el contenido
de la base de datos. Estos archivos se copian entonces en el portatil en el que se ha
instalado previamente una copia de la aplicacion.
La aplicacion debera poder discernir si se esta ejecutando de forma local o
conectada a la red. Podemos determinarlo intentando conectar con la base de
datos y comprobando si no se nos permite hacerlo, detectando la presencia de un
archivo local de "maletin" o creando algun indicador de diseiio propio. Si la
aplicacion se esta ejecutando en mod0 de maletin, es necesario utilizar
L o a d F r o m F i l e para cada tabla en lugar de definir C o n n e c t e d como T r u e
para las conexiones ADOConnections y Act ive como True para 10s conjuntos
de datos ADO. Ademas, la aplicacion en mod0 de maletin necesita utilizar
SaveTo Fi le en lugar de UpdateBa t c h siempre que se guarden 10s datos. A1
volver a la oficina, el usuario necesita que haya un proceso de actualizacion que
cargue cada tabla del archivo local, se conecte el conjunto de datos a la base de
datos y aplique 10s cambios usando UpdateBatch.

TRUCO: Para ver una completa implementaci6n &I modelo de &&a,


convendria consultar el ejemplo Batchupdates ya mcnciaaadcr.

Unas palabras sobre ADO.NET


ADO.NET forma parte de la nueva arquitectura .NET de Microsoft, el rediseiio
de la empresa de herramientas de desarrollo de aplicaciones mas adecuadas para
las necesidades del desarrollo Web. ADO.NET supone una importante evolucion
de ADO. Presta atencion a 10s problemas del desarrollo Web y de la solucion
ADO. El problema de la solucion ADO es que se basa en COM. Para aplicaciones
de una y dos capas, COM no supone mucho problema, per0 en el mundo del
desarrollo Web resulta inaceptable como mecanismo de transporte. COM sufre de
tres problemas principales que limitan su uso para el desarrollo Web: basicamen-
te solo funciona sobre Windows, la transmision de 10s conjuntos de registros
desde un proceso requiere el control de COM y, por ultimo, las llamadas COM no
pueden atravesar cortafuegos corporativos. La solucion de ADO.NET a todos
estos problemas es utilizar XML.
Algunas otras cuestiones de rediseiio se centran en dividir el conjunto de regis-
tros de ADO en varias clases. Las clases resultantes sirven para resolver un
problema unico, en lugar de varios problemas. Por ejemplo, la clase ADO.NET
llamada DataSetReader es parecida a un conjunto de registros de solo lectu-
ra, solo avance y de servidor y, como tal, se adecua mas a la lectura de un
conjunto de resultados con gran rapidez. Un DataSet es casi como un conjunto de
registros desconectado y de cliente. Un DataRelation comparte cierto parecido
con el proveedor MSDataShape OLE DB. Como puede comprobarse, el conoci-
miento sobre el funcionamiento de ADO es de gran valia de cara a la comprension
de 10s principios basicos de ADO.NET.
Aplicacio'nes
DataSnap
multicapa

Las grandes empresas suelen tener necesidades que son mucho mas amplias de
lo que pueden cubrir aplicaciones que usen bases de datos locales y servidores
SQL. En 10s ultimos aiios, Borland Software Corporation se ha enfrentado a las
necesidades de las grandes empresas e incluso carnbio temporalmente su nombre
por Inprise para subrayar su enfoque orientado a la empresa. Finalmente el nom-
bre volvio a cambiarse por Borland, pero el tener como objetivo el desarrollo de la
empresa sigue permaneciendo.
Delphi se dirige a muchas tecnologias diferentes: arquitecturas de tres capas
basadas en Windows NT y DCOM, aplicaciones TCPIIP y de sockets y, sobre
todo, servicios Web basados en XML y SOAP. Este capitulo se centra en las
arquitecturas multicapa orientadas a bases de datos, mientras que del resto de las
tecnologias se hablara mas adelante.
Antes de continuar, deberiamos resaltar dos elementos importantes. En primer
lugar, las herramientas para soportar este tip0 de desarrollo solo se encuentran
disponibles en la version Enterprise de Delphi; y, en segundo lugar, con Delphi 7
no hay que pagar derechos de desarrollo para aplicaciones DataSnap. Se adquiere
el entorno de desarrollo y despues se despliegan 10s programas en tantos servido-
res como se quiera, sin deber dinero a Borland. Se trata de un carnbio muy signi-
ficativo (el mas significativo en Delphi 7) de la politica de distribucion de DataSnap,
que solia requerir el pago de derechos por servidor (una cantidad inicialmente
muy elevada, que a lo largo del tiempo se fue reduciendo significativamente).
Esta nueva licencia de desarrollo aumentara con toda seguridad el atractivo de
DataSnap para 10s desarrolladores, que es una buena razon para comentar esta
herramienta con algo de detalle. Este capitulo trata 10s siguientes temas:
Arquitectura logica de tres capas.
Fundamento tecnico de DataSnap.
Los protocolos de conexion y 10s paquetes de datos.
Componentes de soporte de Delphi (de cliente y de sewidor).
El agente de conexion y otras caracteristicas extendidas.

Niveles uno, dos y tres en la historia


de Delphi
En un principio, las aplicaciones informaticas de bases de datos constituian
unicamente soluciones centradas en el entorno del cliente: el programa asi como
10s archivos que componian la base de datos estaban ubicados en el mismo orde-
nador. Desde entonces, algunos osados programadores trasladaron 10s archivos
que integraban las bases de datos a un servidor de archivos en red. El ordenador
del cliente aun albergaba el software de aplicacion y todo el motor propio de la
base de datos, pero, en cambio, ahora 10s archivos de las bases de datos estaban a
disposicion de varios usuarios a la vez. En la actualidad, todavia se puede echar
mano de este tipo de configuracion a traves aplicaciones Delphi o de archivos
Paradox (0, claro esta, con el propio sistema Paradox), no obstante, este enfoque
estaba mas extendido hace unos afios.
La siguiente gran fase de transicion estuvo protagonizada por el desarrollo
clientelservidor acogido por Delphi desde su primera version. En el context0 clientel
servidor; el ordenador cliente solicita datos a un ordenador servidor, que alberga
tanto 10s ficheros de la base de datos como el motor necesario para acceder a
estos. Esta arquitectura minimiza el papel del cliente, a la vez que reduce 10s
requisitos en cuanto a potencia de procesamiento de nuestro ordenador. Segun el
mod0 de implernentacion de sistemas clientelservidor adoptado por 10s programa-
dores, la mayor parte (o incluso toda) de la carga de procesamiento de datos se
puede realizar en el servidor. Asi, se permite que un potente servidor ofrezca
sewicios de abastecimiento de datos a multiples clientes de menor potencia.
Claro esta que estas no son las unicas razones que llevan a la utilizacion de
servidores centralizados de bases de datos: se suman, por ejemplo, la preocupa-
cion por la seguridad y la integridad de 10s datos, estrategias de copias de seguri-
dad mas sencillas, la gestion central de las restricciones de datos, etc. Los servidores
de bases de datos suelen llamarse servidores SQL, dado que ese es precisamente
el lenguaje que mas suele utilizarse para efectuar consultas sobre 10s datos. Tam-
bien pueden denominarse RDBMS (sistema de administracion de bases de datos
relacionales), recalcando de esta forma que el servidor proporciona una serie de
herramientas para la administracion de 10s datos, como el soporte de la seguridad
y las tareas de replicacion de la informacion.
Por supuesto, alguna de las aplicaciones creadas puede no necesitar las venta-
jas que ofrece un RDBMS completo, con lo que tal vez baste con una solucion
orientada unicamente a1 cliente. Por otra parte, podria darse el caso en el que se
necesitase parte de la robustez de un RDBMS, per0 en un unico ordenador aisla-
do. Ante una situacion como esa, puede emplearse una version local de un semi-
dor SQL, como InterBase. El desarrollo clientelservidor tradicional suele realizarse
bajo una arquitectura de dos capas. Sin embargo, si el RDBMS realiza principal-
mente tareas de almacenamiento de datos mas que de calculo de datos y numeros,
el cliente podria contener tanto codigo para la interfaz de usuario (formateando la
salida y la entrada mediante informes personalizados, formularios de entrada de
datos, pantallas de consulta, etc.) como codigo relacionado con la gestion de 10s
datos (tambien llamado reglas de negocio). En este caso, suele ser buena idea
tratar de separar estas dos secciones del programa y crear una arquitectura logica
de tres capas. El termino "logica" implica que seguimos contando con dos ordena-
dores (es decir, dos capas fisicas), per0 ahora la aplicacion se ha partido en tres
elementos diferentes. Delphi 2 proporcionaba soporte para arquitecturas logicas
de tres capas mediante modulos de datos. Como recordara, un modulo de datos
consiste en un contenedor no visual para 10s componentes de acceso a datos de
una aplicacion (u otras componentes no visuales), si bien a menudo incluye varios
controladores para eventos relacionados con bases de datos. Puede compartirse
un unico modulo de datos entre diferentes formularios y ofrecer diferentes interfaces
de usuario para 10s mismos datos. Podrian existir uno o mas formularios de entra-
da de datos, informes, formularios maestroldetalle y diversos formularios de sali-
da dinamica o en diagrama.
El enfoque logico de tres capas representa la solucion a multiples problemas,
per0 tambien tiene varios inconvenientes. En primer lugar, es precis0 reproducir
la parte del programa dedicada a la administracion de datos en 10s ordenadores de
diferentes clientes, lo cual podria afectar a1 rendimiento, aunque el aspect0 mas
importante es la complejidad que esto aiiade a1 mantenimiento del codigo. En
segundo lugar, dado que 10s mismos datos estan siendo actualizados por diversos
usuarios, no existe un mod0 simple de gestionar 10s conflictos de actualizacion
resultantes. Por ultimo, las aplicaciones logicas de tres capas de Delphi requieren
la instalacion y configuracion del motor de la base de datos (si existe) y de la
biblioteca de cliente del servidor SQL en cada ordenador cliente.
El siguiente paso logico desde el escenario clientelservidor consiste en trasla-
dar la parte de la aplicacion integrada por el modulo de datos a un servidor
independiente y diseiiar todos 10s programas cliente de tal forma que Sean capaces
de interactuar con dicho servidor. ~ s t era
e precisamente el objetivo de 10s m6du-
10s de datos remotos introducidos en Delphi 3 . Los modulos de datos remotos se
ejecutan en un ordenador servidor, llamado generalmente servidor de aplicacion.
Por su parte, este se comunica con el RDBMS (que puede ejecutarse en el servi-
dor de aplicacion o en otro ordenador dedicado a ese fin). Asi, las maquinas
cliente no se conectan directamente a1 sewidor SQL, sino de forma indirecta a
traves del servidor de aplicacion.
En este punto se plantea una cuestion importante: ~ S i g u esiendo necesario
instalar el software de acceso a la base de datos? La tradicional arquitectura
clientelservidor de Delphi (incluso con tres capas logicas) requiere la instalacion
de dicho software en cada cliente, lo que puede resultar bastante problematico
cuando la cantidad de ordenadores a configurar y mantener es elevada. En la
arquitectura de tres capas fisica, basta con instalar y configurar el software de
acceso solo el servidor de aplicacion, no en las maquinas cliente. Ya que 10s
programas cliente contienen tan solo el codigo de la interfaz de usuario y su
instalacion es realmente sencilla, ahora entran en la categoria denominada clien-
tes ligeros. Para usar el lenguaje del mercado, podriamos llamar a esto una arqui-
tectura de clientes ligeros de configuracion cero. Pero sera mejor que nos
dediquemos a cuestiones tecnicas en lugar de a la terminologia del mercado.

Fundamento tecnico de DataSnap


Cuando Borland introdujo en Delphi la arquitectura fisica multicapa, la Ilamo
MIDAS (Middle-tier Distributed Application Services). Por ejemplo, Delphi 5
incluia la tercera version de esta tecnologia, MIDAS 3 . Despues de eso, Borland
renombro la tecnologia como DataSnap y amplio sus capacidades.
DataSnap requiere la instalacion de bibliotecas especificas en el sewidor (real-
mente el ordenador de la capa intermedia), que proporciona a 10s ordenadores
cliente 10s datos extraidos de las bases de datos del sewidor SQL o de otras
fuentes de datos. DataSnap no necesita un servidor SQL para almacenar 10s da-
tos. Puede ofrecer datos procedentes de una amplia variedad de fuentes, como
sewidores SQL, otros servidores DataSnap, o incluso datos calculados sobre la
marcha. Como cabria esperar, la parte cliente de DataSnap es muy ligera y facil
de aplicar. El unico archivo necesario es Midas . dl1,una pequeiia DLL que
implements 10s componentes Client Dataset y Remoteserver y propor-
ciona la conexion al sewidor de aplicacion. Como alternativa a la distribucion de
esta DLL, se puede incluir el codigo de esta biblioteca en el archivo ejecutable
incluyendo la unidad MidasLib en la sentencia uses.

La interfaz AppServer
Las dos partes de una aplicacion DataSnap se comunican mediante la interfaz
IAppServer.La definicion de esta interfaz se muestra en el listado 16.1. Es
extraiio que se necesite llamar directamente a 10s metodos de la interfaz
IAppServer,ya que Delphi incluye componentes que implementan esta interfaz
en las aplicaciones del lado del servidor y componentes que llaman a la interfaz en
las aplicaciones del cliente. Estos componentes simplifican el soporte de la interfaz
IAppServer y en ocasiones incluso la ocultan por completo. En la practica, el
servidor pondra a disposicion del cliente ob.jetos que implementen esta interfaz,
posiblemente junto con otras interfaces personalizadas.

Listado 16.1. La definicion de la interfaz IAppServer.

type
IAppServer = interface (IDispatch)
[ ' /lAEPCC20- 7 A 2 4 - 1 lD2-9SBO-C69BEB+'B5B6D/ ']
function AS-Applyupdates (const ProviderName: WideString;
Delta: OleVariant;
MaxErrors: Integer; out Errorcount: Integer;
var Ownerdata: OleVarlant): OleVariant; safecall;
function AS-GetRecords (const ProviderName: WideString;
Count: Integer;
out RecsOut: Integer; Options: Integer; const
CommandText: WideString;
var Params: OleVariant; var OwnerData: OleVariant):
OleVarlant; safecall;
function AS-DataRequest(c0nst ProviderName: WideString;
Data: OleVariant): OleVariant; safecall;
function AS-GetProviderNames: OleVariant; safecall;
function AS-GetParams(const ProviderName: Widestring;
var Ownerdata: OleVariant): OleVariant; safecall;
function AS-RowRequest(const ProviderName: WideString; Row:
OleVariant;
RequestTpe: Integer; var OwnerData: OleVariant):
OleVariant; safecall;
procedure AS-Execute(const ProviderName: WideString;
const CommandText: WideString; var Params: OleVariant;
var OwnerData: OleVariant); safecall;
end;
- - -- -

NOTA: Un servidor DataSnap expone una interfaz mediante el uso de una


biblioteca de tip0 COM, una tecnologia de la que ya hemos hablado.

Protocolo de conexion
DataSnap define unicamente la arquitectura de nivel superior y puede emplear
diferentes tecnologias para transferir datos desde la capa intermedia a1 entorno
del cliente. Soporta muchos protocolos distintos, entre 10s que destacan:
Distributed COM (DCOM) y Stateless COM (MTS o COM+): DCOM
esta disponible directamente en Windows NT/2000/XP y 98/Me, y no ne-
cesita aplicaciones en tiempo de e.jecucion adicionales en el servidor. DCOM
es basicamente una ampliacion de la tecnologia COM; que permite a las
aplicaciones cliente utilizar objetos de servidor que ya existen y ejecutar-
10s en un ordenador aparte. La infraestructura DCOM permite la utiliza-
cion de objetos COM sin estado (stateless), disponibles en las arquitecturas
de COM+ y de versiones anteriores de MTS (Microsoft Transaction Sewer).
COM+ y MTS ofrecen ciertas caracteristicas como seguridad, capacidad
de gestion de componentes y transacciones de bases de datos y estan dispo-
nibles en Windows NTl2000lXP y en Windows 98lMe. Debido a la com-
plejidad de configuracion de DCOM y sus problemas a la hora de atravesar
cortafuegos, incluso Microsoft trata de abandonar DCOM en favor de
soluciones basadas en SOAP.
Sockets TCPIIP: Estan disponibles en la mayoria de 10s sistemas. Usando
TCPIIP, podremos distribuir 10s clientes por la toda la Web, donde DCOM
no siempre es aplicable, y podemos ahorrarnos problemas de configura-
cion. Para utilizar 10s sockets, el ordenador de la capa intermedia debe
ejecutar la aplicacion scktsrvr . e x e proporcionada por Borland: un
sencillo programa ejecutable como aplicacion o como servicio. Este pro-
grama recibe las peticiones del cliente y las reenvia a1 modulo de datos
remoto (que se ejecuta en el mismo servidor) a traves de COM. Los sockets
no ofrecen ninguna proteccion frente a 10s posibles errores que puedan
surgir en el cliente, ya que el servidor no recibe informacion y podria no
liberar recursos~cuandoun cliente se desconecte de forma inesperada.
H T T P y SOAP: El uso de HTTP como protocolo de transporte de Internet
simplifica las conesiones a traves de 10s cortafuegos o 10s servidores inter-
medios o prosy (que no suelen gustar de 10s sockets TCPIIP personalizados).
.
Se requiere una aplicacion de servidor Web especifica, h t tpsrvr dl 1;
que acepte las solicitudes de 10s clientes y Cree 10s modulos de datos remo-
tos pertinentes mediante COM. Estas conexiones Web tambien pueden em-
plear la seguridad SSL. Ademas, las conexiones Web basadas en el
transporte HTTP pueden usar el soporte de interconesion de objetos de
DataSnap.

NOTA: El transporte HTTP de DataSnap puede utilizar XML como for-


mato de 10s paquetes de datos, permitiendo que cualquier plataforma o
herramienta capaz de leer dicho formato pueda formar parte de una arqui-
tectura DataSnap. Se trata de una extension del formato original de paque-
tes de datos de DataSnap, que tampoco estaba supeditado a ningun tip0 de
plataforma en particular: La utilization de XML sobre HTTP es tarnbikn la
base de SOAP.

Hasta Delphi 6, tambien se podia usar CORBA (Common Object Request


Broker Architecture) como mecanismo de transporte para aplicaciones DataSnap.
Debido a problemas de compatibilidad con las nuevas versiones de la solucion
CROBA de Borland, VisiBroker, esta caracteristica ha dejado de estar presente
en Delphi 7.
Por ultimo, hay que fijarse en que como extension de esta arquitectura se
pueden convertir 10s paquetes de datos a XML y enviarlos a un navegador Web.
En este caso, solo se contara con una unica capa adicional: el servidor Web toma
10s datos de la capa intermedia y 10s hace llegar a1 cliente. En un capitulo poste-
rior hablaremos de esta arquitectura, llamada Internet Express.

Proporcionar paquetes de datos


El conjunto de la arquitectura multicapa de Delphi de acceso a datos se centra
en el concept0 de 10s paquetes de datos. En este contexto, un paquete de datos es
un bloque de datos que va desde el servidor de aplicacion a1 cliente o viceversa.
Desde el punto de vista tecnico, un paquete consiste en una especie de subconjunto
de un conjunto de datos. Describe 10s datos que contiene (por lo general, unos
cuantos registros de datos) y enumera las denominaciones y 10s tipos de 10s cam-
pos de datos. Por si fuera poco, un paquete de datos incluye las restricciones (es
decir, las reglas aplicables a1 conjunto de datos). Dichas restricciones suelen
establecerse en el servidor de aplicacion que las envia a las aplicaciones cliente
junto con 10s datos.
Toda comunicacion entre cliente y servidor tiene lugar a traves del intercam-
bio de paquetes de datos. El componente proveedor del servidor gestiona la trans-
mision de varios paquetes de datos dentro de un conjunto de datos de grandes
dimensiones, con el objeto de responder con mayor rapidez a1 usuario. En cuanto
el cliente recibe un paquete de datos en un componente ClientDataSet, el usuario
puede editar 10s registros que contiene. Como ya se ha mencionado con anteriori-
dad, durante este proceso el cliente tambien recibe y verifica las restricciones, que
se aplican durante las operaciones de edicion.
Cuando el cliente ha actualizado 10s registros y devuelve un paquete de datos,
el paquete recibe el nombre de delta. El paquete delta (o incremental) se encarga
del seguimiento de las diferencias entre 10s registros originales y 10s actualizados,
registrando todas las modificaciones que el cliente solicita a1 servidor. Cuando el
cliente solicita la aplicacion de las modificaciones en el senidor, le envia el pa-
quete delta y el servidor trata de aplicar todas las modificaciones.
Decimos "trata", porque si el servidor esta conectado a varios clientes, puede
darse el caso de que 10s datos ya hayan variado y la solicitud de actualizacion
puede fallar.
Dado que el paquete delta incluye 10s datos originales, el servidor puede deter-
minar rapidamente si estos ya han sido modificados por otro cliente, en cuyo
caso, el servidor lanzara un evento OnRe c o n c i leEr ror, que es uno de 10s
elementos principales para las aplicaciones de cliente ligero. En otras palabras, la
arquitectura de tres capas emplea un mecanismo de actualizacion similar a1 que
utiliza Delphi para las actualizaciones almacenadas. El C l i e n t D a t a S e t ges-
tiona 10s datos en una cache de memoria y solo suele leer un subconjunto de 10s
datos disponibles en el servidor, cargando mas elementos a medida que 10s necc-
sita. Cuando el cliente actualiza registros o inserta unos nuevos, almacena estos
cambios pendientes en otra cache local en el cliente, la cache delta.
El cliente tambien puede guardar 10s paquetes de datos en un disco y trabajar
con ellos desconectado, gracias a1 soporte de MyBase ya comentado. El protocolo
de paquetes de datos mueve incluso la informacion sobre errores y otros datos,
por eso se trata de uno de 10s elementos clave de esta arquitectura.
-

NOTA: Es fundamental recordar que 10s paquetes de datos no estan sujetos


-
a ningun protocolo. Se tratan unicamente de una secuencia de bytes, de
mod0 que alla donde se pueda transferir una secuencia de bytes, se podra
asimismo enviar un paquete de datos. Se proporciono esta prestacion para
que la arquitectura resultara adecuada para multiples protocolos de trans-
porte (inclusive DCOM,HTTP y TCPIIP) y para multiples plataformas.

Componentes de soporte Delphi (entorno


cliente)
Una vez analizados 10s fundamentos generales de la arquitectura de tres capas
de Delphi, podcmos centrarnos en 10s componentes sobre 10s que se apoya. Para
el desarrollo de aplicaciones clientc, Delphi proporciona el componente
ClicntDataSet, que ofrece todas las herra~nientasestandar de 10s conjuntos de
datos e incluye el entorno de cliente de la interfaz IAppServer. En este caso, 10s
datos se transportan a traves de la conexion remota.
La conexion a1 servidor de aplicacion tiene lugar a traves de otro componente
que tambien es necesario en la aplicacion cliente. Deberia emplearse uno de estos
tres componentes de conexion especificos (disponibles en la ficha Datasnap):
El componente DCOMConnection: Puedc utilizarse en el cliente para
establecer una conesion a un servidor DCOM y MTS, que se encuentre en
el ordenador actual o en otro indicado por medio de la propiedad
ComputerName. La conexion se realiza con un objeto registrado que
time un S e r v e r G U I D o S e r v e r N a m e dado.
El componente Socketconnection: Puede utilizarse para conectar con el
servidor a traves de un socket TCPIIP. Es precis0 indicar la direccion IP (o
el nombre de la maquina) y el GUID del objeto del servidor (en la propie-
dad I n t e r c e p t G U I D ) . Este componente de conesion tiene una propie-
dad adicional: S u p p o r t C a l l b a c k s , que se podra desactivar si no se
utilizan retrollamadas (callbacks) y se desea desplegar el programa en
ordenadores de cliente con Windows 95 en 10s que no se ha instalado
Winsock 2.

NOTA: En la ficha WebServices, tambien se: puede encontrar el compo-


nente SoapConnection, que exige un tipo especiifico de servidor.

El componente Webconnection: Se utiliza para manejar conexiones HTTP


que pueden atravesar facilmente un cortafuego. Deberia indicarse el URL
en la que se encuentra la copia de h t tpsrvr . d l 1 y el nombre o GUID
del objeto remoto en el servidor.
En Delphi 6 se aiiadieron unos cuantos compones de cliente mas a la arquitec-
tura de DataSnap, la mayoria destinados a la administracion de las conesiones.
El componente ConnectionBroker: Puede usarse para sustituir a un com-
ponente de conesion real, lo cual resulta bastante util cuando se tiene una
unica aplicacion con multitud de conjuntos de datos de cliente. Para modi-
ficar la conexion fisica de cada conjunto de datos, bastara con modificar la
propiedad c o n n e c t i o n del ConnectionBroker. Asimismo, se podran usar
10s eventos de este componente virtual de conesion en lugar de 10s de las
conesiones reales, lo cual evitara tener que modificar codigo alguno cuan-
do se cambie la tecnologia de transporte de datos. Por esta misma razon,
tambien sera posible referirse al objeto AppServer del ConnectionBroker
en lugar de a la propiedad correspondiente de una conexion fisica.
EI componente SharedConnection: Puede utilizarse para conectar con un
modulo de datos secundario (tambien denominado "hijo") de una aplica-
cion remota, adhiriendose a una conesion fisica ya esistente con el modulo
de datos principal. Dicho de otra forma, una aplicacion puede conectarse a
varios modulos de datos del servidor a traves de una unica conesion com-
partida.
El componente LocalConnection: Puede utilizarse para atacar a un pro-
veedor local de conjuntos de datos como fuente del paquete de datos. Co-
nectando directamente el ClientDataSet al proveedor se lograrh el mismo
efecto. No obstante, a1 usar Localconnection se puede escribir una aplica-
cion local con el mismo codigo que una aplicacion multicapa completa,
mediante la interfaz IAppServer de la conesion "ficticia". A consecuencia
de esto, el programa sera mas facil de ampliar, en comparacion con un
programa de conexion directa.
ficha DataSnap presenta otros componentes relacionados con la transfor-
macion del paquete de datos de DataSnap en formatos XML personalizados. Estos
componentes (XMLTransform, XMLTransformProvider y XMLTransformClient)
se trataran mas adelante.
Componentes de soporte Delphi (entorno
servidor)
En el entorno del servidor (realmente la capa intermedia) se necesitara crear
una aplicacion o biblioteca que incluya un modulo de datos remoto, una version
especial de la clase TDataModule. Una segunda alternativa es el uso de un
modulo de datos remoto especializado que soporte el mod0 transaccional de COM.
En la ficha Multitier del cuadro de dialogo New Items (a1 que se llega mediante
la opcion de menu File>New>Others) existen asistentes especificos para crear
ambos tipos de modulos de datos remotos.
El unico componente especifico requerido en el servidor es Datasetprovider.
Se requiere uno de estos componentes para cada tabla o consulta que se desee
poner a disposicion de las aplicaciones cliente, que a su vez emplearan un compo-
nente ClientDataSet aparte para cada conjunto de datos exportado.

Construccion de una aplicacion de ejemplo


Ahora podemos crear un programa a mod0 de ejemplo. Esto nos permitira
observar como funciona alguno de 10s componentes que se acaban de mencionar,
conocer otros problemas y desentraiiar mas enigmas del laberinto multicapa de
Delphi. En este caso construiremos las secciones de cliente y servidor de aplica-
cion de una aplicacion de tres capas en dos fases. La primera fase consistira
unicamente en probar la tecnologia utilizando un numero minimo de elementos.
Estos programas seran muy sencillos.
A partir de aqui, se aiiadira mas potencia a1 cliente y a1 servidor de aplicacion.
En cada uno de 10s ejemplos, se mostrarin 10s datos tornados de una tabla InterBase
local mediante dbExpress y se preparara todo para permitir la prueba de 10s
programas en un ordenador independiente. No vamos a comentar 10s pasos a
seguir para instalar 10s ejemplos en multiples ordenadores con diversas tecnolo-
gias, ya que ese tema requeriria otro libro por si solo.

El primer servidor de aplicacion


La parte de servidor de este ejemplo basico es muy facil de construir. Basta
con crear una nueva aplicacion y aiiadirle un modulo de datos remoto mediante el
icono correspondiente que aparece en la ficha Multitier del Object Repository.
El asistente Remote Data Module Wizard (vease figura 16.1) pedira que se
indique un nombre de clase y el estilo de instanciacion. Tras escribir el nombre
solicitado, como AppServerOne y hacer clic sobre el boton OK, Delphi aiiadi-
ra un modulo de datos a1 programa. Dicho modulo tendra las propiedades y even-
tos habituales, per0 su clase incluira la siguiente declaracion en Delphi:
type
TAppServerOne = class(TRemoteDataModule, IAppServerOne)
private
( Private declarations )
protected
c l a s s p r o c e d u r e UpdateRegistry(Register: Boolean;
c o n s t ClassID, ProgID: string); override;
public
{ Public declarations )
end;

!nrlanci-g I ~ u l i i l eInstance
AI

Figura 16.1. El asistente Remote Data Module Wizard.

Ademas de tomar muchos elementos de la clase baseTRemoteDataModule,


esta clase implementa la interfaz personalizada IAppServerOne, que se deriva
de la interfaz estandar de DataSnap (IAppServer). Por otra parte, tambien
sobrescribe el metodo U p d a t eReg is t r y para aiiadir el soporte necesario de
cara a1 transporte mediante sockets y Web, como puede observarse en el codigo
generado por el asistente. A1 final de la unidad, se encontrara la declaracion de la
fabrica de clase:
initialization
TComponentFactory.Create(ComServer, TAppServerOne,
Class-AppServerOne, ciMultiInstance, tmApartment);
end.

Ahora se puede aiiadir un componente de conjunto de datos a1 modulo de datos


(en el ejemplo se ha utilizado el SQLData s e t de dbEspress). Se conecta a una
base de datos y a una tabla o consults, se activa, se aiiade un DataSetProvider y,
por ultimo, se enlaza con el componente de conjunto de datos. El resultado sera un
archivo DFM como este:
object AppServerOne: TAppServerOne
object SQLConnectionl: TSQLConnection
ConnectionName = ' I B L o c d l l
LoginPrompt = False
end
object SQLDataSetl: TSQLDataSet
SQLConnection = SQLConnectionl
CommandText = 'select * f r o m EMPLOYEE'
end
object DataSetProviderl: TDataSetProvider
DataSet = SQLDataSetl
Constraints = True
end
end

El formulario principal dc cste programa es casi completamente inutil, de mod0


que sc puede simplemente insertar una etiqueta para indicar que se trata del for-
mulario de la aplicacion del servidor. Cuando se construya el servidor, deberia
compilarse y c.jecutarse una vez. Esta operacion lo registrars automaticamente
como servidor de Automatizacion en el sistema, a disposicion de las aplicaciones
clicnte. Por supuesto, el servidor deberia registrase en el ordenador en el que se
quiera ejecutar, ya sea el del cliente o el de la capa intermedia.

El primer cliente ligero


Ahora que contamos con un servidor operativo. podemos crear un cliente que
se conectc a el. De nuevo partiremos de una aplicacion estandar y le aiiadiremos
un componente DCOMConnect ion (o el componente acorde con el tipo especi-
fico dc conexion quc se desec probar). Este componente define una propiedad
ComputerName que se ha de utilizar para especificar el ordenador que alberga
el scrvidor de aplicacion. Si se desea probar el cliente y el servidor dc aplicacion
desde el mismo ordenador, no es necesario rellenar este campo.
Despues de haber seleccionado un ordenador para el servidor de aplicacion,
puede visualizarse sin mas la lista de cuadro combinado de la propiedad
ServerName para ver 10s servidores DataSnap disponibles. El cuadro combi-
nado mostrara 10s nombres registrados de 10s servidorcs: de manera predefinida.
el nombre del archivo ejecutable del servidor, seguido del nombre del modulo de
datos remoto, como por ejemplo AppServl .AppServerOne. Asimismo, tam-
bien se podra escribir el GUID del objeto de servidor en la propiedad
ServerGUID.Delphi rellenara automaticamente esta propiedad cuando se esta-
blezca la propiedad ServerName, detcrminando el GUID a traves de una bus-
queda en el Registro.
Si se define como T r u e la propiedad C o n n e c t e d del componente
DCOMConnection. aparecera el formulario del servidor: que indicara que el cliente
ha activado el servidor. Por lo general no sera necesario efectuar esta operacion,
ya que el componente ClientDataSet suele activar por si mismo el componente
Remoteserver. Pero esto es lo que sucedc detras del telon.

TRUCO: Por regla general, en 1a fase (je diseiio, la propiedad Con~ n e c t e d


.a-I -- - r- lt~i o~ne deberia permanecer coma1 False,
. .
&- n**.,P.-
UGI c o ~ ~ ~ p u u ~
u u~r u
c r n ~ u c;
para asi poder abrir el proyecto en De~pn~,
-
. . rnc~usoen un oraenador en el
que a h no estt registrado el servidor DataSnap.
Como cabe esperar, el siguiente paso consiste en afiadir a1 formulario un com-
ponente ClientDataSet. El ClientDataSet debe conectarse a1 componente
D C O M C o n n e c t i o n 1 mediante la propiedad R e m o t e s e r v e r , y a traves de
esta a uno de 10s proveedores que esporta. La lista de proveedores disponibles
puede consultarse en la propiedad P r o v i d e r N a m e , a traves del habitual cuadro
combinado. En este ejemplo, se podra seleccionar unicamente D a t a S e t P r o -
v i d e r l , puesto que se trata del unico proveedor disponible en el servidor que
acabamos de crear. Con esta operacion, el conjunto de datos que figura en la
memoria del cliente queda conectado con el conjunto de datos dbEspress del
servidor. Si se activa el conjunto de datos del cliente y se afiaden una serie de
controles data-aware (o un control DBGrid), apareceran inmediatamente en ellos
10s datos dcl servidor, tal y como muestra la figura 16.2.

m1
11
OEPT-NO~EMP-NO FIRST-NAME IFULL-WE IHIRE~-
-
) SUO , 2'R0beit Pidsan, Robnt 28/12
-E 2 b ~ ~ ~ ~ m w e c & o n ~ Yang, B~uce 28/12
130 5 Kim Lambert, Kim 6/2/1
Johnson, Lesb 5/4/1
- 622 Clia;DaaSdl 9 Phil Forest, Phil 17/11
- 130
000 i +
11 K. J.
12Te1ri
Wedm, K. J
Len. Terri
17tlt-
1/5/1
1 9 ~ "
~
623 DataSou~cel
149emt Hall. Stewart 4/6/1
- 15 Kalheme Y- Katherine
- 671 M Chr~s Papmkrpoulos. Chris
- 671 24 Pete F~her,Pete 1219/
- 120 28 Ann Bemet. Ann 1/2/
- 623 29 Roger De Swza. R o w 18/21
110 34 Janel B a l k . Jan&

Figura 16.2. Al activar un componente ClientDataSet conectado a un modulo de


datos remoto durantela fase de diseiio, 10s datos del servidor se mostraran de la
forrna habitual.

Este es el archivo DFM de nuestra aplicacion de clientc ligero, ThinCli 1


o b j e c t Forml : TForml
Caption = ' T h i n C l i e n t l '
object DBGridl: TDBGrid
Align = alClient
Datasource = DataSourcel
end
o b j e c t DCOMConnectionl: TDCOMConnection
ServerGUID = ' ( O 9 E l l D 6 3 - 4 A 5 5 - 1 l D 3 - B 9 F l -OOOOOlOOA,?7B) '
ServerName = ' A p p S e r v l . A p p S e r v e r O n e '
end
o b j e c t ClientDataSetl: TClientDataSet
Aggregates = <>
Params = <>
ProviderName = ' D a t a S e t P r o v i d e r l l
Remoteserver = DCOMConnectionl
end
o b j e c t DataSourcel: TDataSource
DataSet = ClientDataSetl
end
end

Los programas de esta primera aplicacion de tres capas son obviamente muy
sencillos, aunque sirven de ejemplo sobre como crear un visualizador de conjun-
tos de datos capaz de repartir el trabajo entre dos ficheros ejecutables diferentes.
A estas alturas, nuestro cliente solo desempeiia funciones de visualizacion. Si 10s
datos se editan en el cliente, estos no se actualizaran en el servidor. Para llevar a
acabo esta operacion, sera precis0 aiiadir a1 programa algo de codigo adicional.
No obstante, antes de proceder con ello, aiiadiremos algunas caracteristicas mas
a1 servidor.

Adicion de restricciones al servidor


A1 escribir un modulo de datos tradicional en Delphi, puede aiiadirse sin ma-
yor problema parte de la logica de aplicacion (o reglas de negocio), tratando 10s
eventos del conjunto de datos y configurando propiedades de objetos de campo y
tratando sus eventos. No es recomendable realizar esta tarea en la aplicacion
cliente. En lugar de eso, es preferible escribir las reglas de negocio en la capa
intermedia.
En la arquitectura Datasnap, pueden enviarse una serie de restricciones del
servidor a1 cliente, permitiendo luego que el programa cliente las imponga duran-
te la entrada del usuario. Tambien pueden enviarse a1 cliente propiedades de
campo (como un valor minimo y maximo y las mascaras de visualizacion y edi-
cion) y (mediante alguna de las tecnologias de acceso de datos) procesar actuali-
zaciones sirviendose del conjunto de datos utilizado para acceder a 10s datos (o un
objeto U p d a t esql asociado).

Restricciones de campo y conjuntos de datos


Cuando la interfaz del proveedor crea paquetes de datos para enviarselos a1
cliente, incluye las definiciones de campo, las restricciones de campo y tabla, y
uno o mas registros (conforme a lo solicitado por el componente ClientDataSet).
Esto permite personalizar la capa intermedia y crear una logica de aplicacion
distribuida, a partir de restricciones basadas en SQL.
Las restricciones creadas mediante expresiones SQL pueden asignarse tanto a
un conjunto de datos completo como a campos especificos. El proveedor envia las
restricciones a1 cliente junto con 10s datos, y el cliente las aplica antes de enviar
actualizaciones a1 servidor. Esto implica una reduccion del trafico de red, en
comparacion con el que se ocasiona cuando el cliente envia actualizaciones a1
servidor de aplicacion e incluso a1 servidor SQL, solo para descubrir que 10s
datos no son validos. Otra de las ventajas que ofrece la codificacion de las restric-
ciones en el lado del servidor consiste en el hecho de que si cambian las reglas de
negocio, bastara con actualizar el unico servidor de aplicacion y no todos y cada
uno de 10s diversos clientes instalados en diferentes ordenadores.
Para definir las restricciones, se pueden utilizar varias propiedades:
Los conjuntos de datos BDE tienen una propiedad constraints, que
consiste en un conjunto de objetos TChec kCons traint. Cada objeto
tiene unas pocas propiedades, entre las que se incluyen la expresion y el
mensaje de error.
Cada objeto de campo define las propiedades c u s t omcons t raint y
C o n s t r a i n t E r r o r M e s s a g e . Tambien existe una propiedad
Import edCo ns t r a i nt para las restricciones importadas desde el ser-
vidor SQL.
Cada objeto de campo tiene una propiedad Def aultExpres s ion que
puede utilizarse localmente o que puede transferirse al ClientDataSet. No
se trata de una restriccion real, es tan solo una sugerencia para el usuario
final.
El ejemplo siguiente, AppServ2, aiiade unas cuantas restricciones a un modulo
de datos remoto conectado a la base de datos de muestra EMPLOYEE de InterBase.
Despues de conectar la tabla a la base de datos y tras haber creado 10s objetos de
campo pertinentes, pueden establecerse las siguientes propiedades especiales:
o b j e c t SQLDataSetl: TSQLDataSet
.. .
o b j e c t SQLDataSetlEMP-NO: TSmallintField
Customconstraint = ' x > 0 a n d x < 1 0 0 0 0 '
ConstraintErrorMessage =
'Employee number m u s t b e a p o s i t i v e i n t e g e r b e l o w 10000'
FieldName = 'EMP-NO'
end
o b j e c t SQLDataSetlFIRST-NAME: TStringField
CustomConstraint = ' x <> ' # 3 9 # 3 9
ConstraintErrorMessage = ' T h e f i r s t n a m e i s r e q u i r e d '
FieldName = 'FIRST-NAME'
Size = 15
end
o b j e c t SQLDataSetlLAST-NAME: TStringField
CustomConstraint = ' n o t x i s n u l l '
ConstraintErrorMessage = ' T h e l a s t n a m e i s r e q u i r e d '
FieldName = ' LAST-NAME'
end
end

La expresion 'x <> '#39#39 es la transposicion DFM de la cadena x <> ",que


indica que no se desea recibir una cadena vacia. La restriccion final, 'not x is
null', acepta en cambio cadenas vacias, per0 no valores nulos.
Inclusion de propiedades de campo
Utilizando del valor po IncFie ldProps de la propiedad Options del
DataSetProvider, se puede controlar si las propiedades que presentan 10s objetos
de campo de la capa intermedia se envian a1 ClientDataSet (y se copian en 10s
objetos de campo correspondientes a1 lado del cliente). Este indicador controla la
descarga de las propiedades de campo Alignment, DisplayLabel,
DisplayWidth,Visible, DisplayFormat,EditFormat,MaxValue,
MinValue, Currency, EditMask y DisplayValues, siempre y cuando
esten disponibles en el campo. A continuacion, se ofrece un ejemplo de otro de 10s
campos del ejemplo AppServ2 con propiedades que se pueden personalizar:
object SQLDataSetlSALARY: TBCDField
DefaultExpression = ' 1 0 0 0 0 '
FieldName = ' SALARY'
DisplayFormat = ' # , # # # I
EditFormat = ' # # # # I
Precision = 15
Size = 2
end

Con esta configuracion, se puede actualizar la capa intermedia de la misma


forma en que se configuran 10s campos de las aplicaciones servidortcliente estandar.
Este enfoque agiliza a su vez el paso de aplicaciones existentes desde una arqui-
tectura clientetservidor a una arquitectura multicapa. El principal inconveniente
que presenta el envio de campos a1 cliente es que transmitir toda la informacion
adicional consume tiempo. Si se desactiva poIncFieldProps,se puede expe-
rimentar una notable mejora en el rendimiento en red de 10s conjuntos de datos
dotados de muchas columnas.
Por lo general un servidor puede filtrar 10s campos deweltos a1 cliente; realiza
esta operacion declarando objetos de campo persistentes con el editor Fields y
omitiendo algunos de 10s campos. Es posible que uno de 10s campos filtrados sea
necesario para identificar el registro en futuras actualizaciones (si el campo for-
ma parte de la clave primaria), por lo que tambien se puede utilizar la propiedad
Provider Flags del campo en el servidor para enviar el valor de campo a1
cliente, per0 evitar que este disponible para el componente ClientDataSet (de esta
forma, se conseguira algo de seguridad adicional, en comparacion con lo que
supondria enviar el campo a1 cliente y luego ocultarlo en dicho entorno).

Eventos de campo y tabla


Podemos escribir conjuntos de datos y controladores de eventos de campo de
la capa intermedia como de costumbre y dejar que el conjunto de datos procese de
la forma habitual las actualizaciones recibidas por el cliente. Esto significa que
las actualizaciones se consideraran operaciones sobre el conjunto de datos, exac-
tamente igual que cuando un usuario edita, inserta, o borra directamente campos
de manera local.
Esto proceso de actualizacion se solicita estableciendo la propiedad
ResolveToDataSet del componente TDat a s e t p r o v i d e r , conectando de
nuevo el conjunto de datos utilizado para la entrada o un segundo conjunto usado
para actualizaciones. Este enfoque es posible con conjuntos de datos aptos para
operaciones de edicion, como 10s conjuntos de datos de BDE, ADO, e InterBase
Express, per0 no 10s pertenecientes a la nueva arquitectura dbExpress.
Con esta tecnica, es el propio conjunto de datos el que realiza las actualizacio-
nes, lo que implica un alto grado de control (se lanzan 10s eventos estandar)
asociado generalmente a una disminucion del rendimiento. El grado de flexibili-
dad es mucho mayor, ya que se puede utilizar incluso metodos estandar de pro-
gramacion. Ademas, adaptar las aplicaciones de bases de datos locales o clientel
servidor existentes, que utilizan eventos de conjuntos de datos y campos, es mu-
cho mas directo de acuerdo con este modelo. Sin embargo, hay que tener en
cuenta que 10s usuarios del programa cliente recibiran 10s mensajes de error emi-
tidos solo cuando la cache local (10s paquetes delta) se envien de vuelta a la capa
intermedia. Decir a1 usuario que algunos datos preparados hace media hora ya no
son validos, podria parecer algo raro. Si se adopta este enfoque, probablemente
sea precis0 aplicar las actualizaciones en la cache cada vez que se produzca un
evento A f t e r P o s t en el entorno del cliente.
Por ultimo, si se permite que sea el conjunto de datos y no el proveedor el
encargado de realizar las actualizaciones, Delphi resulta de gran utilidad para
tratar posibles excepciones. Cualquier excepcion lanzada por 10s eventos de ac-
tualizacion de la capa intermedia (por ejemplo OnBef o r e P o s t ) , Delphi la trans-
formara automaticamente en un error de actualizacion que activara el evento
OnRe c o n c i l e E r r o r en el cliente. En la capa intermedia no se muestra excep-
cion alguna, sino que el error vuelve a1 cliente.

Adicion de caracteristicas al cliente


Despues de afiadir alguna que otra restriccion y propiedad de campo a1 servi-
dor, podemos volver nuestra atencion a la aplicacion cliente. La primera version
era muy sencilla, per0 ahora podemos afiadir varias caracteristicas para mejorar
su rendimiento. En el ejemplo ThinCli2 se ha incluido el soporte necesario para
comprobar el estado del registro y acceder a la informacion delta (las actualiza-
ciones que han de enviarse de vuelta a1 servidor), mediante alguna de las tecnicas
de ClientDataSet. El programa tambien trata errores de reconciliacion y soporta
el modelo de maletin.
Mientras se utiliza este cliente para la edicion local de datos, se recibira infor-
macion acerca de cualquier fallo producido en cuanto a1 cumplimiento de las
reglas de negocio de la aplicacion, definidas en el servidor mediante restricciones.
El servidor tambien proporcionara un valor predefinido para el campo Salary (de
salario) de un nuevo registro y transmitira el valor de su caracteristica
Display Format. En la figura 16.3 puede verse uno de 10s mensajes de error
que puede mostrar esta aplicacion cliente, que a su vez recibe el servidor. Este
mensaje se muestra durante la edicion local de 10s datos, no cuando se remiten 10s
datos a1 servidor.

Update I Snapshot. I Reload. I Show Delta ). I I


1Status IDEPT- NO^ EMP-NO ( FIRST-NAME (HIRE-DATE
usunmodified 125 121 Roberto 12/ 7/1993

0 99912 is not a vaM value fa field 'EMPMPNONO.


The allowed range is -32768to 32767.

...............................,
,

L"*
,~!s.Jus.s-w ,- -. -. .--.
kodified ---
621 99912 Bruce 28/12/1988

- - -

Figura 16.3. Mensaje de error mostrado por el ejemplo ThinCli2 cuando el


identificador del empleado es demasiado largo.

Secuencia de actualizacion
Este programa cliente incluye tambidn un boton que sirve para aplicar las
actualizaciones a1 servidor y un dialog0 estandar de reconciliation. A continua-
cion. se ofrece un resumen de la secuencia completa de operaciones relacionadas
con una solicitud de actualizacion y 10s posibles eventos de error:
1. El programa cliente llama el metodo Applyupdates de un ClientDataSet.
2. El delta se envia a1 proveedor de la capa intermedia. El proveedor lanza el
evento OnUpdat e Dat a en el que se podran examinar las modificaciones
solicitadas antes de que dstas lleguen a1 senidor de la base de datos. En
este punto sera posible modificar el delta, que se transmite en un formato
compatible con 10s datos de un ClientDataSet.
3 . El proveedor (tecnicamente, una parte del proveedor llamada "resolver" o
resolutor) aplica cada una de las filas del delta a1 servidor de la base de
datos. Antes de aplicar cada actualizacion, el proveedor recibe un evento
Bef oreUpdateRecord. Si se ha activado el indicador ResolveTo-
DataSet, esta actualizacion acabara lanzando eventos locales del con-
junto de datos de la capa intermedia.
4. En caso de producirse un error de servidor, el proveedor lanzara el evento
OnUpda t e E r r o r (en la capa intermedia), dando a1 programa la oportu-
nidad de solucionar el error a dicho nivel.
5. Si el programa de la capa intermedia no soluciona el error, la peticion de
actualizacion correspondiente permanecera en el delta. El error se devuel-
ve a1 cliente en ese precis0 instante o una vez alcanzado un determinado
numero de errores, segun el valor del parametroMaxErrors de la llama-
da A p p l y U p d a t e s .
6. Por ultimo, el paquete delta que incluya las actualizaciones restantes se
reenvia a1 cliente, lanzando el evento 0 n R e c o n c i 1e E r r o r del
ClientDataSet para cada una de estas actualizaciones. En este controlador
de eventos, el programa cliente puede tratar de solucionar el problema
(posiblemente solicitando ayuda a1 usuario), modificando la actualizacion
en el delta y luego volviendola a enviar.

Refresco de datos
El metodo R e f r e s h del ClientDataSet permite obtener una version actuali-
zada de 10s datos que podrian haber sido modificados por otros usuarios. Sin
embargo, esta operacion solo podra realizarse si en la cache no figura ninguna
tarea de actualizacion pendiente, puesto que si se llama a1 metodo y no esta vacio
el registro de modificaciones, R e f r e s h lanzara una excepcion:
if cds .ChangeCount = 0 then
cds.Refresh;

Si solo se han modificado algunos registros, se puede refrescar el resto llaman-


do a1 metodo R e f r e s hRe c o r d s . ~ s t solo
e refrescara el registro actual, per0
solo deberia utilizarse cuando el usuario no haya modificado el registro actual.
En este caso, el metodo R e f r e s hRe c o r d s no hace mas que apuntar 10s cam-
bios no aplicados en el registro de modificaciones. Como ejemplo, se puede re-
frescar un registro cada vez que pasa a ser el activo, a menos que ya haya sido
modificado y las modificaciones aun no se hayan enviado a1 servidor:
procedure TForml.cdsAfterScrol1(DataSet: TDataSet);
begin
if cds .Updatestatus = usUnModified then
cds.RefreshRecord;
end;

Cuando 10s datos se ven sometidos a modificaciones frecuentes efectuadas por


multiples usuarios y cada usuario deba tener constancia de 10s cambios inmedia-
tamente, deberia aplicarse inmediatamente cualquier rnodificacion en 10s metodos
A f t e r P o s t y A f t e r D e l e t e , y llamar despues a1 m e t o d o R e f r e s h ~ e c o r d s
para el registro activo (como se mostro anteriormente) o para cada uno de 10s
registros visibles en una cuadricula. Este codigo forma parte del ejemplo
ClientRefresh, conectado al servidor AppServ2. A efectos de depuracion, el pro-
grama tambien registra el campo EMP-NO para cada registro que refresca, como
muestra la figura 16.4.

RJresh Lop.

DEPT-NO~EMP-NO IFIRST-NAMEIHIREDATE I~q.1


- 600 2 Robe11 28112/1988
vPJ
621 4 Bruce 2811211988 En
5 Kun 6/2/1989
8 Leslie 5/4/1989
9 PhJ 171411989
11 K.J 17/1/1990
12 Terri 1/St1 990
ill
1
Figura 16.4. El formulario del ejemplo ClientRefresh refresca automaticamente el
registro activo y permite actualizaciones mas extensas haciendo clic sobre 10s
botones.

Esta operacion se ha llevado a cab0 aiiadiendo un boton a1 ejemplo


ClientRefresh. El controlador del boton del registro actual a1 primer registro visi-
ble de la cuadricula y luego al ultimo registro visible. Esto se logra teniendo en
cuenta que hay R o w C o u n t - 1 filas visibles, partiendo de que la primera fila es
la que se ha fijado para albergar 10s nombres de campo. El programa no llama el
metodo R e f res h R e c o r d cada vez, ya que cada movimiento desencadenaria un
evento A f t e r S c r o l l dotado con el codigo mostrado mas arriba. Este codigo
refresca las filas visibles, algo que podria desencadenarse mediante un
temporizador:
/ / arreglo de acceso protegido
type
TMyGrid = class (TDBGrid)
end ;

procedure TFOrrnl.Button2Click(Sender: T O b j e c t ) ;
var
i: Integer;
bm: TBookmarkStr;
begin
/ / refresca las filas visibles
cds.DisableControls;
/ / comienza con la fila actual
i := TMyGrid (DbGridl).ROW;
b m : = cds.Bookmark;
try
/ / vuelve a1 primer registro visible
while i > 1 do
begin
cds.Prior;
Dec (i);
end ;
/ / v u e l v e a 1 regis t r o a c t u a l
i : = TMyGrid (DbGridl).Row;
c d s - B o o k m a r k : = bm;
// s i g u e a d e l a n t e h a s t a q u e l a c u a d r i c u l a e s t a c o m p l e t a
w h i l e i < TMyGrid (DbGridl).Rowcount do
begin
cds .Next;
Inc (i);
end;
finally
// d e f i n e t o d o d e n u e v o y r e f r e s c a
c d s - B o o k m a r k : = bm;
cds.EnableControls;
end;
end;

Este enfoquc genera un trafico de red niuy denso, por lo que podria desearse
desencadenar actualizaciones unicamente cuando se produzcan modificaciones
rcales. Esta operacion se puede implcmentar aiiadiendo tecnologia de retrollamadas
a1 servidor, para que este pueda informar a todos 10s clientes conectados de que se
ha modificado un determinado registro. El cliente podra determinar si la modifi-
cation es de su interes y, en caso oportuno, lanzar la solicitud de rnodificacion.

Caracteristicas avanzadas de DataSnap


DataSnap presenta muchas mas caracteristicas de las que se han tratado hasta
el momcnto. A continuacion se ofrece un breve repaso de alguna de las caracteris-
ticas mas avanzadas de esta arquitectura, presentadas en parte en 10s ejemplos
AppSPlus y ThinPlus. Lamentablemente, mostrar cada parte de funcionalidad
convertiria este capitulo en un libro completo, asi que trataremos de mantener
algo de contencion.

ADVERTENCIA: El ejemplo ThinPlus requiere que se ejecute el semi-


dor de sockets de Delphi (que se encuentra en la carpeta bin de Delphi). Sin
este programa, se vera un error de socket cuando el cliente intente conectar-
se con el servidor. La parte positiva es que se puede desplegar el programa
facilmente sobre una red modificando la direccion IP del semidor en el
programa cliente.

Ademas de las caracteristicas que trataremos en las secciones siguientes, 10s


ejemplos AppSPlus y ThinPlus muestran la utilizacion de una conexion de socket,
el registro limitado de eventos y actualizaciones en el servidor y la captura directa
de un registro en el entorno del cliente. La ultima caracteristica se logra mediante
esta llamada:
p r o c e d u r e TClientForm.ButtonFetchClick(Sender: TObject);
begin
B u t t o n F e t c h - C a p t i o n : = IntToStr (cds.GetNextPacket);
end;

Esta caracteristica permitira obtener mas registros que 10s requeridos por la
interfaz de usuario del cliente (la DBGrid). En otras palabras, se pueden conse-
guir directamente 10s registros sin esperar a que el usuario se desplace por toda la
cuadricula. Es aconsejable estudiar 10s detalles de estos ejemplos complejos des-
pues de la lectura del resto de esta seccion.

Consultas por parametros


Si se quieren utilizar parametros en una consulta o procedimiento almacenado,
en lugar de crear una solucion personalizada (con una llamada de metodo
personalizada a1 servidor), se puede emplear la ayuda de Delphi. En primer lugar,
hay que definir la consulta en la capa intermedia con un parametro como:
select * f r o m customer w h e r e Country = :Country

Para fijar el tip0 y el valor predeterminado del parametro usamos la propiedad


Params. En el entorno del cliente, puede utilizar la orden Fetch Params del
menu de metodo abreviado del ClientDataSet, despues de haberlo conectado a1
proveedor adecuado. En tiempo de ejecucion, se puede llamar a1 metodo
Fet chParams equivalente del componente ClientDataSet . Ahora, a1 actuar
sobre la propiedad Params se puede dotar a1 p a r h e t r o de un valor local predefinido.
El valor del parametro se enviara a la capa intermedia a1 realizar la extraccion de
datos. El ejemplo ThinPlus refresca el parametro con el siguiente codigo:
p r o c e d u r e TFormQuery.btnParamClick(Sender: TObject);
begin
cdsQuery-Close;
cdsQuery.Params[O].AsString : = EditParam.Text;
cdsQuery.0pen;
end;

La figura 16.5 muestra el formulario secundario de este ejemplo, en el que se


muestra el resultado de la consulta por parametros en una cuadricula. En la figu-
ra, se aprecian ademas una serie de datos personalizados enviados por el servidor
tal y como se explica mas adelante.

Llamadas a metodos personalizados


Como el servidor presenta una interfaz COM normal, podemos aiiadirle mas
metodos o propiedades y llamarlos desde el cliente. Basta con abrir el editor de la
biblioteca de tipos del servidor y utilizarlo igual que con cualquier otro servidor
COM. En el ejemplo AppSPlus, hemos aiiadido un metodo L o g i n personalizado
con la siguiente implementation:
procedure TAppServerPlus.Login(const Name, Password:
Widestring) ;
begin
// TODO: a d a d i r c o d i g o d e l o g i n r e a l . . .
i f Password <> Name t h e n
raise Exception .Create ( ' W r o n g n a r n e / p a s s w o r d c o m b i n a t i o n
received' )
else
Query .Active := True;
S e r v e r F o r m - A d d ( ' L o g i n : ' + Name + ' / ' + Password) ;
end;

er SCUBA Company
IMl
FO Box Sn 91
I -
6582 N&'a SCURA L i i e d PO Box 6834 I

Figura 16.5. Formulario secundario del ejemplo ThinPlus, que muestra 10s datos de
una consulta por parametros.

El programa realiza una comprobacion sencilla, en lugar de contrastar la com-


binacion de nombre y contraseiia de acceso con una lista de autorizaciones tal y
como deberia hacer una aplicacion real. Ademas la inhabilitacion de Q u e r y no
funciona realmente, ya puede activarla el proveedor. La inhabilitacion del
DataSetProvider resulta realmente mas adecuada. El cliente tiene una manera
sencilla de acceder al servidor: la propiedad A p p S e r v e r del componente de
conexion remota. Esta es una llamada de muestra del ejemplo ThinPlus que tiene
lugar en el evento A f t e r c o n n e c t del componente de conexion:
procedure TClientForm.ConnectionAfterConnect(Sender: TObject);
begin
Connection.AppServer.Login (Edit2.Text, Edit3.Text);
end;

Hay que tener en cuenta que tambien se puede llamar a metodos adicionales de
la interfaz COM a traves de DCOM, asi como utilizando una conexion de socket
o HTTP. Dado que el programa utiliza la convencion de llamada s a f e c a l l , la
exception que se genere en el servidor se reenvia y se muestra automaticamente
en el cliente. Asi, cuando un usuario marca la casilla de verificacion Connect, se
interrumpe el controlador de eventos utilizado para habilitar 10s conjuntos de
datos de cliente, de mod0 que un usuario con una clave de acceso incorrecta no
podra acceder a 10s datos.

NOTA: Ademas de llarnadas de mktodo direct0 desde el cliente a1 servidor,


tambitn se pueden implementar retrollamadas desde el servidor a1 cliente.
Este enfoque
- puede
- servir, por ejemplo, para infonnar a cada uno de 10s
clientes acerca de eventos esieciflcos. LO; eventos COM son una forma de
realizar esto. Como alternativa, puede aiiadirse una nueva interfaz,
. - - -. . - .. - . - .. .
implementada por el cliente, que transmits el 0bjet0 de implementacibn a1
servidor. Asi el servidor puede llamar el metodo ubicado en el ordenador
cliente. No obstante, las conexiones HTTP no permiten aealizar
retrollamadas.

Relaciones maestroldetalle
Si la aplicacion de la capa intermedia exporta diversos conjuntos de datos,
estos se pueden extraer por medio de diversos componentes ClientDataSet en la
parte del cliente y conectarlos localmente formando una estructura maestroldeta-
Ile. Esto ocasionara ciertos problemas para el conjunto de datos de detalle. a
menos que se estraigan todos 10s registros de forma local.
Esta solucion tambien dificulta la aplicacion de actualizaciones: normalmente,
un registro maestro no se puede cancelar hasta que se han eliminado todos 10s
rcgistros detallados relacionados, del mismo mod0 que tampoco es posible la
adicion de registros detallados hasta que el nuevo registro macstro esta ubicado
adecuadamente. (Distintos servidores se enfrentan a esta situacion de formas dis-
tintas, pero en la mayoria de 10s casos en 10s que se utilizan claves externas: este
es el procedimiento estandar.) Para solucionar este problema, se puede escribir
codigo complejo en el cliente para actualizar 10s registros de las dos tablas segun
las reglas especificas.
Un enfoque completamente diferente consiste en obtener un unico conjunto de
datos que ya incluya el detalle como un campo de conjunto de datos; un campo de
tipo T D a t a s e t F i e l d . Para ello es necesario preparar la relacion maestro/
detalle en la aplicacion de servidor:
o b j e c t Tablecustomer: TTable
DatabaseName = ' DBDEMOS'
TableName = ' cus torner. db'
end
o b j e c t Tableorders : TTable
DatabaseName = ' DBDEMOS'
MasterFields = ' C u s t N o '
Mastersource = DataSourceCust
TableName = ' ORDERS. DB'
end
o b j e c t DataSourceCust: TDataSource
DataSet = TableCustomer
end
o b j e c t Providercustomer: TDataSetProvider
DataSet = TableCustomer
end

En el lado de cliente, la tabla detallada aparecera como campo adicional del


ClientDataSet y el control DBGrid la mostrara como una columna adicional con
un boton con tres puntos. A1 hacer clic sobre el boton aparecera un formulario
secundario con una cuadricula que presenta la tabla de detalle (vease la figura
16.6). Si se necesita crear una interfaz de usuario flexible en el entorno del clien-
te, mediante la propiedad D a t a S e t F i e l d podra aiiadirse un ClientDataSet
secundario conectado a1 campo del conjunto de datos del conjunto de datos maes-
tro. Basta con crear campos permanentes para el ClientDataSet principal y enla-
zar a continuacion la propiedad:
o b j e c t cdsDet: TClientDataSet
DataSetField = cdsTableOrders
end

Con este parametro se puede visualizar el conjunto de datos detallados en una


DBGrid aparte ubicada como de costumbre en el formulario (la cuadricula infe-
rior de la figura 16.6) o del mod0 que se prefiera. Hay que tener en cuenta que,
con esta estructura, las actualizaciones solo afectaran a la tabla maestra y que el
servidor deberia tratar la secuencia de actualizacion adecuada incluso en situa-
ciones complejas.

Uso del agente de conexion


Ya hemos comentado que el componente C o n n e c t i o n B r o k e r puede resul-
tar util si se desea cambiar la conexion fisica utilizada por diversos componentes
C 1i e n t D a t a S e t de un unico programa. A1 enlazar cada ClientDataSet a1
ConnectionBroker, bastara con actualizar la conexion fisica del agente para mo-
dificar la conexion fisica de 10s componentes. El ejemplo ThinPlus utiliza las
siguientes configuraciones:
o b j e c t Connection: TSocketConnection
ServerName = ' AppSPlus. AppServerPl us'
Afterconnect = ConnectionAfterConnect
Address = ' 1 2 7 . 0 . 0 . 1 '
end
o b j e c t ConnectionBrokerl: TConnectionBroker
Connection = Connection
end
o b j e c t c d s : TClientDataSet
ConnectionBroker = ConnectionBrokerl
end
// e n el formulario secundario
o b j e c t cdsQuery: TClientDataSet
ConnectionBroker = C1ientForm.ConnectionBrokerl
end

Figura 16.6. El ejernplo ThinPlus rnuestra corno puede rnostrarse un carnpo de un


conjunto de datos en una cuadricula dentro de una ventana flotante o extraerse
rnediante un ClienteDataSet para rnostrarlo en un forrnulario secundario.
Generalrnente se hare una de estas cosas, no arnbas.

Basicamente no hay que hacer nada mas. Para modificar la conexion fisica,
elegimos un nuevo componente de conexion DataSnap para el formulario princi-
pal y establecemos la propiedad Connection del agente para que utilice esa
conexion.

Mas opciones de proveedor


Y a se h a mencionado antes la propiedad o p t i o n s del componente
Datasetprovider, destacando que puede utilizarse para aiiadir las propiedades de
campo a1 paquete de datos. Existen otras opciones que tambien pueden usarse
para personalizar el paquete de datos y el comportamiento del programa cliente:
S e puede minimizar la descarga de datos BLOB con la opcion
po F e t chBlobsOnDemand. En este caso la aplicacion cliente puede
realizar una descarga de 10s BLOB estableciendo la propiedad
FetchOnDemand del ClientDataSet con el valor True o llamando el
metodo FetchBlobs para registros especificos. De la misma forma, puede
desactivarse la descarga autornatica de datos de registros detallados esta-
bleciendo la opcion po FetchDetailsOnDemand.De nuevo, el cliente
puede utilizar la propiedad Fe t chOnDemand o llamar el metodo
FetchDetails.
Cuando se aplica una relacion maestro/detalle, las cascadas pueden con-
trolarse con cualquiera de las dos opciones. El indicador pocascade-
De1etes controla si el proveedor debe borrar 10s registros detallados
antes de borrar un registro maestro. Esta opcion puede activarse si el ser-
vidor de bases de datos realiza borrados en cascada como parte de su
soporte de integridad referencial. Del mismo modo, la opcion
poCas cadeupdates puede activarse cuando el servidor pueda realizar
automaticamente actualizaciones de valores clave de una relacion maestro/
detalle.
Pueden limitarse las operaciones en el entorno del cliente. La opcion mas
restrictiva, poReadOnly,inhabilita cualquier actualization. Si se desea
dotar a1 usuario de una capacidad de edicion limitada, se puede usar
poDisableInserts,poDisableEditsopoDisableDeletes.
Se puede usar poAutoRef resh para volver a enviar al-cliente una copia
de 10s registros que haya modificado el cliente, lo cual resulta util cuando
otros usuarios hayan efectuado simultaneamente cambios compatibles.
Tambien se puede especificar la opcion poPropagateChanges para
devolver a1 cliente las modificaciones efectuadas en 10s controladores de
eventos Bef oreUpdateRecord o AfterUpdateRecord.Esta op-
cion tambien resulta util cuando se utilizan campos autoincrementales,
disparadores y otras tecnicas que modifican 10s datos en el servidor o en la
capa intermedia, mas alla de las modificaciones solicitadas desde la capa
cliente.
Si se desea habilitar a1 cliente para que lleve a cab0 las operaciones, puede
activarse la opcion poAllowComrnandText.Esto permitira determinar
la consulta o nombre de tabla SQL de la capa intermedia desde el cliente,
mediante 10s metodos GetRecords o Execute.

Agente simple de objetos


El componente SimpleObj ectBro ker brinda una forma sencilla de ubi-
car una aplicacion de servidor entre varios ordenadores servidor. Bastara con
proporcionar una lista de ordenadores disponibles y el cliente probara por orden
cada uno de ellos hasta encontrar el que se encuentre libre.
Ademas, si se activa la propiedad LoadBa lanced el componente escogera
aleatoriamente uno de 10s servidores; cuando un numero elevado de clientes utili-
cen la misma configuracion, las conexiones se distribuiran automaticamente entre
10s semidores. Si parece una especie de agente de objetos "para pobres", hay que
tener en cuenta que algunos sistemas de balance0 de carga muy costosos no ofre-
cen mucho mas que esto.

Pooling de objetos
Cuando varios clientes se conectan a1 servidor a1 mismo tiempo, existen dos
opciones: en primer lugar, se puede crear un objeto de modulo de datos remoto
para cada uno y permitir que cada solicitud sea procesada secuencialmente (el
comportamiento estandar de un servidor COM con el estilo ciMultiInstance). Por
otra parte, se puede dejar que el sistema Cree una instancia diferente de la aplica-
cion para cada cliente (ciSingleInstance). Este enfoque necesita mas recursos y
mas conexiones (y posiblemente licencias) del servidor SQL.
El soporte de DataSnap para el pooling de objetos ofrece un enfoque alternati-
vo. Todo lo que se necesita hacer para solicitar esta caracteristica es aiiadir una
llamada a Registerpooled en el metodo UpdateRegistry sobrescrito.
En combinacion con el soporte sin estado integrado en esta arquitectura, la capa-
cidad de pooling permite compartir ciertos objetos de la capa intermedia entre un
numero mucho mayor de clientes. En COM+ se incluye un mecanismo de pooling,
per0 DataSnap permite que este disponible tambien para conexiones basadas en
sockets y HTTP.
Los usuarios de 10s ordenadores cliente invertiran la mayor parte de su tiempo
leyendo y registrando actualizaciones, y en general no continuan solicitando datos
ni enviando actualizaciones. Cuando el cliente no llama a un metodo del objeto de
la capa intermedia, este modulo de datos remoto puede ser utilizado por otro
cliente. A1 carecer de estado, cada una de las solicitudes llega a la capa intermedia
como si fuese una operacion nueva, aun cuando un servidor este dedicado a un
cliente especifico.

Personalizacion de paquetes de datos


Hay muchas formas de incluir informacion personalizada en el paquete de
datos manejado por la interfaz IAppServer. La mas sencilla posiblemente sea
manipular el evento OnGet DatasetProperties del propio proveedor. Este
evento cuenta con un parametro Sender,consistente en un conjunto de datos
que indica la procedencia de 10s datos, y el parametro Properties de tipo de
matriz OleVar iant,en el que se puede incluir la informacion adicional. Habra
que definir una matriz de variantes para cada propiedad adicional e indicar el
nombre de la propiedad en cuestion, su valor y si queremos que 10s datos vuelvan
a1 servidor con el delta de actualizacion (el parametro Include InDelta).
Por supuesto, se pueden pasar propiedades del componente de conjunto de
datos relacionado, per0 tambien cualquier otro valor (propiedades ficticias ex-
tra). En el ejemplo AppSPlus, se transmite a1 cliente la hora en la que se ejecuto la
consulta y sus parametros:
procedure TAppServerPlus.ProviderQueryGetDataSetProperties(
Sender: TObject; DataSet: TDataSet; out Properties:
Olevariant) ;
begin
Properties : = VarArrayCreate([O,l], varvariant);
Properties [O] : = VarArrayOf ( [ ' Time', Now, True] ) ;
Properties [l] : = VarArrayOf ( [ ' Param',
Query. Params [O] .Asstring, False] ) ;
end ;

En el entorno del cliente, el componente C l i e n t D a t a Se t tiene un metodo


G e t O p t i o n a 1P a r a m e t e r para obtener el valor de la propiedad adicional
con el nombre indicado. El componente C l i e n t D a t a S e t cuenta ademas con el
metodo s e t op t i o na 1P a r a m e t e r para aiiadir mas propiedades a1 conjunto
de datos. Estos valores se grabaran en disco (en el modelo de maletin) e incluso se
devolveran a la capa intermedia (a1 establecer como T r u e el componente
I n c l u d e I n D e l t a de la matriz de variantes). A continuacion se presenta un
ejemplo de la obtencion del conjunto de datos en el codigo anterior:
Caption : = 'Data sent at ' + TimeToStr (TDateTime (
cdsQuery.GetOptionalParam( 'Time' ) ) ) ;
Label1 .Caption : = ' Param ' +
cdsQuery. GetOptionalParam ( Param') ;

El efecto de este codigo se apreciaba en la figura 16.5. Un enfoque alternativo


y mas potente para personalizar el paquete de datos enviado a1 cliente, consiste en
tratar el evento O n G e t D a t a del proveedor, que recibe el paquete de datos sa-
liente en forma de conjunto de datos de cliente. Mediante 10s metodos de este
conjunto de cliente se pueden editar 10s datos antes de enviarlos a1 cliente. Por
ejemplo, se podrian codificar algunos de 10s datos o filtrar 10s registros sensibles.
Creacion de
componentes
de bases
de datos
Ya hemos comentado la creacion de componentes Delphi en profundidad en un
capitulo anterior. Ahora que ya hemos comentado la programacion de bases de
datos, podemos volver a1 tema anterior y centrarnos en el desarrollo de compo-
nentes relacionados con bases de datos.
Basicamente existen dos familias de este tip0 de componentes: controles data-
aware que pueden usarse para presentar 10s datos de un campo o un registro a1
completo a 10s usuarios de un programa y componentes de conjuntos de datos que
se pueden definir para proporcionar datos a controles data-aware ya existentes,
leer datos desde una base de datos o cualquier otra fuente de datos. Este capitulo
trata 10s siguientes temas:
Componentes data-aware: el enlace de datos.
Controles data-aware orientados a campos.
TrackBar y ProgressBar data-aware.
Controles data-aware orientados a registros.
Un visualizador de registros .
Creacion de conjuntos de datos personalizados.
Guardar un conjunto de datos a un flujo local
El enlace de datos
Cuando escribimos un programa de base de datos en Delphi, solemos conectar
algunos controles data-aware (controles "conscientes de 10s datos") con un com-
ponente DataSource y, a continuacion, conectar este ultimo a un conjunto de
datos.
La conexion entre 10s controles data-aware y el DataSource se denomina enla-
ce de datos y se representa mediante un objeto de la clase T D a t a L i n k . El
control data-aware crea y gestiona este objeto, a la vez que constituye su unica
conexion con 10s datos. Desde un punto de vista mas practico, para elaborar un
componente data-aware, hay que acoplarle un enlace de datos y exteriorizar algu-
nas de las propiedades del objeto interno, como las propiedades D a t a S o u r c e y
DataField.
Delphi utiliza 10s objetos DataSource y DataLink para una comunicacion
bidireccional. El conjunto de datos utiliza la conexion para avisar a 10s controles
data-aware de que hay nuevos datos disponibles (porque se ha activado el con-
junto de datos, o se ha modificado el registro, etc). Los controles data-aware
utilizan la conexion para solicitar el valor actual de un campo o actualizarlo, a la
vez que avisan a1 conjunto de datos de este evento.
Las relaciones entre todos estos componentes son complejas, ya que algunas
de las conexiones pueden ser de uno a varios. Por ejemplo, se pueden conectar
diversas fuentes de datos a1 mismo conjunto y, generalmente, tenemos varios
enlaces de datos a la misma fuente de datos, por la sencilla razon de que se
necesita un enlace para cada componente data-aware. Ademas, en la mayoria de
10s casos, conectamos varios controles data-aware a cada fuente de datos.

La clase TDataLink
En gran parte de este capitulo trabajaremos con T D a t a L i n k y sus clases
derivadas, que estan definidas en la unidad DB. Este tipo cuenta con un grupo de
metodos virtuales protegidos, 10s cuales tienen una funcion muy similar a la de
10s eventos.
Se trata de metodos que "no hacen casi nada" y podemos sobrescribir en una
subclase especifica para interceptar operaciones del usuario y otros eventos de la
fuente de datos. A continuacion, aparece un listado extraida del codigo fuente de
esta clase:
type
TDataLink = class (TPersistent)
protected
procedure ActiveChanged; virtual;
procedure CheckBrowseMode; virtual;
procedure DataSetChanged; virtual;
procedure DataSetScrolled(Distance: Integer); virtual;
procedure FocusControl(Field: TFieldRef); virtual;
procedure Editingchanged; virtual;
procedure LayoutChanged; virtual;
procedure ~ e c o r d C h a n g e d ( F i e 1 d : T F i e l d ) ; virtual;
procedure UpdateData; virtual;

El metodo privado Da t a E v e n t , una especie de procedimiento ventana para


una fiente de datos, llama a todos estos metodos virtuales. Este procedimiento se
desencadena mediante varios eventos de datos (que se encuentran definidos en la
enumeracion T D a t e E v e n t ) . Estos eventos tienen origen en el conjunto de da-
tos, en 10s campos o en la fiente de datos y generalmente se aplican a1 conjunto de
datos. El metodo Da t a E v e n t del componente del conjunto de datos envia 10s
eventos a las fuentes de datos conectadas. Cada fiente de datos llama a1 metodo
N o t i fy D a t a L i n k s para reenviar el evento a cada enlace de datos conectado
y, despues, la fiente de datos activa su propio O n D a t a C h a n g e o un evento
OnUpdateData.

Clases de enlaces de datos derivadas


La clase T D a t a L i n k no es tecnicamente una clase abstracta, per0 en raras
ocasiones podremos utilizarla directamente. Cuando necesitemos crear controles
data-aware, habra que emplear una de sus clases derivadas o derivar una nueva
propia. La clase mas importante derivada de T D a t a L i n k es la clase
T F i e l d D a t a L i n k, que utilizan 10s controles data-aware relacionados con un
solo campo del conjunto de datos. La mayoria de 10s controles data-aware se
encuentran dentro de esta categoria y la clase T F i e l d D a t a L i n k resuelve 10s
problemas mas comunes de este tipo de componentes.
Todos 10s controles data-aware orientados a registros o tablas definen subclases
especificas de T D a t a L i n k , tal y como comentaremos mas adelante. La clase
T F i e l d D a t a L i n k cuenta con una lista de eventos que se corresponden con 10s
metodos virtuales de la clase basica que sobrescribe. De este modo, resulta mas
sencillo personalizar la clase, ya que se pueden utilizar controladores de eventos,
en lugar de tener que heredar una nueva clase de ella. A continuacion, se muestra
un ejemplo de un metodo sobrescrito, que lanza el evento correspondiente, si se
encuentra disponible:
p r o c e d u r e TFieldDataLink-ActiveChanged;
begin
UpdateField;
i f Assigned(F0nActiveChange) t h e n FOnActiveChange(Se1f);
end;

La clase T F i e l d D a t a L i n k tambien contiene las propiedades F i e l d y


F i e l d N a m e que permitiran conectar el control data-aware a un campo especifi-
co de un conjunto de datos. El enlace conserva una referencia a1 componente
visual actual utilizando la propiedad C o n t r o 1 .
Creacion de controles data-aware orientados
a campos
Una vez que hemos comprendido la teoria que explica como funcionan las
clases de enlace de datos, podemos comenzar a construir controles data-aware.
Los dos primeros ejemplos son versiones data-aware de 10s habituales controles
ProgressBar y TrackBar. Podemos utilizar el primero para mostrar de forma
visual un valor numerico, como por ejemplo un porcentaje. El segundo permite
que el usuario pueda ademas modificar el valor numerico.

NOTA: El codigo para todos 10s componentes qteados en'esk capitdo se


encuentra en la carpeta MdDataPack, q u e ~ i e incluye
n un paquete s;op .
para instalarlos todos. Ottas catpetas incluyen pmgra-
usan estos componentes.

Una ProgressBar de solo lectura


Una version data-aware del control ProgressBar es un caso relativamente sen-
cillo de control data-aware, ya que es un control de solo lectura. Este componente
se deriva de la version que no es data-aware y afiade algunas propiedades del
objeto de enlace de datos que encapsula:
type
TMdDbProgress = class (TProgressBar)
private
FDataLink: TFieldDataLink;
function GetDataField: string;
procedure SetDataField (Value: string);
function GetDataSource: TDataSource;
procedure SetDataSource (Value: TDataSource);
function GetField: TField;
protected
// controlador del evento de enlace de datos
procedure Datachange (Sender: TObject) ;
public
constructor Create (AOwner: TComponent) ; override;
destructor Destroy; override;
property Field: TField read GetField;
pub1ished
property DataField: string read GetDataField write
SetDataField;
property Datasource: TDataSource read GetDataSource write
SetDataSource;
end;
A1 igual que cualquier otro componente data-aware conectado a un solo cam-
po, este control permite disponer de las propiedades D a t a s o u r c e y D a t a F i e l d .
Hay que escribir un codigo muy breve, que simplemente consiste en exportar las
propiedades del objeto de enlace de datos interno de la siguiente manera:
function TMdDbProgress.GetDataFie1d: string;
begin
Result : = FDataLink.FieldName;
end;

procedure TMdDbProgress-SetDataField (Value: string);


begin
FDataLink-FieldName : = Value;
end ;

function TMdDbProgress.GetDataSource: TDataSource;


begin
Result : = FDataLink.DataSource;
end;

procedure TMdDbProgress.SetDataSource (Value: TDataSource);


begin
FDataLink.DataSource : = Value;
end;

function TMdDbProgress.GetFie1d: TField;


begin
Result : = FDataLink-Field;
end ;
Resulta evidente que para que funcione este componente, hay que crear y des-
truir el enlace de datos despues de haber creado o destruido el propio componente:
constructor TMdDbProgress-Create (AOwner: TComponent);
begin
inherited Create (AOwner);
FDataLink : = TFieldDataLink.Create;
FDataLink-Control : = Self;
FDataLink-OnDataChange : = Datachange;
end ;
destructor TMdDbProgress.Destroy;
begin
FDataLink-Free;
FDataLink : = nil;
inherited Destroy;
end ;

Es en el constructor anterior donde el componente instala uno de sus metodos


como controlador de eventos para el enlace de datos. Es aqui donde se encuentra
el codigo mas importante del componente. Cada vez que se modifiquen 10s datos,
modificaremos la salida de la barra de progreso para reflejar 10s valores del
campo actual:
procedure TMdDbProgress-Datachange (Sender: TObject);
begin
i f (FDataLink.Field <> n i l ) and (FDataLink.Field i s
TNumericField) then
Position : = FDataLink.Field.As1nteger
else
Position : = Min;
end :

Segun la convencion de 10s controles da/a-aware VCL, si el tipo del campo no


es valido, el componente no muestra un mensaje de error, simplemente no permite
la salida. Del mismo modo, podriamos querer comprobar el tip0 de campo cuando
cl metodo de S e t D a t a F i e l d lo asigna a un control. En la figura 17.1, pode-
mos ver un ejemplo dc la salida de una aplicacion DbProgr, que utiliza tanto una
etiqueta como una barra de progreso para mostrar informacion cuantitativa de un
pedido (order). Gracias a esta indicacion visual, podemos saltar de un registro a
otro y ver 10s pedidos de diversos elementos con facilidad. Una ventaja evidente
de este componente es que la aplicacion apenas contiene codigo, ya que todo el
codigo importante se encuentra en la unidad MdProgr que define el componente.

Figura 17.1. La ProgressBar data-aware en funcionamiento en el ejemplo DbProgr.

Como hemos visto no es dificil escribir un componente data-aware de solo


lectura. No obstante, puede resultar estremadamente complejo utilizarlo dentro
de un contenedor DBCtrlGrid,

NOTA: Si recordarnos el analisis realizado sobre el mCtodo Not i fi ca -


tion, podemos preguntarnos quC ocurriria si se destruye la fuente de da-
--- - ---
tns a l-- I- - ---- ----
---
I-I-- -- - . -----------
----- -- ..-. Podmna
a m e hace -referencia el cnntrnl dnta-owore - -- -,
e d a r trannuilns
--'----

ya que la hente de datos contiene un destructor que la elimina de sus


propios enlaces de datos. Por lo tanto, no es necesario un metodo
Notification para 10s controles data-mare, aunque en algunos sitios
se sugiera asi y la VCL incluya mucho c6digo inutil de este t i p .
Controles data-aware que podemos reproducir
Arnpliar un control data-aware para soportar su uso dentro de un compo-
nente DBCtrlGrid es una tarea bastante compleja - - y - poco
- documentada.
Podemos encontrar una version completa que podemos reproducir de la
barra de progreso en la unidad MdRepPr del paquete MdDataPack, y un
ejemplo de su utilizacibn en la carpeta R e p P r o g r , junto con un archivo
. .
n I IVIL que
1 T L
aescnoe
3 - - I1 - F l -----
su aesarrouo. c~cornponenre ~ E L lbr
3
L ~ l a tlene- un
----A- h'..-.L .--a -_-
L -1 A
:
-.
.

comportarniento muy especial, ya que muestra en pantalla diversas versio-


nes del mismo control fisico utilizando trucos visuales. La cuadricula pue-
de enlazar el control a un buffer de datos distinto a1 registro
- actual y desvia
las operaciones de representacibn a otra parte del monitor. En resumen,
para que un componente aparezca en el componente D B C t r l G r i d , debe
tener establecido su t i p de control como c s R e p l i c a t a b l e , un indica-
3- _ -
aor que - 1 1 i . . . .
solo senala que el componenre i -
soporra el que esrerr -.-- 13-
conreniao en una - - - -

cuadricula de control. En primer lugar, el componente debe responder a1


mensaje de Delphi c m G e t Da t a L i n k y devolver un punter0 a1 enlace de
datos, para que la cua%icula de control pueda utilizarlo y modificarlo. En
segundo lugar, necesita un metodo Pa i n t personalizado para dibujar la
saIida sobre el objeto lienzo adecuado, que se proporciona en un p a r h e t r o
del mensaie wm P a i n t si se activo el indicador c s p a i n t c o ~ vde la

utiliza este componente:

1R ~ N O 1
l ~ v e n l ~ o CUSINO INmTickelt l ~ m l _ ~ a i dI~y-~e(hod(Card-No 1-1
15 8 6 7 C52 50 DINERS 256335017856420371
B E40 00 DINERS 6146617034656232
6 C45.00 DINERS 481853612351817
3 E37.50 DINERS 2513715852358158
10 E50 00 DINERS 0521773736155304 - 1
Una TrackBar de lectura y escritura
El siguiente paso consiste en escribir un componente que permita a1 usuario
modificar una base de datos, no solo explorarla. La estructura global de este tip0
de componente no se diferencia demasiado de la version anterior, per0 existen
algunos elementos adicionales. En concreto, cuando el usuario comienza a
interactuar con el componente, el codigo debe poner a1 conjunto de datos en el
mod0 de edicion y, a continuacion, avisar a1 conjunto de datos que estos se han
modificado. En ese momento, el conjunto de datos utilizara un controlador de
eventos de F i e 1dDataL ink para pedir 10s valores actualizados.
Para mostrar como podemos crear un componente data-aware que modifique
10s datos, hemos ampliado el control TrackBar.No es el mas simple de 10s
ejemplos, pero muestra varias tecnicas importantes.
Esta es la definicion de la clase de componente (extraida de la unidad MdTrack
del paquete MdDataPack):
type
TMdDbTrack = class(TTrackBar)
private
FDataLink: TFieldDataLink;
function GetDataField: string;
procedure SetDataField (Value: string);
function GetDataSource: TDataSource;
procedure SetDataSource (Value: TDataSource);
function GetField: TField;
procedure CNHScroll(var Message: TWMHScroll); message
CN-HSCROLL;
procedure CNVScroll (var Message: TWMVScroll) ; message
CN-VSCROLL;
procedure CMExit(var Message: TCMExit); message CM-EXIT;
protected
/ / controladores de eventos de enlace de datos
procedure Datachange (Sender: TObject) ;
procedure UpdateData (Sender: TObject) ;
procedure Activechange (Sender: TObject) ;
public
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
property Field: TField read GetField;
published
property DataField: string read GetDataField write
SetDataField;
property Datasource: TDataSource read GetDataSource write
SetDataSource;
end;

En comparacion con el control data-aware construido anteriormente, esta cla-


se solo es mas compleja porque tiene tres controladores de mensajes, entre 10s que
se incluyen 10s controladores de notification de componentes, y dos nuevos
controladores de eventos para 10s enlaces de datos. El componente instala estos
controladores de eventos en el constructor, que ademas inhabilita el componente:
constructor TMdDbTrack-Create (AOwner: TComponent);
begin
inherited Create (AOwner);
FDataLink : = TFieldDataLink.Create;
FDataLink.Contro1 : = Self;
FDataLink.0nDataChange : = Datachange;
FDataLink.0nUpdateData : = UpdateData;
FDataLink.0nActiveChange : = Activechange;
Enabled : = False;
end;

Todos 10s metodos de adquisicion y establecimiento (get y set) y el controlador


d e eventos D a t a C h a n g e son muy parecidos a 10s del componente
TMdDbProgress.La unica diferencia radica en que siempre que se modifique
la fuente de datos o el campo de datos, el componente comprobara el estado actual
para determinar si debe habilitarse a si mismo.
procedure TMdDbTrack.SetDataSource (Value: TDataSource);
begin
FDataLink.DataSource : = Value;
Enabled : = FDataLink.Active and (FDataLink.Field <> nil) and
not FDataLink.Field.ReadOnly;
end;

Este codigo comprueba tres condiciones: el enlace de datos deberia estar acti-
vo, el enlace deberia hacer referencia a un campo real y el campo no deberia ser
de solo lectura. Cuando el usuario modifica el campo, el componente deberia
tener en cuenta que el nombre del campo podria no ser valido. Para comprobar
esta condicion, el componente usa un bloque try/finally:
procedure TMdDbTrack-SetDataField (Value: string);
begin
try
FDataLink.Fie1dName : = Value;
finally
Enabled : = FDataLink.Active and (FDataLink.Field <> nil)
and
not FDataLink.Field.Read0nly;
end ;
end ;

El control realiza el mismo analisis cuando se activa o desactiva el conjunto de


datos:
procedure TMdDbTrack.ActiveChange (Sender: TObject);
begin
Enabled : = FDataLink-Active and (FDataLink.Field <> nil) and
not FDataLink.Field.ReadOn1y;
end :
La parte mas interesante del codigo de este componente es la referente a su
interfaz de usuario. Cuando un usuario comienza a mover el control de desplaza-
miento, el componente debe hacer lo siguiente: poner el conjunto de datos en el
mod0 de edicion, dejar que la clase base actualice la posicion del control de
desplazamiento y avisar a1 enlace de datos (y por lo tanto a la fuente de datos) de
que 10s datos han sido modificados. El codigo es el siguiente
p r o c e d u r e TMdDbTrack.CNHScroll(var Message: TWMHScroll);
begin
// e n t r a en e l modo e d i c i d n
FDataLink.Edit;
// a c t u a l i z a 10s d a t o s
inherited;
// n o t i f i c a a 1 s i s t e m
FDataLink-Modified;
end :

p r o c e d u r e TMdDbTrack.CNVScroll(var Message: TWMVScroll);


begin
// e n t r a e n e l m o d o e d i c i d n
FDataLink.Edit;
// a c t u a l i z a 1 0 s d a t o s
inherited;
// n o t i f i c a a 1 s i s t e m
FDataLink-Modified;
end :

Cuando el conjunto de datos necesita datos nuevos, por ejemplo para realizar
una nueva operacion Post, lo unico que hace es solicitarlos a1 componente me-
diante el evento OnUpdateData de la clase TFieldDataLink:
p r o c e d u r e TMdDbTrack.UpdateData (Sender: TObject);
begin
if F D a t a L i n k - F i e l d i s TNumericField t h e n
FDataLink.Field.As1nteger : = Position;
end ;

Si se dan las condiciones adecuadas, el componente simplemente actualiza 10s


datos en el campo apropiado de la tabla. Por ultimo, si el componente pierde el
foco de entrada, deberia forzar una actualizacion de 10s datos (si 10s datos han
cambiado), de tal forma que cualquier otro componente data-aware que muestre
el valor de ese campo mostrara 10s valores correctos tan pronto como el usuario
se desplace a un campo diferente. Si 10s datos no hubiesen cambiado, el compo-
nente no se molestara en actualizar 10s datos de la tabla. Este es el codigo estandar
de CmExit para componentes utilizados por la VCL y que nuestro componente
tambien toma prestado:
p r o c e d u r e TMdDbTrack.CmExit(var Message: TCrnExit);
begin
try
FDataLink-UpdateRecord;
except
SetFocus;
raise;
end ;
inherited;
end;

Una vez mas, existe un programa de muestra para probar este componente,
podemos observar su salida en la figura 17.2. El programa DbTrack contiene una
casilla de verificacion que activa o desactiva la tabla, 10s componentes visuales y
un par de botones que podemos utilizar para separar el componente T r a c k B a r
vertical del campo a1 que esta relacionado. Se han colocado en el formulario para
comprobar la habilitacion e inhabilitacion de la barra de seguimiento.

Figura 17.2. Las barras de seguirniento del ejernplo DbTrack perrniten introducir
datos en una tabla de la base de datos. La casilla de verificacion y 10s botones
comprueban el estado de activacion de 10s cornponentes.

Creacion de enlaces de datos


personalizados
Todos 10s controles data-aware construidos hasta el momento hacen referen-
cia a campos especificos del conjunto de datos, por lo cual hemos utilizado un
objeto T F i e l d D a t a L i n k para establecer la conexion con una fuente de datos.
Ahora vamos a construir un componente data-aware que funcione con todo un
conjunto de datos a1 completo: un sencillo visualizador de registros.
La cuadricula de base de datos de Delphi muestra el valor de varios campos y
registros a la vez. En el componente visualizador de registros, se va a mostrar una
lista de todos 10s campos del registro actual, utilizando un control de cuadricula
personalizado. Este ejemplo demostrara como crear un control de cuadricula per-
sonalizado y un enlace de datos tambien personalizado.
Un componente visualizador de registros
En Delphi no existen componentes data-aware que manipulen varios campos
de un solo registro sin mostrar otros registros. Los unicos dos componentes que
muestran varios campos de la misma tabla son DBGrid y DBCtrlGrid, que gene-
ralmente muestran multiples campos y registros.
El componente visualizador de registros que se describe en esta seccion esta
basado en una cuadricula de dos columnas: la primera muestra 10s nombres de 10s
campos de la tabla, mientras que la segunda muestra 10s valores de campo corres-
pondientes. El numero de filas de la cuadricula se corresponde con el numero de
campos, y existe una barra de desplazamiento vertical por si no caben en la zona
visible.
El enlace de datos que necesitamos para construir este componente es una
clase que este conectada solo a1 componente visualizador de registros y declarada
directamente en la seccion de implementacion de su unidad. Este es el mismo
enfoque que utiliza la VCL para algunos enlaces de datos especificos. A conti-
nuacion se muestra la definition de esta nueva clase:
type
TMdRecordLink = class (TDataLink)
private
RView: TMdRecordView;
public
constructor Create (View: TMdRecordView);
p r o c e d u r e ActiveChanged; override;
p r o c e d u r e Recordchanged (Field: TField); override;
end;

Como se puede observar, la clase sobrescribe 10s metodos relacionados con el


evento principal (en este caso tan solo se modifica la activacion y 10s datos, o
registros). De forma alternativa, podriamos haber exportado eventos y despues
dejar que el componente 10s controlase, como hace T F i e l d D a t a L i n k .
El constructor requiere como hnico parametro el componente asociado.
constructor T M d R e c o r d L i n k - C r e a t e (View: TMdRecordView);
begin
i n h e r i t e d Create;
R V i e w : = View;
end;

Una vez que se ha almacenado una referencia a1 componente asociado, 10s


otros metodos pueden funcionar sobre el directamente:
p r o c e d u r e TMdRecordLink.ActiveChanged;
var
I : Integer;
begin
// d e f i n e e l numero d e f i l a s
RView.RowCount : = DataSet.FieldCount;
// l o p i n t a t o d o d e n u e v o . . .
RView.Invalidate;
end :

procedure TMdRecordLink.RecordChanged;
begin
inherited;
// l o p i n t a t o d o d e nuevo.. .
RView.Invalidate;
end ;

El codigo del enlace de registros es muy sencillo. La mayor parte de las difi-
cultades de la construccion de este ejemplo se deben a la utilizacion de la cuadri-
cula. Para evitar las propiedades innecesarias, hemos derivado la cuadricula del
visualizador de registros de la clase TCustomGrid.Esta clase incluye gran
parte del codigo para las cuadriculas, per0 la mayoria de sus propiedades, even-
tos y metodos estan protegidos. Esta es la razon por la que la especificacion de la
clase resulta bastante larga, ya que es necesario publicar muchas de las propieda-
des existentes. Este es un fragment0 de codigo (en el que se excluyen las propie-
dades de la clase basica):
type
TMdRecordView = class (TCustomGrid)
private
// s o p o r t a data-aware
FDataLink: TDataLink;
function GetDataSource: TDataSource;
procedure SetDataSource (Value: TDataSource) ;
protected
// rnetodos r e d e f i n i d o s TCustomGrid
procedure Drawcell (ACol, ARow: Longint ; ARect : TRect;
AState: TGridDrawState); override;
procedure ColWidthsChanged; override;
procedure RowHeightsChanged; override;
public
constructor Create (AOwner: TComponent) ; override;
destructor Destroy; override;
procedure SetBounds (ALeft, ATop, AWidth, AHeight:
Integer) ; override;
procedure Defineproperties (Filer: TFiler) ; override;
// p r o p i e d a d e s p a d r e p d b l i c a s ( o m i t i d a s . . . )
published
// p r o p i e d a d e s d a t a - a w a r e
property DataSource: TDataSource read GetDataSource write
SetDataSource;
/ / p r o p i e d a d e s p a d r e p u b l i c a d a s ( o m it i d a s . . . )
end ;

Ademas de volver a especificar las propiedades para publicarlas, el compo-


nente define un objeto de enlace de datos y la propiedad Datasource.No existe
la propiedad Data Field para este componente, ya que hace referencia a todo
un registro. El metodo constructor del componente es muy importante porque
establece 10s valores de numerosas propiedades no publicadas, entre las que se
incluyen las opciones de cuadricula:
constructor TMdRecordView.Create (AOwner: TComponent);
begin
inherited Create (AOwner);
FDataLink : = T M d R e c o r d L i n k - C r e a t e (self);
// d e f i n e e l numero d e c e l d a s y d e c e l d a s f i j a s
RowCount : = 2; // p r e d e f i n i d o
ColCount : = 2 ;
FixedCols := 1;
FixedRows : = 0;
Options : = [goFixedVertLine, goFixedHorzLine,
goVertLine, goHorzLine, goRowSizing];
DefaultDrawing : = False;
ScrollBars : = ssvertical;
FSaveCellExtents : = False;
end ;

La cuadricula tiene dos columnas (una de ellas fija) y ninguna fila fija. La
columna fija se usa para modificar el tamaiio de cada fila de la cuadricula. Por
desgracia. el usuario no puede utilizar la fila fija para ajustar el tamaiio de las
columnas; ya que no es posible modificar el tamaiio de elementos fijos y la cuadri-
cula ya cuenta con una colurnna fija.

NOTA: Un enfoque alternativo consistiria en tener una columna adicional


vacia, como tiene el control DBGrid. De esta forma, seria posible modificar
el tamaiio de las otras dos columnas despues de aiiadir una fila fija.

Se ha utilizado un enfoque alternativo para modificar el tamaiio de las colum-


nas. Se puede modificar la primera columna (que contiene 10s nombres de campo)
utilizando codigo de programacion o de forma visual en tiempo de diseiio y la
segunda columna (que contiene 10s valores de 10s campos) se modificara para
utilizar el area restante:
procedure T M d R e c o r d V i e w - S e t B o u n d s (ALeft, ATop, AWidth,
AHeight : Integer) ;
begin
inherited;
ColWidths [ I ] : = Clientwidth - ColWidths [O];
end;

Esta modification del tamaiio tiene lugar cuando cambia el tamaiio del compo-
nente y cambia alguna de las columnas. Con este codigo, la propiedad
DefaultColWidth del componente se convierte en el ancho fijo de la primera
columna. Despues de haberlo preparado todo, el metodo clave del componente es
el metodo DrawCell sobrescrito, que se detalla en el listado 17.1. En este meto-
do, el control muestra la informacion sobre 10s campos y sus valores. Tiene que
representar tres cosas. Si el enlace de datos no esta conectado a una fuente de
datos, la cuadricula muestra un simbolo de "elemento vacio" ([]).Cuando se re-
presenta la primera colurnna, el visualizador de registros muestra la propiedad
DisplayName del campo, que es el mismo valor que el utilizado por la DBGrid
para el encabezamiento. A1 pintar la segunda colurnna, el componente accede a la
representacion textual del valor del campo, extraida de la propiedad
DisplayTex t (o con la propiedad Ass t r i n g para 10s campos de memo).

Listado 17.1. El rnetodo Drawcell del cornponente Recordview personalizado

procedure TMdRecordVie~.DrawCe11(ACol, ARow: Longint; ARect:


TRect;
AState: TGridDrawState) ;
var
Text: string;
CurrFleld: TField;
Bmp : TBitmap ;
begin
CurrField : = nil;
Text : = ' [ I ' ; / / p o r d e f e c t 0
// p i n t a e l fondo
if (ACol = 0 ) then
Canvas.Brush.Color : = Fixedcolor
else
Canvas.Brush.Color : = Color;
Canvas. FillRect (ARect);
// d e j a u n p e q u e d o b o r d e
InflateRect (ARect, -2, -2) ;
if (FDataLink.DataSource <> nil) and FDataLink.Active then
begin
CurrField : = FDataLink.DataSet.Fields[ARow];
if ACol = 0 then
Text : = CurrField.DisplayName
else if CurrField is TMemoField then
Text : = TMemoField (CurrField).AsString
else
Text : = CurrField.Disp1ayText;
end;
if (ACol = 1) and (CurrField is TGraphicField) then
begin
Bmp : = TBitmap.Create;
try
Bmp-Assign (CurrField);
Canvas.StretchDraw (ARect, Bmp) ;
finally
Bmp .Free ;
end;
end
else if (ACol = 1) and (CurrField is TMemoField) then
begin
DrawText (Canvas.Handle, PChar (Text), Length (Text), ARect,
dt-WordBreak o r dt-Noprefix)
end
else / / d i b u j a una l i n e a s i m p l e c e n t r a d a d e forma v e r t i c a l
DrawText (Canvas.Handle, PChar (Text), Length ( T e x t ) , ARect,
dt-vcenter o r dt-Singleline o r dt-Noprefix);
i f gdFocused i n AState t h e n
Canvas. DrawFocusRect (ARect);
end;

En la ultima parte del metodo, el componente tiene en cuenta 10s campos gra-
ficos y de memo. Si el campo es un TMemoField, la llamada a la funcion
DrawText no especifica el indicador dt SingleLine, sino que utiliza el
indicador dt WordBreak para partir las palabras cuando no hay espacio sufi-
ciente. ~ l a r o & i que para un campo grafico, el componente utiliza un enfoque
totalmente distinto, que consiste en asignar a la imagen de campo un mapa de bits
temporal y despues ampliarlo hasta que cubra la superficie de la celda.
Observe ademas que el componente establece como False la propiedad
DefaultDrawing, de tal forma que tambien es responsable de pintar el fondo
y el rectangulo de foco, igual que ocurre en el metodo Drawcell. El componen-
te tambien llama a la funcion In f 1 a t eRe ct de la API para dejar un pequeiio
espacio entre el borde de celda y el texto de salida. La salida real se obtiene a1
llamar a otra funcion de la API de Windows, DrawText, la cual centra el texto
verticalmente dentro de su celda.
Este codigo de representacion funciona tanto en tiempo de ejecucion, como
podemos comprobar en la figura 17.3, como en tiempo de diseiio. La salida puede
que no sea perfecta, per0 este componente puede ser util en muchos casos. Si
queremos mostrar 10s datos de un solo registro, en lugar de construir un formula-
rio personalizado con etiquetas y controles data-aware, podemos utilizar de for-
ma sencilla esta cuadricula de visualizacion de registros. No obstante, es importante
tener presente que el visualizador de registros es un componente de solo lectura.
Es posible ampliarlo para que adquiera capacidades de edicion (ya forman parte
de la clase TCustomGrid), pero, de todas formas, en lugar de aiiadir estc so-
porte, hemos decidido hacer que el componente sea mas completo afiadiendo so-
porte para mostrar campos BLOB.
Para mejorar la salida grafica, el control traza las lineas de 10s campos BLOB
el doble de altas que 10s campos de texto simple. Esta operacion se realiza una vez
que se activa el conjunto de datos conectado a1 control data-aware. El metodo
Activechanged del enlace de datos tambien se activa mediante 10s metodos
RowHeightsChanged conectados a la propiedad Def aultRowHeight de
la clase basica:
p r o c e d u r e TMdRecordLink.ActiveChanged;
var
I: Integer;
begin
// d e f i n e e l numero d e f i l a s
RView-RowCount : = DataSet.FieldCount;
/ / duplica la altura del memo y del grafico
f o r I : = 0 to DataSet.FieldCount - 1 d o
i f DataSet.Fields [I] is TBlobField then
RView.RowHeights [I] : = RView.DefaultRowHeight * 2;
// volver a pintarlo todo.. .
RView-Invalidate;
end ;

-I

Blue Angelhsh

Species Name Panafanltusnararchus

11 81 1 a236220472
Habrtal 1s around bouldels. caves.

I
coral ledges and crevices m shallow
waters. Swims alone or in groups.

Figura 17.3. El ejemplo ViewGrid muestra la salida del componente RecordView,


mediante el uso de la tabla de base de datos de muestra BioLife de Borland.

En este punto, nos encontramos con un pequeiio problema. En el metodo


Def ineproperties,la clase TCustomGrid guarda 10s valores de las pro-
piedades RowHeight s y ColHeights.Podriamos desactivar este proceso de
streaming sobrescribiendo el metodo y no llamando a inherited (que es, en
general, una mala tecnica), per0 tambien cabe la posibilidad de modificar el cam-
po protegido FSaveCellExtents para desactivar esta caracteristica (como
en el codigo del componente).

Personalization del componente DBGrid


Ademas de construir componentes data-aware nuevos y personalizados, 10s
programadores de Delphi suelen personalizar el control DBGrid. La finalidad del
siguiente componente es dotar a1 DBGrid del mismo tipo de salida personalizada
que hemos utilizado para el componente RecordView,mostrando directamente
campos graficos y memo. Para hacer esto, la cuadricula necesita tener una altura
de filas modificable que deje espacio para una cantidad de texto razonable y que
sea suficiente para contener graficos y una cantidad respetable de texto. La figura
17.4 muestra un ejemplo de esta cuadricula en tiempo de diseiio.
. ggerl~sh lnhab~ts
Bdrtudes conwch &ter lee! areas and feeds
upon crudaceam and
rnollusks bv c~ush~nathem
Called seaperch In
Austraha lnhablls h e
Snappei Red Emperor Lut~anussebae

I
This is the la~gesld dl h e
uaassn It is l w n d in
W~asse Giant Maor1W~asse Che~l~nur
undulatus dense reef areas. 1eedh-g ,,:A
an a Wide valbty d
mobks, fishes, sea
Hab~tatis a w n d boulders,
caves. c n d ledges and
hwptlsh Bbe Angdlish Pmacanlhus nauarchus uewces n ~hanowwaters
Swrms alone or in pwps.

Figura 17.4. Un ejemplo del componente MdDbGrid en tiempo de disefio.

Mientras que para crear la salida tan solo hub0 que adaptar el codigo utilizado
cn el componente visualizador de registros, para establecer la altura de las celdas
de la cuadricula se planteo un problema de dificil solucion. iLas lineas del codigo
para esa operacion puede quc sean pocas, per0 cost6 horas llegar a esta solucion!
-

NOTA: A diferencia de la cuadricula genkrica que hemos utilizado antes,


una DBGrid es una vista virtual del conjunto de datos. No existe ninguna
relacion entre el numero de filas que se muestran en la pantalla y el numero
de filas del conjunto de datos. Cuando nos movemos hacia arriba y hacia
abajo por 10s registros de datos del conjunto de datos, no nos estamos
moviendo por las filas de la DBGrid: las filas son estaticas mientras que 10s
datos se mueven de una fila a otra para dar sensacion de movimiento. Por
este motivo, el programa trata de mar la altura de una fila individual para
adaptarla a sus datos, sino que establece la altura de todas las filas de datos

En esta ocasion, el control no tiene que crear un enlace de datos personalizado,


ya que se deriva de un componente que ya tiene una conesion compleja con 10s
datos. La nueva clase tiene una nueva propiedad para especificar el numero de
lineas de texto para cada fila y sobrescribe algunos metodos virtuales:
type
TMdDbGrid = c l a s s (TDbGrid)
private
FLinesPerRow: Integer;
procedure SetLinesPerRow (Value: Integer);
protected
procedure DrawColurnnCell(const Rect: TRect; DataCol: Integer;
Column: TColurnn; State: TGridDrawState); override;
procedure LayoutChanged; override;
public
constructor Create (AOwner: TComponent); override;
published
property LinesPerRow: Integer
read FLinesPerRow write SetLinesPerRow default 1;
end;

El constructor establece el valor predeterminado para el campo F L i n e s -


PerRow. A continuacion, se muestra el metodo de establecimiento de la propie-
dad:
procedure TMdDbGrid.SetLinesPerRow(Va1ue: Integer);
begin
if Value <> FLinesPerRow then
begin
FLinesPerRow : = Value;
LayoutChanged;
end ;
end ;

El efecto lateral de variar el numero de lineas es una llamada a1 metodo virtual


L a y o u t C h a n g e d . El sistema suele llamar a este metodo cuando varia uno de
10s numerosos parametros de salida. En el codigo de este metodo, el componente
llama en primer lugar a la version heredada, y despues establece la altura de cada
fila. Como base para el calculo, utiliza la misma formula que la clase
TCus t omDBGr i d : la altura del texto se calcula utilizando la palabra de mues-
tra Wg en la fuente actual (se utiliza este texto porque incluye tanto una mayuscu-
la de altura completa como una minuscula con extensiones inferiores). Este es el
codigo:
procedure TMdDbGrid.LayOutChanged;
var
PixelsPerRow, PixelsTitle, I: Integer;
begin
inherited LayOutChanged;

Canvas.Font : = Font;
PixelsPerRow : = Canvas.TextHeight ( ' W g ' ) + 3;
if dgRowLines i n Options then
Inc (PixelsPerRow, GridLineWidth) ;

Canvas.Font : = TitleFont;
PixelsTitle : = Canvas .TextHeight ( ' W g ' ) + 4;
if dgRowLines i n Options then
Inc (PixelsTitle, GridLineWidth);

// d e f i n e e l n u r n e r o d e f i l a s
RowCount : = 1 + (Height - PixelsTitle) div (PixelsPerRow *
FLinesPerRow) ;

// d e f i n e l a a l t u r a d e cada f i l a
DefaultRowHeight : = PixelsPerRow * FLinesPerRow;
RowHeights [0] : = PixelsTitle;
f o r I : = 1 to RowCount - 1 do
RowHeights [I] : = PixelsPerRow * FLinesPerRow;

// e n v i a u n m e n s a j e -SIZE para p e r m i t i r q u e e l cornponente


// b a s e v u e l v a a c a l c u l a r l a s f i l a s v i s i b l e s e n e l metodo
/ / p r i v a d o Upda t e R o w C o u n t
PostMessage (Handle, WM-SIZE, 0 , MakeLong(Width, Height));

-
end;

ADVERTENCIA: Font y T i t l e Font son 10s valores predefinidos de


la cuadricula, que pueden ser sobrescritos por Ias propiedades de 10s obje-
tos individuales de columna de DBGrid. El componente realmente ignora
esos parhetros.

Lo mas dificil de conseguir en este metodo fue que las cuatro ultimas lineas
fueran correctas. Podemos definir la propiedad D e f a u l t R o w H e i g h t , per0 es
probable que en ese caso el titulo de fila sea demasiado alto. En un principio se
intento establecer la propiedad DefaultRowHeight y, despues, la altura de la
primera fila, per0 este enfoque complicaba el codigo usado para calcular el nume-
ro de filas visibles en la cuadricula (la propiedad de solo lectura V i s i b l e -
R o w C o u n t ) . Si especificamos el numero de filas, para que las filas no queden
escondidas debajo del extremo inferior de la cuadricula, la clase basica continua
sigue calculandolo. Este es el codigo usado para representar 10s datos, tomado del
componente R e c o r d v i e w y ligeramente adaptado para la cuadricula:
p r o c e d u r e TMdDbGrid.DrawColumnCel1 (const Rect: TRect; DataCol:
Integer;
Column: TColumn; State: TGridDrawState);
var
Bmp: TBitmap;
OutRect : TRect;
begin
i f FLinesPerRow = 1 t h e n
inherited DrawColurnnCell (Rect, DataCol, Column, State)
else
begin
// lirnpia l a zona
Canvas. FillRect (Rect);
// copia e l r e c t d n g u l o
OutRect := Rect;
// r e s t r i n g e l a s a l i d a
InflateRect (OutRect, - 2 , - 2 ) ;
// s a l i d a d e d a t o s d e l carnpo
i f Column.Field i s TGraphicField then
begin
Bmp : = TBitmap.Create;
try
Bmp.Assign (Column.Field) ;
Canvas .StretchDraw (OutRect, Bmp) ;
finally
Bmp.Free;
end ;
end
else i f Column.Field i s TMemoField then
begin
DrawText (Canvas-Handle, PChar
(Column.Field.AsString) ,
Length (Column.Fie1d.AsString), OutRect,
dt-WordBreak or dt-Noprefix)
end
else // dibuja una sola linea centrada verticalmente
DrawText (Canvas.Handle, PChar
(Column.Field.DisplayText),
Length (Column.Field. DisplayText) , OutRect,
dt-vcenter or dt-Singleline or dt-Noprefix);
end ;
end ;

En este codigo, se puede comprobar que si el usuario muestra una unica linea,
la cuadricula utiliza la tecnica estandar de representacion, sin salidas para cam-
pos graficos ni de memo. No obstante, en cuanto aumenta el numero de lineas se
podra comprobar como mejora la salida.
Para ver como funciona este codigo, hay que ejecutar el ejemplo GridDemo.
Este programa cuenta con dos botones que podemos utilizar para aumentar o
disminuir la altura de las filas de la cuadricula y otros dos mas para cambiar la
fuente. Se trata de una comprobacion importante, ya que la altura de cada celda
en pixeles es la altura de la fuente multiplicada por el numero de lineas.

Construir conjuntos de datos personalizados


Cuando hablamos de la clase T D a t a S e t y de las familias alternativas de
componentes de conjuntos de datos disponibles, comentamos la posibilidad de
escribir una clase de conjunto de datos personalizada. Los motivos para escribir
un conjunto de datos personalizado radican en que no tendremos que poner en
marcha un motor de base de datos, per0 aun asi se podra sacar todo el rendimiento
posible a la arquitectura de bases de datos de Delphi, que engloba recursos como
campos de bases de datos permanentes y controles data-aware.
Escribir un conjunto de datos personalizado es una de las tareas mas comple-
jas para un desarrollador de componentes, ya que se trata de una de las areas mas
avanzadas (hasta el nivel de las tecnicas de programacion a bajo nivel, incluyendo
millones de punteros) del libro. Aun mas, Borland aun no ha publicado ninguna
documentacion oficial sobre la creacion de conjuntos de datos personalizados. Si
se trata de las primeras experiencias con Delphi, podria ser aconsejable saltarse el
resto de este capitulo y volver a este punto mas adelante.
La clase TData Set es una clase abstracta que declara varios metodos abs-
tractos virtuales (23 metodos en Delphi, ahora solo unos cuantos, ya que la mayo-
ria han sido sustituidos por metodos virtuales vacios que habra que sobrescribir).
Cada subclase de TDataSet debe sobrescribir todos esos metodos.
Antes de analizar el desarrollo de un conjunto de datos personalizado, debe-
mos estudiar algunos elementos tecnicos de la clase TDataSet,en concreto 10s
buffers de 10s registros. La clase mantiene una lista de buffers que almacenan 10s
valores de 10s diversos registros. Estos buffers almacenan 10s datos, per0 tambien
suelen almacenar informacion adicional para que la utilice el conjunto de datos
cuando este trabajando con 10s registros. Estos buffers no tienen una estructura
predefinida y cada conjunto de datos personalizado debe ubicarlos, rellenarlos y
destruirlos. El conjunto de datos personalizado tambien debe copiar 10s datos
desde 10s buffers de registro a 10s distintos campos del conjunto de datos, y
viceversa. En otras palabras, el conjunto de datos personalizado es completamen-
te responsable de la gestion de estos buffers.
Ademas de la gestion de 10s buffers de datos, el componente tambien es res-
ponsable de la navegacion entre 10s registros, la gestion de 10s marcadores, la
definicion de la estructura del conjunto de datos y la creacion de 10s campos de
datos adecuados. La clase TDataSet no es mas que un entorno de trabajo que
hay que rellenar con el codigo apropiado. Afortunadamente, la mayor parte del
codigo sigue una estructura estandar que utilizan las clases derivadas de
TDataset de la VCL. Una vez que se hayan comprendido las ideas clave, se
podran crear multiples conjuntos de datos personalizados tomando prestada una
gran cantidad de codigo.
Para simplificar este tip0 de reciclaje, hemos agrupado las caracteristicas co-
munes que necesita todo conjunto de datos personalizado de la clase
TMDCustomDataSet.Sin embargo, no vamos a comentar en primer lugar la
clase base y despues la implernentacion especifica, porque resultaria algo compli-
cado. En su lugar, detallaremos el codigo necesario para un conjunto de datos,
presentando 10s metodos de las clases genericas y especificas a1 mismo tiempo,
segun un esquema logico.

La definicion de las clases


Como es habitual, el punto de partida es la declaracion de las dos clases ana-
lizadas en esta seccion, el conjunto de datos generic0 escrito y un componente
especifico para el almacenamiento de datos en un stream de archivo. Las declara-
ciones de estos valores estan recogidas en el listado 17.2. Ademas de 10s metodos
virtuales, las clases contienen una serie de campos protegidos que se utilizan para
gestionar 10s buffers, hacer un seguimiento de la posicion actual y el numero de
registros y controlar muchas otras caracteristicas. Deberiamos prestar atencion a
otra declaracion que se encuentra a1 principio: una estructura utilizada para al-
macenar datos adicionales para cada registro de datos que se coloca en un buffer.
El conjunto de datos ubica esta inforrnacion en el buffer de cada registro, a conti-
nuacion de 10s datos reales.
Listado 18.2. La declaracion de TMdCustomDataSet y TMdDataSetStream

// e n l a u n i d a d M d D s C u s t o m
type
EMdDataSetError = class (Exception);
TMdRecInfo = record
Bookmark: Longint;
BookmarkFlag: TBookmarkFlag;
end ;
PMdRecInfo = "TMdRecInfo;

TMdCustomDataSet = class (TDataSet)


protected
// e s t a d o
FIsTableOpen: Boolean;
// d a t o s d e l r e g i s t r o
FRecordSize, // e l t a m a d o d e 1 0 s d a t o s r e a l e s
FRecordBufferSize, // d a t o s + t a r e a s d e m a n t e n i m i e n t o
// ( T R e c I n f o )
FCurrentRecord, // r e g i s t r o a c t u a l ( 0 a F R e c o r d C o u n t - 1 )
BofCrack, // a n t e s d e l p r i m e r r e g i s t r o ( c r a c k )
EofCrack: Integer; // d e s p u e s d e l u l t i m o r e g i s t r o
// ( c r a c k )
// c r e a , c i e r r a , e t c .
procedure Internalopen; override;
procedure Internalclose; override;
function IsCursorOpen: Boolean; override;
// f u n c i o n e s p e r s o n a l i z a d a s
function InternalRecordCount: Integer; virtual; abstract;
procedure Internalpreopen; virtual;
procedure InternalAfterOpen; virtual;
procedure InternalLoadCurrentRecord(Buffer: PChar);
virtual; abstract;
// a d m i n i s t r a c i d n d e m e m o r i a
function AllocRecordBuffer: PChar; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
function GetRecordSize: Word; override;
// m o v i m i e n t o y n a v e g a c i o n o p c i o n a l ( u t i l i z a d a p o r l a s
// c u a d r i c u l a s )
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean) :
TGetResult; override;
procedure InternalFirst; override;
procedure InternalLast; override;
function GetRecNo: Longint; override;
function GetRecordCount: Longint; override;
procedure SetRecNo (Value: Integer) ; override;
// m a r c a d o r e s
procedure InternalGotoBookmark(Bookmark: Pointer);
override;
procedure InternalSetToRecord (Buffer: PChar) ; override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer);
override;
procedure GetBookmarkData (Buffer: PChar; Data: Pointer) ;
override;
procedure SetBookmarkFlag(Buffer: PChar; Value:
TBookmarkFlag); override;
function GetBookmarkFlag (Buffer: PChar) : TBookmarkFlag;
override;
// e d i c i d n ( v e r s i o n e s f i c t i c i a s )
procedure InternalDelete; override;
procedure InternalAddRecord(Buffer: Pointer; Append:
Boolean) ; override;
procedure InternalPost; override;
procedure InternalInsert; override;
// o t r o
procedure InternalHandleException; override;
published
// p r o p i e d a d e s d e l c o n j u n t o d e d a t o s r e d e c l a r a d a s
property Active;
property Bef oreopen;
property Afteropen;
property Beforeclose;
property Afterclose;
property BeforeInsert;
property AfterInsert;
property Bef oreEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property Beforecancel;
property Aftercancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property Afterscroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
end ;

// e n l a u n i d a d M d D s S t r e a r n
type
TMdDataFileHeader = record
VersionNumber: Integer;
Recordsize: Integer;
Recordcount: Integer;
end :

TMdDataSetStream = class (TMdCustomDataSet)


private
procedure SetTableName(const Value: string);
protected
FDataFileHeader: TMdDataFileHeader;
FDataFileHeaderSize, // tannfio d e cabecera d e a r c h i v o
// o p c i o n a l
FRecordCount: Integer; // n u m e r o a c t u a l d e r e g i s t r o s
FStream: TStream; // l a t a b l a f i s i c a
FTableName: string; // nombre d e a r c h i v o y r u t a d e t a b l a
FFieldOffset: TList; // d e s p l a z a m i e n t o s d e campo e n e l
// b u f f e r
protected
// a b r e y c i e r r a
procedure Internalpreopen; override;
procedure InternalAfterOpen; override;
procedure Internalclose; override;
procedure InternalInitFieldDefs; override;
// s o p o r t e d e e d i c i d n
procedure InternalAddRecord(Buffer: Pointer; Append:
Boolean) ; override;
procedure InternalPost; override;
procedure InternalInsert; override;
// c a m p o s
procedure SetFieldData(Fie1d: TField; Buffer: Pointer);
override;
// z k t o d o s v i r t u a l e s d e l c o n j u n t o d e d a t o s
// p e r s o n a l i z a d o
function InternalRecordCount: Integer; override;
procedure InternalLoadCurrentRecord(Buffer: PChar);
override;
public
procedure CreateTable;
function GetFieldData(Fie1d: TField; Buffer: Pointer):
Boolean; override;
published
property TableName: string read FTableName write
SetTableName;
end :

A1 dividir 10s metodos en apartados (como podemos comprobar en 10s archivos


de codigo fuente), 10s hemos marcado con un numero romano. Veremos dichos
numeros en un comentario donde se describe el metodo, de tal forma que mientras
nos desplacemos por esta larga lista podamos saber en todo momento en cual de
10s tres apartados nos encontramos.
Apartado I: Inicio, apertura y cierre
Los primeros metodos que se examinaremos son responsables de iniciar el
conjunto de datos y de abrir y cerrar el stream de archivo que utilizamos para
almacenar 10s datos. Ademas de iniciar 10s datos internos del componente, estos
metodos son responsables de iniciar y conectar 10s objetos TFields adecuados
a1 componente del conjunto de datos. Para conseguir que funcione, todo lo que se
necesita es iniciar la propiedad FieldsDef con las definiciones de 10s campos
de nuestro conjunto de datos, y despues llamar a algunos metodos estandar para
generar y enlazar 10s objetos T Field. Este es el metodo Internalopen gene-
ral:
procedure TMDCustomDataSet.Internal0pen;
begin
InternalPreOpen; / / m e t o d o p e r s o n a l i z a d o p a r a subclases

// i n i c i a l a s d e f i n i c i o n e s d e campos
InternalInitFieldDefs:

// s i n o h a y o b j e t o s d e campo p e r m a n e n t e s , c r e a 1 0 s c a m p o s d e
/ / forma d i n d m i c a
if DefaultFields then
CreateFields;

// c o n e c t a 1 0 s o b j e t o s T F i e l d c o n 1 0 s c a m p o s r e a l e s
BindFields (True);

InternalAfterOpen; / / m e t o d o p e r s o n a l i z a d o p a r a subclases

// d e f i n e 1 0 s c r a c k s y l a p o s i c i o n y tamado d e l r e g i s t r o
BofCrack : = - 1 ;
EofCrack : = InternalRecordCount;
FCurrentRecord : = BofCrack;
FRecordBufferSize : = FRecordSize + sizeof (TMdRecInfo);
Bookmarksize : = sizeof (Integer);

// t o d o O K : a h o r a l a t a b l a e s t d a b i e r t a
FIsTableOpen : = True;
end;

Podemos ver que el metodo define gran parte de 10s campos locales de la clase,
asi como el campo Boo kmar kSi ze de la clase base TDa taSet . En este meto-
do, se ha llamado a dos metodos personalizados que se han introducido en la
jerarquia del conjunto de datos personalizado: I n t e r n a 1 P r e o p e n e
InternalAfterOpen.
El primero, Internal PreOpen, se utiliza para operaciones que son nece-
sarias a1 principio, como la comprobacion de si puede abrirse el conjunto de datos
y la lectura de la informacion del cabecera del archivo. El codigo comprueba si un
numero de version interno se corresponde con el valor que se guard6 cuando se
creo la tabla por primera vez. A1 crear una excepcion en este metodo, podemos
detener en ultimo termino la operacion de apertura.
A continuacion, aparece el codigo de 10s dos metodos del conjunto de datos
derivado basado en streams:
cons t
HeaderVersion = 10;

procedure TMdDataSetStream-InternalPreOpen;
begin
// e l tamafio d e l a c a b e c e r a
FDataFileHeaderSize : = sizeof (TMdDataFileHeader);

// v e r i f i c a s i e x i s t e e l a r c h i v o
i f n o t FileExists (FTableName) then
r a i s e EMdDataSetError .Create ( ' O p e n : T a b l e f i l e not
found' ) ;

// c r e a u n s t r e a m p a r a e l a r c h i v o
F S t r e a m : = T F i l e S t r e a m - C r e a t e (FTableName, fmOpenReadWrite);

// i n i c i a d a t o s l o c a l e s (cargando el t i t u l o )
F S t r e a m - R e a d B u f f e r (FDataFileHeader, FDataFileHeaderSize);
i f FDataFileHeader-VersionNumber <> HeaderVersion then
r a i s e EMdDataSetError. Create ( ' I l l e g a l P i l e V e r s i o n ' ) ;
// v a m o s a l e e r e s t o , v e r i f i c a r d e n u e v o m d s a d e l a n t e
FRecordCount : = FDataFi1eHeader.RecordCount;
end;

procedure TMdDataSetStream.InternalAfter0pen;
begin
// v e r i f i c a e l t a m a f i o d e l r e g i s t r o
i f FDataFi1eHeader.RecordSize <> FRecordSize then
r a i s e EMdDataSetError-Create ( ' F i l e r e c o r d s i z e
mismatch' ) ;
// v e r i f i c a e l n u m e r o d e r e g i s t r o s en o p o s i c i o n a 1 t a m a f i o d e l
// a r c h i v o
i f (FDataFileHeaderSize + FRecordCount * FRecordSize) <>
FStream-Size then
r a i s e EMdDataSetError .Create ( ' I n t e r n a l o p e n : I n v a l i d
Record S i z e ' ) ;
end ;

El segundo metodo, In t ernalAf t eropen, se utiliza para operaciones que


son necesarias una vez que se hayan establecido las definiciones de campo y va
seguido por codigo que compara el tamaiio del registro leido desde el archivo con
el valor calculado en el metodo InternalInitFieldDef s . El codigo tam-
bien comprueba si el numero de registros que se leen desde la cabecera es compa-
tible con el tamaiio real del archivo. Esta verificacibn puede fallar si el conjunto
de datos no esta cerrado de forma adecuada: podria desearse modificar este codi-
go para permitir que el conjunto de datos refresque aun asi el tamaiio del registro
en la cabecera. Es responsabilidad especifica del metodo 1 nternalopen de la
clase del conjunto de datos personalizado llamar a InternalInit FieldDef s,
que establece las definiciones de 10s campos (en tiempo de ejecucion o de diseiio).
Para este ejemplo, se ha decidido basar las definiciones de campo en un archivo
externo (un archivo IN1 que proporciona un apartado para cada campo). Cada
apartado contiene el nombre y 10s tipos de dato del campo, asi como su tamaiio si
se trata de datos de cadena. El listado 17.3 muestra el archivo Con t r ib . IN I
que utilizaremos en la aplicacion de prueba del componente:

Listado 17.3. El archivo Contrib.lNI para la aplicacion de prueba.

[Fields]
Number = 6

[Fieldl]
Type = f t S t r i n g
Name = Name
S i z e = 30

[Field2]
Type = f t I n t e g e r
Name = L e v e l

[Field31
Type = f t D a t e
Name = B i r t h D a t e

[Field41
Type = f t c u r r e n c y
Name = S t i p e n d

[Fields]
Type = f t S t r i n g
Name = Email
S i z e = 50

[Field61
Type = f t B o o l e a n
Name = E d i t o r

Este archivo, o uno similar, debe tener el mismo nombre que el archivo de
tabla y debe ubicarse en el mismo directorio.
El metodo InternalInit FieldDef s (que se muestra en el listado 17.4)
lo leera utilizando 10s valores que encuentra para establecer las definiciones de
campo y determinar el tamaiio de cada registro.
El metodo tarnbien inicia un objeto TLis t interno que almacena el desplaza-
miento de cada campo dentro del registro. Se utiliza esta TLis t para acceder a
10s datos de 10s campos en el buffer de registro, como se puede comprobar en el
fragment0 de codigo.
Listado 17.4. El metodo InternallnitFieldDefs del conjunto de datos basado en
streams.

procedure TMdDataSetStream.InternalInitFie1dDefs;
var
IniFileName, FieldName: string;
IniFile: TIniFile;
nFields, I, TmpFieldOff set, nSize: Integer;
FieldType: TFieldType;
begin
FFieldOffset : = TList.Create;
FieldDefs-Clear;
TmpFieldOffset : = 0;
IniFilename : = ChangeFileExt(FTableName, '.init);
Inifile : = TIniFile.Create (IniFilename);
// protege el archivo INI
try
nFields : = IniFile.ReadInteger ( ' Fields' , 'Number', 0) ;
if nFields = 0 then
raise EDataSetOneError-Create ( ' InitFieldsDefs: 0
fields?' ) ;
for I : = 1 to nFields do
begin
// crea el campo
FieldType : = TFieldType (GetEnumValue (TypeInfo
(TFieldType),
IniFile. Readstring ( 'Field' + IntToStr (I), ' Type',
' I ) ) ) ;

FieldName : = IniFile .Readstring ( 'Field' + IntToStr


(I), 'Name', " ) ;
if FieldName = " then
raise EDataSetOneError-Create ('InitFieldsDefs: No
name for field ' + IntToStr (I));
nSize : = IniFile-ReadInteger ('Field' + IntToStr (I),
'Size', 0) ;
FieldDefs.Add (FieldName, FieldType, nSize, False);
/ / guarda el desplazamiento y calcula el tarnado
FFieldOf fset .Add (Pointer (TmpFieldOffset) ) ;
case FieldType of
ftString: Inc
(TmpFieldOffset, nSize + 1) ;
ftBoolean, ftSmallInt, ftWord: Inc
(TmpFieldOffset, 2) ;
ftInteger, ftDate, ftTime: Inc
(TmpFieldOffset, 4) ;
ftFloat, ftcurrency, ftDateTime: Inc
(TmpFieldOffset, 8) ;
else
raise EDataSetOneError-Create ('InitFieldsDefs:
Unsupported field type' ) ;
end ;
end; / / del for
finally
1niFile.Free;
end ;
FRecordSize : = TrnpFieldOffset;
end ;

Para cerrar la tabla, solo hay que desconectar 10s campos utilizando llamadas
estandar. Cada clase debe encargarse de 10s datos que asigno y actualizar la
cabecera del archivo, la primera vez que se aiiaden 10s registros y cada vez que se
modifique el numero de registros:
procedure TMDCustomDataSet.InternalClose;
begin
// d e s c o n e c t a r o b j e t o s d e campo
BindFields (False);
// d e s t r u y e e l o b j e t o d e campo ( s i n o e s p e r m n e n t e )
i f DefaultFields then
DestroyFields;
// c i e r r a e l a r c h i v o
FIsTableOpen : = False;
end ;

procedure TMdDataSetStream.InternalClose;
begin
// s i e s n e c e s a r i o , g u a r d a l a c a b e c e r a a c t u a l i z a d a
if (FDataFi1eHeader.RecordCount <> FRecordCount) or
(FDataFi1eHeader.RecordSize = 0) then
begin
FDataFi1eHeader.RecordSize : = FRecordSize;
FDataFi1eHeader.RecordCount : = FRecordCount;
i f Assigned (FStrearn) then
begin
FStream. Seek (0, soFromBeginning) ;
FStrearn.WriteBuffer (FDataFileHeader,
FDataFileHeaderSize);
end ;
end ;
// l i b e r a 1 0 s d e s p l a z a m i e n t o s d e campo d e l a l i s t a i n t e r n a y
el stream
FField0ffset.Free;
FStream. Free;
i n h e r i t e d Internalclose;
end ;

Se utiliza otra funcion relacionada para comprobar si el conjunto de datos esta


abierto, algo que se puede resolver utilizando el campo local correspondiente:
function TMDCustomDataSet.IsCursor0pen: Boolean;
begin
Result : = FIsTableOpen;
end ;

Estos son 10s metodos de apertura y cierre que debemos implementar en cual-
quier conjunto de datos personalizados. No obstante, en la mayoria de 10s casos,
tambien tendremos que aiiadir un metodo para crear la tabla. En este ejemplo, el
metodo CreateTable crea un archivo vacio e inserta informacion en la cabe-
cera: un numero fijo de version, un tamaiio de registro ficticio (no se conoce el
tamaiio real hasta que se inicien 10s campos) y el recuento de registros (que a1
empezar sera cero):
procedure TMdDataSetStream-CreateTable;
begin
CheckInactive;
InternalInitFieldDefs;

// c r e a e l a r c h i v o n u e v o
if FileExists (FTableName) then
raise EMdDataSetError.Create ( ' P i l e ' + FTableName + ' .
already e x i s t s ' );
FStream : = TFileStream.Create (FTableName, fmCreate or
fmShareExclusive);
try
// guarda l a c a b e c e r a
FDataFi1eHeader.VersionNumber : = Headerversion;
FDataFileHeader .Recordsize : = 0; / / s e u t i l i z a mds t a r d e
FDataFileHeader .Recordcount : = 0; // v a c i o
FStream-WriteBuffer (FDataFileHeader, FDataFileHeaderSize);
finally
// c i e r r a e l a r c h i v o
FStream. Free;
end ;
end :

Apartado II: Movimiento y gestion


de marcadores
Como se menciono anteriormente, uno de 10s elementos que todo conjunto de
datos debe implementar es la gestion de marcadores, necesaria para navegar por
el conjunto de datos. Como es logico, un marcador es una referencia a un registro
especifico del conjunto de datos. Esta marca identifica inequivocamente el regis-
tro, de tal forma que un conjunto de datos puede acceder a el y compararlo con 10s
otros registros. Tecnicamente, 10s marcadores son punteros. Se pueden implementar
como tales para estructuras de datos especificas que almacenen informacion de
registros o se pueden implementar como un simple numero de registro. Para sim-
plificar este proceso, se utilizara la segunda opcion.
Para un marcador dado, deberiamos poder encontrar el registro correspon-
diente; per0 con un buffer de registro dado tambien deberiamos poder recuperar el
marcador correspondiente. Esta es la razon por la que se anexa la estructura
T M d R e c I n fo a 10s datos del registro en cada buffer de registro. Esta estructura
de datos almacena 10s marcadores para el registro en el buffer, asi como algunos
indicadores de marcadores que se definen como sigue:
type
TBookmarkFlag = (bfcurrent, bfBOF, bfEOF, bf Inserted) ;

El sistema nos va pedir que almacenemos estos indicadores en el buffer de


cada registro y, mas adelante, nos pedira que recuperemos 10s indicadores para un
buffer de registro en concreto.
En resumen, la estructura de un buffer de registro almacena 10s datos del
registro, el marcador y 10s indicadores de marcador como se puede observar en la
figura 17.5.

FRecordSize

Bookmark BookmarkFlag

Figura 17.5. La estructura de cada buffer del conjunto de datos personalizado, junto
con 10s diversos campos locales que hacen referencia a sus partes.

Para acceder a 10s marcadores y 10s indicadores, se puede utilizar como des-
plazamiento el tamaiio de 10s datos reales, convirtiendo el valor a1 tip0 de puntero
P M d R e c Inf o. Despues habra que acceder a1 campo apropiado de la estructura
T M d R e c Inf o mediante el puntero.
Los dos metodos que se utilizan para establecer y obtener 10s indicadores de
marcador muestran esta tecnica:
procedure TMDCustomDataSet.SetBookmarkF1ag (Buffer: PChar;
Value: TBookmarkFlag);
begin
PMdRecInfo(Buffer + FRecordSize).BookmarkFlag : = Value;
end;

function TMDCustomDataSet.GetBookmarkF1ag (Buffer: PChar):


TBookmarkFlag;
begin
Result := PMdRecInf o (Buffer + FRecordSize) .BookmarkFlag;
end;

Los metodos que se utilizan para establecer y obtener el marcador actual de un


registro son muy parecidos a 10s dos anteriores, per0 aiiaden complejidad ya que
se recibe un puntero a1 marcador en el p a r h e t r o Data. Se obtiene el valor del
marcador convirtiendo el valor referido por este puntero a un entero:
procedure TMDCustomDataSet.GetBookmarkData (Buffer: PChar; Data:
Pointer) ;
begin
PInteger (Data)^ : = PMdRecInfo (Buffer +
FRecordSize).Bookmark;
end;

procedure TMDCustomDataSet.SetBookmarkData (Buffer: PChar;


Data: Pointer) ;
begin
PMdRecInfo (Buffer + FRecordSize) .Bookmark : =
PInteger (Data)";
end;

El metodo clave para la gestion de marcadores es I n t e r n a l G o t o B o o kmar k,


que utiliza el conjunto de datos para convertir un registro dado en el actual. No se
trata de la tecnica de navegacion estandar, es mucho mas habitual desplazarse a1
registro anterior o a1 siguiente (lo cual se puede realizar utilizando el metodo
G e t R e c o r d del que hablaremos en breve) o desplazarse a1 primer o ultimo
registro (para lo cual se utilizan 10s metodos I n t e r n a 1F i r s t e
I n t e r n a l L a s t que tambien describiremos).
Por sorprendente que pueda resultar, el metodo I n t e r na l G o t oBoo kmar k
no espera un parametro de marcador, sin0 un punter0 a un marcador, por lo tanto
hay que resolver la referencia para determinar el valor del marcador. El metodo
siguiente, I n t e r n a l S e t T o R e c o r d , es el que se utiliza para saltar a un mar-
cador determinado, per0 debe extraer el marcador de un buffer de registro pasado
como parametro. En ese momento, I n t e r n a l S e t T o R e c o r d llama a
I n t e r n a l G o t oBoo kmar k . A continuation, se muestran 10s dos metodos:

procedure TMDCustomDataSet.InternalGotoBookmark (Bookmark:


Pointer) ;
var
ReqBookmark: Integer;
begin
ReqBookmark : = PInteger (Bookmark)";
i f (ReqBookmark >= 0) and (ReqBookmark <
InternalRecordCount) then
FCurrentRecord : = ReqBookmark
else
raise EMdDataSetError.Create ( 'Bookmark ' +
IntToStr (ReqBookmark) + ' not found' ) ;
end ;

procedure TMDCustomDataSet.1nternalSetToRecord (Buffer: PChar);


var
ReqBookmark: Integer;
begin
ReqBookmark : = PMdRecInfo (Buffer + FRecordSize) .Bookmark;
InternalGotoBookmark (@ReqBookmark);
end;
Ademas de 10s metodos de gestion de marcadores que se acaban de describir,
existen algunos otros metodos de navegacion que se utilizan para moverse hasta
ubicaciones especificas dentro del conjunto de datos, como el primer registro o el
ultimo. Estos dos metodos no mueven realmente el punter0 de registro actual a1
primer o ultimo registro, sino que lo desplazan a una ubicacion especial de entre dos
posibles, antes del primer registro y despues del ultimo. No se trata de registros
reales, Borland 10s denomina cracks (brechas). El crack del comienzo de un archi-
vo, o Bof Crac k, tiene un valor -I(establecido en el metodo Internalopen ),
ya que la posicion del primer registro es cero. El crack de final de archivo, o
Eo fCra c k, tiene el valor del numero de registros, ya que el ultimo registro esta en
la posicion FRecordCount - 1. Se han utilizado dos campos locales, llamados
Eo fCrac k y Bo fCrac k para facilitar la lectura del codigo:
procedure TMDCustomDataSet.Interna1First;
begin
FCurrentRecord : = BofCrack;
end;

procedure TMDCustomDataSet.Interna1Last;
begin
EofCrack : = InternalRecordCount;
FCurrentRecord : = EofCrack;
end :

El metodo I nt ernalRecordCount es un metodo virtual que se introdujo


en la clase TMDCustomDataSet, ya que distintos conjuntos de datos pueden
tener un campo local para este valor (como en el caso del conjunto de datos
basado en streams, que contiene un campo FRecordCount) o calcularlo sobre
la marcha.
Se utiliza otro grupo de metodos opcionales para conseguir el numero de regis-
tro actual (utilizado por el componente DBGrid para mostrar una barra de despla-
zamiento vertical proporcional), establecer dicho numero o determinar el numero
de registros. Estos metodos son muy sencillos, si se tiene presente que el rango del
campo interno FCur r ent Re cor d se encuentra entre 0 y el numero de registros
menos 1. Por el contrario, el numero de registro que se comunica a1 sistema varia
entre 1 y el numero de registros:
function TMDCustomDataSet.GetRecordCount: Longint;
begin
CheckActive;
Result : = InternalRecordCount;
end;

function TMDCustomDataSet.GetRecNo: Longint;


begin
UpdateCursorPos;
i f FCurrentRecord < 0 then
Result : = 1
else
Result := FCurrentRecord + 1;
end;

p r o c e d u r e TMDCustomDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
i f (Value > 1 ) and (Value <= FRecordCount) t h e n
begin
FCurrentRecord : = Value - 1;
Resync ( [ I ) ;
end ;
end;

Fijese en que la clase del conjunto de datos personalizado generic0 implementa


todos 10s metodos de este apartado. El conjunto de datos derivado basado en
streams no necesita modificar ninguno de ellos.

Apartado Ill: Buffers de registro y gestion


de campos
Una vez que ya se han estudiado todos 10s metodos de apoyo, examinaremos el
nucleo de un conjunto de datos personalizado. Ademas de abrir, crear y navegar
por 10s registros, el componente necesita mover 10s datos desde el stream (el
archivo permanente) a 10s buffers de registro y de estos a 10s objetos TField que
estan conectados a 10s controles data-aware. La gestion de 10s buffers de registro
resulta bastante compleja, ya que cada conjunto de datos tambien necesita asig-
nar, vaciar y liberar la memoria necesaria:
f u n c t i o n TMdCustomDataSet.AllocRecordBuffer: PChar;
begin
G e t M e m (Result, FRecordBuf ferSize) ;
end;

p r o c e d u r e TMdCustomDataSet.FreeRecordBuffer (var Buffer:


PChar) ;
begin
FreeMem (Buffer) ;
end;

La razon para reservar memoria de este mod0 es que el conjunto de datos suele
aiiadir mas informacion a1 buffer del registro, por lo tanto el sistema no puede
saber cuanta memoria debe asignar. Hay que fijarse en que, en el metodo
A1 1ocRecordBuf f er, el componente reserva la memoria para el buffer de
registro, en el que se incluyen tanto 10s datos de la base de datos como la informa-
cion de registro. En el metodo Internalopen se escribio:
FRecordBufferSize : = InternalRecordSize + sizeof (TMdRecInfo);
El componente tambien necesita implementar una funcion para reiniciar el
buffer, InternalI nit Recor d,generalmente rellenandolo con ceros numeri-
cos o espacios.
Por sorprendente que parezca, tambien se debe implementar un metodo que
devuelva el tamaiio de cada registro, per0 solo del fragment0 de datos, no del
buffer de registro completo. Este metodo es necesario para implementar la propie-
dad de solo lectura Re cordSi ze,que solo se utiliza en un par de casos particu-
lares en todo el codigo fuente de la VCL. En el conjunto de datos personalizado
generico, el metodo G e t R e c o r d S i z e devuelve el valor del campo
FRecordSize.
Hemos llegado a1 nucleo de un componente de conjunto de datos personaliza-
do. Los metodos de este grupo son GetRecord,que lee 10s datos desde el archi-
vo, InternalPost e InternalAddRecord,que actualizany aiiadennuevos
datos a1 archivo e I nt ernal De l e te, que elimina 10s datos y no esta
implementado en este conjunto de datos de muestra.
El metodo mas complejo de este grupo probablemente sea GetRecord,que
sirve para fines muy diversos. De hecho, el sistema utiliza este metodo para
recuperar 10s datos del registro actual, rellenar un buffer pasado como parametro
y conseguir 10s datos del registro anterior o del siguiente. El parametro GetMode
determina su accion:
tme
TGetMode = (gmcurrent, gml\lext, gmPrior);

Resulta evidente que un registro anterior o posterior puede no existir; incluso


puede no existir el registro actual (por ejemplo, cuando la tabla esta vacia o en
caso de un error interno). En estos casos no se obtienen 10s datos, sino un codigo
de error. Por lo tanto, el resultado de este metodo puede ser uno de 10s siguientes
valores:

La comprobacion de si existe un registro determinado puede diferir ligeramen-


te de lo que se podria pensar. No es necesario determinar si el registro actual esta
en el rango adecuado, solo si lo esta ese registro determinado. Por ejemplo, en la
bifurcacion gmcur rent de la sentencia case,se utiliza la expresion estandar
CurrentRecord>=InternalRecourdCount.Para comprender plenamente
10s diversos casos, seria aconsejable leer el codigo un par de veces.
Hay que tener cuidado con la modificacion de este codigo, pues se llego a el
mediante el procedimiento de prueba y error (y una p a n cantidad de bloqueos en
la maquina, debido a las llamadas recursivas). Para comprobarlo, hay que tener
en cuenta que si utilizamos un componente DBGrid, el sistema realizara una
serie de llamadas a GetRecord,hasta que la cuadricula este llena o GetRecord
devuelva grEOF. A continuacion se muestra el codigo completo del metodo
GetRecord:
/ / 111: Recupera datos para el registro actual, el anterior o el
// siguiente (yendo a el si es necesario) y devuelve el estado
function TMdCustomDataSet.GetRecord(Buffer: PChar;
GetMode: TGetMode; DoCheck: Boolean): TGetResult;
begin
Result : = grOK; // predefinido
case GetMode of
gmNext: // sigue adelante
i f FCurrentRecord < InternalRecordCount - 1 then
Inc (FCurrentRecord)
else
Result : = grEOF; / / fin de archivo
gmPrior: / / retrocede
i f FCurrentRecord > 0 then
Dec (FCurrentRecord)
else
Result : = grBOF; / / comienzo de archivo
gmcurrent: / / verifica si estd vacio
i f FCurrentRecord >= InternalRecordCount then
Result : = grError;
end;
/ / carga 10s datos
i f Result = grOK then
InternalLoadCurrentRecord (Buffer)
e l s e i f (Result = grError) and DoCheck then
r a i s e EMdDataSetError.Create ('GetRecord: Invalid record');
end ;

Si hay un error y el parametro D o C h e c k era T r u e , G e t R e c o r d lanza una


excepcion. Si no todo va bien durante la seleccion de registros, el componente
carga 10s datos del stream, pasando a la posicion del registro actual (obtenido
mediante el tamaiio del registro multiplicado por el numero del registro). Ademas,
necesitamos iniciar el buffer con el indicador de marcador adecuado y el valor del
marcador (o numero de registro). Otro metodo virtual nos permite llevar a cab0
este proceso, de tal forma que las clases derivadas solo necesiten implementar
esta porcion de codigo, mientras que el complejo metodo G e t R e c o r d permane-
ce invariable:
procedure TMdDataSetStream.Interna1LoadCurrentRecord (Buffer:
PChar) ;
begin
FStream.Position : = FDataFileHeaderSize + FRecordSize *
FCurrentRecord;
FStream.ReadBuffer (BufferA, FRecordSize);
with PMdRecInfo(Buffer + FRecordSize)" do
begin
BookmarkFlag := bfcurrent;
Bookmark : = FCurrentRecord;
end ;
end ;
Movemos 10s datos a1 archivo en dos casos distintos: cuando modificamos el
registro actual (esto es, un envio despues de una edicion) o cuando aiiadimos un
nuevo registro (un envio despues de una insercion o aiiadido). En ambos casos se
utiliza el metodo Int erna 1Post, per0 se puede comprobar la propiedad st ate
del conjunto de datos para determinar que tipo de envio estamos realizando. En
ninguno de 10s casos se recibe un buffer de registro como uno de 10s parametro,
por lo que es necesario utilizar la propiedad Act iveRecord de TDataSet,
que indica apunta a1 buffer para el registro actual:
procedure TMdDataSetStream.Interna1Post;
begin
CheckActive;
if State = dsEdit then
begin
// s u s t i t u i r d a t o s p o r d a t o s n u e v o s
FStream.Position : = FDataFileHeaderSize + FRecordSize *
FCurrentRecord;
FStream.WriteBuffer (ActiveBufferA, FRecordSize);
end
else
begin
// a d a d i r s i e m p r e
InternalLast;
FStream.Seek (0, soFromEnd);
FStream-WriteBuffer (ActiveBufferA, FRecordSize);
Inc (FRecordCount);
end;
end;

Existe ademas otro metodo relacionado: InternalAddRecord. A este


metodo lo llama el metodo A d d R e c o rd, a1 que a su vez es llamado por
InsertRecord y AppendRecord.
Los dos ultimos son metodos publicos a 10s que puede llamar cualquier usua-
rio. Este recurso es una alternativa para insertar o anexar un nuevo registro a1
conjunto de datos, editando 10s valores de diversos campos y enviando despues
10s datos, ya que las llamadas Insert Record y AppendRecord reciben 10s
valores de 10s campos como parametros. Todo lo que hay que hacer en ese mo-
mento es repetir el codigo utilizado para aiiadir un nuevo registro en el metodo
InternalPost:

procedure TMdDataSetOne.InternalAddRecord(Buffer: Pointer;


Append: Boolean) ;
begin
// a d a d i r s i e m p r e a 1 f i n a l
InternalLast;
FStream. Seek (0, soFromEnd) ;
FStream.WriteBuffer (ActiveBufferA, FRecordSize);
Inc (FRecordCount);
end;
La ultima operacion de archivo que se deberia haber implementado era una
que eliminase el registro actual. Esta operacion, aunque habitual, es bastante
compleja. Si se usa un enfoque sencillo, como puede ser crear un punto vacio en el
archivo, habra que realizar un seguimiento de dicho punto y conseguir que el
codigo de lectura y escritura de un registro especifico funcione se salte esa posi-
cion. Una solucion alternativa es hacer una copia de todo el archivo, sin el regis-
tro especifico, y despues sustituir a1 archivo original por la copia.

Apartado IV: De buffers a campos


En 10s ultimos metodos, se pudo comprobar como 10s conjuntos de datos tras-
ladan 10s datos desde el archivo de datos a1 buffer de memoria. Sin embargo,
Delphi poco puede hacer cuando se trata del buffer de registro, ya que todavia no
sabe como interpretar 10s datos del buffer. Es necesario proporcionar dos meto-
dos adicionales: G e t D a t a , que copia 10s datos del buffer de registro a 10s obje-
tos de campo del conjunto de datos, y S e t D a t a , que devuelve 10s datos de 10s
campos a1 buffer de registro. El proceso que Delphi realiza automaticamente es
llevar 10s datos de 10s objetos de campo a 10s controles data-aware y viceversa.
El codigo de estos dos metodos no es complejo, sobre todo porque se han
guardado 10s desplazamientos de campo dentro de 10s datos del registro en un
objeto T L i s t llamado F F i e l d O f f s e t . Incrementando el puntero a la posi-
cion inicial en el buffer del registro con el desplazamiento del campo actual, se
podran recuperar 10s datos especificos, que ocupan F i e l d .D a t a S i z e bytes.
Un elemento confuso de estos dos metodos es que ambos aceptan un parametro
F i e l d y un parametro B u f f e r . En un primer momento, se puede pensar que el
buffer que se pasa como parametro es el buffer de registro. Sin embargo, B u -
f f e r es un puntero a 10s datos en bruto del objeto del campo. Si utilizamos uno
de 10s metodos de objetos de campo para mover esos datos, llamaremos a 10s
metodos G e t D a t a o S e t D a t a del conjunto de datos, lo cual probablemente
causara una recursion infinita. En lugar de esto, deberiamos utilizar el puntero
A c t i v e B u f f e r para acceder a1 buffer del registro, utilizar el dcsplazamiento
adecuado para obtener 10s datos del campo actual del buffer de registro y, enton-
ces, usar el B u f f e r proporcionado para acceder a 10s datos del campo. La unica
diferencia entre 10s dos metodos es la direccion en la que se mueven 10s datos:
function TMdDataSet0ne.GetFieldData (Field: TField; Buffer:
Pointer) : Boolean;
var
FieldOffset: Integer;
Ptr: PChar;
begin
Result : = False;
i f not IsEmpty and (Field.FieldNo > 0 ) then
begin
FieldOffset : = Integer (FFieldOffset [ F i e l d - F i e l d N o - I ] ) ;
Ptr : = ActiveBuffer;
Inc (Ptr, FieldOffset) ;
i f Assigned (Buffer) then
Move (Ptr", BufferA, Field.DataSize);
Result : = True;
i f (Field is TDateTimeField) and (PInteger (Ptr)A = 0)
then
Result : = False;
end ;
end ;

procedure TMdDataSetOne.SetFieldData(Fie1d: TField; Buffer:


Pointer) ;
var
FieldOffset: Integer;
Ptr: PChar;
begin
i f Field.FieldNo >= 0 then
begin
FieldOffset : = Integer (FFieldOffset [Field.FieldNo - I ] ) ;
Ptr : = ActiveBuffer;
Inc (Ptr, FieldOffset) ;
i f Assigned (Buffer) then
Move (BufferA, PtrA, Field.Datasize)
else
raise Exception.Create ('Very bad error in
TMdDataSetStream. SetField data') ;
DataEvent (deFieldChange, Longint (Field)) ;
end;
end;

El metodo G e t F i e l d deberia devolver 10s valores T r u e o F a l s e para


indicar si el campo contiene datos o esta vacio (es un campo nulo, para ser mas
precisos).
No obstante, salvo que utilice un marcador especial para campos en blanco, es
muy dificil determinar esta condicion, ya que se almacenan valores de diferentes
tipos de datos. Por ejemplo, una prueba como Pt r < > # 0 solo tiene sentido si se
A

esta utilizando una representacion de cadena para todos 10s campos. Si utilizamos
esta prueba, 10s valores enteros que Sean cero y las cadenas vacias apareceran
como valores nulos (10s controles data-aware estaran vacios). El problema es que
10s valores booleanos falsos no aparecen o, todavia peor, 10s valores de coma
flotante sin decimales y con pocos digitos no se mostraran, ya que la parte
exponencial de su representacion sera cero.
No obstante, para conseguir que este ejemplo funcione, es necesario conside-
rar como vacios 10s campos de fecha y hora con un cero inicial. Sin este codigo,
Delphi intenta convertir la fecha cero interna no valida (internamente, 10s campos
de datos no utilizan un tipo de datos T D a t e T i m e , sino una representacion dife-
rente) creando una excepcion. El codigo funcionaba con las versiones anteriores
de Delphi.
--. - - -
[- ADYERTENCIA: Al tratar & salucionar este problems, tambib hemos
descubierto b e si se llama a I s ~ u l lpara un campo, esta pctici6n se
resuehe I W d o a Get FieldData sin que se pase ningun buffer a relle-
nar, sino GCmprobando el resultado de la llama& a esta funcion. Este b el
motivo de 1a comprobacion if A s signed ( Buffer ) dentr.o del c6digo.

Existe un metodo final que no entra en ninguna categoria: I n t e r n a l H a n d l e -


E x c e p t i o n . En general, este metodo silencia la excepcion, ya que solo se acti-
va en tiempo de diseiio.

Comprobacion el conjunto de datos basado


en streams
Ya estamos listos para probar un ejemplo de aplicacion del componente con-
junto de datos personalizado, que esta instalado en el paquete del componente
para este capitulo. El formulario que muestra el programa StreamDSDemo es
sencillo. como muestra la figura 17.6. Esta formado por un panel con dos boto-
nes, una casilla de verification y un componente de navegacion, ademas de un
DBGrid que cubre la zona del cliente.

ad 1 A-

) Marco 1 9/8/1965 C1.000 00 marco@rnarcocanlu corn


- salsald 2 3/12/1990 E500 00 pau@borland corn
-Paul 5 E50 W lohn@ma~lcorn
F=?
~&m&l

Figura 17.6. El formulario del ejemplo de StreamDSDemo. El conjunto de datos


personalizado se ha activado para que se puedan ver 10s datos en tiempo de diseiio.

La figura 17.6 muestra el formulario del ejemplo en tiempo de diseiio, pero se


ha activado el conjunto de datos de forma que 10s datos ya esten visibles. Eviden-
temente, ya se ha preparado el archivo IN1 con la definicion de la tabla (se trata
del archivo a1 que ya se hizo referencia cuando se analizo el inicio del conjunto de
datos), y se ha ejecutado el programa para afiadir algunos datos a1 archivo.
Tambien es posible modificar el formulario utilizando el editor de campos de
Delphi y establecer las propiedades de 10s diversos objetos de campo. Todo fun-
ciona igual que con uno de 10s controles de conjuntos de datos estandar. Sin
embargo, para conseguir que funcione, sera necesario escribir el nombre del ar-
chive del conjunto de datos personalizado en la propiedad T a b l e N a m e , utilizan-
do la ruta completa.
-- - -
ADVERTENCIA: El programa dc prueba de& ldrufa completa del ar-
chive de la tabla en tiempo de diseiio, por lo que sera necesario corregirla si
copiamos 10s ejemplos a un directorio o disco distinto. En el ejemplo, la
propiedad TableName solo se utiliza en tiempo de disefio. En tiempo de
ejecucion, el programa busca la tabla en el directorio actual.

El codigo del ejemplo es bastante sencillo, sobre todo si se compara con el


codigo del conjunto de datos personalizado. Si la tabla todavia no esiste, se puede
hacer clic sobre el boton Create New Table:
procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : TObject);
begin
MdDataSetStreaml.CreateTab1e;
MdDataSetStreaml.0pen;
CheckBoxl.Checked : = MdDataSetStream1.Active;
end ;

Vemos que primer0 se crea el codigo, abriendolo y cerrandolo dentro de la


llamada C r e a t e T a b l e . y despues se abre la tabla. Este es el mismo comports-
miento que el del componente T T a b l e (que realiza este proceso utilizando el
metodo C r e a t e T a b l e ) . Para abrir o cerrar la tabla, podemos hacer clic sobre
la casilla de verificacion:
procedure TForml.CheckBoxlClick(Sender: T O b j e c t ) ;
begin
MdDataSetStream1.Active : = CheckBoxl.Checked;
end;

Por ultimo, se ha creado un metodo que comprueba el codigo de gestion de


marcadores de conjuntos de datos personalizados (funciona).

Un directorio en un conjunto de datos


Una idea importante relacionada con 10s conjuntos de datos en Delphi es que
representan un conjunto de datos, sin importar la procedencia de 10s mismos. Un
servidor SQL o un archivo local son ejemplos de conjuntos de datos tradicionales,
per0 se puede utilizar la misma tecnologia para mostrar una lista de usuarios de
un sistema, una lista de archivos de una carpeta, las propiedades de algunos
objetos, algunos datos basados en XML, etc.
A mod0 de ejemplo, el segundo conjunto de datos que se presenta en este
capitulo es una lista de archivos. Se ha elaborado un conjunto de datos generico
basado en una lista de objetos en memoria (utilizando una TObjectList), del que
despues se ha derivado una version en la que 10s objetos se corresponden con 10s
archivos de una carpeta. El ejemplo se ha simplificado por el hecho de que es un
conjunto de datos de solo lectura, por lo que podria parecer incluso mas sencillo
que el anterior.

NOTA: &lguna de las ideas,coment- tmbi4s agwecqn en an


afticula publicado en Eunib de 2000 a el ORL bdn,borkd.darticb/
b,"ll0,2#587,~0.~titd,

Una lista como conjunto de datos


El conjunto de datos generico basado en una lista se I l a m a T M d L i s t D a t a S e t
y contiene una lista de objetos que se crea a1 abrir el conjunto de datos y se libera
cuando se cierra. Este conjunto de datos no almacena 10s datos del registro real
dentro del buffer; en cambio, solo guarda en el buffer la posicion que ocupan en la
lista de la entrada correspondiente a 10s datos del registro. Esta es la definicion de
la clase:
type
TMdListDataSet = class (TMdCustornDataSet)
protected
// l a l i s t a q u e c o n t i e n e 10s d a t o s
FList: TObjectList;
// m e t o d o s v i r t u a l e s d e l c o n j u n t o d e da t o s
procedure Internalpreopen; override;
procedure Internalclose; override;
// m e t o d o s v i r t u a l e s d e l c o n j u n t o d e d a t o s p e r s o n a l i z a d o
function InternalRecordCount: Integer; override;
procedure InternalLoadCurrentRecord (Buffer: PChar);
override;
end ;

Se puede ver que a1 escribir una clase de datos personalizada es posible


sobrescribir algunos metodos virtuales de la clase T D a t a S e t y de esta clase de
conjunto de datos personalizada y tener un conjunto de datos que funcione (aun-
que se trate todavia de una clase abstracta, que necesita codigo adicional de
subclases para funcionar). Cuando se abre el conjunto de datos, se debe crear la
lista y establecer el tamaiio de registro para indicar que solo se va a guardar el
indice de la lista en el buffer:
procedure TMdListDataSet-InternalPreOpen;
begin
FList : = T0bjectList.Create (True); // posee 10s objetos
FRecordSize : = 4 ; // un entero, el identificador d e elemento
de la lista
end ;

Las subclases adicionales tambien deben llenar la lista con objetos.

~antiene..msdatos.enpwmoria. Sbr embargo, mediante tecnicas inteligen-


tes tambiCn se puede w a r bna,lista iTe bbjetos falsos y cargar desputs 'los
objeto$ kales cuando pe acceda a ellos.

El cierre es una simple cuestion de liberacion de la lista, que tiene un numero


de registros que se corresponde con el tamaiio de la lista:
function TMdListDataSet.Interna1RecordCount: Integer;
begin
Result : = fList.Count;
end;

Solo existe otro metodo que se utiliza para guardar 10s datos del registro ac-
tual en el buffer del registro, incluyendo la informacion sobre 10s marcadores.
Los datos centrales se reducen a la posicion del registro actual, que se correspon-
de con el indice de la lista (y tambien con el marcador):
procedure TMdListDataSet.Interna1LoadCurrentRecord (Buffer:
PChar) ;
begin
PInteger (Buffer)* := fCurrentRecord;
with PMdRecInfo (Buffer + FRecordSize) " do
begin
BookmarkFlag : = bfcurrent;
Bookmark : = fCurrentRecord;
end;
end ;

Datos del directorio


La clase de conjunto de datos de directorio derivada tiene que proporcionar un
mod0 de cargar 10s objetos en memoria cuando el conjunto de datos se abre, para
definir 10s campos adecuados, y para leer y escribir 10s valores de dichos campos.
Ni que decir tiene que tambien cuenta con una propiedad que indica el directorio
en el que debe trabajar o, para ser mas precisos, el directorio mas la mascara de
archivo que se utilizara para filtrar 10s archivos (como ocurre en c: \docs\
* .txt):
type
TMdDirDataset = class(TMdListDataSet)
private
FDirectory: string;
procedure SetDirectory (const NewDirectory: string) ;
protected
// r n e t o d o s v i r t u a l e s d e T D a t a S e t
procedure InternalInitFieldDefs; override;
procedure SetFieldData (Field: TField; Buffer: Pointer) ;
override;
function GetCanModify: Boolean; override;
// r n e t o d o s v i r t u a l e s d e l c o n j u n t o d e d a t o s p e r s o n a l i z a d o
procedure InternalAfterOpen; override;
public
function GetFieldData (Field: TField; Buffer: Pointer) :
Boolean; override;
published
property Directory: stringread FDirectorywrite SetDirectory;
end;

La funcion GetCanModif y es otro metodo virtual de TDataSet que se


utiliza para determinar si el conjunto de datos es de solo lectura. En ese caso su
salida es Fa1 s e . No es necesario escribir codigo para el procedimiento
S e t Fie 1dDa t a, per0 si hay que definirlo ya que se trata de un metodo abstrac-
to virtual. Ya que vamos a manejar una lista de objetos, la unidad incluye una
clase para esos objetos. En este caso se trabaja 10s datos de archivo se extraen
desde un buffer T Sear chRe c gracias a1 constructor de la clase T F i leDa t a .
type
TFileData = class
public
ShortFileName: string;
Time: TDateTime;
Size: Integer;
Attr: Integer;
constructor Create (var FileInfo: TSearchRec);
end;

constructor TFileData.Create (var FileInfo: TSearchRec);


begin
ShortFileName : = FileInfo.Name;
Time : = FileDateToDateTime (FileInfo.Time);
Size : = FileInfo-Size;
Attr : = FileInfo.Attr;
end;

Para cada carpeta se llama a este constructor durante la apertura del conjunto
de datos:
procedure TMdDirDataset.InternalAfter0pen;
var
Attr: Integer;
FileInfo: TSearchRec;
FileData: TFileData;
begin
// define todos 10s archivos
Attr : = faAnyFile;
FList .Clear;
if SysUtils.FindFirst(fDirectory, Attr, FileInfo) = 0 then
repeat
FileData : = TFileData. Create (FileInfo);
FList .Add (FileData);
until SysUtils. FindNext (FileInfo) <> 0;
SysUtils.FindClose(FileInfo);
end;

El siguiente paso es definir 10s campos del conjunto de datos que; en este caso,
son fijos y dependen de 10s datos disponibles del directorio:
procedure TMdDirDataset.InternalInitFie1dDefs;
begin
i f fDirectory = " then
raise EMdDataSetError.Create ('Missing directory');

// definiciones de campos
FieldDefs.Clear;
FieldDefs .Add ( 'FileNamel, ftstring, 40, True) ;
FieldDefs .Add ( ' Timestamp', ftDateTime) ;
FieldDefs .Add ( ' Size1, ftInteger) ;
FieldDefs .Add ( 'Attributes', ftstring, 3) ;
FieldDefs.Add ('Folder', ftBoolean);
end;

Por ultimo, el componente tiene que mover 10s datos desde el objeto de la lista
a1 que hace referencia el buffer del registro actual (el valor A c t i v e B u f f e r )
hasta cada uno de 10s campos del conjunto de datos, tal como lo solicita el metodo
G e t F i e l d D a t a . Esta funcion utiliza Move o S t r C o p y , segun el tipo de da-
tos, y realiza algunas conversiones para 10s codigos de atributos (H para 10s
ocultos, R para 10s de solo lectura y s para 10s de sistema) que sc extraen de 10s
indicadores correspondientes y que tambien se utilizan para determinar si un ar-
chive es realmente una carpeta. A continuacion, se muestra el codigo:
function TMdDirDataset.GetFie1dData (Field: TField; Buffer:
Pointer) : Boolean;
var
FileData: TFileData;
Booll: WordBool;
strAttr: string;
t : TDateTimeRec;
begin
FileData : = £List [PInteger (ActiveBuffer)" 1 as TFileData;
case Field. Index of
0 : // nombre de archivo
StrCopy (Buffer, pchar(FileData.ShortFi1eName));
1: // s e l l o d e ultima m o d i f i c a c i d n
begin
t := DateTimeToNative (ftdatetime, FileData.Time) ;
Move (t, Buffer", sizeof (TDateTime)) ;
end ;
2: / / tarnafio
Move (FileData.Size, Buffer", sizeof (Integer));
3: / / a t t r i b u t o s
begin
strAttr : = ' 1 .

if (FileData.Attr and SysUtils.faReadOnly) > 0 then


strAttr [I] : = ' R ' ;
i f (FileData.Attr and SysUtils. faSysFile) > 0 then
strAttr [2] : = ' S t ;
i f (FileData.Attr and SysUtils .faHidden) > 0 then
strAttr [3] : = 'HI;
StrCopy (Buffer, pchar (strAttr)) ;
end;
4: / / d i r e c t o r i o
begin
Booll : = FileData.Attr and SysUtils.faDirectory > 0;
Move (Booll, Buff er", sizeof (WordBool)) ;
end ;
end; // d e c a s e
Result : = True;
end;

La parte mas compleja de la escritura de este codigo fue diseiiar el formato


interno de las fechas almacenados en 10s campos de fecha y hora. No se trata del
habitual formato TDateTime que se utiliza en Delphi, ni siquiera el TTimeStamp
interno, sino del que se conoce, de forma interna, como formato de fecha y hora
"nativo". Se ha escrito una funcion de conversion a partir de una que se encontro
en el codigo de la VCL para campos de fecha y hora:
function DateTimeToNative(DataType: TFieldType; Data:
TDateTime) : TDateTimeRec;
var
Timestamp: TTimeStamp;
begin
Timestamp : = DateTimeToTimeStamp(Data);
case DataType of
ftDate: Result.Date : = TimeStamp.Date;
ftTime: Result.Time : = TimeStamp.Time;
else
Result.DateTime : = TimeStampToMSecs(TimeStamp);
end ;
end ;

Una vez que el conjunto de datos esta disponible, la tarea de construir un


programa de muestra, como el que muestra la figura 17.7, se reduce a conectarle
un componente DBGrid y aiiadirle un componente de seleccion de carpetas, el
control ShellTreeView de ejemplo. Se prepara este control para trabajar solo
con archivos fijando su propiedad Root como C : \ . Cuando el usuario seleccio-
na una nueva carpeta, el controlador de eventos OnChange del control
ShellTreeView actualiza el conjunto de datos:
procedure TForml.ShellTreeViewlChange(Sender: TObject; Node:
TTreeNode) ;
begin
MdDirDatasetl.Close;

end;

13/9/2002 10:49:10 AN Tru. -


13/9/2002 10:49:1O M Tru.
9/8/2002 2:OO:OO PI 64.272 Falsa
9/8/2002 2:00:00 PI 645.792 False
9/8/2002 2:00:00 PH 6.646 False
14/9/2002 6:28:32 Pn 3.712 ?.Is@
9/8/2002 4:OO:OO Pn 66.560 Pals*
3 13
Altova
70.bpl 9/8/2002 4:00:00 PH 160.256 Ialsm
9/812002 4:OO:OO PI 4.449 hlsc
9/8/2002 4:OO:OO PI 3.120 ?aha
HTHLZ-strict.dcd 9/8/2002 4:OO:OO PI 18.796 False
3/8/2002 4:OO:OO PI 18.110 Fmlse
9/8/2002 4:OO:OO PI 21.875 Ialse
9/8/2002 4:OO:OO PI 11.956 False
9/8/2002 4:OO:OO PII 4.114 Ids*
9/9/2002 4:DO:dD PR 14 -447 Fmlr.

Figura 17.7. La salida del ejernplo DirDemo, que usa un conjunto de datos algo
inusual para rnostrar datos de directorio.

.- -- - -
ADVERTENCIA: Si la v e m h & Win&m que ~sewtiILa h n e proble-
mas con los cdroles de la shell & e-b disponibles a Dew,sq p d e
usar k versih DirDemoNoShell del ejempio, qae asa 1- v i e s d o l e s
de arcihivos de Delphi ~ornpatiblescon Windows 3.1.

Un conjunto de datos de objetos


Como hemos visto en el ejemplo anterior. una lista de objetos es similar en
concept0 a las filas de una tabla de un conjunto de datos. En Delphi se puede
construir un conjunto de datos que envuelva a una lista de objetos, como en el
caso de la clase T FileData.Resulta interesante ampliar este ejemplo para
construir un conjunto de datos que soporte objetos genericos, algo que se puede
hacer gracia a la RTTI extendida disponible en Delphi.
Este componente de conjunto de datos hereda de TMdLis t D a t a S e t , como
en el ejemplo anterior. Solo hay que proporcionar un unico parametro: la clase
objetivo, guardada en la propiedad o b j c l a s s (el listado 17.5 muestra la defini-
cion cornpleta de la clase TMdOb jD a t a S e t ) .

Listado 17.5. La definicion cornpleta de la clase TMdObjDataSet.

type
TMdObjDataSet = class (TMdListDataSet)
private
PropList: PPropList;
nProps : Integer;
FObjClass: TPersistentClass;
ObjClone: TPersistent;
FChangeToClone: Boolean;
procedure SetObjClass (const Value: TPersistentClass);
function Getobjects (I: Integer) : TPersistent;
procedure SetChangeToClone (const Value: Boolean);
protected
procedure InternalInitFieldDefs; override;
procedure Internalclose; override;
procedure InternalInsert; override;
procedure InternalPost; override;
procedure Internalcancel; override;
procedure InternalEdit; override;
procedure SetFieldData (Field: TField; Buffer: Pointer) ;
override;
function GetCanModify: Boolean; override;
procedure Internalpreopen; override;
pub1 i c
function GetFieldData ( Field: TField; Buffer: Pointer) :
Boolean; override;
property Objects [I: Integer] : TPersistent read
GetObj ects;
function Add: TPersistent;
published
property ObjClass : TPersistentClass read FObjClass write
SetObjClass;
property ChangeToClone: Boolean read FChangeToClone
write SetChangeToClone default False;
end :

La clase se usa en el metodo I n t e r n a l I n i t F i e l d D e f s para determinar


10s campos del conjunto de datos basandose las propiedades publicadas de la
clase objetivo, que se extraen mediante la RTTI:
procedure TMdObjDataSet.InternalInitFie1dDefs;
var
i: Integer;
begin
i f FObjClass = nil then
raise Excepction.Create ( ' TMdObjDataSet: Unassigned class' ) ;
// d e f i n i c i o n e s d e c a m p o s
FieldDefs-Clear;
nProps : = G e t T y p e D a t a ( f O b j C l a s s . C l a s s I n f ~ ) ~ . P r o p C o u n t ;
GetMem(PropList, nProps * SizeOf (Pointer)) ;
GetPropInfos (fObjClass.ClassInfo, PropList);

for i : = 0 to nProps - 1 do
case PropList [i] .PropTypeA .Kind of
tkInteger, tkEnumeration, tkset:
FieldDefs.Add (PropList [i].Name, ftInteger, 0);
tkChar: FieldDefs.Add (PropList [i].Name, ftFixedChar, 0) ;
tkFloat: FieldDefs.Add (PropList [i].Name, ftFloat, 0);
tkstring, tkLString:
FieldDefs.Add (PropList [i].Name, ftstring, 50);
// TODO: a r r e g l a r t a m a d o
tkwstring: FieldDefs.Add (PropList [i].Name,
ftWideString, 50) ;
/ / TODO: a r r e g l a r t a m a f i o
end ;
end ;

En 10s metodos G e t F i e l d D a t a y S e t F i e l d D a t a se usa un codigo basa-


do en la RTTI similar para acceder a las propiedades del objeto actual cuando se
solicita una operacion de acceso a un campo del conjunto de datos. La gran
ventaja de usar propiedades para acceder a 10s datos del conjunto de datos es que
las operaciones de lectura y escritura se pueden proyectar directamente sobre
datos per0 tambien usan el metodo correspondiente. De esta manera, se pueden
escribir las reglas de negocio de la aplicacion en cuestion implementando reglas
en 10s metodos de lectura y escritura de las propiedades (un enfoque definitiva-
mente mas orientado a objetos que enganchar codigo a objetos de cambio y vali-
darlos).
Esta es una version ligeramente simplificada de G e t F i e l d D a t a (el otro
metodo resulta simetrico):
function TObjDataSet.GetFiledData (
Field: TField; Buffer: Pointer) : Boolean;
var
Obj: TPersistent;
TypeInfo: PTypeInfo;
IntValue: Integer;
FlValue: Double;
begin
if FList .Count = 0 then
begin
Result : = False;
exit;
end;
Obj : = fList [Integer (ActiveBuffer") ] as TPersistent;
TypeInfo : = PropList [Field.FieldNo-l]".PropTypeA;
c a s e TypeInfo.Kind of
tkInteger, tkChar, tkWChar, tkClass, tkEnumeration, tkSet:
begin
IntValue : = GetOrdProp(Obj, PropList [Field-FieldNo-
11 ) ;
Move (IntValue, B u f f e r A , sizeof (Integer)) ;
end ;
tkFloat :
begin
FlValue : = GetFloatProp (Obj, PropList
[Field.FieldNo-11);
Move (FlValue, B u f f e r A , sizeof (Double)) ;
end ;
tkstring, tkLString, tkWString:
StrCopy (Buffer, pchar (GetStrProp (Obj, PropList
[Field.FieldNo-11 ) ) ) ;
end ;
Result : = True;
end ;

Este codigo basado en punteros tiene un aspect0 horrible, per0 si se ha aguan-


tad0 hasta aqui el analisis de 10s detalles tecnicos del desarrollo de un conjunto de
datos personalizado, no aiiadira mucha complejidad mas a1 esquema global. Usa
algunas de las estructuras de datos definidas (y brevemente comentadas) en la
unidad TypInfo, que deberia ser la referencia a usar para cualquier pregunta
sobre el codigo anterior. A1 usar este enfoque tan ingenuo de editar directamente
10s datos del objeto, podriamos preguntarnos por lo que sucederia si el usuario
cancelara la operacion de edicion (algo de lo que suele preocuparse Delphi). Este
conjunto de datos ofrece dos enfoques alternativos, controlados por la propiedad
ChangeToClone y basados en la idea de clonar objetos mediante la copia de
sus propiedades publicadas. El procedimiento basico DoClone codigo RTTI
similar a1 que ya se ha visto para copiar todos 10s datos publicados de un objeto
en otro objeto, creando una copia efectiva (o un clon).
Este proceso de clonacion tiene lugar en ambos casos. Segun el valor de la
propiedad ChangeToClone, o las operaciones de edicion se realizan sobre el
objeto clonado (que despues vuelve a copiarse sobre el objeto real durante la
operacion de aceptacion de 10s cambios, Post) o se realizan sobre el objeto real, y
el clon se utiliza para recuperar 10s valores originales si la edicion termina con
una solicitud de cancelacion, Cancel. Este es el codigo de 10s tres metodos
involucrados :
procedure TObjDataSet.Interna1Edit;
begin
DoClone (fList [FCurrentRecord] a s TDbPers, ObjClone) ;
end ;

procedure TObjDataSet.Interna1Post;
begin
i f FChangeToClone and Assigned (Obj Clone) then
DoClone (ObjClone, TDbPers(fList [fCurrentRecord]));
end ;

procedure TObjDataSet.InternalCance1;
begin
i f n o t FChangeToClone and Assigned (ObjClone) then
DoClone (ObjClone, TPersistent (£List [fCurrentRecord]) ) ;
end :

En el metodo S e t F i e l d D a t a hay que modificar el objeto clonado o la ins-


tancia original. Para complicar las cosas algo mas, debemos tener tambien en
cuenta esta diferencia en el metodo G e t F i e l d D a t a : si se van a leer campos del
objeto actual, podriamos tener que usar su clon modificado (de otro modo, 10s
cambios del usuario sobre 10s otros campos desaparecerian).
Como muestra el listado 17.5, la clase tambien tiene una matriz Ob j e c t s que
accede a 10s datos de un mod0 orientado a objetos y un metodo A d d que es similar
a1 metodo ~ d de d un conjunto. A1 llamar a ~ d del, codigo crea un nuevo objeto
vacio de la clase objetivo y lo aiiade a la lista interna:
f u n c t i o n TMdObjDataSet.Add: TPersistent;
begin
i f n o t Active then
Open;
Result : = fObjClass.Create;
£List .Add (Result);
end ;

P a r a mostrar el uso de este componente, hemos escrito el ejemplo


ObjDataSetDemo. Tiene una clase objetivo de prueba con unos cuantos campos y
botones para crear automaticamente objetos, como muestra la figura 17.8. No
obstante, la caracteristica mas interesante del programa es algo que habra que
probar para poder ver. Habra que ejecutar el programa y prestar atencion a las
columnas del componente DbGrid. Despues editaremos la clase objetivo, T D e m o ,
aiiadiendole una nueva propiedad publicada. A1 volver a ejecutar el programa, la
cuadricula incluira una nueva columna para la propiedad.

21 3242.43 12
33 6716,54
28 -
3722,38
24 4747.94 23
John 62 597,24 14

Figura 17.8. El ejemplo ObjDataSetDemo dispone de un conjunto de datos


proyectados sobre objetos rnediante el uso de RTTI.
Generacion
de informes
con Rave

Las aplicaciones de bases de datos permiten ver y editar datos, per0 normal-
mente 10s datos deberian imprimirse fisicamente en papel. Tecnicamente, Delphi
soporta la impresion de muchas maneras distintas, desde la salida directa de texto
a1 uso de la impresora C a n v a s , desde sofisticados informes de bases de datos a
la generacion de documentos en varios formatos (como Microsoft Word u
Openoffice de Sun). En este capitulo vamos a centrarnos en 10s informes y en
particular en el uso del motor de informes Rave, una herramienta de terceras
partes incluida en Delphi 7. Si se tiene interes en otras tecnicas de Delphi para
controlar la impresora, puede estudiarse el material relacionado con este tema en
el sitio Web del autor.
Las herramientas de generacion de informes son importantes porque pueden
llevar a cab0 procesos complejos por si mismas. El subsistema de informes puede
convertirse en una aplicacion independiente. Aunque este capitulo se centra en el
mod0 de producir un informe a partir de un conjunto de datos desde 10s progra-
mas Delphi, siempre deberia tenerse en cuenta la naturaleza autonoma de 10s
informes a1 evaluar una herramienta de este tipo.
Hay que crear 10s informes con cuidado, ya que representan una interfaz de
usuario para la aplicacion que transciende, y que a veces es mas importante que el
propio software. Es normal que haya mas gente que se fije en 10s informes impre-
sos que usuarios que generen 10s informes mediante 10s programas. Es por esto
que resulta importante disponer de informes de gran calidad y de una arquitectura
flexible que permita que 10s usuarios puedan personalizarlos.

NOTA:Este capitulo no podria ser como es sin la ayuda de Jim G


Nevrona Designs, la empresa que ha desarrollado el motor Rave.

Este capitulo trata 10s siguientes temas:


Presentacion de Rave (Report Authoring V ~ s u aEnvironment).
l
Componentes Delphi de Rave.
Componentes de Rave Designer.
De bases de datos a informes.
Caracteristicas avanzadas de Rave.

Presentacion de Rave
Los informes son uno de 10s principales medios de adquisicion de informacion
a partir de 10s datos que se manejan en una aplicacion. Para resolver 10s proble-
mas vinculados con la presentacion de un informe visual de datos que resulte
claro y lleno de significado, las aplicaciones tradicionales de generacion de infor-
mes visuales han proporcionado herramientas de disposicion en bandas con la
intencion de proporcionar listas de datos con el aspect0 de tablas. Sin embargo,
hoy en dia esisten informes con necesidades mas completas que no se manejan
con facilidad mediante estas herramientas.
Rave Reports es un entorno de diseiio de informes visuales que ofrece muchas
prestaciones unicas que ayudan a simplificar, acelerar y volver mas eficaz el
proceso de generacion de informes. Rave puede manejar una gran variedad de
formatos de informes e incluye tecnologias avanzadas como las copias reflejo
para fomentar la reutilizacion del contenido de 10s informes con el fin de conse-
guir un mantenimiento mas sencillo y cambios mas rapidos.
Este capitulo sirve como breve presentacion de las caracteristicas de Rave. Se
puede encontrar informacion adicional sobre Rave en 10s archivos de ayuda, en la
documentacion PDF que se encuentra en el CD de Delphi, en varios proyectos de
muestra; y en el sitio Web del fabricante del software, www.nevrona.com.
--.-
NOTA: Una caracteristica clave de Rave (y una de las razones por las que
Borland ha escogido este product0 sobre otras soluciones) es que es una
solucion completamentemultiplatafonna que puede usarse tanto en Windows
como en Linux. No solo 10s componentes de Rave se integran tanto con la
Rave: el entorno visual de creacion de informes
Para arrancar el entorno de diseiio de informes visuales Rave, hay que hacer
doble clic sobre un componente TRvPro j e c t que haya sobre un formulario o
seleccionar Tools>Rave Designer en el IDE de Delphi. Cuando se active este
entorno, se vera una ventana como la que muestra la figura 18.1. Como se puede
comprobar, el Rave Designer incluye varias secciones: el Page Designer, el
Event Editor, el panel Property, el panel Project Tree, barras de herramientas,
la Toolbar Palette y la barra de estado.

This is the Title . .. -.

Thrs Islome text I r e added l a Vlc memo

And me#. toO And mow text And mow Id


text And mom text And mom l e a And mom

And mole text. And m o w text. And more text


text And more text And m w l t n b An4 men
JI
more text. And more tckt And mole b*. An4
And m o l l text And mole text

Figura 18.1. El Rave Designer con un informe sencillo.

NOTA: S i e m p ~ - q w , squiera
e yer el resultado dcl trabajo, habra que pul-
sar la tech F9 c% e l k w e Designer para tener una vista previa del infor-
-

Hay que tener presente que Rave permite que 10s usuarios finales puedan crear
o modificar sus propios informes. Se puede configurar Rave Designer a nivel
Beginner, Intermediate o Advanced mediante el cuadro de dialogo Edit>
Preferences (en la seccion Environment), para que 10s usuarios finales traba-
jen a1 nivel con el que se sientan comodos y no dispongan de mas potencia de la
que se les quiera ofrecer.
Tambien se pueden bloquear algunas caracteristicas del informe para que no
Sean modificables.
El Page Designer y el Event Editor
La parte central de la ventana del Rave Designer contiene el Page Designer
(donde se estructura el informe) y el Event Editor (donde se pueden crear guio-
nes para particularizar el informe en tiempo de ejecucion).
El Page Designer es el aspecto mas sobresaliente de Rave. Esta pagina es la
base de un informe, donde se realizan todas las acciones de diseiio. La pagina
muestra una cuadricula, aunque puede modificarse el aspecto de la pagina me-
diante 10s parametros de preferencias. Los nombres de las paginas en proceso de
diseiio en cada momento aparecen en las pestaiias que se encuentran en la parte
superior del Page Designer (Page1 en la figura).
El Event Editor permite definir codigo interpretado personalizado para 10s
componentes del informe. Cada componente tiene distintos tipos de eventos que
pueden usarse para realizar calculos, manipular cadenas de texto o aplicar una
logica particular a1 informe. El Event Editor es una caracteristica avanzada de
Rave, por ello hablaremos brevemente sobre ella a1 final de este capitulo.

El panel Property
El panel Property que se encuentra a la izquierda en el Rave Designer
ayuda a personalizar el aspecto o comportamiento de 10s componentes. Este panel
tiene una funcion parecida a la del Object Inspector de Delphi: cuando se selec-
ciona un componente en la pagina, el panel Property refleja la seleccion realiza-
da mostrando las distintas propiedades asociadas con ese componente. Si no hay
seleccionado ningun componente, el panel esta en blanco.
A1 igual que en el IDE de Delphi, se puede modificar el valor de una propiedad
manipulando el contenido del cuadro de edicion, seleccionando una opcion de una
lista desplegable o haciendo que aparezca un cuadro de dialogo de edicion. Se
puede hacer doble clic sobre cualquier propiedad que tenga una lista de opciones
(en lugar de hacer clic sobre el boton Flecha abajo y seleccionar a continuacion
la opcion) para pasar a1 siguiente elemento de la lista.

El panel Project Tree


El panel Project Tree que se encuentra a la derecha en el diseiiador propor-
ciona mucha informacion. Tambien ofrece un mod0 sencillo de navegacion por la
estructura de un proyecto de informe. El Project Tree contiene tres nodos princi-
pales: Report Library, Global Page Catalog y Data View Dictionary:
Report Library: Contiene todos 10s informes del proyecto. Cada informe
tiene una o mas paginas. Cada una de estas paginas incluira normalmente
uno o mas componentes.
Global Page Catalog: Gestiona las plantillas de informes. Las plantillas
de informes pueden tener uno o mas componentes y reutilizarse mediante la
tecnologia de espejo de Rave. Pueden incluir elementos como cabeceras y
pies de carta, formularios preimpresos, diseiios de marcas de agua o defi-
niciones completas de pagina que puedan servir de base a otros informes.
Se puede pensar en el Global Page Catalog como en un repositorio, una
ubicacion centralizada para almacenar 10s elementos de informes que se
quieran tener a mano para multiples informes.
Data View Dictionary: Define todas las vistas de datos y otros objetos
relacionados con datos para informes.

Barras de herramientas y la Toolbar Palette


A1 igual que en Delphi, Rave incluye dos tipos de barras de herramientas:
barras de herramientas de componentes y barras de herramientas del IDE. No
obstante, a1 contrario que Delphi (que utiliza exclusivamente para componentes
la Component Palette), en Rave se puede colocar cualquier tipo de barra de
herramientas en el area libre en la parte superior del diseiiador o anclarlas en la
Toolbar Palette con pestaiias. A1 principio este proceso puede resultar algo
confuso, per0 tras reorganizar las barras de acuerdo con las necesidades propias
(mediante 10s comandos de menu abreviado Dock y Undock, y no arrastrando-
las) probablemente se llegue a1 convencimiento de que es un mecanismo bastante
flexible. En Rave las barras de herramientas de componentes predeterminadas
con Standard, Drawing, Report y Bar Code. Mas adelante hablaremos en
detalle de estas barras de herramientas. Por el momento, bastara con decir que
pueden existir otras barras de herramientas de componentes si se han instalado
paquetes adicionales en el Rave Designer, y que 10s componentes pueden reorga-
nizarse en las barras de herramientas. Las barras de herramientas del editor per-
miten modificar el proyecto o alguno de 10s componentes existentes. Este es un
resumen de 10s comandos que contienen:
Project Toolbar: Es igual que una barra de herramientas de componentes,
porque permite crear un nuevo informe, pagina y componentes de objetos
de datos dentro del proyecto de informe. Esta barra de herramientas tam-
bien puede usarse para crear nuevos proyectos de informe, guardar y car-
gar proyectos ya existentes y enviar el informe actual a la impresora o
conseguir una vista previa.
Alignment Toolbar: Tiene una gran cantidad de herramientas para alinear
y colocar componentes en una pagina. El primer componente seleccionado
suele determinar la referencia de una operacion de alineacion. Se puede
modificar el orden de impresion de 10s componentes mediante 10s botones
de orden: Move Forward, Move Behind, Bring to Front y Send to Back.
Los botones de pulsar permiten desplazar componentes poco a poco.
Fonts Toolbar: Puede usarse para cambiar 10s atributos de la fuente como
el nombre, el tamafio, el estilo y la alineacion.
Fills Toolbar: Proporciona el estilo de relleno para formas como recthn-
gulos y circulos.
Lines Toolbar: Permite modificar el ancho del borde y 10s estilos de lineas
y borde.
Colors Toolbar: Determina 10s colores primario y secundario (normal-
mente el color frontal y el de fondo, respectivamente). El boton izquierdo
del raton sirve para seleccionar el color primario y el boton derecho el
secundario. Hay disponibles ocho cuadros de color personalizados para
10s colores escogidos usados mas habitualmente. Si se hace doble clic so-
bre el cuadro de color primario o secundario que se encuentra a la derecha
de la barra de herramientas, se abrira el dialogo Color Editor, que propor-
ciona herramientas adicionales para la seleccion de color.
Zoom Toolbar: Proporciona muchas herramientas que permiten ampliar o
reducir la imagen del Page Designer para facilitar la edicion.
Designer Toolbar: Permite personalizar el Page Designer y el Rave
Designer mediante el dialogo de preferencias.

La barra de estado
En la parte inferior del Rave Designer se encuentra la barra de estado. Esta
barra de estado proporciona informacion sobre el estado de la conexion de la vista
de datos directa y la posicion y tamafio del raton. El color de 10s indicadores de la
conexion de datos permiten conocer el estado del sistema de datos de Rave
(DirectDataView): gris y verde indican una conexion inactiva o activa, respecti-
vamente; amarillo y rojo indican situaciones especificas del acceso a datos (res-
pectivamente, espera de una respuesta o fuera de plazo).
Los valores X e Y son las coordenadas del cursor del raton en las unidades de
pagina. Cuando se deja caer un componente sobre la pagina, si no se suelta el
boton del raton, se mostrara el tamafio del componente mediante 10s valores dX y
dY (con d de delta, incremento).

Uso del componente RvProject


Como se ha visto en la anterior figura 18.1, se mostraba un informe de Rave
muy simple, que se guarda en un archivo .rav. Para conectar este informe con una
aplicacion de Delphi 7 se puede usar la pagina Rave, que proporciona una serie
de componentes. El componente central es R v P r o j e c t . Hay que colocar este
componente sobre un formulario o modulo de datos, conectar su propiedad
P r o j e c t F i l e con un archivo de Rave y escribir este codigo de control de
eventos para un boton:

Ahora dispondremos de una aplicacion (el ejemplo Raveprint que se encuen-


tra entre 10s archivos de codigo fuente) que puede imprimir un informe y que
incluye la posibilidad de ver una vista previa del resultado impreso, como mues-
tra la figura 18.2. El archivo al que se hace referencia deberia distribuirse de
forma independiente, y puede modificarse sin necesidad de modificar el programa
Delphi. Como alternativa, tambien se puede incrustar el archivo .rav en el archivo
ejecutable de Delphi cargandolo en el archivo DFM. Para realizar esto, hay que
utilizar la propiedad S t o r e R A V del componente de proyecto Rave.

This is the Title


This i s s o m e text ILe added t o the memo.

I And more text And more text. And more text. And more text. And more
text And more text And more text. And more text.

And more text And more text. And more text. And more text. And more
I
text And more text. And more text. And more text. And more text. And
more text And more text. And more text. And more text. And more text
And more text And more text.

I
I
-- -
.----
And more text. And more text. And more text. And more l e x t And more
text. And more text And more text. And more text. And more text And

- - .- -
.- - .
"I
- -- -- A\
Figura 18.2. La ventana de vista previa del inforrne de Rave.

ha comentado, el
todos 10s cornponentes disponibles en ~ a v Reports
e
camp-
-

pue.de usarst en h a
nnliracilin VCl . n rl .X El mntnr R n v e au rmnletmmente mi~ltinlatafnrma

Para controlar 10s parametros mas importantes del informe y la vista previa,
puede conectarse un componente RvNDRWriter o RvSystem con la propiedad
E n g i n e del componente RvProject:
Componente RvNDRWriter: Genera un archivo con formato NDR cuan-
do se ejecuta el informe. Los archivos con formato NDR son archivos con
un formato binario propietario y almacenan toda la informacion que nece-
sitan 10s componentes de representacion para reproducir el informe en una
gran variedad de formatos.
componente RvSystem: Combina el componente RvNDRWr iter con
una interfaz de usuario estandar de representacion, vista previa e impre-
sion. Esisten muchas propiedades para personalizar la interfaz de usuario,
y se pueden definir eventos sobrescritos para sustituir 10s dialogos estandar
con versiones particularizadas.

Formatos de representacion
El motor Rave produce un archivo o flujo NDR, generado por el
RvNDRWr iter.El motor de representacion de Rave puede convertir esta repre-
sentacion interna en una amplia gama de formatos. Para permitir a un usuario
escoger entre uno de 10s formatos para el archivo final, hay que colocar 10s com-
ponentes de representacion deseados dentro de un formulario del programa Delphi.
Cuando se ejecuta el metodo Execute del componente RvProject (como en
el ejemplo Raveprint), se puede escoger uno de 10s formatos de archivo en el
cuadro de dialog0 que muestra Rave y que se puede ver en la figura 18.3.

Figura 18.3. Despues de ejecutar un proyecto Rave, un usuario puede escoger el


formato de salida o el motor de representacidn.

Estos son 10s componentes de representacion disponibles a traves de la pagina


Rave de la Component Palette de Delphi:
RvRenderPreview: Puede usarse para mostrar un archivo o flujo NDR en
la pantalla. Se usa un componente ScrollBox para mostrar las paginas del
informe, y hay disponibles muchos mktodos y propiedades para crear dia-
logos de vista previa personalizados. A no ser que se necesite una vista
previa personalizada de impresion, deberia utilizarse el componente
RvSystem en lugar de RvRenderPreview para la visualizacion previa.
RvRenderPrinter: Envia un archivo o flujo NDR a la impresora. A no ser
que se necesite una interfaz de vista previa o impresion personalizada, no
deberia ser necesario el uso de este componente (RvSystem proporciona la
funcionalidad estandar de impresion).
RvRenderPDF: Convierte un archivo o flujo NDR a1 formato PDF (de
Adobe Acrobat). Se pueden usar propiedades y eventos para personalizar
el tip0 de salida que se crea y proporcionar soporte de compresion.
RvRenderHTML: Convierte un archivo o flujo NDR a1 formato HTML
(DHTML). Cada pagina se convierte en una pagina independiente y puede
combinarse con plantillas para conseguir un mayor control sobre el resul-
tado.
RvRenderRTF: Convierte un archivo o flujo NDR a1 formato RTF. El
formato RTF usa areas de campo para colocar y reproducir con precision
el informe.
RvRenderText: Convierte un archivo o flujo NDR a1 formato de texto.
Muchos comandos y componentes graficos, como lineas y rectangulos, se
ignoraran durante la generacion del texto.
Ademas de permitir a1 usuario seleccionar un formato de archivo en el cuadro
de dialogo, se puede realizar la generacion de forma programada. Por ejemplo,
para convertir directamente un informe a un archivo PDF, se puede escribir el
codigo siguiente (extraido una vez mas del ejemplo Raveprint):
procedure TFormRave. btnPdfClick (Sender: TObject) ;
begin
RvSysteml.DefaultDest : = rdFile;
RvSysteml.DoNative0utput : = False;
RvSysteml.RenderObject : = RvRenderPDFl;
RvSysteml.0utputFileName := 'Simple2.pdf';
RvSystem1.SystemSetups : = RvSystem1.SystemSetups -
[ssAllowSetup];
RvProj ectl .Engine : = RvSysteml;
RvProj ectl .Execute;
end;

Conexiones de datos
Los componentes de conexion de datos proporcionan un enlace entre 10s datos
contenidos en una aplicacion Delphi y las DirectDataView disponibles en el Rave
Designer. Fijese en que el valor definido en la propiedad N a m e de cada compo-
nente de conexion de datos se utiliza para proporcionar el enlace con el informe de
Rave. Es por este motivo por el que hay que tener cuidado en evitar modificar 10s
nombres de componentes despues de que se creen las DirectDataViews en Rave.
Los componentes de conexion de datos son 10s siguientes:
RvCustomConnection: Proporciona datos a 10s informes de Rave me-
diante eventos programados y puede usarse para enviar datos que no for-
men parte de una base de datos a un informe visual.
RvDataSetConnection: Conecta cualquier componente descendiente de la
clase T D a t a S e t con una DirectDataView de Rave. Mediante la propie-
dad F i e l d A l i a s L i s t tambien se pueden modificar 10s nombres de 10s
campos del conjunto de datos, sustituyendolos por nombres mas expresi-
vos para desarrolladores o usuarios finales que vayan a crear el informe.
Si se necesita ordenacion o filtrado para busquedas o relaciones maestro1
detalle en 10s informes de Rave, se pueden controlar 10s eventos
OnSetSort y OnSetFilter.

RvTableConnection y RvQueryConnection: Conecta 10s componentes


BDE Table y Query con una DirectDataView de Rave. Rave proporciona
de mod0 nativo soporte de ordenacion y filtrado para conexiones de datos.
Como primer ejemplo de creacion de un informe relacionado con una base de
datos, hemos creado el ejemplo RaveSingle, que es una actualizacion del progra-
ma DbxSingle del capitulo 14, con un proyecto Rave y una conexion:
o b j e c t RvDataSetConnectionl: TRvDataSetConnection
Runtimevisibility = rtDeveloper
DataSet = SimpleDataSetl
end
o b j e c t RvProjectl: TRvProject
ProjectFile = ' R a v e S i n g l e . rav'
end

Hemos creado un nuevo proyecto en el Rave Designer, el archivo


Rave S i n g l e . r a v que se encuentra en la carpeta Rave S i n g l e. Para referir-
se a 10s datos que expone el programa escrito en Delphi, es necesario aiiadir una
vista de datos haciendo clic sobre el boton New Data Object que se encuentra en
la barra de herramientas Project, seleccionar la opcion Direct Data View y
escoger una conexion disponible. La lista que se presenta depende de las conexio-
nes disponibles en el proyecto que se encuentra activo en ese momento en el IDE
de Delphi.
Ahora se puede crear un informe con la ayuda de un asistente. En el menu del
Rave Designer habra que escoger la opcion de menu Tools> Report Wizards>
Simple Table. Despues se selecciona la vista de datos (deberia existir una si se
han seguido 10s pasos), y en la siguiente ventana hay que escoger 10s campos del
conjunto de datos que se desean incluir en el informe. Deberia verse un informe
como el que muestra la figura 18.4.
Tal y como se puede ver en el Project Tree, el asistente ha generado una
pagina de informe con una zona (Region) que contiene tres componentes de banda
(Band): una para el titulo, una para la cabecera de la tabla y una banda data-
aware para 10s elementos. Hablaremos de 10s detalles del uso de estos componen-
tes en la seccion sobre Region y Band que se encontrara mas adelante. Por el
momento basta con experimentar con un ejemplo funcional.

Figura 18.4. El inforrne Ravesingle (generado con ayuda de un asistente) en tiempo


de diseiio.

Componentes del Rave Designer


En la seccion anterior hemos comentado 10s componentes de Rave que se en-
cuentran disponibles en Delphi, ya que cuando se usa este motor de generacion de
informes la mayoria del trabajo se realiza en el sistema de diseAo de informes. El
elemento basico de un informe (disponible en la parte superior de la vista Project
Tree) es el componente RaveProject, tambien llamado Project Manager. No se
trata de un componente que se pueda colocar en un informe, ya que solo puede
haber un objeto de este tip0 por informe (a1 igual que esiste un unico objeto
A p p l i c a t i o n global en un programa Delphi).
El componente RaveProject es el propietario del resto de componentes del
informe. A1 igual que todo componente de Rave, el Project Manager tiene ciertas
propiedades. Para poder inspeccionarlas, hay que seleccionar RaveProject en el
Project Tree y, a continuacion, fijarse en el panel Property. Para crear un
RaveProject nuevo, se hace clic sobre el boton New Project de la barra de herra-
mientas Project. A1 hacer esto se creara un archivo nuevo, a1 igual que el nuevo
Pmject Manager.
Cada proyecto puede contener varios informes, representados por el compo-
nente Report. Un componente Report contiene las paginas de un informe. Pueden
existir varios componentes Report en un unico proyecto, y cada Report puede
tener varias paginas (componentes Page). El componente Page es el componente
visual basico sobre el que se pueden colocar 10s componentes visuales del infor-
me. Es aqui donde se completa el diseiio y disposicion de un informe.
Ademas de la lista de informes disponible bajo el nodo Report Library, un
proyecto Rave tiene un Global Page Catalog, del que ya hemos hablado, y un
Data View Dictionary. El Data View Dictionary es una lista detallada de las
conexiones de datos que ofrece la aplicacion Delphi anfitriona (mediante el uso de
10s componentes de conexion Rave de Delphi, de 10s que ya hemos hablado) o que
se activan directamente desde el informe a una base de datos.
Para diseiiar el informe hay que colocar directarnente 10s componentes visua-
les sobre la pagina o en otro contenedor como un componente Band (una banda) o
Region (una zona). Algunos de estos componentes no estan conectados con 10s
datos de la base de datos (por ejemplo, 10s componentes Text, Memo y Bitmap en
la barra de herramientas estandar del diseiiador). Otros componentes pueden estar
conectados con un campo en una tabla de una base de datos (o ser data-aware,
por usar el termino habitual de Delphi), como 10s componentes DataText y
DataMemo de la barra de herramientas Report.

Componentes basicos
La barra de herramientas Standard tiene siete componentes: Text, Memo,
Section, Bitmap, Metafile, FontMaster y PageNumInit. Muchos de 10s compo-
nentes estandar se suelen utilizar cuando se diseiian informes.
Componentes Text y Memo
El componente Text es util para mostrar una unica linea de texto en el informe.
Actua como una etiqueta que puede contener texto sencillo (datos no). Cuando se
coloca en el informe, el cuadro de texto queda rodcado por un marco que indica
sus bordes. Los componentes Memo son parecidos a 10s componentes Text, per0
pueden contener varias lineas de texto. Cuando se determina la fuente del memo,
todo el texto del componente usara la misma fuente, como en el caso del compo-
nente Memo de Delphi. Una vez que se ha escrito el texto deseado, se puede
redimensionar el cuadro Memo, con lo que el texto interior se reorganizara como
corresponda. Si parece que falta texto en el cuadro de Memo despues de haberlo
escrito en el Memo Editor, hay que ajustar el tamaiio del cuadro para que se
pueda ver todo el texto.
El componente Section
El componente Section se utiliza para agrupar componentes, como un Panel en
Delphi. Ofrece ventajas como permitir el desplazamiento de todos 10s componen-
tes que forman parte de la seccion con un solo clic, en lugar de tener que mover
cada componente de forma individual o seleccionar todos 10s componentes antes
del desplazamiento.
El Project Tree resulta util cuando se maneja el componente Section. A partir
de un nodo expandido resulta sencillo comprobar quC componentes se encuentran
en que seccion, ya que 10s componentes compuestos pueden formar un arbol con
relaciones padre-hijo.
- - - -

I TRUCOZI componente Section es i m p A m t e cuando se r e a l i i n eopias I


de espejo, ya que pemiten la herencia del disefio visual dentro de 10s dise-
fios de infome.

Componentes graficos
Los componentes Bitmap y Metafile permiten disponer imagenes en un infor-
me. Bitmap soporta archivos de mapas de bits con la extension .bmp, y Metafile
soporta archivos de imagenes vectoriales con las estensiones .w m f y . emf.
Cuando se utiliza Rave en una aplicacion CLX no se soportan 10s componentes
Metafile, porque estan basados en una tecnologia especifica de Windows.
El componente FontMaster
Cada componente Text de un informe tiene un propiedad F o n t . Al establecer
esta propiedad, se puede asignar una fuente especifica al componente. En muchos
casos puede ser util y necesario establecer las mismas propiedades de fuente para
mas de un objeto. Aunque se puede hacer esto si se selecciona mas de un compo-
nente a1 mismo tiempo, este metodo tiene un inconveniente: hay que hacer un
seguimiento de que fuentes deben tener el mismo tipo, tamaiio y estilo, lo que no
resulta sencillo para mas de un informe. El componente FontMaster permite defi-
nir fuentes estandar para distintas partes del informe, como las cabeceras, el
cuerpo y 10s pies de pagina. El FontMaster es un componente no visual (que se
indica mediante el color verde del boton), por lo que no se tendra ninguna referen-
cia visual sobre el en la pagina (no como en Delphi). Al igual que otros compo-
nentes no visuales, solo puede accederse a el mediante el Project Tree.
Una vez que se haya establecido la propiedad F o n t del componente FontMaster,
es sencillo vincularlo con un conjunto de texto. Hay que seleccionar un compo-
nente Test o Memo en el informe y utilizar a continuacion el boton Flecha abajo
que se encuentra junto a la propiedad F o n t M i r r o r en el panel Property para
escoger un enlace con un FontMaster. Cualquier componente cuya propiedad
Fo n t M i r r o r se haya vinculado con el FontMaster se vera afectada por la pro-
piedad F o n t del FontMaster.
Cuando se fija la propiedad F o n t M i r r o r de un componente, se reemplazara
la propiedad F o n t del componente por la propiedad F o n t del FontMaster. Otro
efecto secundario del uso del FontMaster es que se inhabilita la barra de herra-
mientas de fuentes cuando se fija la propiedad FontMirror para ese compo-
nente.
Puede existir mas de un FontMaster por pagina; sin embargo, es muy aconse-
jable renombrar 10s componentes FontMaster para describir su funcion. Tambien
deberian colocarse en una pagina global, para que puedan utilizarse en todos 10s
informes que formen parte de un proyecto y se consiga asi una estructura tipogra-
fica mas consistente.

Nlimeros de pagina
PageNumInit es un componente no visual que permite reiniciar la numeracion
de paginas dentro de un informe. Se utiliza de un mod0 similar a otros componen-
tes no visuales. Lo mas normal es utilizar este componente cuando Sean necesa-
rios formatos mas avanzados .
Por ejemplo, podemos tener en cuenta un informe de estado de cliente para una
cuenta bancaria. Los balances de estado que reciban 10s clientes cada mes pueden
variar en el numero de paginas. Supongamos que la primera pagina define la
estructura de la pagina de resumen de la cuenta, la segunda define 10s creditos o
depositos del cliente, y la tercera define las deudas y las retiradas de fondos.
Puede que 10s dos primeros informes necesiten una sola pagina; per0 si la activi-
dad de la cuenta de un cliente es muy alta, entonces la seccion de retiradas puede
ocupar varias paginas. Si el usuario que genera el informe quiere numerar indivi-
dualmente las paginas de cada seccion, las paginas de resumen y de depositos
deberian marcarse como "1 de 1". Si la cuenta de un cliente activo tiene tres
paginas de retiradas y deudas, esta seccion del balance deberia numerarse como
" 1 de 3", "2 de 3" y "3 de 3 ". PageNumInit es un componente muy practico para
este tipo de numeracion de paginas.

Componentes de dibujo
A1 igual que 10s componentes estandar, 10s componentes de dibujo no tienen
que ver con 10s datos. Los informes de Rave pueden incluir tres tipos de compo-
nentes para lineas: Las lineas genericas se dibujan en cualquier direccion e inch-
yen lineas oblicuas; las lineas horizontales y verticales tienen una direccion fija.
Entre las formas geometricas disponibles hay cuadrados, rectangulos, circunfe-
rencias y elipses.
Se puede dejar caer una forma sobre un informe y despues esconderla tras otro
elemento. Por ejemplo, puede colocarse un rectangulo alrededor de un componen-
te DataBand, ajustar su tamaiio para que ocupe completamente la banda y situar-
lo despuis tras el resto de 10s componentes de la banda.

Componentes de codigo de barras


Los componentes de codigo de barras se utilizan para crear varios tipos de
codigos de barras distintos en un informe. Los codigos de barras estan destinados
para usuarios que saben exactamente lo que necesitan, porque es necesario un
cierto conocimiento previo sobre codigos de barras y su uso. Para definir el valor
de un codigo de barras, hay que acceder a1 panel Property y escribir el valor en el
cuadro de propiedad Text. Entre 10s codigos de barras que soporta Rave estan 10s
siguientes:
POSTNET (Postal Numeric Encoding Technique): Es un codigo de ba-
rras utilizado de forma particular por el servicio postal de EEUU.
I2ofSBarCode (entrelazado 2 de 5 ) : Es un codigo solo para informacion
numerics.
Code39BarCode: Es un codigo de barras alfanumerico que puede codifi-
car numeros decimales, el alfabeto de las mayusculas y algunos simbolos
especiales.
CodelZSBarCode: Es un codigo de barras alfanumerico de alta densidad
diseiiado para codificar 10s 128 caracteres ASCII al completo.
UPCBarCode (Universal Product Code): Es un codigo que tiene una
longitud fija de 12 digitos y se diseiio para codificar productos.
EANBarCode (European Article Numbering System): Es un codigo que
es identico al UPC per0 tiene 13 digitos: 10 caracteres numericos, 2 carac-
teres de codigo de pais y un digito de comprobacion.

Objetos de acceso a datos


En la mayoria de 10s casos, un informe se basara en un grupo de datos, propor-
cionados directamente desde una base de datos o desde una aplicacion Delphi.
Los datos podrian proceder de un conjunto de datos conectado con una base de
datos o de algun tipo de proceso interno de un programa Delphi. Cuando se hace
clic sobre el boton New Data Object en la barra de herrarnientas Project, se
pueden ver las alternativas que se muestran en la siguiente imagen y se describen
en la lista:

WdObjscl fW
D d a Lookup Secwly Conlrdln
@ Database Cmnedion
Direct D d a Vlew
a Driver D d a V ~ e w
fl Simple Securny Cordroller
Componente RaveDatabase (conexi6n con la vista de datos): Ofrece
parametros de conexion con una base de datos para el componente
DriverD a t aView (la vista de datos del controlador). Solo se permiten
conexion a bases de datos para las que esten instalados controladores
DataLink.
Componente RaveDirectDataView (vista directa de datos): Proporcio-
na un medio de obtener datos desde un componente de conexion de datos
que se encuentre en la aplicacion Delphi anfitriona, como en el ejemplo
anterior. (La seleccion se llama vista directa de datos o Direct Data View
incluso aunque no exista la conexion directa con la base de datos desde el
informe, sino que se trate de la conexion indirecta basada en datos extrai-
dos de la base de datos de una aplicacion anfitrion.)
Componente RaveDriverDataView (vista de datos de controlador):
Proporciona un mod0 de definir una consulta para una conexion de base de
datos especifica mediante un lenguaje de consultas como SQL. Se mostra-
ra un constructor de consultas para definir la consulta.
Componente RaveSimpleSecurity (controlador de seguridad simple):
Implementa la forma mas basica de seguridad mediante el empleo de una
sencilla lista de pares de nombre de usuario y contraseiia en la propiedad
US erList . user Lis t contiene un par de nombre de usuario y contra-
seiia por linea, de acuerdo con este formato: username = password.
Ca seMa t ter s es una propiedad booleana que controla si la contraseiia
tiene en cuenta las mayusculas.
Componente RaveLookupSecurity (seguridad de busqueda de datos):
Permite verificar 10s pares de identificacion mediante entradas en una ta-
bla de base de datos. La propiedad Dat aview especifica la vista de datos
a utilizar para buscar el nombre de usuario y la contraseiia. Las propieda-
des UserField y PasswordField se usan para buscar el nombre de
usuario y la contraseiia que se deben verificar.

Regiones y bandas
Un componente Region es un contenedor de componentes Band. En su forma
mas simple, la region podria ser todo el componente Page. Esto seria cierto para
un informe que sea un tipo lista. Muchos informes maestro-detalle podrian enca-
jar en un diseiio de una unica region. Sin embargo, no hay que limitarse a pensar
en una region como en toda la pagina; las propiedades de una region tienen que
ver con su tamaiio y posicion en la pagina. El uso creativo de las regiones ofrece
mas flexibilidad cuando se diseiian informes complejos. Se pueden colocar varias
regiones en una unica pagina; pueden estar adosados, apilados, o distribuidos por
la pagina.
TRUCO: N o hay que confundir una region (Region) con una seccion
(Section). Los componentes Region solo pueden contener componentes Band.
Un componente Section puede contener cualquier grupo de componentes,
como cimponentes ~ e ~ i oper0
n , no directameite componentes ~ & d .

Cuando se trabaja con bandas hay que seguir una regla muy sencilla: Las
bandas tienen que estar en una region. Hay que tener en cuenta que no hay limite
a1 numero de regiones en una pagina ni a1 numero de bandas en una region.
Mientras se puede ver mentalmente el informe, se puede usar una combinacion de
regiones y bandas para resolver cualquier dificultad que surja a la hora de plas-
mar esas ideas en forma de diseiio.
Esisten dos tipos de banda:
DataBand: Se usan para mostrar informacion repetitiva procedente de una
vista de datos. En general, un componente DataBand contendra varios com-
ponentes DataTest. La propiedad D a t a V i e w de un DataBand debe fijar-
se a1 componente DataView (la vista de datos) sobre la que habra que
realizar las repeticiones, y tipicamente contendra otros componentes data-
aware que trabajaran con la misma D a t a V i e w .
Band: Se usa para mostrar bandas de cabecera y pie de pagina en una
region. Entre 10s tipos soportados estan Body, Group y Row: se escogen
mediante la propiedad B a n d S t y l e . No se necesitan que las cabeceras o
pies de pagina esten en una banda porque se pueden dejar caer directamen-
te sobre la pagina, fuera del componente Region.
Una propiedad importante del componente Band es C o n t r o 1l e r B a n d . Esta
propiedad determina a que DataBand pertenece un componente Band (o por que
componente esta controlado).
Cuando se establece la DataBand de control, hay que fijarse en que el simbolo
grafico de la banda apunta en la direccion de su controlador y que 10s colores de
10s simbolos se corresponden. A continuacion explicaremos 10s codigos de letras
que se muestran en la banda.

El Band Style Editor


Tenemos que acceder a la propiedad B a n d S t y l e de un componente Band o
DataBand y haremos clic sobre el boton de puntos suspensivos para abrir el Band
Style Editor.
Este editor proporciona un metodo sencillo para escoger las caracteristicas
que queramos para una banda mediante casillas de verificacion. Hay que fijarse
en que una banda puede tener activadas a1 mismo tiempo varias caracteristicas
distintas. Esto significa que es posible que una banda sea a1 mismo tiempo una
cabecera y un pie de pagina.
El area de muestra del Band Style Editor esta pensada para representar el flujo
de un informe en una especie de estructura aprosimada. Los componentes DataBand
se rcpiten tres veccs para mostrar que son iterativos. La banda que se esta editan-
do aparecc resaltada. Aunque se pueden ver otras bandas en el editor, solo se
pueden modificar 10s parametros de la actual.
El Band Style Editor utiliza simbolos y letras en el area de representacion y en
el area Page Layout (como muestra la figura 18.6) para informar del comports-
miento de cada banda. La principal diferencia entre estas dos representaciones es
que el Band Style Editor organiza las bandas con una estructura falsa segun la
definicion de cada banda. En el Band Style Editor se organizan las bandas de
acuerdo con el flujo logico y el orden en que se colocan en el informe en tiempo de
diseiio. La secuencia de las bandas en el resultado del informe tiene que ver
basicamente con este orden. Las cabeceras (letras mayusculas BGR, que signifi-
can Body. Group y Row, respectivamente) se imprimiran en primer lugar, segui-
das por las bandas de datos (DataBand) y 10s pies de pagina (letras minusculas
bgr) para cada nivel. No obstante, si se define mas de una cabecera para un nivel
particular, entonces las bandas de cabecera se procesan en el orden en que se
organizan en la region. Por ello es posible colocar todas las cabeceras en la parte
superior, todas las bandas de datos en medio y todos 10s pies de pagina en la parte
inferior de una region para todos 10s niveles de un informe maestro-detalle. Ade-
mas, se puede agrupar cada nivel con las cabeceras, pies de pagina y bandas de
datos apropiados juntos para cada nivel. Rave permite usar la estructura de la
region de tal manera que tengan el mayor sentido posible para el flujo de diseiio.
Hay que recordar que el orden de precedencia para las bandas del mismo nivel
esta controlado por su orden dentro de la region.
Dos simbolos distintos muestran las relaciones padre-hijo o maestro-detalle de
las distintas bandas:
El simbolo de triangulo (Flecha arriba o Flecha abajo): Indica que la
banda esta controlada por una banda maestra con el mismo color (nivel),
que puede encontrarse en la direccion de la flecha.
El simbolo diamante: Representa una banda maestra o de control.
Estos simbolos tienen una codification de colores e indentacion para represen-
tar el nivel de flujo maestro-esclavo. Recordemos que se puede tener una relacion
maestro-detalle-detalle en la que ambos detalles esten controladores por el mismo
maestro o uno de 10s detalles este controlado por el otro detalle.

Cornponentes data-aware
Se pueden colocar distintos componentes data-aware de Rave en un DataBand.
La opcion mas comun es el componente DataText, utilizado para mostrar un
campo de texto procedente de un conjunto de datos como se muestra en el ejemplo
Ravesingle.
Hay disponibles dos opciones para introducir datos en una propiedad
D a t a F i e l d . La primera es seleccionar un unico campo mediante la lista desple-
gable; este enfoque esta bien para informes normales en que solo se necesite un
campo de datos para cada elemento DataText. La segunda es utilizar el Data Text
Editor.
El Data Text Editor
Muchos informes necesitan combinar varios campos. Dos ejemplos bastante
comunes son las combinaciones de ciudad, provincia y codigo postal, o de nombre
y apellido. En el codigo, se pueden realizar estas combinaciones mediante senten-
cias como las siguientes:
City + ' , ' + State + ' ' + Zip
FirstName & LastName

La propiedad D a t a F i e l d tiene un Data Text Editor, como muestra la figura


18.5, que ayuda a crear campos compuestos. Si se hace clic sobre 10s puntos
suspensivos se abrira el Data Text Editor, que permite encadenar campos,
parametros o variables para construir un campo de texto data-aware complejo
seleccionando elementos de 10s cuadros de lista. Este editor incluye una gran
cantidad de combinaciones; hablaremos sobre ellas brevemente, ya que es mejor
probarlas en la practica.
Fijese en que el cuadro de dialog0 se divide en cinco grupos: Data Fields,
Report Variables, Project Parameters, Post Initialize Variables y Data
Text. Data Text es la ventana resultante: Hay que prestar atencion a esta venta-
na cuando se introducen elementos. Los dos botones que se encuentran a la dere-
cha de esta ventana con la suma (t) y &. El boton + junta dos elementos sin
espacios, y el boton & 10s encadena con un unico espacio (siempre que el campo
anterior no estuviera en blanco). Por eso, el primer paso es decidir si se quiere
usar + o &, y, despues, escoger el texto de no de 10s tres grupos de la parte
superior de la ventana Data Text.
El componente DataTest no esta limitado a imprimir datos de una base de
datos: tambien se pueden usar parametros de proyecto y variables de informe. Si
accedemos a1 grupo Report Variables y nos fijamos en el cuadro de lista, podre-
mos ver las variables que se encuentran disponibles. La lista Project Parameters
podria contener parametros UserName, Repor tTit le o UserOpt ion con
valores iniciales proporcionados por la aplicacion. Para crear la lista de 10s
parametros de objeto hay que escoger el nodo Project en el Project Tree (el
elemento superior). Despues, en el panel Properties, hay que hacer clic sobre el
boton de puntos suspensivos que se encuentra junto a la propiedad Parameters
para abrir el tipico editor de cadena, donde se pueden escribir distintos parametros
que se deseen pasar a Rave desde la aplicacion (como UserName).

--
I W a Ted --
- - - --

1 FIRST-NAME

Figura 18.5. El Data Text Editor del dave ~ e s i ~ n e r .

De Text a Memo
El componente DataMemo muestra un campo de memo procedente de una
Dataview. La diferencia principal entre el componente DataMemo y el compo-
nente DataTest es que el primer0 se utiliza para mostrar texto que necesita mas de
una linea y necesita adaptar su forma. Por ejemplo, podria utilizarse para impri-
mir comentarios sobre un cliente en la parte inferior de cada pagina de una factu-
ra.
Un posible uso para el componente DataMemo son las funciones de fusion de
correos. El mod0 mas sencillo de realizar esto es establecer las propiedades
DataView y DataField como la fuente del campo Memo. Despues, se arran-
ca el Mail Merge Editor haciendo clic sobre el boton de puntos suspensivos que se
encuentra junto a la propiedad Mai 1MergeI tems.Este editor permite determi-
nar 10s elementos del Memo que se modificaran.
Para usar el Mail Merge Editor, hay que hacer clic sobre el boton Add. En la
ventana Search Token, se escribe el elemento que esta en el Memo y que sera
sustituido. Despues, se escribe la cadena de sustitucion en la ventana Replacement
o se hace clic sobre el boton Edit para arrancar el Data Text Editor que ayudara
a seleccionar las distintas vistas de datos y campos.

Calculo de totales
El componente CalcText es un componente de recuentodata-aware. La mayor
diferencia entre el componente DataText y el componente CalcText es que
este ultimo esta diseiiado especialmente para realizar calculos y mostrar 10s resul-
tados. La propiedad CalcType determina el tipo de calculo que se va a realizar,
como promedio (Average), recuento (Count), maximo (Maximum), minimo
(Minimum) y sumatorio (Sum). Por ejemplo, se puede utilizar este componente
para imprimir 10s totales de una factura en la parte superior de cada pagina.
La propiedad CountBlanks determina si 10s valores de 10s campos vacios
se incluyen en 10s metodos de calculo de recuento y promedio. Si RunningTotal
es True,entonces no se volvera a poner a 0 el valor del calculo cada vez que se
imprima.

Repeticion de datos en paginas


El componente DataCycle es basicamente un DataBand invisible que aiiade
directamente a un pagina capacidades de iteracion de datos. Este componente es
util para informes con aspect0 de formularios en 10s que puede resultar engorroso
usar regiones y bandas. Es importante asegurarse de que el componente
DataCycle aparece antes que ningun otro componente de la pagina que procese
informacion procedente de la misma DataView

Rave avanzado
En esta extensa introduccion de Rave hemos visto que este sistema de genera-
cion de informes es tan complejo que podria dedicarsele todo un libro. Ya hemos
creado algunos ejemplos, y podriamos continuar mostrando una relacion maestro-
detalle u otros informes con una estructura compleja. Sin embargo, con 10s asis-
tentes y la informacion disponible hasta ahora, deberian poderse hacer ejemplos
similares sin gran dificultad. Por esto, en esta seccion solo vamos a crear un unico
ejemplo de este tipo, y despues ofreceremos informacion sobre unos cuantos as-
pectos importantes de Rave que no son faciles de entender mediante el procedi-
miento de prueba y error.
NOTA: Entre Ias caracteristicas de Rave que no vamos a comentar aqui
esth una que permite distribuir el disefiador de informes a 10s usuarios
r---i-- I ---- -----I&:-
irnales (para ---- pc;rsonanc;en
pcrmlur qut: I: --.-I - - L C ------
\
10s mrorrntts), --- --I---J-
ya sea ---
enlazaoo con
un programa escrito en Delphi o como herramienta independiente. Rave
tambien tiene una version de servidor que permite obtener informes de un
servidor Web.

TRUCO: Para aprender mhs sobre estas caracteristicas, y otros aspectos


de Rave, es recomendable visitar el sitio Web de Nevrona. Deberia explorarse
la colecci6n de trucos que se encuentra disponible en www.nevrona.com/
raveltinc chtml

lnformes maestro-detalle
Para crear un informe maestro-detalle en Rave, se necesitan dos conjuntos de
datos en la correspondiente aplicacion Delphi, per0 no es necesario que estos
conjuntos de datos tengan definida una relacion maestro-detalle en el programa,
ya que el propio informe puede definir una relacion de este tipo. En el programa
de muestra RaveDetails, se expone cada uno de 10s conjuntos de datos a traves de
una conexion Rave:
o b j e c t dsDepartments: TSimpleDataSet
Connection = SQLConnectionl
DataSet. CommandText = ' select * from DEPARTMENT'
end
o b j e c t dsEmployee: TSimpleDataSet
Connection = SQLConnectionl
DataSet.CommandText = 'select * from EMPLOYEE'
end
o b j e c t RvConnectionDepartments: TRvDataSetConnection
DataSet = dsDepartments
end
o b j e c t RvConnectionEmployee: TRvDataSetConnection
DataSet = dsEmployee
end

El informe tiene dos vistas de datos correspondientes, cada una conectada a un


componente DataBand (ambos contenidos en una region). El primer DataBand,
utilizado para el conjunto de datos principal, no tiene parametros especiales. El
DataBand secundario define la relacion maestro-detalle mediante unas cuantas
propiedades. La propiedad MasterDataView se refiere a la vista de datos del
conjunto de datos maestro, y las propiedades MasterKey y DetailKey a 10s
campos que definen la union (en este caso ambos se refieren a1 campo DEPT-NO).
La propiedad C o n t r o 1l e r B a n d se refiere a1 DataBand que muestra 10s datos
del coniunto de datos maestro.
En el caso del informe maestro-detalle, el parametro mas importante lo gestio-
na cl Band Style Editor, en el que hay que definir la banda como un detalle. La
figura 18.6 muestra este editor para el ejemplo RaveDetails. La propiedad
KeepRowTogether de la vista de datos maestra se define como T r u e para
evitar tener detalles quc se muestren en una pagina distinta a 10s datos maestro.

1v oasv*lrRegm -W1-
Simple T a b d l

Figura 18.6. El informe maestro-detalle. El Band Style Editor aparece por delante.

ADVERTENCIA: Para crear un infoxme maestro-detalle se podria utili-


zar el asistente correspondiente disponible en Rave. En la version que se
incluye con Delphi 7 no funciona este asistente. Aun no hay disponible una
actualizacih para sotucionar este y otros problemas.

Guiones de informes
A1 comienzo de este capitulo hablamos de la ventana Event Editor del Rave
Designer, per0 aun no lo hemos usado. Esta herramienta se utiliza para escribir
codigo (guiones o scripts) en un informe, que responda a eventos de 10s diversos
componentes, como se haria en Delphi. La escritura de guiones en el Rave
Designer permite personalizar o ajustar el resultado de un informe de un mod0
muy sofisticado. El lenguaje que utiliza Rave para 10s guiones se basa en Pascal
y es una variante del lenguaje Delphi, por lo que no deberia haber mucho proble-
ma en su comprension.
El ejemplo RaveDetails muestra en negrita 10s sueldos que son mayores que
una cantidad cspecificada. El mod0 mas obvio de realizar esto es escribir un
codigo interpretado que se ejecute para cada instancia de la banda detalle (es
decir, para cada registro dc la base de datos de empleados). En lugar de modificar
directamente la propiedad Font, hemos decidido aiiadir dos componentes
FontManager distintos a la pagina del informe y cambiarles el nombre para que sc
comprenda su funcion: fmPlain Font y fmBold Font. Se puede abrir el in-
forme para vcr sus propiedades y estructura.
En cl informe, para resaltar 10s valores que se encuentrcn por encima de un
rango dado, se controla el evento Bef orePr int del componente Da t a T e x t .
Para cllo, iremos a la pagina Event Editor, escogeremos el componente DataText
conectado con el campo Salary y escogeremos el evento. En la ventana de edicion
de codigo dcl evento, escribiremos este codigo:
if DataView2Salary.AsFloat > 1 0 0 0 0 0 then
self.FontMirror : = fmBoldFont;
else
self.FontMirror := fmPlainFont;
end if;

El guion modifica la propiedad FontMirror del objeto actual (self) para


hacer referencia a uno de 10s dos componente FontManager de la pagina, de
acuerdo con el valor del campo. Fijese en que DataView2Salary es una refe-
rcncia a uno de 10s dos campos de la vista de datos (el que csta conectado con el
componentc DataTest actual). Compilaremos el guion y ejecutaremos el informe
para coinprobar su efecto. como muestra la figura 18.7.

ADVERTENCIA: Cada vez que se modifique un guion hay que acordarse


de hacer clic sobre el boton Compile, para que tengan efecto 10s carnbios.

Espejos
Las plantillas de informes pueden tener uno o mas componentes y reutilizarse
mediante la tecnologia de espejo de Rave. El componente DataMirrorSection re-
fleja otras secciones de acuerdo con 10s contenidos de un DataField. El uso de
secciones espejo permite que la DataMirrorSection sea muy flexible. Conviene
recordar que las secciones pueden contener cualquier otro componente como gra-
ficos, regiones, texto y demas.
Por ejemplo, podria usarse un componente DataMirrorSection para que un
unico informe genere distintos formatos de sobre para direcciones internacionales
o nacionales. La plantilla para 10s usuarios internacionales podria incluir una
linea para el pais con el texto centrado en el sobre, mientras que el formato
nacional podria no incluir la linea de pais y podria tener el testo en la parte
inferior derecha del sobre.

Simple Table Report


I DEPARTMENT

Corporate Headquarters
BudgetCjalary

1 000.000.00
LOCATION

Monterey
Lee, Tern 53 793.00
Bender, Ollver H. 212.850.00

Sales and Marketing 2.000 000,OO San Franc~sco


MacDond. Mary S 111.262.50
Yanowski, Mchael 44.WOPO

I Engineering
Nelson, Robelt
1.100.000.(
105.900.0

I
age 1 o f 3
Brown, Kelly
-
-
270M,C

Figura 18.7. El texto en negrita del informe se establece en tiempo de ejecucion


gracias a un guion.

Lo mas normal es definir una de las dos configuraciones como predetermina-


da. Si no se define una opcion predeterminada y el valor de campo no encaja con
ninguna de las configuraciones, entonces el formato usado sera el contenido nor-
mal del componente DataMirrorSection.

Calculos a tope
Ademas del sencillo componente CalcTest ya comentado, el Rave Designer
incluye tres componentes para manejar situaciones mas complejas: CalcTotal,
CalcController y CalcOp.
CalcTotal
El componente CalcTot a1 es una version no visual del componente
CalcText.Cuando se imprime este componente, lo m h habitual es guardar su
valor en un parametro de proyecto (definido por la propiedad ~estParam) y darle
formato de acuerdo con la propiedad DisplayFormat.Puede resultar util cuando
se realicen calculos totales que se utilizaran en otros calculos antes de presentarse.
Si el valor del CalcTotal solo se va a usar en otros componentes de calculo, como
CalcOp, deberia dejarse en blanco la propiedad De stParam.

CalcController
C a 1cCont ro 11e r es un componente no visual que actua como un contro-
lador para 10s componentes CalcText y CalcTo tal mediante sus propieda-
des contro 1ler. Cuando se imprime el componente controlador, indica a todos
10s componentes de calculo que controla que realicen sus operaciones. Este proce-
so permite que un informe vuelva a calcular 10s totales de de bandas de grupo, de
detalle o de paginas completas segun cual sea la situacion del componente
CalcController.
El componente CalcController tambien puede iniciar un componente CalcText
o CalcTotal con un valor especifico (mediante las propiedades Initcalcvar,
InitDataField e Initvalue). El componente CalcController solo
iniciara 10s valores si se usa en la propiedad Init ia1izer de 10s componentes
CalcTexto CalcTotal.

CalcOp
CalcOp es un componente no visual que permite realizar una operacion (defi-
nida por la propiedad Ope rator) sobre valores de distintas fuentes de datos. El
resultado puede guardarse despuQ de un parametro de proyecto, como CalcTotal,
tal y como indiquen las propiedades De stParam y DisplayFormat.
Por ejemplo, supongamos que necesitamos aiiadir dos componentes DataText,
como en A + B = C (donde A y B representan 10s valores de dos componentes
DataText y c representa el resultado que se almacena en un parametro de proyec-
to). Los tres tipos de fuentes tienen asociados muchos valores distintos.
El calculo puede comenzar con distintos tipos de fuentes de datos:
Una fuente Data Fie ld es un campo en una tabla, o un DataView en
terminos de Rave. Por ello, para escoger un campo hay que seleccionar
antes un Dataview.
Para una fuente Value,se rellenara la propiedad con un valor numerico.
Una fuente Calcvar representa otra variable de calculo; se puede esco-
ger una del menu desplegable que muestra la lista de variables de calculo
disponibles en la pagina. Este valor puede proceder de otro componente
CalcOp o de algun otro componente de calculo.
Despues de escoger las fuentes de datos, hay que seleccionar la operacion que
se realizara con ellos. La propiedad operator tiene un menu desplegable que
puede usarse para realizar la eleccion adecuada. En el ejemplo A + B = C, el
operador es c o ~ d d .
En ocasiones se necesita realizar una funcion con un valor antes de procesarlo
junto con el segundo valor. En este caso es practica la propiedad Function de
la fuente. Con una funcion se puede convertir un valor (corno de horas a minutos),
calcular una funcion trigonometrica (corno el sen0 de un valor) o realizar muchos
otros calculos (corno una raiz cuadrada o el valor absoluto).
Es tan importante asegurarse de que 10s componente estan ordenados en el
Project T r e e para realizar 10s calculos en orden. Un informe ejecuta 10s compo-
nentes en sentido descendente en el Project Tree. Para 10s componentes CalcOp
o cualquier otro componente de calculo, esto significa que deben seguir el orden
correcto. Tambien es importante tener en cuenta que si un valor de fuente depende
de otro componente (corno de otros componentes C a l c O p o componentes
DataText), esos componentes deben encontrarse antes en el Project Tree.
Parte IV
Delphi e Internet
Programacion
para Internet:
sockets e lndy

Con la llegada de la era de Internet, la creacion de programas basados en


protocolos de Internet se ha convertido en algo muy comun, por lo que vamos a
dedicar a este tema 10s proximos cinco capitulos. Este capitulo se centra en la
programacion de sockets a bajo nivel y de protocolos de Internet, el capitulo 20 se
dedica a la programacion Web de servidor, el 2 1 habla de IntraWeb, y 10s capitu-
10s 22 y 23 tratan de XML y 10s servicios Web. En este capitulo comenzaremos a
fijarnos en la tecnologia de sockets en general. Despues pasaremos a1 uso de 10s
componentes Indy (de Internet Direct) que soportan tanto la programacion de
sockets a bajo nivel como 10s protocolos de Internet mas habituales. Presentare-
mos algunos elementos del protocolo HTTP, hasta llegar a la creacion de archivos
HTML a partir una base de datos. Aunque probablemente quiera utilizar un pro-
t o c o l ~de alto nivel, la explicacion de la programacion para Internet comienza a
partir de 10s conceptos basicos y las aplicaciones a bajo nivel. Comprender el
protocolo TCPIIP y 10s sockets ayudara a asimilar el resto de 10s conceptos mas
facilmente. En este capitulo trataremos 10s siguientes temas:
Uso de sockets.
Componentes Internet Direct (Indy).
Programacion de sockets a bajo nivel
La API WinInet.
Protocolos de correo (SMTP y POP3).
El protocolo HTTP.
Generacion de HTML.
De bases de datos a HTML.

Creacion de aplicaciones con sockets


Delphi 7 incluye dos conjuntos de componentes TCP: componentes de socket
de Indy (IdTCPClient e IdTCPServer) y componentes nativos de Borland. Ambos
conjuntos tambien estan disponibles en Kylix y se encuentran en la pagina Internet
de la Component Palette. Los componentes de Borland, TcpClient y TcpServer,
probablemente se desarrollaron para sustituir a 10s componentes ClientSocket
y Server Soc ket disponibles en anteriores versiones de Delphi. Sin embargo,
ahora que 10s componentes Client Soc ket y ServerSoc ket se han decla-
rado obsoletos (aunque sigan estando disponibles), Borland sugiere usar en su
lugar 10s correspondientes componentes de Indy.
En este capitulo nos centraremos en el uso de Indy durante el analisis de la
programacion de sockets a bajo nivel, no solo cuando hablemos del soporte para
protocolos de Internet de alto nivel. A continuacion hablaremos algo m b acerca
del proyecto Indy.
Antes de mostrar un ejemplo de una comunicacion basada en sockets a bajo
nivel, daremos un repaso a 10s conceptos basicos de TCPIIP para que se pueda
comprender la base de la tecnologia de red mas estendida.

Componentes Internet Direct (Indy) de c6digo abierto


Delphi se distribuye con un conjunto de componentes de c6digo abierto
para Internet llamado Internet Direct (Indy). Los componentes Indy, antes
llamados WinShoes (una broma con WinSock [zapatos y calcetines]. el
nombre de la biblioteca de sockets de Windows), 10s creo un grupo de
desarrolladc-xes dirinido
~ - - ~-
~ - "~~ or Chad
- - -
-~ Hower.
-~ .~ . Tambien
. - - - -se
- -
-.
~ -encuentran
- ..- .~ dis~oni-
---r-
---
bles en Kylix. Se puede encontrar mas information y las versiones mas
recientes de 10s componentes en www.nevrona.com/indy.
Delphi 7 se distribuye con Indy 9, per0 deberia consultarse el sitio Web en
busca de versiones mas actualizadas. Los componentes son gratuitos y se
1 *-- --- L - - -f I - - _..... ---L:-.- 3->- L - - * - - - * - .,-Lfl
cornpiemenu con mucnos ejernplos y un arcnwo ae ayuaa oasrante UUI.
Indy 9 incluye muchos componentes mas que la version anterior (Indy 8,
disponible en Delphi b), y tiene dos paginas nuevas en la paleta de compo-
nentes (Indy Intercepts e lndy I f 0 Handlers).
. . . .

Con mas de 160 componkntes instaradoseda pafeta de Delphi, h d y tiene


una gran cantidad de prestaciones, desde el desarrollo de aplicaciones TCPI
IP de cliente y servidor para varios protocolos a1 cifrado y la seguridad. Se
pueden reconocer 10s componentes Indy por el prefijo I d . En lugar de
------A 3 - I - - >: 2
presenrar una I~isra
:-&-
ae L-LI
10salversos componenies, namarernos ae unos cuan- A_-

tos de ellos durante este capitulo.

Conexiones bloqueantes y no bloqueantes


Cuando se trabaja con sockets en Windows, leer datos desde un socket o
escribir en el puede realizarse de manera asincrona, para que no se bloquee
la ejecucion de otro codigo en la aplicacion de red. Se trata de una conexion
no bloqueante. El soporte de sockets de Windows envia un mensaie cuando
hay data1s disponibles. Wn enfoque alternativo es el uso de conexiones
bloquear~tes,en que Ia aplicacion espera a que se complete la lectura o
escritura a l l r w uA,G GA,,..
,-horn +,
, 1, .411..;,..+b 1 L b n A- I.AA;rrm P..,.,.A,-. -, ..+;l;-..
2. "U l G l l L G I r u G a UF; b u U I L ; u . buanuu 3 G u w u a
l G b u r a l la 3 l X w

una conexion bloqueante, se debe usar una hebra en e 1 servidor y, general-


mente, tambien se utilizara una hebra en el cliente.
r _- r.-3-. ..AIII--- --.- l..-l :-
LOS componentes may utwzan exclusivamente conexiones bloqueantes. Por
A-

eso, cualquier operaci6n con sockets en clientes que pueda ser de gran
duracion, deberia Ilevarse a cab0 dentro de una hebra o empIeando el com-
ponente IdAntiFreeze de Indy como una alternativa mas simple pero tam-
bien limitada. El uso de conexiones bloqueantes para implementar un
protocolo tiene la ventaja de simplificar la logica del programa, ya que no - -

es necesario utilizar
uti la tecnica de mdquina dekstados be las conexiones no
bloqueantes.
T'odos 10s servidores
servi Indy utilizan una arquitectura multihilo que se puede
--- I10s -- --------- T A - ~ L - - - A ~ ~ - - ~ - L - - I ~- r > - r t - - - A n # - - n - - i
conirolar con
- - - A - - ~ - -
cornponentes la I nreaalvlgrueraulr e IU I nreaamgrrool.
A--

El primer0 se utiliza de manera predeterminada, mientras que el segundo


soporta una reserva de hebras y deberia permitir conexiones mas rapidas.

Bases de la programacion de sockets


Para comprender el comportamiento de 10s componentes de sockets; se necesi-
ta scntirse como con varios vocablos relacionados con Internet en general y con
10s sockets en particular. El corazon de Internet consiste en el protocolo TCPIIP
(7'ransmission Control Protocolllnternet Protocol), que realmente es una combi-
nation de dos protocolos independientes que trabajan juntos para ofrecer co-
nesiones sobre Internet (y que tambien pueden permitir una conexion sobre una
intranet privada). En pocas palabras, IP es el responsable de la definition y
encaminamiento de dntagrnmns (unidades de transmision en Internet) y de especi-
ficar el sistema de direccionamiento. TCP es responsable de servicios de trans-
porte de mayor nivel.
Configuracion de una red local: direcciones IP
Si se puede acceder a una red local, se podra probar 10s programas de que
hablaremos a continuacion sobre esa red; de no ser asi, se puede utilizar el mismo
ordenador como cliente y servidor. En este caso, como en 10s ejemplos, utilizare-
mos la direccion 127.0.0.1 (o l o c a l h o s t ) , que es siempre la direccion del
ordenador actual. Si la red es algo compleja, deberia solicitarsele a1 administra-
dor de red que configure las direcciones IP apropiadas. Si se quiere configurar
una red sencilla con un par de ordenadores sueltos, se puede configurar la direc-
cion IP facilmente; es un numero de 32 bits que suele representarse con cada uno
de sus cuatro componentes (Ilamados octetos) separados por puntos. Estos nume-
ros tienen una compleja logica subyacente, y el primer octeto indica la clase de la
direccion.
Algunas direcciones IP especificas se reservan para redes internas sin regis-
trar. Los equipos de encaminamiento de Internet ignoran estos rangos de direccio-
nes, asi que se puede probar sin problemas sin ninguna interferencia con ninguna
red real. Por ejemplo, el rango de direcciones 1P "libres" de 192.168.0.0 a
192.168.0.255 se puede usar para experimentar con una red de menos de 255
maquinas.

Nombres de dominio local


~ C o m ose proyecta la direccion IP sobre un nombre? En Internet, el programa
cliente busca 10s valores en un servidor de nombres de dominio. Pero tambien es
posible tener un archivo hosts local (un archivo de maquinas), que es un archivo
de texto facil de editar para proporcionar correspondencias locales. Habra que
buscar el archivo HOSTS.SAM (instalado en un subdirectorio del directorio
Windows, segun la version de Windows de que se disponga) para tener un ejem-
plo; se puede renombrar el archivo como HOSTS, sin la extension, para activar
las proyecciones locales de maquinas.
Puede que se pregunte si utilizar una direccion IP o un nombre de maquina en
sus programas. Los nombres de maquina son mas faciles de recordar y no supo-
nen un cambio si cambia la direccion IP (por la razon que sea). Por otra parte, las
direcciones IP no necesitan ningun tipo de resolution, mientras que 10s nombres
de maquina tienen que ser resueltos (una operacion que toma tiempo si la busque-
da se realiza mediante la Web).

Puertos TCP
Cada conexion TCP se realiza a traves de un puerto, que se representa como
un numero de 16 bits. La direccion IP y el puerto TCP especifican juntos una
conexion de Internet o un socket. Distintos procesos activos en la misma maquina
no pueden utilizar el mismo socket (el mismo puerto).
Algunos puertos TCP tienen un uso estandar para protocolos y servicios de
alto nivel especificos. En otras palabras, deberian utilizarse esos numeros de
puerto cuando se implementen esos servicios y no utilizarlos en ningun otro caso.
Esta es una breve lista:

HTTP (Protocolo de transferencia de hipertexto) 80


FTP (Protocolo de transferencia de archivos) 21
SMTP (Protocolo de transferencia simple de correo) 25
POP3 (Protocolo de oficina de envio, version 3) 110
Telnet 23

El archivo s e r v i c e s (otro archivo de texto parecido a1 archivo H O S t s )


contiene una lista de 10s puertos estandar que utilizan 10s servicios. Se pueden
aiiadir entradas propias a la lista, proporcionando a esos servicios un nombre
propio. Los sockets cliente siempre especifican el numero de puerto o el nombre
de servicio del socket de servidor a1 que desean conectarse.
Protocolos de alto nivel
Hasta ahora hemos utilizado muchas veces el termino protocolo. Un protocolo
es un conjunto de reglas que acuerdan el cliente y el servidor para determinar el
flujo de comunicacion. Los protocolos de Internet de bajo nivel, como TCPIIP,
suelen estar implementados por un sistema operativo. Pero el termino protocolo
tambien se utilizar para protocolos esthdar de Internet de alto nivel (como HTTP,
FTP, o SMTP). Estos protocolos se definen en documentos esthdar disponibles
en el sitio Web de la Internet Engineering Task Force (www.ietf.org).
Si se quiere implementar una comunicacion personalizada, se puede definir un
protocolo propio (posiblemente sencillo), un conjunto de reglas que determina
que peticiones puede enviar el cliente a1 servidor y como puede responder el
servidor a las distintas peticiones. Mas adelante veremos un ejemplo de un proto-
c o l personalizado.
~
Los protocolos de transferencia estan a un nivel superior que 10s protocolos de
transmision, porque pueden abstraerse del mecanismo de transporte que propor-
ciona TCPIIP. Esto hace que 10s protocolos Sean independientes no solo el siste-
ma operativo y del hardware sin0 tambien de la red fisica.
Conexiones de socket
Para iniciar la comunicacion a traves de un socket, el programa servidor debe
comenzar a ejecutarse en primer lugar; per0 simplemente esperara una peticion
procedente de un cliente. El programa cliente solicita una conexion indicando el
servidor a1 que desea conectarse. Cuando el cliente envia la peticion, el servidor
puede aceptar la conexion, iniciando un socket de servidor especifico que se co-
necte a1 socket del cliente. Para soportar este modelo esisten tres tipos de co-
nexiones de socket:
Las conexiones d e cliente: Son iniciadas por el cliente y conectan un
socket del cliente local con un socket del servidor remoto. Los sockets de
clientc deben describir el servidor a1 que se quieren conectar, proporcio-
nando su nombre (o su direccion IP) y su puerto.
L a s conexiones d e escucha: Son sockets pasivos de servidor que espcran
una peticion de un cliente. Una vez que un clientc efectua una nueva peti-
cion, cl servidor crea un nuevo socket dedicado a esa conesion especifica y
dcspuks vuelve a1 estado de escucha. Los sockets de escucha de scrvidor
deben indicar el pucrto quc reprcscnta el servicio quc proporcionan. (El
cliente sc conectara mediante esc puerto.)
Las conexiones d e servidor: Son activadas por 10s servidores; aceptan
una peticion procedente de un cliente.
Los distintos tipos de conesiones son solo importantcs en el cstablecimiento
dcl enlace entre el clientc y el servidor. Una vez que se ha establecido el enlace,
ambos lados pueden realizar peticiones y enviar datos a1 otro lado.

Uso de componentes TCP de lndy


Para permitir quc dos programas se comuniquen a traves de un socket (ya sea
sobre una red de arca local o sobre Internet), se pueden utilizar componentes
IdTCPClient e IdTCPServer.
Hay que colocar uno de ellos en el formulario de un programa y otro en otro
formulario de un programa distinto: a continuacion, hay que hacer que usen el
mismo puerto, y dejar que el programa cliente haga referencia a la maquina que
contenga el programa servidor. Entonces se podra estableccr una conesion entre
las dos aplicaciones. Por ejemplo, en el grupo de proyecto IndySockl, hemos
usado dos componentcs con estas configuraciones:
// programa s e r v i d o r
object IdTCPServerl: TIdTCPServer
Defaultport = 1050
end
// p r o g r a m a c l i e n t e
object IdTCPClient 1: TIdTCPClient
Host = l l o c a l h o s t l
Port = 1050
end

NOTA: Los sockets de servidor de Indy permiten conectarse a multiples


direcciones IP ylo puertos, mediante el conjunto Bindings.
En este momento, en el programa cliente se puede conectar con el sewidor si se
ejecuta

El programa sewidor tiene un cuadro de lista que se utiliza para registrar


informacion. Cuando se conecta o desconecta un cliente, el programa muestra la
direccion IP de ese cliente junto con la operacion, como en el siguiente controla-
dor del evento Onconnect:
procedure TFormServer.IdTCPServerlConnect(AThread:
TIdPeerThread) ;
begin
1bLog.Items.Add ( ' C o n n e c t e d f r o m : ' +
AThread.Connection.Socket.Binding.Peer1P);
end :

Ahora que se ha establecido una conexion, se necesita hacer que 10s dos pro-
gramas se comuniquen. Tanto 10s sockets de cliente como de servidor tienen me-
todos de lectura y escritura que pueden utilizar para enviar datos, per0 escribir un
servidor multihilo que pueda recibir muchas ordenes distintas (normalmente ba-
sadas en cadenas) y trabajar de distinto mod0 con cada una de ellas no es algo
trivial. Sin embargo, Indy simplifica el desarrollo de un servidor mediante su
arquitectura de comandos. En un servidor se puede definir un cierto numero de
comandos, que se guardan en la coleccion CommandHandlers de IdTCPSewer.
En el ejemplo IndySockl el servidor tiene tres controladores, todos ellos
implementados de distinta manera para mostrar algunas de las posibles alternati-
vas. La primera orden de servidor, llamada test,es la mas simple, porque esta
completamente definida en sus propiedades. Hemos preparado la cadena de la
orden, un codigo numeric0 y una cadena resultante en la propiedad ReplyNorma1
del controlador de ordenes:
object IdTCPServerl: TIdTCPServer
CommandHandlers = <
i tern
Command = ' t e s t '
Name = ' T I d C o r r u n a n d H a n d l e r O '
Parseparams = False
ReplyNormal.NumericCode = 100
ReplyNorma1.Text.Strings = (
'Hello from your Indy Server')
ReplyNormal.TextCode = '10 0 '
end

El codigo de cliente utilizado para ejecutar el comando y mostrar su respuesta


es el siguiente:
procedure TFormClient.btnTestClick(Sender: TObject);
begin
IdTCPClientl.SendCmd ( ' t e s t ') ;
ShowMessage (1dTCPClientl.LastCmdResult.TextCode + ' : ' +
1dTCPClientl.LastCmdResult.Text.Text);
end;

Para casos mas complejos deberia ejecutarse el codigo en el servidor y leer y


escribir directamente sobre la conexion del socket. Este enfoque se muestra en la
segunda orden del protocolo tan trivial que hemos creado para este ejemplo. La
segunda orden del servidor se llama execute,y no tiene ningun conjunto espe-
cial de propiedades (solo el nombre de la orden), per0 tiene el siguiente controla-
dor para el evento Oncommand:
procedure TFormServer.IdTCPServerlTIdCommandHandlerlCommand(
ASender : TIdCommand) ;
begin
ASender.Thread.Connection.Write1n ('This is a dynamic response');
end :

El codigo de cliente correspondiente escribe el nombre de la orden en la co-


nexion del socket y despues lee una unica linea de respuesta, mediante metodos
distintos a1 primero:
procedure TFormClient.btnExecuteClick(Sender: TObject);
begin
IdTCPClientl .WriteLn ( 'execute ' ) ;
ShowMessage ( IdTCPClient 1. ReadLn) ;
end ;

El efecto es similar a1 del ejemplo anterior, per0 ya que utiliza un enfoque de


bajo nivel, deberia ser mas facil de particularizar para las necesidades del mo-
mento. Una de estas extensiones se ofrece como tercer y ultimo comando del
ejemplo, que permite que el programa cliente solicite un archivo de mapa de bits
del servidor (como una especie de arquitectura para compartir archivos). La or-
den del servidor tiene parametros (el nombre de archivo) y se define de esta
manera:
object IdTCPServerl: TIdTCPServer
CommandHandlers = <
item
CmdDelimiter = ' '
Command = 'getfile'
Name = ' T I d C o m n d H a n d l e r 2 '
Oncommand = IdTCPServerlTIdCommandHandler2Command
ParamDelimiter = ' '
ReplyExceptionCode = 0
ReplyNormal.NumericCode = 0
Tag = 0
end>

El codigo utiliza el primer parametro como nombre de archivo y lo devuelve en


un flujo. En caso de error lanza una excepcion, que interceptara el componente
del senidor, que a su vez finalizara la conexion (no se trata de una solucion muy
realista, per0 es un enfoque mas seguro y facil de implementar):
procedure TFormServer.IdTCPServerlTIdCommandHandler2Command(
ASender: TIdCommand);
var
filename: string;
fstream: TFileStream;
begin
.
i f Assigned (ASender Params) then
filename := HttpDecode (ASender.Params [O] ) ;
i f not FileExists (filename) then
begin
ASender .Response.Text : = 'File not found';
1bLog.Items.Add ('File not found: ' + filename);
r a i s e E1dTCPServerError.Create ('File not found: ' +
filename) ;
end
else
begin
fstream : = TFileStream.Create (filename, fmOpenRead);
try
ASender.Thread.Connection.WriteStream(fstream, True,
True) ;
1bLog.Items.Add ('File returned: ' + filename +
' ( ' + IntToStr (fStream.Size) + ' ) ' ) ;
finally
fstream-Free;
end;
end ;
end;

La llamada a la funcion auxiliar H t t p D e c o d e sobre el p a r h e t r o es necesa-


ria para codificar un nombre de ruta que incluya espacios como p a r h e t r o unico,
a1 contrario del programa cliente que llama a H t t p E n c o d e . Como puede verse,
el sewidor tambien registra 10s archivos devueltos y sus tamaiios, o un mensaje de
error.
El programa cliente lee el flujo y lo copia en un componente Image, para
mostrarlo directamente (vease figura 19.1):
procedure TFormClient.btnGetFileClick(Sender: TObject);
var
stream: TStream;
begin
IdTCPClientl.WriteLn('getfi1e ' + HttpEncode
(edFileName.Text) ) ;
stream : = TMemoryStream.Create;
try
IdTCPClientl .ReadStream(stream);
stream.Position : = 0;
Imagel.Picture.Bitmap.LoadFromStream (stream);
finally
stream. Free;
end;
end ;

Figura 19.1. El prograrna cliente del ejernplo IndySockl.

Envio de datos de una base de datos a traves


de una conexion de socket
Mediante las tecnicas que hemos visto hasta ahora puede escribirse una apli-
cacion que lleve registros de una base de datos a traves de un socket. La idea es
escribir una interfaz para la entrada de datos y un sistema de respaldo para el
almacenamiento de datos. La aplicacion cliente tendra un sencillo formulario de
entrada de datos y utiliza una tabla de base de datos con campos de cadena para la
empresa, direccion, estado, pais, direccion de correo electronico y contacto, asi
como un campo de coma flotante para el identificador de la empresa (Ilamado
CompID).

NOTA ~lAr registmr & una base de da&s a tn& de un socket es


exactamente b qye re pus& hacer con Datasnap y un componente de co-
I
nexi6n de sockets (& en el capitulo 16) o con sopoite SOAP (como en el
capftulo 23).

El programa cliente que hemos creado trabaja con un ClientDataSet con esta
estructura guardada en el directorio actual. (Se puede ver el codigo pertinente en
el controlador del evento oncreate.) El metodo principal en el cliente es el
controlador del evento OnClic k del boton Send All, que envia todos 10s nuevos
registros a1 servidor. Se ve que un registro es nuevo fijandose en si el registro
tiene un valor valido para el campo CompID. Este campo no lo establece el usua-
rio, si no la aplicacion servidor cuando se envian 10s datos.
Para todos 10s registros nuevos, el programa cliente empaqueta la informacion
de campos en una lista de cadenas, utilizando la estructura F i e l d N a m e =
Fie ldValue.La cadena correspondiente a la lista completa, que es un registro,
se envia entonces a1 servidor. En este punto, el programa espera a que el servidor
envie de vuelta el identificador de la empresa, que se guarda entonces en el regis-
tro actual. Todo este codigo se ejecuta en una hebra, para evitar bloquear la
interfaz de usuario durante operaciones pesadas. A1 hacer clic sobre el boton
Send, un usuario lanza una nueva hebra:
procedure TForml.btnSendClick(Sender: TObject);
var
SendThread: TSendThread;
begin
SendThread : = TSendThread.Create(cds);
SendThread.OnLog : = OnLog;
SendThread.ServerAddress : = EditServer.Text;
SendThread.Resume;
end :

La hebra tiene unos cuantos parametros: el conjunto de datos que se pasa en el


constructor, la direccion del servidor guardada en la propiedad ServerAddress,
y un evento de registro para escribir en el formulario principal (dentro de una
llamada S ynch r o n i z e segura). El codigo de la hebra crea y abre una conexion
y sigue enviando registros hasta completar su labor:
procedure TSendThread.Execute;
var
I: Integer;
Data: TStringList;
Buf: String;
begin
try
Data : = TStringList.Create;
f IdTcpClient : = TIdTcpClient .Create ( n i l );
try
f1dTcpClient.Host : = ServerAddress;
fIdTcpClient.Port : = 1051;
fIdTcpClient.Connect;
fDataSet.First;
while n o t fDataSet.Eof do
begin
// s i el r e s g i s t r o a u n n o s e ha registrado
i f fDataSet.FieldByNarne('CompID1) .IsNull or
(fDataSet.FieldByName('CompID').AsInteger = 0 ) then
begin
FLogMsg : = 'Sending ' +
fDataSet.FieldByNarne('Company1) .Asstring;
Synchronize (DoLog);
Data.Clear;
// crea cadenas con la estructura
"FieldName=Value "
for I : = 0 to fDataSet.Fie1dCount - 1 do
Data.Values [fDataSet.Fields[I].FieldName] : =
fDataSet .Fields [I] .Asstring;
// envia el registro
fIdTcpClient .Writeln ( 'senddata ' ) ;
fIdTcpC1ient.WriteStrings (Data, True);
// espera una respuesta
Buf : = fIdTcpC1ient.ReadLn;
fDataSet.Edit;
fDataSet .FieldByName( ' C o m p I D f ).Asstring : =
Buf ;
fDataSet .Post;
FLogMsg :=
fDataSet.FieldByName('Companyr).AsString +
logged a s ' +
£Dataset. FieldByName ( 'CompID') .Asstring;
Synchronize (DoLog);
end;
£Dataset.Next;
end ;
finally
fIdTcpC1ient.Disconnect;
fIdTcpClient.Free;
Data.Free;
end ;
except
/ / atrapa expeciones en caso de errores del conjunto d e
// da tos (edicidn concurrente, etc. . ) .
end;
end;

Fijemonos ahora en el servidor. Este programa tiene una tabla de base de


datos, guardada una vez mas en el directorio local, con dos campos mas que la
tabla de la aplicacion cliente: LoggedBy, un campo de cadena; y LoggedOn, un
campo de datos. Los valores de estos dos campos adicionales 10s determina
automaticamente el servidor cuando recibe datos, junto con el valor del campo
CompID. Todas estas tres operaciones se realizan en el controlador de la orden
senddata:
procedure TForml.IdTCPServerlTIdCommandHandlerOCommand(
ASender: TIdCommand) ;
var
Data: TStrings;
I: Integer;
begin
Data : = TStringList.Create;
try
ASender.Thread.Connection.ReadStrings(Data);
cds.Insert;
// d a v a l o r a 1 0 s c a m p o s c o n l a s c a d e n a s
f o r I : = 0 t o cds.FieldCount - 1 d o
cds.Fields [I] .Asstring : =
Data.Values [cds.Fields[I] .FieldName];
/ / c o m p l e t a c o n I D , remitente y f e c h a
Inc ( I D ) ;
cdsCompID.AsInteger : = ID;
cdsLoggedBy.AsString : =
ASender.Thread.Connection.Socket.Binding.Peer1P;
cdsLogged0n.AsDateTime : = Date;
c d s . Post;
// d e v u e l v e e l ID
ASender.Thread.Connection.WriteLn(cdsComp1D.AsString);
finally
Data.Free;
end;
end ;

Excepto por el hecho de que podrian perderse algunos datos, no hay ningun
problema cuando 10s campos tienen un orden distinto y si no se corresponden, ya
que 10s datos se guardan con la estructura FieldName=FieldValue. Des-
pues de recibir todos 10s datos y enviarlos a la tabla local, el servidor envia de
vuelta el identificador de la empresa a1 cliente. Cuando se recibe esta respuesta, el
programa cliente guarda el identificador de la empresa, con lo que el registro se
marca como enviado. Si el usuario modifica este registro, no existe ningun mod0
de enviar una actualizacion a1 servidor. Para conseguir esto, podria aiiadirse un
campo modificado a la tabla de la base de datos de cliente y hacer que el servidor
comprobase si esta recibiendo un campo nuevo o un campo modificado. Con un
campo modificado, el servidor no deberia aiiadir un nuevo registro, sin0 actuali-
zar el existente.
Como muestra la figura 19.2, el programa servidor tiene dos paginas: una con
un registro y otra como un DBGrid que muestra 10s datos actuales en la tabla de
la base de datos del servidor. El programa cliente es una entrada de datos basada
en formularios, con botones adicionales para enviar 10s datos y eliminar 10s regis-
tros ya enviados (y para 10s cuales se haya recibido un identificador).

Envio y recepcion de correo electronico


Probablemente la tarea mas habitual que se realice con Internet sea enviar y
recibir correo electronico. En general no existe mucha necesidad de escribir una
aplicacion completa para manejar el correo electronico, ya que algunos progra-
mas de entre 10s existentes son bastante completos. Por este motivo, no vamos a
escribir aqui un programa de correo electronico de proposito general. Se pueden
encontrar algunos ejemplos relacionados entre 10s programas de demostracion de
Indy. Ademas de crear una aplicacion de correo electronico de proposito general,
podemos hacer muchas cosas con componentes y protocolos de correo electroni-
co. Hemos agrupado estas posibilidades en dos areas:
Generation automitica d e mensajes d e correo: Una aplicacion que se
haya escrito puede tener un cuadro Acerca d e ... para enviar un mensaje
de registro a1 departamento de comercializacion o un elemento de menu
especifico para enviar una peticion a1 grupo de soporte tecnico. Podria
decidirse incluso habilitar una conexion con el soporte tecnico cuando se
produzca una escepcion. Otras tareas relacionadas podrian automatizar el
envio de un mensaje a una lista de gente o generar un mensaje automatico
desde el sitio Web (un ejemplo que mostraremos hacia el final de este
capitulo).

Addless
l~lmanza
Stale cuml~aunlly
: 1 plaly
I Emd
I rnarco@marcocanluc a n
, Canla3
Marco Canlu

Figura 19.2. Los programas cliente y servidor del ejemplo de sockets de base de
datos (IndyDbSock).

Uso d e protocolos de correo electr6nico para la comunicaci6n con usua-


rio que s61o se conectan ocasionalmente: Cuando haya que llevar datos
entre usuarios que no siempre esten conectados, se puede escribir una apli-
cacion en un servidor para sincronizar entre ellos, y se puede ofrecer a
cada usuario una aplicacion cliente especializada en la interaccion con
dicho servidor. Una alternativa es utilizar una aplicacion de servidor ya
existente, como un servidor de correo, y escribir 10s dos programas espe-
cializados basandose en 10s protocolos de correo. Los datos enviados me-
diante esta conexion tendran en general un formato especial, por lo que se
querran utilizar direcciones de correo electronico especificas para estos
mensajes (y no la direccion de correo principal). Como ejemplo, se podria
rescribir el anterior ejemplo IndyDbSock para enviar mensajes de correo
en lugar de utilizar una conexion de socket personalizada. Este enfoque
tiente tambien la ventaja de funcionar mejor con 10s cortafuegos, y de
permitir que el servidor este temporalmente desconectado, porque las peti-
ciones se guardaran en el servidor de correo.

Correo recibido y enviado


Utilizar 10s protocolos de correo con Indy implica colocar un componente de
mensaje (IdMessage) en la aplicacion, rellenarlo con datos y despues utilizar el
componente IdSMTP para enviar el mensaje de correo. Para recuperar un mensa-
je de correo del buzon de correo, se utilizara el componente IdPop3, que devolve-
ra un objeto IdMessage. Para hacernos una idea de como funciona el proceso,
hemos escrito un programa para enviar correo a varias personas a1 mismo tiempo,
utilizando una lista guardada en un archivo ASCII. En un principio utilizabamos
este programa para enviar correo a gente que firmaba en un sitio Web, per0 mas
adelante extendimos el programa aiiadiendo soporte para bases de datos y la
capacidad de leer automaticamente 10s registros de suscripcion. La version origi-
nal del programa sigue siendo una buena introduccion a1 uso del componente
SMTP de Indy.
El programa SendList guarda una lista de nombres y direcciones de correo
electronico en un archivo local, que se muestra en un cuadro de lista. Unos cuan-
tos botones permite aiiadir y eliminar elementos, o modificar un elemento elimi-
nandolo, editandolo y volviendolo a aiiadir. Cuando se cierra el programa, se
guarda automaticamente la lista actualizada. Ahora, vamos a analizar la parte
interesante del programa. El panel superior, que se muestra en tiempo de diseiio
en la figura 19.3, permite escribir el asunto, la direccion del remitente, y la infor-
macion utilizada para conectarse con el servidor de correo (nombre de maquina,
de usuario y, en caso necesario, una contraseiia).
Probablemente se desee que el valor de estos cuadros de edicion sea permanen-
te, tal vez en un archivo .INI. NO hemos hecho esto solo por no mostrar 10s
detalles de nuestra conexion de correo! El valor de estos cuadros de edicion, junto
con la lista de direcciones, permite enviar la serie de mensajes de correo (despues
de personalizarlos) con el codigo siguiente:
procedure TMainForm.BtnSendAllClick(Sender: TObject);
var
nItem: Integer;
Res : Word;
begin
Res : = MessageDlg ( ' S t a r t s e n d i n g f r o m i t e m ' +
IntToStr (ListAddr.ItemIndex) + ' ( ' +
.
ListAddr Items [ListAddr.ItemIndex] + ') ? ' $ 1 3 +
' (No s t a r t s f r o m 0 ) ', mtconfirmation, [ d y e s , mbNo,
mbcancel] , 0) ;
if Res = mrCancel then
Exit;
if Res = mrYes then
nItem : = ListAddr.ItemIndex
else
nItem : = 0;
// c o n e c t a
Mail.Host : = eServer.Text;
Mail.UserName : = eUserName.Text;
if ePassword.Text <> " then
begin
Mail-Password : = ePassword.Text;
Mai1.AuthenticationType : = atlogin;
end;
Mail-Connect;
// e n v i a 1 0 s m e n s a j e s , u n o a u n o , p r e c e d i d o s d e u n m e n s a j e
// p e r s o n a l i z a d o
try
/ / e s t a b l e c e l a p a r t e f i j a de l a cabecera
MailMessage.From.Name : = eFrom.Text;
MailMessage-Subject : = eSubject.Text;
M a i l M e s s a g e - B o d y - S e t T e x t (reMessageText.Lines.GetText);
MailMessage.Body.Insert ( 0 , ' H e l l o ' ) ;
while nItem < ListAddr.1tems.Count do
begin
// m u e s t r a l a selection a c t u a l
Application.ProcessMessages;
ListAddr-ItemIndex : = nItem;
MailMessage.Body [O] := ' H e l l o ' + ListAddr.Items [nItem];
MailMessage.Recipients.EMai1Addresse.s : =
ListAddr. Items [nItem];
Mail. Send (MailMessage);
Inc (nItem);
end;
finally // ya e s t d
Mail.Disconnect;
end;
end:

Otro interesante ejemplo del uso del correo es informar a 10s desarrolladores
de problemas de las aplicaciones (una tecnica que podria desearse utilizar en una
aplicacion interna en lugar de en una distribuida para el publico). Se puede conse-
guir este efecto modificando el ejemplo ErrorLog del capitulo 2 para enviar co-
rreo cuando se produzca una excepcion (o solo una de un tip0 dado).
Figura 19.3. El programa SendList en tiempo de diseiio.

Trabajo con HTTP


Manejar mensajes de correo es realmente interesante, y 10s protocolos de co-
rreo probablemente sigan siendo 10s protocolos de Internet mas utilizados. El otro
protocolo mas popular es HTTP, que utilizan 10s servidores y navegadores Web.
Dedicaremos el resto de este capitulo a este protocolo (junto con un analisis de
HTML); 10s dos capitulos siguientes tambien hablaran de el.
En el lado de cliente de la Web, la actividad principal es la navegacion, la
lectura de archivos HTML. A d e m b de construir un navegador personalizado, se
pueden incrustar controles ActiveX de Internet Explorer en 10s programas (como
en el ejemplo WebDemo del capitulo 12). Tambien se puede activar directamente
el navegador instalado en el ordenador del usuario (abriendo, por ejemplo, una
pagina HTML llamando a1 metodo ShellExecute, definido en la unidad
ShellApi):
ShellExecute (Handle, 'open ', FileName, ' ' , ' ', sw-ShowNormal) ;

Mediante She1 lExecut e, se puede ejecutar simplemente un documento,


como un archivo. Windows arrancara entonces el programa asociado con la ex-
tension HTM, utilizando la accion pasada como parametro (en este caso open,
per0 si se pasara nil se llamaria a la accion estandar, produciendo el mismo
efecto). Se puede utilizar una llamada similar para visitar un sitio Web, utilizan-
do una cadena como 'http://www.ejemplo.com'en lugar de un nombre de archivo.
En este caso, el sistema reconoce la seccion http de la solicitud como la necesidad
de un navegador Web, y lo arrancara.
En el lado del servidor, se generan y se ofrecen las paginas HTML. De vez en
cuando, puede que baste con tener un mod0 de producir paginas estaticas, extra-
yendo de vez en cuando nuevos datos a partir de una base de datos para actualizar
10s archivos HTML cuando se necesite. En otros casos, se requerira generar
paginas dinamicas basadas en una peticion de un usuario.
Como punto de partida, hablaremos de HTTP para crear un servidor y un
cliente sencillos per0 completos. Despues analizaremos 10s componentes que pro-
ducen HTML. En el capitulo siguiente pasaremos de este nivel basico de la tecno-
logia a1 estilo desarrollo RAD para la Web soportado por Delphi, comentando las
tecnologias de extension del servidor Web (CGI, ISAP y modulos de Apache) y
las arquitecturas de WebBroker y WebSnap.

Obtencion de contenido HTTP


Como ejemplo del uso de 10s protocolos HTTP, hemos escrito una aplicacion
de busqueda especifica. El programa se conecta con el sitio Web de Google,
busca una palabra clave, y consigue la lista de 10s primeros 100 sitios. En lugar
de mostrar el archivo HTML resultante, lo procesa para extraer unicamente 10s
URL de 10s sitios relacionados y presentarlas en un cuadro de lista. La descrip-
cion de estos sitios se guarda en una lista de cadena independiente y se muestra
cuando se hace clic sobre un elemento del cuadro de lista. Asi, el programa sirve
para mostrar dos tecnicas a1 mismo tiempo: obtener una pagina Web y procesar
su codigo HTML.
Para mostrar como deberia trabajarse con conexiones bloqueantes, como las
que utiliza Indy, hemos implementado el programa empleando una hebra en se-
gundo plano para el procesamiento. Este enfoque tambien ofrece la ventaja de
permitir iniciar varias busquedas a1 mismo tiempo. La clase de hebra utilizada
por la aplicacion WebFind recibe como entrada un URL que buscar, strur1.
Esta clase tiene dos procedimientos de salida, AddToLis t y Showsta tus,
a 10s que se llama desde el metodo Synchronize.El codigo de estos dos meto-
dos envia algunos resultados o algunas respuestas a1 formulario principal, ya sea
aiiadiendo una linea a1 cuadro de lista o modificando la propiedad simpleText
de la barra de estado. El metodo clave de esta hebra es Execute.Sin embargo,
antes de analizarlo vamos a ver la manera en que se activa la hebra en el formula-
rio principal:
cons t
strSearch = ' h t t p : / / w w w . g o o g l e . corn/search?as-q= ';

procedure TForml.BtnFindClick(Sender: TObject);


var
FindThread: TFindWebThread;
begin
// c r e a r s u s p e n d i d o , f i j a r v a l o r e s i n i c i a l e s y a r r a n c a r
FindThread : = TFindWebThread.Create (True);
FindThread.Free0nTerminate : = True;
// o b t e n e r l a s primeras 100 e n t r a d a s
FindThread. strUrl : = strsearch + EditSearch.Text + ' & n ~ m = 1 0 0';
FindThread.Resume;
end ;

La cadena URL se forma con la direccion principal del motor de busqucda.


seguida de unos cuantos parametros. El primero, a s q, indica las palabras que
se estin buscando. El segundo, nun= 10 0, indica el %mere de sitios a obtener:
no se pueden usar 10s numeros que se quiera, sin0 que se esta limitado a unas
cuantas alternativas; siendo 100 el mayor valor posible

ADVERTENCIA: El programa WebFind funciona con el servidor del si-


tio Web de Google en el momento de la elaboracibn y comprobacibn de este
l!B--- XT- -a --n
-l--*-~-A- ..-L!-~-l-~ 3-1 --... .-.. L!-. 1-
11or0. IYO oosranre, el sonware particular ael sirlo pueae cammar,
-?A:-
con lo
que se podria impedir que WebFind funcionara correctamente. Este progra-
ma tambikn estaba en la edicion anterior de este libro; sin embargo carecia
de la cabecera HTTP de agente usuario, y despues de un tiempo Google
modifico el software de su servidor y bloqueo las peticiones. Aaadir un
valor cualquiera como agente usuario servia para solucionar el problema.

El metodo Execute de la hebra, activado mediante la llamada Resume,


llama a 10s dos metodos que realizan el trabajo (como muestra el listado 19.1). En
el primero, GrabHtml,el programa se conecta con el servidor HTTP utilizando
un componente IdHttp creado dinamicamente y lee el codigo HTML con el resul-
tad0 de la busqueda. El segundo metodo, HtmlToList, estrae las URL que
hacen referencia a otros sitios Web a partir del resultado, la cadena strRead.

Listado 19.1. La clase TFindWebThread (del prograrna WebFind).

unit FindTh;

interface

uses
Classes, Idcomponent, SysUtils, IdHTTP;

type
TFindWebThread = class (TThread)
protected
Addr, Text, Status : string;
procedure Execute; override;
procedure AddToList;
procedure ShowStatus;
procedure GrabHtml;
procedure HtmlToList;
procedure HttpWork (Sender: TObject; AWorkMode:
TWorkMode;
const AWorkCount: Integer);
public
strUrl: string;
strRead: string;
end;

implementation

uses
WebFindF;

procedure TFindWebThread.AddToList;
begin
if Forml. ListBoxl. Items.Indexof (Addr) < 0 then
begin
Forml.ListBoxl.1tems.Add (Addr);
Forml.DetailsList.Add (Text);
end;
end;

procedure TFindWebThread-Execute;
begin
GrabHtml;
HtmlToList;
Status : = 'Done with ' + StrUrl;
Synchronize (ShowStatus);
end ;

procedure TFindWebThread-GrabHtml;
var
Httpl: TIdHTTP;
begin
Status := 'Sending query: ' + StrUrl;
Synchronize (ShowStatus);
Httpl : = TIdHTTP-Create (nil);
try
Http1.Request.UserAgent : = 'User-Agent: NULL';
Httpl.OnWork : = HttpWork;
strRead : = Httpl .Get (StrUrl);
finally
Httpl.Free;
end;
end;

procedure TFindWebThread.Htm1ToList;
var
strAddr, strText: string;
nText : integer;
nBegin, nEnd: Integer;
begin
Status := ' E x t r a c t i n g d a t a f o r : ' + StrUrl;
Synchronize (ShowStatus);
strRead : = Lowercase (strRead);
repeat
// e n c u e n t r a l a p a r t e i n i c i a l d e l a r e f e r e n c i a HTTP
nBegin : = Pos ( ' h r e f = h t t p r , strRead) ;
if nBegin <> 0 then
begin
// o b t i e n e l a p a r t e r e s t a n t e d e l a c a d e n a , d e s d e h t t p
strRead : = Copy (strRead, nBegin + 5, 1000000);
/ / e n c u e n t r a e l f i n a l d e l a r e f e r e n c i a HTTP
nEnd : = Pos ( ' > I , strRead) ;
strAddr : = Copy (strRead, 1, nEnd - 1);
// c o n t i n u a
strRead : = Copy (strRead, nEnd + 1, 1000000) ;
/ / a f i a d e e l URL s i n o s e e n c u e n t r a g o o g l e
if Pos ( ' g o o g l e ' , strAddr) = 0 then
begin
nText : = Pos ( ' < / a > ', strRead) ;
strText : = copy (strRead, 1, nText - 1);
// e l e m i n a l a s r e f e r e n c i a s y d u p l i c a d o s d e l a c a c h e
if (Pos ( ' c a c h e d ' , strText) = 0) then
begin
Addr : = strAddr;
Text : = strText;
AddToList;
end;
end;
end;
until nBegin = 0;

end;

procedure TFindWebThread.HttpWork(Sender: TObject; AWorkMode:


TWorkMode;
const AWorkCount: Integer);
begin
Status : = ' R e c e i v e d ' + IntToStr (AWorkCount) + ' f o r ' +
strUrl;
Synchronize (ShowStatus);
end;

procedure TFindWebThread.AddToList;
begin
Forml.StatusBarl.SimpleText : = Status;
end;

end.
El programa busca apariciones consecutivas de la cadena href=http, co-
piando el testo que sigue hasta el caracter de cierre >. Si la cadena encontrada
contienc la palabra google o el testo incluye la palabra cached,se ignora. La
figura 19.4 muestra el efecto de este codigo. Se pueden iniciar varias busquedas
a1 mismo tiempo, per0 hay que tener en cuenta que 10s resultados se aiiadiran a1
mismo componente de memo

b e a c h [use + to sepcwate vah~es]


Borland

Ihllu//www bolland corn/

hllp //www borland co p /


hllp //www borland co ~p/cppbu~lde!/f~eecomp~ler/
hllp //w bodand de/
hllp / / w w bodand de/lbu~lder/
hllp//www bolland ru/
hllp //www bodand I d
hllp //mlo borland corn/newsg~oups/
hltp //~nloborland com/devsupporl/bde/
hNp //WWWborland cz/
hNp l/www borland esl
hllp Nwww borland 111
hllp ! / ~ M w !#and L l w ~ m e s l =I
I tb>bodand<h, developer netwnk horn page

Figura 19.4. La aplicacion WebFind puede utilizarse para buscar una lista de sitios
en el motor de busqueda de Google.

La API Winlnet
Cuando se necesita utilizar 10s protocolos FTP y HTTP, como alternativas a1
uso de componentes particulares de la VCl, se puede usar una API especifica de
Microsoft, que se ofrece en la DLL WinInet. Esta biblioteca forma parte del
nucleo del sistema operativo e implementa 10s protocolos FTP y HTTP sobre la
API de sockets de Windows.
Con solo tres llamadas (Internetopen, InternetOperURL e
InternetRead Fi le) se puede conseguir un archivo que se corresponda con
cualquier URL y guardarlo como una copia local o analizarlo. Se pueden utilizar
otros metodos simples para FTP; es aconsejable analizar el codigo fuente de la
unidad Delphi win1 net .pas,que ofrece una lista de todas las funciones.

TRUCO:El archivo de ayuda de la b i b i i l%dnet


~ no form@parte dt la
SDK Help incluida con Delphi, pero puede mmbtrarse en Meinet eh el
sitio Web de MSDN en ~ . m i c r o s o f i . ~ i h a r y / e me
-.u. ~ h & t /
wininet_refere&% asp.
La funcion 1n t e r ne t Ope n establece una conexion generica y devuelve un
manejador que puede usarse en la llamada a InternetOpenURL.Esta segunda
llamada devuelve un manejador de la URL que puede pasarse a la funcion
Internet ReadFile para leer bloques de datos. En el siguiente codigo de
ejemplo, 10s datos se guardan en una cadena local. Cuando se han leido todos 10s
datos, el programa cierra la conexion a la URL y la sesion de Internet mediante
dos llamadas a la funcion InternetCloseHandle:
var
hHttpSession, hReqUrl: HInternet;
Buffer: array [O. .I0231 o f Char;
nRead: Cardinal;
strRead: string;
nBegin, nEnd: Integer;
begin
strRead := ";
hHttpSession : = Internetopen ( ' F i n d w e b ' ,
INTERNET-OPEN -TYPE-PRECONFIG,
nil, nil, 0) ;
try
hReqUrl : = InternetOpenURL (hHttpSession, PChar(StrUrl),
nil, 0 , 0 , 0 ) ;
t r y / / l e e t o d o s 10s d a t o s
repeat
InternetReadFile (hReqUrl, @Buffer, sizeof
(Buffer), nRead) ;
strRead : = strRead + string (Buffer);
u n t i l nRead = 0;
finally
InternetCloseHandle (hReqUrl);
end;
finally
InternetCloseHandle (hHttpSession);
end;
end ;

Un navegador propio
Aunque no es probable que interese escribir un nuevo navegador Web, podria
resultar interesante comprobar como se puede conseguir un archivo HTML de
Internet y mostrarlo localmente, empleando el visor HTML disponible en CLX (el
control TextBrowser). Si se conecta este control a un cliente HTTP de Indy, se
puede conseguir rapidamente un navegador Web de texto con unas capacidades
de navegacion limitadas. La base es:
TextBrowserl.Text := 1dHttpl.Get (NewUrl);

donde NewUrl es la ubicacion completa del recurso Web a1 que se desea


acceder. En el ejemplo BrowseFast, se escribe este URL en un cuadro combinado,
que hace el seguimiento de las ultimas peticiones. El efecto de una llamada de este
tipo es devolver la parte de texto de una pagina Web (vease la figura 1 9 . 9 , ya que
recuperar el contenido grafico requiere una codificacion mucho mas compleja. El
control TextBrowser se define realmente mejor como un visor de archivos locales
que como navegador.

h~tp//www m.3rcocantucorn 4 GO 1~~4.1

II Menu lor Home


Books D c v ~ m t &

O c t a s 231d The D e w 7 verslon ol GnTods W~zardsIS available lor dowload in


CmTooln sectton ol the EL
O c t o b ~1st I'm l~nish~nq:o wnle MdsteiulgD e r ~ t7.i lh book should be out in h M L
JanuarylFebruary !5-6June 2002

11
l-t 8lh. 2 m Ths sde has been moved to a Li-
problems here and there w t h case inc~sislencies.If you
w c l c a r e or lowncasc the lir* IeUer, a email me with the problem..

4 1
I. . . :.....
.. .T,. .,. ...
-- -
Goto:

Figura 19.5. El aspect0 del navegador de solo texto BrowseFast.

Solamente hemos aiiadido a1 programa un soporte muy limitado de


hipervinculos. Cuando un usuario mueve el raton sobre un enlace, su texto de
enlace se copia en una variable local ( N e w R e q u e s t ) , que se utiliza despues en
caso de que se haga clic sobre el control para calcular la nueva peticion HTTP a
enviar. Sin embargo, fusionar la direccion actual (LastUrl) con la peticion no es
nada trivial, incluso con la ayuda de la clase IdUrl que proporciona Indy. Este es
el codigo, que solo maneja 10s casos mas simples:
procedure TForml.TextBrowserlClick(Sender: TObject);
var
Uri: TIdUri;
begin
if NewRequest <> " then
begin
Uri : = TIdUri-Create (LastUrl);
if Pos ( ' h t t p : ', NewRequest) > 0 then
GoToUrl (NewRequest )
e l s e if NewRequest [I] = ' / ' t h e n
GoToUrl ( ' h t t p : / / I + Uri .Host + NewRequest)
else
GoToUrl ( ' h t t p : / / ' + Uri.Host + Uri.Path + NewRequest);
end;
end :

Una vez mas, este ejemplo es trivial y poco funcional, per0 construir un
navegador implica poco mas que la capacidad de conectarse mediante HTTP y
mostrar archivos HTML.

Un sencillo servidor HTTP


El caso de desarrollo de un servidor HTTP es bastante distinto. Crear un
servidor que envie paginas estaticas basadas en archivos HTML no es nada sim-
ple, aunque algunos de 10s programas de demostracion de Indy proporcionan un
buen punto de partida. Sin embargo, un servidor HTTP personalizado podria
resultar interesante cuando se construye un sitio Web completamente dinamico,
algo de lo que hablaremos en profundidad en el siguiente capitulo.
Para mostrar el inicio del desarrollo de un servidor HTTP personalizado, he-
mos creado el ejemplo HttpServ. Este programa tiene un formulario con un cua-
dro de lista que se utiliza para registrar las peticiones y un componente
IdHTTPServer con esta configuracion:
object IdHTTPServerl: TIdHTTPServer
Active = True
Defaultport = 8080
OnCommandGet = IdHTTPServerlCommandGet
end

El sewidor utiliza el puerto 8080 en lugar el puerto 80 estandar para que


pueda ejecutarse junto con otro servidor Web. Todo el codigo personalizado se
encuentra en el controlador del evento O n C o m r n a n d G e t , que devuelve una pagi-
na fija ademas de algo de informacion sobre la peticion:
p r o c e d u r e TForml .I d H T T P S e r v e r l C o r x n a n d G e t (AThread:
TIdPeerThread;
RequestInfo: TIdHTTPRequestInfo; ResponseInfo:
TIdHTTPResponseInfo);
var
HtmlResult: String;
begin
// r e g i s t r o
Listboxl.Items.Add (Request1nfo.Document);
// r e s p u e s t a
HtmlResult : = '<hl>Ht t p S e r v Demo</hl> ' +
' < p > T h i s i s t h e o n l y page y o u " l 1 g e t f r o m t h i s
e x a m p l e . </p><hr> ' +
' < p > R e q u e s t : ' + Request Inf o .Document + ' < / p > ' +
' < p > H o s t : ' + RequestInfo.Host + ' < / p > ' +
'<p>Pararns: ' + RequestInfo .UnparsedParams + ' < / p > ' +
' < p > T h e h e a d e r s o f t h e r e q u e s t f o l l o w : <br>' +
Request1nfo.RawHeaders.Text + ' < / p > ' ;
ResponseInfo.ContentText : = HtmlResult;
end;

A1 pasar una ruta y algunos parametros en la linea de comandos a1 navegador,


se podran ver estos datos reinterpretados y representados. Por ejemplo, en la
figura 19.6 se puede ver el efecto de esta linea de comandos:

' w

-
['d
I http ,,localhost 8080/1e.

I HttpSen-Demo
Recargar
-
Inca

Thus 1s the only page you'll get &om this example.

Request /test

The headers of the request follow


Host localhost 8080 User-Agent M o d a t 5 0 (Wadows. U. Wmdows NT 5 1. es-AR, rv 1 4b) GeckoQ0030516
Moz.lla FuebudlO 6 Accept
text/..unl,apptcahonl~~ddapptcahod~h~+wnl,te~/html,q=O ~,textfplm,q=O8 , ~ 1 d e o / x - ~ . l m a g e / p n g , r n a g e / j p e g . ~
Accept Language es-AR,es.q=O 5 Accept-Encodmg gap.deflate.compress.q=O 9 Accept-Charset
ISO-8859-1.utf-8.q=0 7.*.q=O 7 Keep-PiLve 300 Comechon keep-&ve

Figura 19.6. La pagina mostrada al conectar un navegador con el programa HttpServ


personalizado.

Si este ejemplo parece demasiado trivial, se podra ver una version algo mas
interesante en la prosima seccion, en la que hablaremos de la generacion de codi-
go HTML con 10s componentes productores de Delphi.
- - . ..
NOTA: Si se tiene planeado crear un servidor Web avanzado u otros servi-
dores de Internet con Delphi, entonces, como alternativa a 10s componentes
Indy, deberian consultarse 10s componentes DXSock de Brain Patchwork
DX (www.dxsock.com).
Generacion de HTML
El lenguaje de marcas de hipertexto, HTML, es el formato mas extendido para
distribuir contenido en la Web. HTML es el formato que tipicamente leen 10s
navegadores Web; es un estandar definido por el W3C (World Wide Web
Consortium, www.w3.org), que es uno de 10s organismos que controlan Internet.
El documento del HTML estandar esta disponible en www.w3 .org/markUp. junto
con algunos enlaces bastante interesantes.

Los componentes productores de codigo HTML


de Delphi
Los componentes productores de HTML de Delphi (que se encuentran en la
pagina Internet de la Component Palette) pueden utilizarse para generar 10s
archivos HTML y, en particular, convertir una tabla de una base de datos en una
tabla HTML. Muchos desarrolladores creen que el uso de este tipo de componen-
tes solo tiene sentido cuando se escribe una extension de un servidor Web. Aun-
que se introdujeron con este proposito y son parte de la tecnologia WebBroker,
aun pueden usarse tres de 10s cuatro componentes productores en cualquier apli-
cacion en la que se deba generar un archivo HTML estatico.
Antes de fijarse en el ejemplo HtmlProd, que muestra el uso de estos compo-
nentes productores de HTML, vam0.s a resumir su funcion:
El PageProducer: Es el componente productor de HTML mas sencillo, es
el que manipula un archivo HTML en el que se han incluido etiquetas
especiales. El HTML puede guardarse en un archivo interno o en una lista
interna de cadenas. La ventaja de este enfoque es que puede generarse un
archivo de este tipo mediante el editor HTML que se prefiera. En tiempo de
ejecucion, el PageProducer convierte las etiquetas especiales en codigo
HTML, con lo que consigue un metodo sencillo para modificar partes de
un documento HTML. Las etiquetas especiales tienen el formato basico
< #nombredee t i q u e t a>, per0 tambien se pueden proporcionar
parametros con nombre dentro de la etiqueta. Las etiquetas se procesaran
en el controlador del evento OnTag del PageProducer.
El DataSetPageProducer: Amplia a1 PageProducer sustituyendo
automaticamente las etiquetas que se correspondan con nombres de campo
de una fuente de datos conectada.
El componente DataSetTableProducer: Resulta util generalmente para
mostrar 10s contenidos de una tabla, consulta u otro conjunto de datos. La
idea es producir una tabla HTML a partir de un conjunto de datos, de un
mod0 simple aunque flexible. El componente ofrece una vista previa apro-
piada, para que pueda verse el aspect0 que tendra la salida HTML en un
navegador, directamente en tiempo de diseiio.
Los componentes QueryTableProducer y SQLQueryTableProducer:
Son parccidos a1 DataSetTableProducer, pero estan especificamente adap-
tados para construir consultas con parametros (para BDE o dbEspress,
respectivamente) basadas en la entrada de un formulario HTML de bus-
queda. Este componente tiene poco sentido en un programa independiente,
y por ello no lo trataremos hasta el proximo capitulo.

Generacion de paginas HTML


Un ejemplo muy simple del uso de etiquetas (precedidas del simbolo #) es la
creacion de un archivo HTML que muestre campos con la fecha actual o una
fecha calculada, relativa a la fecha actual, como una fecha de caducidad. Si se
analiza el ejemplo HtmlProd, ser vera un componente PageProducer 1 con codigo
HTML interno, especificado en la lista de cadena HTMLDoc.
<html>
<head>
< t i tle>Producer Demo</ti tle>
</head>
<body>
<hl>Producer Demo</hl>
<p>This is a demo of the page produced by the <b><#appname></b>
application o n
<b><#date></b>.</p>
<hr>
<p>The prices in this catalog are valid until <b><#expiration
days=2 l></b>.</p>
</body>
</html>

ADVERTENCIA: Si se prepara este archivo con un editor HTML (algo


muy aconsejable), puede que coloque automaticamente comillas en torno a
Inn msrbmntmn
LVJ ~ ~ A U A I I W ~ I V J
AD
UW
1-n
1-3
nt;n**atmn t-nmn an
b b ~ y u \ r b a a , WAUV C~AI
A a. r e = rr 9 1 11
uaya- L L , y a yuu
-7- n n a n nn + r a t - An1
.YU b s c z u z uw

fonnato necesario para HTML 4 y XHTML. El componente PageProducer


tiene una propiedad S t r ipParamQuot es que puede activarse para eli-
minar estas comillas adicionales cuando el componente procesa el c M g o
(antes de llamar a1 controlador del evento OnHTMLTag).
-
El boton Demo Page copia la salida del componente PageProducer en la pro-
piedad Text de un componente de memo. Cuando se llama a la funci6n Content
del componente PageProducer, lee el codigo HTML de entrada, lo procesa y lanza
el controlador del evento OnTag para cada etiqueta especial. En el controlador
para este evento, el programa comprueba el valor de la etiqueta (que se pasa en el
parametro T a g S t r ing) y devuelve un testo HTML distinto (en el parametro de
referencia ReplaceText), con lo que se produce el resultado que muestra la
figura 19.7.

thlmb
t head,
-
*
<l~lle>P~oducerDunocJt~llel
</head>
<body,
thl>Producer Democ/hl>
<p>Thisis a demo d the page producedby the
tb>HtmlP~odexe</b> apphcation on <b>12/4/2002t/b>.</p>
<hr,
tp>The prices in lhis catalog are valrd unld
tb>12/25RWZclb>.<lp>
</body>

Figura 19.7. El resultado del ejemplo HtmlProd, una sencilla demostracion del
componente PageProducer, cuando el usuario hace clic sobre el boton Demo Page.

procedure TFormProd.PageProducerlHTMLTag(Sender: TObject;


Tag: TTag; c o n s t TagString: String; TagParams: TStrings;
var ReplaceText : String) ;
var
nDays: Integer;
begin
if TagString = ' d a t e ' then
ReplaceText : = DateToStr ( N o w )
else i f TagString = ' a p p n a m e ' then
ReplaceText : = ~ x t r a c t ~ i l e n a m e
(Forms.App1ication.Exename)
e l s e i f TagString = ' e x p i r a t i o n ' then
begin
nDays : = StrToIntDef (TagParams .Values [ ' d a y s ' 1 , 0 ) ;
i f nDays <> 0 then
ReplaceText : = DateToStr ( N o w + nDays)
else
ReplaceText := '<i> [ e x p i r a t i o n t a g e r r o r ] < / i > ';
end;
end ;

Fijese en particular en el codigo escrito para convertir la ultima etiqueta,


#expiration, que requiere un parametro. El PageProducer coloca todo el
texto del parametro de la etiqueta (en este caso, days=2 1)en una cadena que
forma parte de la lista TagParams. Para extraer la parte del valor de esta
cadena (la partc que se encuentra tras el signo de igual), se puede usar la propie-
dad values de la lista de cadena TagParams y buscar la entrada apropiada a1
mismo tiempo. Si no se pucdc encontrar el parametro o si el valor del parametro
no cs un entero. cl programa mostrara un mensa.jc de error
-
TRUCO: El componente Pageproducer soporta etiquetas definidas por el
usuario, que puede ser cualquier cadena que se quiera, pero deberian revisarse
en primer lugar las etiquetas especiales definidas en la enurneracion TTags.
Entre 10s posibles valores se incluyen t g L i n k (para la etiqueta de enlace),
tgImage (para la etiqueta de imagen), tgTable (para la etiqueta de
tabla) y algunas mas. Si se crea una etiqueta personalizada, como en el
ejemplo Pageprod, el valor del parametro Tag del controlador HTMLTag
sera t g C u s t o m .

Creacion de paginas de datos


El ejcinplo HtmlProd tambien ticne un componente DataSetPageProducer, quc
csta conectado con una tabla de basc de datos y con el siguiente codigo fuentc
HTML:
<html><head><title>Data for <#name></title></head>
<body>
<hl><center>Data for <#name></center></hl>
<p>Capital: <#capital></p>
<p>Continent: < # ~ o n t i n e n t > < / ~ >
<p>Area: < # a r e a > < / p >
<p>Population: < # p o p u l a t i o n > < / p X h r >
<p>Last updated o n <#date><br>
HTML file produced by t h e program <#program>.</p>
</bodyX/html>

A1 usar etiquetas con 10s nombres de 10s campos del conjunto de datos conec-
tado (la tipica tabla de la base de datos COUNTRY.DB), el programa obtendra
automaticamente el wlor de 10s campos del registro actual y 10s sustituira instan-
taneamente. Esto produce la salida quc muestra la figura 19.8; el navegador esta
concctado a1 ejemplo HtmlProd que funciona como un servidor HTTP, como
veremos mas adelante. En el codigo fuente del programa relacionado con este
componente, no esiste ninguna referencia a 10s datos de la base de datos:
p r o c e d u r e TFormProd.DataSetPageProducerlHTMLTag(Sender:
TObject; Tag: TTag;
c o n s t TagString: String; TagParams : TStrings; v a r
ReplaceText: String);
begin
i f TagString = 'program' then
ReplaceText : = ExtractFilename (Forms.Application.Exename)
else i f T a g s t r i n g = ' d a t e ' then
ReplaceText : = DateToStr ( D a t e ) ;
end;

Data for Ar~entina


Cap~talBuenos Ares

Conbnent South Amenca

Area 2 777 815

Populahon 32 300 003

Last updated on 15/06/2003


HTML Ue produced by the program HtmlProd exe

Figura 19.8. El resultado del ejemplo HtmlProd para el boton Print Line.

Produccion de tablas HTML


El tercer boton del ejemplo H t m l P r o d es Print Table. Este boton esta conec-
tado a un componente DataSetTableProducer, llamando una vez mas a su funcion
c o n t e n t y copiando su resultado cn la propiedad T e x t del componente de
memo. A1 conectar la propiedad D a t a S e t del DataSetTableProducer con
C l i e n t D a t a S e t 1,se puede producir una tabla HTML cstandar. El compo-
nente generara de manera predeterminada solo 20 filas, tal y como indica la pro-
piedad MaxRows. Si se quieren obtener todos 10s registros de la tabla, se puedc
fijar esta propiedad como -1 (una configuracion sencilla per0 no documentada).

TRUCO: El componente DataSetTableProducer comienza desde el registro


actual en lugar de desde el primero. Por eso, la segunda vez que se haga clic
., . . . . -.
una Ilamaaa a1 mecoao . ..
sobre el boton Print Table no se verb ningun registro en la salida. A1 afiadir
. .1 I . I I 1
k ' 1 r . s ~ ; ael conjunto ae aaros antes ae llamar a1
A

metodo content del componente productor se solucionara este problema.


Para que la salida de este componente productor sea mas completa, se pueden
realizar dos operaciones. La primera es proporcionar alguna information Header
y Footer (para generar 10s elementos HTML de apertura y cierre) y aiiadir un
Caption a la tabla HTML. La segunda es personalizar la tabla mediante 10s
valores especificados por las propiedades Ro w A t t r ibu t e s , Tab 1e -
Attributes y Columns.El editor de propiedades para las columnas, que es
tambien el editor de componentes predeterminado, permite establecer la mayoria
de estas propiedades, ofreciendo a1 mismo tiempo una vista previa del resultado,
como se puede ver en la figura 19.9. Antes de utilizar este editor, se pueden
preparar las propiedades para 10s campos del conjunto de datos mediante el editor
Fields. Asi, por ejemplo, se puede dar formato a la salida de 10s campos de
poblacion y area para que utilicen separadores de miles.

DataSetTableProducer Demo
II American Clountries

Figura 19.9. El editor de la propiedad Columns del componente


DataSetTableProducer permite acceder a una vista previa de la tabla HTML final (si
la tabla de la base de datos esta activa).

Se pueden utilizar tres tecnicas para personalizar la tabla HTML, y merece la


pena revisarlas:
Puede usarse la propiedad columns del componente productor de la tabla
para establecer propiedades, como el texto y el color del titulo, o el color y
el alineamiento de las celdas en el resto de la columna.
Pueden usarse las propiedades TField, en particular las relacionadas
con la salida. En el ejemplo, hemos fijado la propiedad DisplayFormat
del objeto de campo ClientDataSetlArea como # # # , # # # , # # # .
Se trata del enfoque a utilizar si se quiere definir la salida de cada campo.
Incluso podria irse mas alla e incluir etiquetas HTML en la salida de un
campo.
Se puede controlar el evento 0 n F o r m a t C e 1 1 del componente
DataSetTableProducer para personalizar aun mas la salida. En este even-
to, pueden fijarse 10s diversos atributos de columna solamente para una
celda dada, per0 tambien puede personalizarse la cadena de salida (guar-
dada en el parametro Ce llDat a) e incluir etiquetas HTML. No se puede
hacer esto mediante la propiedad Columns.
En el ejemplo HtmlProd hemos usado un controlador para este evento; asi
convertimos el texto de las columnas Population y Area a una fuente en negrita y
con un fondo rojo para valores grandes (a menos que se trate de la fila de cabece-
ra). Este es el codigo:
procedure TFormProd.DataSetTableProducerlFormatCell(
Sender: TObject; CellRow, Cellcolumn: Integer;
var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String) ;
begin
i f (CellRow > 0 ) and
( ( (CellColumn = 3 ) and (Length (CellData) > 8 ) ) or
((CellColumn = 4 ) and (Length (CellData) > 9 ) ) ) then
begin
BgColor : = 'red ';
CellData : = '<b>' + CellData + '</b>';
end;
end;

El resto del codigo esta resumido en 10s parametros del componente productor
de la tabla, incluidos su cabecera y pie, como puede verse si se abre el codigo
fuente del ejemplo HtmlProd.

Uso de hojas de estilo


Las ultimas versiones de HTML incluyen un potente mecanismo para separar
el contenido de la presentacion: las hojas de estilo en cascada (Cascading Style
Sheets, CSS). Mediante una hoja de estilo puede separarse el formato del codigo
HTML (colores, fuentes, tamaiios de fuente y demas) del texto que se muestra (el
contenido de la pagina). Este enfoque hace que el codigo resulte mucho mas
flexible y el sitio Web m h facil de actualizar. Ademh, se pueden separar las
tareas de hacer que el sitio resulte graficamente atractivo (el trabajo de un diseiiador
Web) de la generacion automatica de contenido (el trabajo de un programador).
Las hojas de estilo son una tecnica complicada, en la que se proporcionan valores
de formato para 10s principales tipos de secciones HTML y para "clases" especia-
les (que no tienen nada que ver con la orientacion a objetos). Una vez m h , puede
verse algun documento de referencia sobre HTML para ver 10s detalles. Se puede
actualizar la generacion de una tabla en el ejemplo HtmlProd para incluir hojas de
estilo proporcionando un enlace con la hoja de estilo en la propiedad H e a d e r de
un segundo componente DataSetTableProducer:

A continuacion se puede actualizar el codigo del controlador del evento


OnForma t C e l l con la siguiente accion (en lugar de las dos lineas que modifi-
quen el color y aiiadan la etiqueta de fuente en negrita):

La hoja de estilo proporcionada ( t e s t . c s s , disponible en el codigo fuente


del ejemplo) define un estilo de resaltado o h i g h l i g h t , que tiene una fuente en
negrita y un fondo rojo que se encontraban incrustados en el codigo en el primer
componente DataSetTableProducer.
La ventaja de este enfoque es que ahora un artista grafico puede modificar el
archivo CSS y proporcionar un mejor aspect0 a la tabla sin tocar su codigo.
Cuando se quieren ofrecer muchos elementos de formato, el uso de una hoja de
estilo tambien puede reducir el tamaiio total del archivo HTML. Se trata de un
elemento muy importante que puede reducir el tiempo de descarga.

Paginas dinamicas de un servidor personalizado


El componente HtmlProd puede utilizarse para generar archivos HTML estati-
cos; duplica a un servidor Web, empleando un enfoque similar a1 mostrado en el
ejemplo HttpServ, per0 en un context0 mas realista. El programa accede a las
peticiones de uno de 10s posibles productores de paginas, pasando el nombre del
componente en una peticion. Esta es una parte del controlador del evento
0 nComma n d G e t del componente IdHTTPServer, que utiliza el metodo
FindComponente para encontrar el componente productor correcto.
var
Req, Html: String;
Comp: TComponent ;
begin
Req : = RequestInfo.Document;
i f Req [I] = ' / ' t h e n
Req : = Copy (Req, 2, 1000) ; / / i g n o r a r ' / '
Comp : = Findcomponent (Req);
i f (Req <> " ) and Assigned (Comp) and
(Comp is TCustomContentProducer) then
begin
ClientDataSetl.First;
Html : = TCustomContentProducer (Comp).Content;
end;
ResponseInfo.ContentText : = Html;
end;
En el caso de que no se encuentre el parametro (o no sea valido), el servidor
respondera con un menu basado en HTML de 10s componentes disponibles:
Html : = ' < h l > H t m l P r o d M e n u < h l > < p > < u l > ';
for I : = 0 to Componentcount - 1 do
if Components [i] is TCustomContentProducer then
Html : = Html + ' < l i > < a href="/' + Components [i].Name +
"'>' + Components [i].Name + ' < / a > < / l i > ' ;
Html : = Html + ' < / u l > < / p > ';

Por ultimo, si el programa devuelve una tabla que utiliza CSS, el navegador
solicitara el archivo CSS a1 servidor; por eso hemos afiadido algo de codigo
especifico para devolverlo. Con las apropiadas generalizaciones, este codigo mues-
tra como puede responder un servidor devolviendo archivos, y tambien como
indicar el tipo MIME de la respuesta ( C o n t e n t T y p e ) :
if Pos ( ' t e s t . c s s f , Req) > 0 then
begin
CssTest : = TStringList.Create;
try
CssTest.LoadFromFile(ExtractFi~ePath(Applicat~on.ExeName)
+ 'test.cssl);
Response1nfo.ContentText : = CssTest.Text;
ResponseInfo.ContentType : = 'text/css';
finally
CssTest.Free;
end;
Exit;
end ;
Programacion
Web con
WebBroker y

Internet ticne cada vcz un papel mas importantc cn el mundo, y gran parte de el
depende dcl Cxito de la World Wide Web, la telaraiia mundial basada en el proto-
colo HTTP. En el capitulo anterior hemos comentado cl protocolo HTTP y el
desarrollo de aplicaciones de cliente y de servidor basadas en el. Con la disponi-
bilidad de varios servidores Web de alto rendimiento, escalables y flexibles, sera
algo muy raro que se desee crear uno propio. Las aplicaciones Web dinarnicas se
construyen en general integrando guiones o programas compilados con servidores
Wcb, en lugar de sustituirlos por un software personalizado.
Este capitulo se centra completamente en el desarrollo de aplicaciones de ser-
vidor, que amplien 10s servidores Web ya esistentes. Antes hemos comentado la
generacion dinamica de paginas HTML. Ahora aprenderemos a integrar csta ge-
neracion dinimica con un servidor. Este capitulo supone la continuacion Iogica
del anterior, pero con el no se completa la esplicacion acerca de la programacion
orientada a Internet. El proximo capitulo se dedicara a la tecnologia IntraWeb
disponible con Delphi 7, y el 22 vuelve a tratar la programacion para Internet
desde el punto de vista de XML.
-- - - - .. ---. ---- --
A D ~ ~ W & K I A :para probpr algunos de 10s ejemplos de este capitulo se
n e c e s ~ a c c e s oa un m i d o r w&. L'B sqlpcibn mas sencilla es utilizar--la I
En este capitulo se tratan 10s siguientes temas:
Paginas Web dinamicas.
CGI, ISAPI y modulos de Apache.
La arquitectura de WebBroker.
Web App Debugger.
La arquitectura de WebSnap.
Adaptadores y guiones de servidor.

Paginas Web dinarnicas


Cuando se navega por un sitio Web, generalmente se descargan paginas estati-
cas (archivos de texto con formato HTML) desde el servidor Web hasta el orde-
nador cliente. Como desarrollador Web, se pueden crear manualmente estas
paginas, pero para la mayoria de 10s negocios, tiene mas sentido crear las paginas
estaticas a partir de la informacion de una base de datos (un servidor SQL, una
serie de archivos, y cosas asi).
Mediante este enfoque, basicamente se genera una instantanea de 10s datos en
formato HTML, lo que resulta bastante razonable si 10s datos cambian muy a
menudo. Este enfoque se comento en el capitulo anterior.
Como alternativa a las paginas HTML estaticas, esiste la posibilidad de ha-
cerlas dinamicas. Para ello, extraemos la informacion directamente desde una
base de datos, en respuesta a la solicitud del navegador, para que el HTML
enviado por la aplicacion nos muestre 10s datos actuales, no una vieja instantanea
de 10s mismos. Este metodo resulta apropiado si 10s datos cambian con frecuen-
cia.
Como ya hemos mencionado, hay un par de maneras de programar el compor-
tamiento personalizado en un servidor Web y estas son tecnicas ideales para
generar paginas HTML dinamicamente. Ademas de las tecnicas basadas en guio-
nes, que son muy populares, 10s dos protocolos mas comunes para programar
servidores Web son CGI (Common Gateway Interface o Interfaz de pasarela
comun) y las API de 10s servidores Web.
- - ---- - -- - - - - - - . - - - - - -- -
- - - -

NOTA: Hay que tener presente que la tecnologia WebBroker de Delphi


(disponible en las ediciones Enterprise Studio y Professional) reduce las
diferencias entre CGI y las API de servidor a1 ofrecer un marco de trabajo
de clases comun. De este modo, se puede convertir con facilidad una aplica-
cion CGI en una biblioteca ISAPI o integrarla con Apache.

Un resumen de CGI
CGI es un protocolo estandar de comunicaciones entre el navegador cliente y
el servidor Web. No se trata de un protocolo particularmente eficaz, per0 se usa
mucho y no es especifico de ninguna plataforma. Este protocolo permite a1
navegador tanto solicitar como enviar datos y se basa en la entradalsalida de linea
de comandos estandar de una aplicacion (normalmente una aplicacion de conso-
la). Cuando el servidor detecta la solicitud de una pagina para la aplicacion CGI,
pone la aplicacion en marcha, pasa 10s datos de linea de comandos desde la soli-
citud de pagina a la aplicacion y despues envia la salida estandar de la aplicacion
a1 ordenador cliente.
Se pueden usar muchas herramientas y lenguajes para escribir aplicaciones
CGI y, Delphi es solo una de ellas. A pesar de la limitacion obvia de que el
servidor Web debe ser un sistema Windows o Linux basado en Intel, se pueden
crear programas CGI bastante complicados en Delphi y Kylix. CGI es una tecni-
ca de bajo nivel, ya que utiliza la entrada y salida estandar de la linea de coman-
dos junto con variables de entorno para recibir information desde el servidor Web
y enviarla de vuelta.
Para crear un programa CGI sin utilizar clases de soporte, podemos generar
simplemente una aplicacion de consola de Delphi, eliminar el codigo fuente tipico
del proyecto y reemplazarlo por las instrucciones siguientes:
program CgiDate;
{ S A P P T Y P E CONSOLE}

u s e s SysUtils;

begin
writeln ( ' c o n t e n t - t y p e : t e x t / h t m l l ):
writeln;
writeln ('<html><head>' ) ;
writeln ( ' <title>Time a t t h i s s i t e < / t i t l e > ' );
writeln ( ' < / h e a d > < b o d y > ' );
writeln ('<hl>Time a t t h i s s i t e < / h l > ' ) ;
writeln ( ' <hr>' ) ;
writeln ( ' <h3>' ) ;
writeln (FormatDateTime ( ' " T o d a y i s d d d d , mmmm d , y y y y , ' +
"'<br> a n d t h e t i m e i s r 1 hh:mrn:ss A M / P M 1 , Now) ) ;
writeln ( ' < / h 3 > ' );
writeln <hr>');
( I

writeln ('<i>Page generated by CgiDate.exe</i>');


writeln ('</body></html>');
end.

Los programas CGI generan normalmente un encabezamiento que va seguido


del texto HTML utilizando la salida estandar. Si ejecutamos directamente este
programa, podremos ver el texto en una ventana de terminal. Si por el contrario,
lo ejecutamos desde un servidor Web y enviamos la salida a un navegador, apare-
cera el texto HTML formateado como muestra la figura 20.1.

0-
--, -t4)
Rhm Ad
-
R y p a C r 7
@ IU
lmo
Mtp {lladhost/cg~-bnlcgsddeexe

--
-
---
yi

Time at this site


Today is domingo, junio 15, 2003,
and the time is 05:31 PRI
- - --- - - . -

Page generated by CgiDate-axe

IS
Figura 20.1. La salida de la aplicacion CgiDate, vista en un navegador.

La creacion de aplicaciones avanzadas y complejas con CGI requiere mucho


trabajo. Por ejemplo, para extraer informacion de estado sobre la solicitud HTTP,
es necesario acceder a variables relevantes del entorno, tal y como sigue:
/ / obtiene el nombre de la ruta
GetEnvironmentVariable ( ' P A T H - I N F O ' , PathName, sizeof
(PathName)) ;

Uso de bibliotecas dinarnicas


Un metodo completamente distinto es la utilizacion de la API de un servidor
Web, la popular ISAPI (Internet Sewer API, presentada por Microsoft) y la no
tan conocida NSAPI (Netscape Server API), o la API de Apache. Estas API nos
permiten escribir una biblioteca que el servidor carga en su propio espacio de
direcciones y que mantiene algun tiempo en memoria. DespuCs de haber cargado
la biblioteca, el servidor puede ejecutar solicitudes individuales mediante hebras
dentro del proceso principal, en lugar de lanzar un nuevo ejecutable para cada
solicitud (corno sucede en las aplicaciones CGI).
Cuando el servidor recibe una solicitud de pagina, carga la DLL (si no lo ha
hecho ya) y ejecuta el codigo adecuado, que puede poner en marcha un nuevo hilo
o thread, o utilizar uno existente para procesar la solicitud. La biblioteca envia
entonces 10s datos HTTP correspondientes a1 cliente que ha solicitado la pagina.
Dado que esta comunicacion se suele producir en memoria, este tipo de aplicacion
es mucho mas rapida que el enfoque CGI.

Tecnologia WebBroker de Delphi


El fragment0 de codigo CGI mostrado demuestra el enfoque simple y direct0
de este protocolo. Podriamos haber ofrecido ejemplos de bajo nivel similares para
ISAPI o modulos de Apache, pero en Delphi es mas interesante utilizar la tecno-
logia WebBroker.
Esta tecnologia implica una jerarquia de clases dentro de las bibliotecas VCL
y CLX (creada para simplificar el desarrollo en el lado de servidor para la Web)
y un tip0 de modulos de datos especifico llamado WebModules. Tanto las edicio-
nes Enterprise Studio como Professional de Delphi incluyen este marco de trabajo
(corno contrapartida del mas novedoso y avanzado entorno WebSnap, que solo
esta disponible en la edicion Enterprise Studio).
La tecnologia WebBroker de Delphi permite desarrollar con gran facilidad una
aplicacion ISAPI o CGI, o un modulo de Apache. En la primera ficha (New) del
cuadro de dialogo New Items, seleccionamos el icono W e b Server Application.
El cuadro de dialogo siguiente ofrecera opciones como ISAPI, CGI, modulo de
Apache 1 o Apache 2 y el Web App Debugger.

En cada caso, Delphi generara un proyecto con un WebModule, que es un


contenedor no visual similar a un modulo de datos. Esta unidad sera identica, sin
importar el tip0 de proyecto; so10 cambia el archivo principal del proyecto. Para
una aplicacion CGI tendra este aspecto:
Este nombre de ruta es una parte de la URL de la aplicacion CGI o ISAPI, que
viene detras del nombre del programa y antes de 10s parametros, como path1 en
la siguiente URL:

Al proporcionar acciones diferentes, la aplicacion puede responder con facili-


dad a solicitudes con diferentes nombres de ruta y podemos asignar un componen-
te productor distinto o llamar un controlador del evento OnAction diferente
para cada nombre de ruta posible. Desde luego, podemos omitir el nombre de la
ruta para manejar una solicitud generica.
Tambien hay que considerar que en vez de basar la aplicacion en un WebModule,
podemos utilizar un simple modulo de datos y aiiadirle un componente
WebDispatcher. Es un buen metodo para convertir una aplicacion Delphi ya exis-
tente en una extension de servidor Web.
. - - - .- - - . - - - -
-7- - -- - -
e bhsica WebDispatcher
ate. Los programas de
w. CUDIUKCI
- - .
- uu
-. uucucu
.
.. . -CCUCI V~LI ,US
.. . . .. - -- U C S
.- .U ~
..L-U ~-L
.-U U
- -I C o- varios
S - . .. - - Web.
. -. .- mbdulos
-- - - -. . .
- --- - - -

Tambitn hay que tener en cuenta que todas las acciones del WebDispatcher
no tienen nada que ver con las acciones almacenadas en un componente
A ~ * lI .. A ~ r -. - I S - - -..

Cuando definimos las paginas HTML adjuntas que ponen en marcha la aplica-
cion, 10s vinculos haran solicitudes de paginas a las URL para cada una de esas
rutas. Tener una sola biblioteca que pueda llevar a cab0 diferentes operaciones en
funcion de un parametro (en este caso el nombre de la ruta), permite que el servi-
dor pueda guardar una copia de esta biblioteca en memoria y responder mucho
mas rapidamente a las solicitudes del usuario. En parte, sucede lo mismo para una
aplicacion CGI: el servidor tiene que ejecutar varias instancias per0 puede guar-
dar en cache el archivo y hacerlo que este disponible mas rapidamente.
En el evento OnAction es donde escribimos el codigo para especificar la
respuesta a una consulta dada, 10s dos parametros principales pasados al contro-
lador del evento. Veamos un ejemplo:
procedure TWebModulel.WebModulelWebActionItemlAction(Sender:
TObj ect;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean) ;
begin
Response.Content :=
' <h tml><head><title>Hello Page</ti tle></head><body>' +
' <hl>Hello</hl>' +
' <hr><p><i>Page genera ted by Marco</i></p></body></html>' ;
end;
En la propiedad C o n t e n t del parametro R e s p o n s e es donde tenemos que
insertar el codigo HTML que queremos que vean 10s usuarios. El unico inconve-
niente de este codigo es que en un navegador la salida se mostrara correctamente
en varias lineas, per0 si miramos el codigo fuente HTML, podremos ver una sola
linea que corresponde a toda la cadena. Para que el codigo HTML sea mas legi-
ble, tenemos que insertar el codigo de caracter #13 (nueva linea), dividiendolo en
multiples lineas (o aun mejor, el valor multiplataforma s L i n e B r e a k ) .
Para permitir que otras acciones controlen esta solicitud, tenemos que definir
el ultimo parametro, Hand l e d , como F a l s e . De otra forma, el valor predeter-
minado sera T r u e y una vez que hayamos controlado la solicitud con la accion,
el WebModule asumira que hemos terminado. La mayor parte del codigo de una
aplicacion Web se encuentra en 10s controladores del evento OnAct i o n para las
funciones definidas en el contenedor WebModule. Estas funciones reciben una
solicitud del cliente y devuelven una respuesta utilizando para ello 10s parametros
Request y Response.
Cuando se utilizan componentes productores, el evento O n A c t i o n suele de-
volver, como R e s p o n s e .C o n t e n t , el C o n t e n t del componente productor,
con una simple operacion de asignacion. Se puede acceder directamente a este
codigo asignando un componente productor a la propiedad P r o d u c e r de la
accion, sin necesidad de tener que escribir nunca mas estos controladores de
evento (pero no hay que hacer ambas cosas, pues podria traer problemas).

'
clases productoras personalizadas que+h&edartde la c b 7 ! c u s tomcon-
tent Producer, sin0 queimplernentan la i n t e r f e 3Pr~duceContent.
&a propiedad P r o d u c e r C o n t e n t es.casi una propiedad de interfaz que
se comporta de la misma manera gracias orsu editor depropiedad y no esta
basada en el soporte para propiedadeg-de i n t e d b de DeIphi 6.
L

Depuracion con Web App Debugger


Depurar aplicaciones Web escritas en Delphi suele ser complicado. No basta
con ejecutar el programa y puntos de ruptura, sino que hay que convencer a1
servidor Web para que ejecute el programa CGI o la biblioteca dentro del depura-
dor de Delphi. Se puede hacer eso si se indica una aplicacion anfitrion en el
cuadro de dialogo Run Parameters de Delphi, per0 este enfoque implica dejar
que Delphi ejecute el servidor Web (que suele ser un servicio de Windows, no un
programa independiente).
Para resolver estas cuestiones, Borland ha desarrollado un prograrna de de-
puracion especifico, Web App Debugger. Este programa que se activa mediante
el elemento correspondiente del menu Tools es un servidor Web que atiende a
pcticiones en un puerto que puede especificarse (de manera predefinida el 1024).
Cuando llega una peticion, el programa puede redirigirla a un ejecutable indepen-
diente. En Delphi 6, esta comunicacion se basaba en las tecnicas de COM; en
Delphi 7 se basa en sockets de Indy. En ambos casos, puede ejecutarse la aplica-
cion de servidor Web desde el IDE de Delphi, establecer todos 10s puntos de
ruptura necesarios y, despues, (cuando el programa se active mediante el Web
App Debugger) depurar el programa tal y como se haria con un sencillo archivo
ejecutable. El Web App Debugger hace un buen trabajo con el registro de todas
las peticiones recibidas y las respuestas devueltas a1 navegador. El programa
tambien tiene una pagina Statistics que realiza el seguimiento del tiempo necesa-
rio para cada respuesta, con lo que se puede comprobar la eficacia de una aplica-
cion en distintas condiciones. Otra nueva caracteristica del Web App Debugger
en Delphi 7 es que ahora es una aplicacion CLX en lugar de una aplicacion VCL.
Este cambio en la interfaz de usuario y la conversion de COM a sockets se han
llevado a cabo para que pueda utilizarse en Kylix.

ADVERTENCIA:Debido a que el Web App Debugger utilivr sockets de


Indy, la aplicacion recibira frecuentes excepciones & tipo EidConn-
C 1o s edGr a ce fu 11y . Por este motivo, esta ex~epcionse inhabilita
automaticamente en todos 10s proyectos de Delphi 7.

A1 utilizar la opcion correspondiente en el dialog0 New Web Server


Application, se puede crear con facilidad una nueva aplicacion compatible con el
depurador. Esta opcion define un proyecto estandar, que crea tanto un formulario
principal como un modulo Web. El formulario (inutil) incluye codigo para pro-
porcionar el codigo de inicializacion y aiiadir la aplicacion al Registro de Windows.
initialization
TWebAppSock0bjectFactory.Create ( 'proqrdmmndme1) ;

El Web App Debugger utiliza esta inforrnacion para conseguir una lista de 10s
programas disponibles. Hace esto cuando se utiliza el URL predeterminado para
el depurador, indicado en el formulario como un enlace, tal y como muestra, por
ejemplo, la figura 20.2. La lista incluye todos 10s servidores registrados, no solo
aquellos en ejecucion, y puede usarse para activar un programa. Sin embargo, no
se trata de una buena idea porque hay que ejecutar el programa dentro del IDE de
Delphi para poder depurarlo. (Fijese en que puede expandirse la lista a1 hacer clic
sobre View Details; esta vista incluye una lista de 10s archivos ejecutables y
muchos otros detalles.) El modulo de datos para este tipo de proyecto incluye
codigo de inicializacion:
uses W e b R e q ;

initialization
i f WebRequestHandler <> n i l then
WebRequestHandler.WebModuleC1ass : = TWebModule2;

Registered Servers
View Llst 1 V ~ e wD e t d s
G
J
o

sewer~nfoSewerlnfo
WSnapl WSnapl
WSnap2 WSnap2
WSnapMD WSnapMD
WSnapSess~onWSnapSession
WSnapTable.WSnapTable

Figura 20.2. Se muestra una lista de aplicaciones registradas con el Web App
Debugger cuando se conecta con su pagina principal.

El Web App Debugger deberia utilizarse solo para depuracion. Para desplegar
la aplicacion, deberia utilizarse alguna de las otras opciones. Pueden crearse 10s
archivos de proyecto para otro tip0 de programa servidor Web y aiiadir al proyec-
to el mismo modulo Web que a la aplicacion de depuracion.
El proceso inverso es ligeramente mas complejo. Para depurar una aplicacion
ya existente hay que crear un programa de este tipo, eliminar el modulo Web,
aiiadir el ya existente y parchearlo aiiadiendole una linea para establecer la varia-
ble W e b M o d u l e C l a s s del W e b R e q u e s t H a n d l e r , como en el fragment0 de
codigo anterior.

ADVERTENCIA: Aunque en la may& de los c m $epoch4adaptar un


programa de una tecnologia Web a @m,no alemprs serh aqi. ?or ejemplo,
en el ejempk, CustQueP (&l que ya hablaratwa), heboa t e d o q u e evitar
la propiedad ScriptName de la pt;tici(m (ips fi&onsr, pare m programa
CGI) y u t d h t e n so hggr propmbd knternb13.@l;$pt~ame.

Existen otros dos elementos bastante interesantes involucrados en el uso de


Web App Debugger. En primer lugar, se pueden probar 10s programas sin tener
instalado un servidor Web y sin tener que ajustar su configuration. En otras
palabras, no es necesario desplegar 10s programas para probarlos (se pueden
probar inmediatamente). En segundo lugar, en vez de realizar un desarrollo rapi-
do de una aplicacion como CGI, se puede comenzar a experimentar inmediata-
mente con una arquitectura multihilo, sin tener que enfrentarse con la carga y
descarga de bibliotecas (que suele implicar el apagado del servidor Web y posi-
blemente incluso del ordenador).

Creacion de un WebModule multiproposito


Para demostrar lo facil que resulta, utilizando el soporte de Delphi, crear una
aplicacion de servidor completamente funcional, hemos creado el ejemplo
BrokDemo. Este ejemplo se ha creado utilizando la tecnologia Web App Debugger,
per0 deberia ser relativamente facil compilarlo como un CGI o una biblioteca de
servidor Web.
Un elemento clave del ejemplo de WebBroker es la lista de acciones. Las
acciones pueden administrarse mediante el editor Action o directamente en el
Object Treeview. Las acciones tambien se visualizan en la pagina Designer
del editor, por lo que podemos ver graficamente sus relaciones con 10s objetos de
la base de datos. Si examinamos el codigo fuente, podremos ver que hemos dado
un nombre especifico a cada accion. Tambien hemos dado nombres significativos
a 10s controladores del evento OnAction.Por ejemplo, TimeAction tiene un
nombre de metodo que es mas comprensible que el que genera Delphi
automaticamente, WebModulelWebActionItemlAction.
Cada funcion tiene un nombre de ruta diferente, una de ellas seiialada como
predeterminada, que se ejecuta aunque no se especifique un nombre de ruta. La
primera idea interesante de este programa es la utilizacion de dos componentes
Pageproducer, usados para la parte inicial y final de cada pagina, PageHead y
PageTail.Centralizar este codigo facilita su rnodificacion, sobre todo si esta
basado en archivos HTML externos. El HTML producido por estos componentes
se aiiade a1 principio y a1 final del HTML resultante en el controlador del evento
OnAfterDispatch del modulo Web:
procedure TWebModulel.WebModulelAfterDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean) ;
begin
Response.Content : = PageHead.Content + Response.Content +
PageTail-Content;
end;

Hemos aiiadido a1 final del proceso de generacion de la pagina, el codigo


HTML inicial y final porque esto permite que 10s componentes produzcan el
HTML como si lo estuvieran haciendo ellos todo. El hecho de empezar con HTML
en el evento OnBeforeDispatch,significa que no podemos asignar de forma
directa 10s componentes productores a las funciones, ya que el componente pro-
ductor sobrescribiria la propiedad Content que ya hemos proporcionado en la
respuesta.
El componente PageTail incluye una etiqueta personalizada para el nombre
del guion, reemplazado por el siguiente codigo, el cual utiliza el objeto de solici-
tud actual disponible dentro del modulo Web:
procedure TWebModulel.PageTailHTMLTag(Sender: TObject; Tag:
TTag;
const TagString: String; Tagparams: TStrings; var
ReplaceText: String);
begin
if TagString = 'script' then
ReplaceText : = Request.ScriptName;
end ;

Este codigo se activa para expandir la etiqueta <#script > de la propiedad


HTMLDoc del componente PageTail.
El codigo de las acciones de hora y fecha es sencillo. Realmente, la parte
interesante empieza con la ruta del Menu, que es la accion predeterminada. En su
controlador del evento OnAct ion,la aplicacion utiliza un bucle for para crear
una lista de las acciones disponibles (usando sus nombres sin las dos primeras
letras, las cuales son siempre Wa, en este ejemplo), proporcionando un vinculo
para cada una de ellas con un ancla (una etiqueta <a>):
procedure TWebModulel.MenuAction(Sender: TObject; Request:
TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
I: Integer;
begin
Response.Content : = ' < h 3 > M e n u < / h 3 > < ~ 1 > ' # 1 3 ;
for I : = 0 to A c t i o n s - C o u n t - 1 do
Response .Content : = Response. Content + <li> <a href="' +
Request .ScriptName + Action[I] .PathInfo + ' "> +
Copy (Action[I].Name, 3, 1000) + '</a>'#13;
Response.Content : = Response.Content + '</ul>';
end;

Otra accion del ejemplo Bro kDemo . proporciona a 10s usuarios una lista de
10s parametros del sistema relacionados con la solicitud, algo que es bastante util
para la depuracion. Tambien es instructivo aprender cuhnta informacion (y exac-
tamente que informacion) transfiere el protocolo HTTP desde un navegador a un
servidor Web y viceversa. Para generar esta lista, el programa busca el valor de
cada propiedad de la clase TWebRequest, como muestra este fragment0 de
codigo:
procedure TWebModulel.StatusAction(Sender: TObject; Request:
TWebRequest ;
Response: TWebResponse; var Handled: Boolean) ;
var
I: Integer;
begin
Response .Content : = ' <h3>Status</h3>'#13 +
'Method: ' + Request.Method + '<br>'#13 +
ProtocolVersion: ' + Request.ProtocolVersion + '<br>'#13 +
'URL: ' + Request-URL + '<br>'#13 +
'Query: ' + Request.Query + '<br>'#13 + ...

lnformes dinamicos de base de datos


El ejemplo BrokDemo define otras dos acciones mas, indicadas mediante 10s
nombres de ruta / t a b l e y / r e c o r d . Para estas dos ultimas acciones, el pro-
grama genera una lista de nombres y luego presenta 10s detalles de un registro,
utilizando un componente DataSetTableProducer para dar formato a toda la tabla
y un componente DataSetPageProducer para crear una vista del registro. Veamos
a continuacion las propiedades de estos dos componentes:
object DataSetTableProducerl: TDataSetTableProducer
DataSet = dataEmployee
OnFormatCell = DataSetTableProducerlFormatCell
end
object DataSetPage: TDataSetPageProducer
HTMLDoc.Strings = (
' <h3>Employee : <#Las tName></h3> '
' <ul><li> Employee ID: <#EmpNo> '
' <li> Name: <#FirstName> <#LastName>'
< l i > Phone: <#PhoneExt>'
'<li> Hired On: <#HireDate>'
' <li> Salary: <#Salary></ul>' )
OnHTMLTag = PageTailHTMLTag
DataSet = dataEmployee
end

Para producir toda la tabla, simplemente conectamos el DataSetTableProducer


a la propiedad P r o d u c e r de las acciones correspondientes sin proporcionar
ningun controlador de evento especifico. La tabla se hace mas potente si aiiadi-
mos vinculos internos a 10s registros especificos. El codigo siguiente se ejecuta
para cada celda de la tabla, per0 solamente se crear un enlace para la primera
columna a partir de la primera fila (no se incluye la celda del titulo):
procedure TWebModulel.DataSetTableProducerlFormatCell(Sender:
TObj ect;
CellRow, CellColumn: Integer; var BgColor: THTMLBgColor;
var Align: THTMLAlign; var VAlign: THTMLVAlign;
var CustomAttrs, CellData: String) ;
begin
if (CellColumn = 0) and (CellRow <> 0) then
CellData : = '<a href="' + ScriptName + '/record?LastName='
+ dataEmployee [ ' LastName' ] + ' &FirstName=' +
dataEmployee [ ' FirstName' ] + ' "> '
+ CellData + ' </a>';
end;
La figura 20.3 muestra el resultado de esta funcion. Cuando el usuario selec-
ciona uno de 10s vinculos, se llama de nuevo a1 programa y puede comprobar la
lista de cadena Q u e r y F i e l d y estraer 10s parametros desde la URL. Es enton-
ces cuando utiliza 10s valores correspondientes a 10s campos de la tabla utilizados
para la busqueda del registro (basada en la llamada a F i n d N e a r e s t ) .

Web Broker Demo


Number Last Name First Name Phone Ext.
-
Hire Date Salary
[I
L

2 Nelson Robert 250 12/28/1988 105900

4 young Bruce 233 12/28/1988


- 97500

5 Kun 22 2/6/1989 102750


Lambcrt
-
Johnson Leshe 410 41511989 64635
2- -- -
9 Forest Phil 229 4/17/1989 75060 w
j i 0 W D ' f =='
Figura 20.3. La sahda correspondiente a la ruta table del ejemplo BrokDemo, que
genera una tabla HTML con h~pervinculosinternos.

procedure TWebModulel.RecordAction(Sender: TObject; Request:


TWebRequest;
Response: TWebResponse; var Handled: Boolean);
begin
dataEmployee.0pen;
// va a 1 registro solicitado
dataEmployee .Locate ( ' L A S T N A M E ; F I R S T N A M E r,
VarArrayOf([Request.QueryFields.Values['LastName'],
Request .QueryFields .Values [ 'FirstNdrnel] ] ) , [ ] ) ;
/ / obtiene l a salida
Response.Content : = Response.Content + DataSetPage.Content;
end;

Consultas y formularios
El ejemplo anterior utilizaba algunos componentes productores de HTML pre-
sentados con anterioridad, per0 hay otro componente de este grupo que no hemos
utilizado aun: el QueryTableProducer (para BDE) y su hermano SQLQueryTable-
Producer (para dbEspress). Como veremos, este componente hace que la creacion
de programas complejos de bases de datos sea algo muy sencillo. Supongamos
que queremos buscar algunos clientes en una base de datos. Para ello, podriamos
crear el siguiente formulario HTML (incrustado en una tabla HTML para que
tenga un formato mejor):
<hl>Customer QueryProducer Search Form</h4>
<form action="/scripts/CustQueP.dll/search" method="POSTW>
<table>
<trXtd>State:</td>
<td><input type="textn n a m e = " S t a t e W X / t d X / t r >
<trXtd>Country:</td>
< t d X i n p u t type="textn n a m e = " C o u n t r y " > < / t d X / t r >
< t r X t d X / td>
< t d X c e n t e r X i n p u t type="Submit"></center></td></tr>
</table></f o m >

NOTA: A1 igual que en Delphi, un formulario HTML contiene una serie de


controles. Existen herramientas visuales que ayudan a disefiar estos formu-
larios, o tambien se puede escribir manuaimente el &dig0 HTML.Entre 10s
controles disponibles se incluyen botones, cuadros de edicibn, (selecciones)
cuadros combinados y botones de entrada (o de radio). Tambien se pueden
. .* .
>-.-~-1-L-*.
aerinir oorones con ripos. especmcos
- . .P ..
como .
3. . .
ae envlo I . . ! - l . ..tl.
o reinlciallzacion. un .I.

elemento muy importante de 10s formularios es el metodo de envio, que


puede ser POST (10s datos se envian de forma oculta, y se reciben en la
. . a .- . - . - . . - - - .. . . . ..
propledad c o n t e n t F i e l d s ) o GET (10s datOS se pasan como parte ael
URL, y se pueden extraer de la propiedad Q u e r y F i e l d s ) .

Hay un elemento importante que debemos tener en cuenta en el formulario: 10s


nombres de 10s componentes de entrada (State y Country) deberian de coincidir
con 10s parametros de un componente SQLQuery:
SELECT customer, State-Province, Country
FROM CUSTOMER
WHERE
State-Province = :State OR Country = :Country

Este codigo se utiliza en el ejemplo CustQueP. Para crearlo, hemos puesto un


componente SQLQuery dentro del WebModule y hemos generado 10s objetos de
campo adecuados. En el mismo WebModule hemos aiiadido un componente
SQLQueryTableProducer que se encuentra conectado a la propiedad P r o d u c e r
de la accion / s e a r c h . El programa genera la respuesta adecuada. Cuando se
activa el componente SQLQueryTableProducer, llamando a su funcion C o n t e n t ,
este inicia el componente SQLQuery obteniendo 10s parametros de la solicitud
HTTP. El componente puede examinar automaticamente el metodo de solicitud y
luego utilizar la propiedad Q u e r y F i e l d s (si la solicitud es una solicitud GET)
o la propiedad C o n t e n t F i e l d s (si la solicitud es POST).
Un problema derivado del uso de un formulario HTML estatico (como el ante-
rior), es que no indica 10s estados y paises que se pueden buscar. Para solucionar
este problema, podemos utilizar un control de seleccion en lugar de un control de
edicion en el formulario HTML. Sin embargo, si el usuario aiiade un nuevo regis-
tro a la tabla de la base de datos, tendremos que actualizar la lista de elementos
automaticamente. Como solucion final, podemos diseiiar la DLL ISAPI para pro-
ducir un formulario sobre la marcha y rellenar 10s controles de seleccion con 10s
elementos disponibles.
Generaremos el HTML para esta pagina en la accion / f o r m , que esta conec-
tada con un componente PageProducer. El PageProducer contiene el siguiente
texto HTML que incluye dos etiquetas especiales:
<h4>Customer Queryproducer Search Form</h4>
<form action="CustQueP.dll/search" method="POST">
<table>
<trXtd>State:</td>
<tdXselect name="StateW><option> </
option><#State-Province></select~/td~/tdX/tr>
<trXtd>Country:</td>
<tdXselect name="Country"><option> </option><#Country></
selectX/tdX/tr>
<trXtdX/td>
<tdXcenterXinput t y p e = " S u b m i t " X / c e n t e r X / t d X / t r >
</ tableX/f o m >

Observara que las etiquetas tienen el mismo nombre que algunos campos de la
tabla. Cuando el PageProducer se encuentra con una de estas etiquetas, aiiade una
etiqneta HTML < o p t ion> para cada valor del campo correspondiente. Veamos
el codigo del controlador del evento OnTag, que es bastante generic0 y reutilizable:
procedure TWebModulel.PageProducerlHTMLTag(Sender: TObject;
Tag: TTag;
const TagString: String; Tagparams: TStrings; var
ReplaceText : String) ;
begin
ReplaceText : = " ;
SQLQuery2.SQL.Clear;
SQLQuery2.SQL.Add ('select distinct ' + TagString + ' from
customer') ;
try
Query2.0pen;
try
SQLQuery2.First;
while not Query2.EOF do
begin
ReplaceText : = ReplaceText +
' <option>' + Query2. Fields [ O ] .Asstring + ' </
option>'#13;
SQLQuery2.Next;
end;
finally
SQLQuery2.Close;
end;
except
ReplaceText := '(wrong field: ' + Tagstring + I ) ' ;

end;
end;

Este metodo utiliza un segundo componente SQLQuery,que hemos colocado


manualmente en el formulario y conectado a un componente SQLConnection com-
partido. La figura 20.4 muestra la salida de este formulario.

State

1
Canada
England
FijI
France
Hang Kong
Italy
Japan
Netherlands
Switzerland
USA

Figura 20.4. La accion de formulario del ejemplo CustQueP produce un formulario


HTML con un componente de selection que se actualiza dinamicamente para reflejar
el estado actual de la base de datos.

Esta extension del servidor Web, como muchas otras que hemos creado, per-
mite a1 usuario ver 10s detalles de un registro especifico. Igual que en el ejemplo
anterior, esto se puede llevar cab0 personalizando la salida de la primera columna
(la columna cero), que es generada por el componente QueryTableProducer:
procedure TWebModulel.QueryTableProducerlFormatCell(
Sender: TObject; CellRow, CellColumn: Integer:
var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustornAttrs, CellData: String);
begin
i f (CellColumn = 0 ) and (CellRow <> 0 ) then
CellData : = ' < a href="' + Request.ScriptName + ' /
record?Company=' + CellData +
r 11, I + CellData + ' < / a > ' # 1 3 ;
i f CellData = " then
CellData : = ' & n b s p ; ' ;
end;
TRUCO: Cuando hay una celda vacia en una tabla HTML,la mayoria de
10s navegadores la representan sin borde. Es por esto que hemos aaadido un
caracter de espacio (&nbsp;) en cada celda vacia. Es necesario hacer esto
en cada tabla HTML generada por 10s productores de tablas de Delphi.

La accion para este vinculo es / r e c o r d y hay que pasar un elemento especi-


fico despues del parametro ? (sin el nombre del parametro; que no es estandar).
El codigo que utilizamos para producir las tablas HTML para 10s registros no
utiliza componentes productores como hemos venido haciendo hasta ahora, sino
que muestra 10s datos de cada campo en una tabla personalizada:
p r o c e d u r e TWebModulel.RecordAction(Sender: TObject; Request:
TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
I: Integer;
begin
i f Request. QueryFields .Count = 0 then
Response.Content : = ' R e c o r d not found'
else
begin
Query2.SQL.Clear;
Query2.SQL.Add ( ' s e l e c t * f r o m customer ' +
' w h e r e Company="' +
Request .QueryFields .values [ ' C o m p a n y ' ] + ' "' ) :
Query2.0pen;
Response. Content : =
'<htn~l><head><title>CustomeR r ecord</title></
h e a d > < b o d y > ' # 13 +
' <hl>Customer Record: ' + Request .QueryFields [O] + ' </
hl>'#13 +
' <table border,' #l3;
f o r I : = 1 t o Query2.FieldCount - 1 d o
Response.Content : = Response-Content +
' < t r > < t d > ' + Query2. Fields [I].FieldName + ' </
td>'#13'<td>' +
Query2.Fields [I].AsString + ' < / t d > < / t r > ' # 1 3 ;
Response .Content := Response .Content + ' </table><hr>'#13 +
/ / e n l a c e a 1 formulario d e consulta
' <a href=lV' + Request. ScriptName + ' / f o r m n > ' +
' Next Query < / a > '# I 3 + ' < / b o d y > < / h t m l > '# l 3 ;
end;
end:

Trabajo con Apache


Si tenemos la intencion de utilizar Apache en lugar de IIS o cualquier otro
servidor Web, podemos sacar partido de la tecnologia CGI para usar nuestras
aplicaciones en casi cualquier servidor Web. Sin embargo, esta opcion significa
una reduccion en la velocidad y algunos problemas a la hora de manejar informa-
cion de estado (ya que no podemos guardar ningun dato en memoria). Esta es una
buena razon para escribir una aplicacion ISAPI o un modulo dinamico de Apa-
che. Utilizando la tecnologia WebBroker de Delphi, podemos compilar facilmente
el mismo codigo para ambas aplicaciones de forma que sea mucho mas sencillo
Ilevar el programa a una plataforma Web diferente. Tambien podemos recompilar
un programa CGI o un modulo dinamico de Apache con Kylix y utilizarlo en un
servidor Linus.
Como ya hemos comentado, Apache ejecuta aplicaciones CGI tradicionales y
tambien utiliza una tecnologia cspecifica para guardar el programa de estension
del servidor cargado sicmpre en memoria para conseguir una respuesta mas rapi-
da. Para crear este programa en Delphi, simplemente hay que utilizar la opcion
Apache Shared Module del cuadro de dialogo New Server Application: hay
quc escoger entre Apache 1 o Apache 2, segun la version del servidor que vaya a
utilizarse.

ADVERTENCIA: Mientras que Delphi 7 soporta la version 2.0.39 de


Apache, no soporta la mas actual y popular version 2.0.40, debido a un
cambio en las interfaces de biblioteca. No se recomienza el uso de la ver-
sion 2.0.39 ya que tiene un pioblema de seguridad. Hay disponible infor-
- - aue
macion sobre como ada~tarel cbdiao de la VCL v conseguir . 10smodulos
Sean compatibles con Apache 2.0.40 y superior en 10s grupos de noticias,
gracias a 10s miembros del equipo de investigation y desarrollo de Borland.
Actualmente se encuentra en el sitio Web de Bob Swart en el U I U
.. .-. ,. . ..-a . a
,
.

Si se decide crear un modulo de Apache, se obtendra una biblioteca que contie-


ne el siguiente tip0 de codigo fuente para este proyecto:
library Apachel;

uses

WebBroker,
ApacheApp,
ApacheWm i n 'ApacheWm.pas' (WebModulel: TWebModule);

exports
apache-module name 'apachel-module';

begin
Application.Initialize;
Application.CreateForm(TWebModulel, WebModulel);
Application.Run;
end.

Preste particular atencion a la clausula exports, que indica el nombre utili-


zado por 10s archivos de configuracion de Apache para hacer referencia a1 modu-
lo dinamico. En el codigo fuente del proyecto, podemos aiiadir dos definiciones
mas: el nombre del modulo y el tip0 de contenido, de la siguiente manera:
ModuleName : = ' Apachel-module' ;
ContentType: = 'Apachel-handler1;

Si no se establecen estos valores, Delphi les asignara valores predeterminados,


que se construyen aiiadiendo 10s sufijos module y -handler a1 nombre de
proyecto (con lo que se consiguen 10s dosiornbres que hemos comentado).
Un modulo de Apache no suele desplegarse dentro de una carpeta de guiones,
sino dentro de la subcarpeta modules del servidor (de manera predefinida,
C:\Archivos de programa\Apache\modules). Hay que editar el archivo http.conf y
aiiadirle una linea para cargar el modulo, de este modo:
LoadModule apachel-module modules/apachel.dll

Finalmente, tenemos que indicar cuando se invocara el modulo. El controlador


definido por el modulo puede asociarse con una extension de archivo dada (para
que el modulo procese todos 10s archivos que tengan esa extension) o con una
carpeta fisica o virtual. En el ultimo caso, la carpeta no existe per0 Apache
simula que esta alli. De esta manera podemos establecer una carpeta virtual para
el modulo de Apache 1:

Ya que Apache tiene en cuenta las maylisculas (debido a su herencia de Linux),


tambien podria desearse aiiadir una segunda carpeta virtual, en minusculas:

Ahora se puede llamar a la aplicacion de muestra mediante el URL http://


localhost/Apachel. Una gran ventaja del uso de carpetas virtuales en Apache es
que un usuario no distingue realmente entre las partes fisicas y dinamicas del
sitio, tal y como puede comprobarse si se experimenta con el ejemplo Apachel
(que incluye el codigo aqui comentado).

Ejemplos practicos
Despues de esta presentacion general del desarrollo de aplicaciones de semi-
dor con WebBroker, completaremos esta parte del capitulo con dos ejemplos
practicos. El primer0 es un clasico contador Web. El segundo es una ampliacion
del programa WebFind del capitulo 19, que genera una pagina dinamica en lugar
de rellenar un cuadro de lista.

Un contador Web grafico de visitas


Las aplicaciones de servidor que hemos creado hasta ahora estaban basadas
imicamente en texto. Por supuesto, se pueden aiiadir facilmente referencias a
archivos graficos existentes. Sin embargo, lo que resulta mas interesante es crear
programas de servidor capaces de generar graficos que cambien con el tiempo.
Un ejemplo tipico es un contador de visitas de pagina. Para escribir un conta-
dor Web, hay que guardar el numero actual de visitas en un servidor y despuis
leer e incrementar el valor cada vez que se llama a1 programa contador. Para
devolver esta informacion, todo lo que se necesita es texto HTML con el numero
de visitas. El codigo es sencillo:
procedure TWebModulel.WebModulelWebActionItemlAction(Sender:
TObject;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean) ;
var
nHit : Integer;
LogFile : Text;
LogFileName: string;
begin
.
LogFileName := ' WebCont log';
System.Assign (LogFile, LogFileName) ;
try
/ / lee si existe el archivo
if FileExists (LogFileName) then
begin
Reset (LogFile);
Readln (LogFile, nHit) ;
Inc (nHit);
end
else
nHit : = 0;
/ / g u a r d a e l n u e v o da t o
Rewrite (LogFile);
Writeln (LogFile, nHit) ;
finally
Close (LogFile);
end;
Response .Content : = IntToStr (nHit);
end;

- - -- - -- - -- - - - --- - - - -- -
ADVERTENCIA: Este manejo simple de archivo no escala. Cuando va-
nos visitantes accedan a la pirgina a la vez, el c6digo puede devolver resul-
tados falsos o fallar con un error de entraddsalida a1 archivo debido a qne
' una peticidn de o6a hebra tenga abieho el archivo p a i a 7 z 6 r a m i e - n t r
esta otra hebra trate de abrir el archivo para escritura. Para soportar una
situacibn parecida, sera necesario utilizar un rnutex (o una seccion critica
en un programa multihilo) para permitir que cada hebra espere hasta que la
hebra actual deje de utilizar el archivo cuando complete su tarea.

Es mas interesante crear un contador grafico que pueda incrustarse facilmente


cn cualquier pagina HTML. Hay dos enfoques para crear un contador grafico: se
puede preparar una imagen de bits para cada digito y combinarlos en el progra-
ma, o dejar que el programa dibuje sobre una imagen en memoria para producir el
grafico quc se quiere devolver. En el programa WebCount hemos escogido la
segunda tdcnica.
Basicamente, puede crearse una componente Image que contenga una imagen
en memoria, que puede pintarse mediante 10s metodos habituales de la clase
TCanvas. Despues se puede conectar esta imagen a un objeto TJpegImage.
A1 acceder a la imagen a travds del componente JpegImage la imagen se convierte
a1 formato JPEG. Despues pueden guardarse 10s datos JPEG en un flujo y devol-
verlos. Como puede versej consiste en muchos pasos, pero el codigo no es compli-
cado:
// c r e a una i m a g e n e n memoria
Bitmap : = TBitmap.Create;
t rY
Bitmap.Width : = 120;
B i t m a p - H e i g h t : = 25;
// d i b u j a 1 0 s d i g i t o s
Bitmap. Canvas. Font. Name : = ' A r i d 1 ';
Bitmap.Canvas.Font.Size : = 14;
Bitmap.Canvas. Font .Color : = RGB (255, 127, 0) ;
Bitmap.Canvas.Font.Sty1e : = [fsBold];
Bitmap.Canvas .Textout (1, 1, ' H i t s : ' +
FormatFloat ( ' # # # , # # # , # # # I , Int (nHit)) ) ;
// c o n v i e r t e a JPEG y m u e s t r a
Jpegl : = TJpegImage.Create;
t rY
Jpegl.CompressionQua1ity : = 50;
Jpegl .Assign (Bitmap);
S t r e a m : = TMemoryStream.Create;
.
Jpegl SaveToStream (Stream);
Stream.Position : = 0;
Response.ContentStream : = Stream;
Response. ContentType := ' i m a g e / j p e g ' ;
Response.SendResponse;
//el o b j e t o d e respuesta liberard el f l u j o
finally
Jpegl.Free;
end;
finally
Bitmap. Free;
end;

Las tres sentencias responsables de devolver la imagen JPEG son las dos que
fijan las propiedades C o n t e n t s t r e a m y C o n t e n t T y p e de R e s p o n s e y la
llamada final a S e n d R e s p o n s e . El tipo de contenido debe corresponderse con
uno de 10s posibles tipos MIME aceptados por el navegador, y el orden de estas
tres sentencias es importante. El objeto R e s p o n s e tambidn time un metodo
S e n d s t r e a m , pero solo deberia llamarse despues de enviar el tipo de 10s datos
con una llamada independiente. Este es el efecto de este programa:

Para incrustar el programa en una pagina, hay que aiiadir el siguiente codigo
a1 codigo HTML:

Busquedas con un motor Web de bkquedas


En el capitulo 19 analiz'amos el uso del componente cliente HTTP de Indy para
conseguir el resultado de una busqueda en el sitio Web de Google. Ampliaremos
este ejemplo, convirtiendolo en una aplicacion de servidor. El programa
Websearcher, disponible como aplicacion CGI o como ejecutable de Web App
Debugger, time dos acciones: la primera devuelve el codigo HTML conseguido
mediante el motor de busqueda, y el segundo procesa el HTML para rellenar un
componente de conjunto de datos de cliente, que esta conectado con un productor
de paginas de tabla para generar el aspect0 final. Este es el codigo para la segun-
da seccion:
cons t
strsearch = ' h t t p : / / w w w . g o o g l e . corn/
s e a r c h ? a s - q = b o r l a n d + d e l p h i & n ~ m = l O O';

procedure TWebModulel.WebModulelWebActionItemlAction(Sender:
T O b j ect ;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean) ;
var
I : integer;
begin
i f not cds .Active then
cds.CreateDataSet
else
cds.EmptyDataSet;
for i : = 0 to 5 do / / n u r n e r o d e p d g i n a s
begin
// c o n s i g u e e l f o r r n u l a r i o d e d a t o s d e l s i t i o d e b u s q u e d a
GrabHtml (strSearch + ' & s t a r t = ' + IntToStr (i*100));
// l o a n a l i z a p a r a r e l l e n a r e l c d s
HtmlStringToCds;
end;
cds .First;
// d e v u e l v e e l c o n t e n i d o d e l p r o d u c t o r
Response.Content : = DataSetTableProducerl-Content;
end:

El metodo G r a b H t m l es identico al ejemplo WebFind. El metodo


HtmlStringToCds es parecido a1 metodo correspondiente del ejemplo WebFind
(que aiiade 10s elementos a un cuadro de lista): aiiade las direcciones y sus textos
descriptivos mediante la Ilamada:
cds . InsertRecord ( [O, strAddr, strText] ) ;

El componente ClientDataSet se configura con tres campos: dos cadenas mas


un contador de linea. Este campo vacio adicional es necesario para incluir la
columna adicional en el productor de la tabla. El codigo rellena la columna en el
evento de formato de celda, que aiiade tambien el hiperenlace:
procedure TWebModulel.DataSetTableProducerlFormatCell(Sender:
TObject; CellRow,
CellColumn: Integer; var BgColor: THTMLBgColor; var Align:
THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String);
begin
i f CellRow <> 0 then
case CellColumn of
0 : CellData := IntToStr (CellRow);
1: CellData : = ' < a h r e f = " ' + CellData + "'>' +
SplitLong (CellData) + ' < / a > ';
2 : CellData : = SplitLong (CellData);
end;
end;

La llamada a SplitLong se utiliza para afiadir espacios adicionales en el


texto de salida, para evitar que las columnas de la cuadricula Sean demasiado
grandes (el navegador no dividira el texto en varias lineas a no ser que contenga
espacios u otros caracteres especiales). El resultado de este programa es una
aplicacion bastante lenta (debido a las varias peticiones HTTP que debe reenviar)
que produce una salida con el aspect0 que muestra la figura 20.5.
d e ~ p h amp. I+ cod
jhimr owe

Figura 20.5. El programa Websearch muestra el resultado de varlas bdsquedas


realizadas mediante Google.

WebSnap
Ahora que hemos presentado 10s elementos mas importantes del desarrollo de
10s clementos dc aplicaciones de servidores Web con Delphi, podemos centrarnos
en una arquitcctura mas comple.ja disponible desde Delphi 6: WebSnap. Habia
dos buenas razones para no abordar este tema desde el principio. Una de cllas es
que WcbSnap se base en 10s conceptos que proporciona WebBroker, por lo que si
no conocemos las caracteristicas subyacentes, no podremos comprender las mas
nuevas. Por e.jemplo, una aplicacion WebSnap tecnicamentc es un programa CGI;
o un modulo ISAPI o de Apache. La segunda razon es que desde WebSnap solo sc
incluye en la version Enterprise Studio de Delphi, y no todos 10s programadores
de Delphi tienen la posibilidad de utilizarla.
WebSnap tiene unas cuantas ventajas sobre WebBroker, ya que pcrmite utili-
zar multiples modulos Web, cada uno para una pagina, la integracion de guiones
de servidor, XSL, y la tecnologia Internet Express (de estos dos ultimos temas
hablaremos mas adelante, en el capitulo 22). Aun mas, dispone de muchos com-
ponentes listos para ser usados para mane.jar tareas comunes tales como el regis-
tro de un usuario, gestion de sesiones, etc. En lugar de dar una lista con todas las
caracteristicas de WebSnap, vamos a analizarlas con una serie de sencillas apli-
caciones centradas en las mismas. Por motivos de comprobacion, todas estas
aplicaciones han sido creadas utilizando eI Web App Debugger, pero no sera
dificil desplegarlas usando cualquier otra tecnologia.
El punto de partida del desarrollo de una aplicacion WebSnap es un cuadro de
dialogo que podemos invocar bien desde la pagina WebSnap del cuadro de dia-
logo New Items (File>New>Other) o bien utilizando la barra de herramientas
Internet del IDE. El cuadro de dialogo resultante, que muestra la figura 20.6, nos
permite escoger el tip0 de aplicacion (como una aplicacion WebBroker) y perso-
nalizar 10s componentes iniciales de la aplicacion (despues podremos ariadir algu-
no mas).
La parte inferior del dialogo determina el comportamiento de la primera pagi-
na (generalmente, la pagina predeterminada o de inicio del programa). Un cuadro
de dialogo similar se mostrara tambiCn para paginas posteriores.

Figura 20.6. Las opciones ofrecidas por el cuadro de dialogo New WebSnap
Application incluyen el tip0 de servidor y un boton que permite seleccionar 10s
cornponentes basicos de la aplicacion.

Si escogemos 10s valores predefinidos y escribimos un nombre para la pagina


de inicio, el cuadro de dialogo creara un proyecto y abrira un TWebApp-
PageModule.Este modulo contiene 10s componentes que hemos escogido, que
son de manera predeterminada:
Un componente WebAppComponents: Es un contenedor para todos 10s
servicios centralizados de la aplicacion WebSnap, tales como la lista de
usuario, repartidores basicos, servicios de sesion, etc. No es obligado acti-
var todas estas propiedades, ya que puede haber alguna aplicacion que no
necesite todos 10s servicios.
El componente PageDispatcher: Ofrece uno de estos servicios basicos, y
alberga automaticamente una lista de las paginas disponibles de la aplica-
c i h , al mismo tiempo que define la pagina predeterminada.
El componente AdapterDispatcher: Manipula 10s formularies HTML de
envio y las peticiones de imageries.
ApplicationAdapter: Es el primer componente de la familia de 10s
adaptadores. Estos componentes proporcionan campos y funciones a 10s
guiones de servidor evaluados por el programa. Concrctamente, el
ApplicationAdapter es un adaptador de campos que muestra el valor de su
propiedad A p p 1ica t ionT it le. Si introducimos un valor para esta
propiedad, 10s guiones tambien podran disponer de ella.
Un PageProducer: El modulo conticne un PageProducer que incluye el
codigo HTML dc la pagina, en este caso la pagina predeterminada del
programa. A diferencia de las aplicaciones WebBroker, el HTML para
este componente no se almacena dentro de su propiedad HTMLDoc de lista
de cadena, ni se hace referencia a el mediante su propiedad HTMLFile.El
archivo HTML es un archivo externo, almacenado de manera predetermi-
nada en la carpeta que alberga cl codigo fuente del proyecto y que se
referencia desde la aplicacion mediante una sentencia similar a una senten-
cia de inclusion de recurso: { * . h t m l }
Archivo HTML: Dado que el archivo HTML incluido por el PageProducer
se guarda como un archivo independiente (el componente LocateFileService
nos ayudara en su despliegue), podemos editarlo para cambiar la salida de
una pagina del programa sin necesidad de recompilar la aplicacion. Gra-
cias a1 soporte del guiones de servidor, estos cambios no se refieren sola-
mente a la parte fija del archivo HTML, sino tambien a su contenido
dinamico. A1 basarse en una plantilla estandar, el archivo HTML por de-
fecto ya contiene algunos guiones.
--- --- - - - - .. --
ADVERTENCIA: El parecido entre incluir recursos y las referencias a
HTML es basicamente sintictico. La referencia HTML se usa ~ 6 1 0para
encontrar en tiempo de disefio el archivo, mientras que la directiva de inclu-
sion de un recurso enlaza 10s datos a 10s que hace referencia con el archivo
..~- L - , x # -

Gracias a esa directiva es posible ver el archivo HTML dentro el editor de


Delphi con un buen resaltado de sintaxis. Simplemente tendremos que seleccionar
la solapa inferior correspondiente. El editor tambien tiene otras paginas para un
modulo WebSnap, incluyendo de manera predefinida una pagina HTML Result
en donde podemos ver el HTML generado despuis de evaluar 10s guiones y una
pagina Preview que contiene lo que el usuario vera en un explorador. El editor de
Delphi 7 para un modulo WebSnap tambien incluye un editor HTML mucho mas
potente que el de Dephi 6; incluye un resaltado de sintaxis y unas prestaciones de
completitud de codigo mucho mejores. Si se prefiere editar el codigo HTML de la
aplicacion Web con otro editor mas sofisticado, podemos determinar esta elec-
cion en la ficha Internet del cuadro de dialog0 Environment Options. A1 hacer
clic sobre el boton Edit para la extension HTML, podremos escoger un editor
externo para el menu de metodo abreviado del editor o un boton especifico de la
barra de herramientas de Internet de Delphi.
La plantilla HTML estandar utilizada por WebSnap aiiade a cualquier pagina
del programa su titulo y el titulo de la aplicacion, utilizando lineas de guion como
las siguientes:
<hl><%= Application.Title %></hl>
<h2><%= Page .Title %></h2>

Mas adelante hablaremos sobre 10s guiones. Por ahora vamos a comenzar el
desarrollo del ejemplo WSnapl creando un programa con varias paginas. Pero
antes, vamos a completar este repaso mostrando el codigo fuente de un modulo de
una pagina Web de muestra:
type
Thome = class (TWebAppPageModule)

end;

function home : Thome;

implementation

{SR . d f m ) {.htzd)

uses WebReq, WebCntxt , WebFact , Variants;

function home : Thome ;

begin
Result : = Thome (Webcontext.FindModuleClass (Thome)) ;
end;

initialization
if WebRequestHandler <> nil then

WebRequestHandler.AddWebModu1eFactory
(TWebAppPageModuleFactory.Create(Thome, TWebPageInfo.
Create([wpPublished { , wpLoginRequired)], ' . h t z d l ) ,
caCache) ) ;
end.

El modulo utiliza una funcion global en vez de un objeto global tipico de


formularios para soportar el almacenamiento en cache de las paginas. Esta apli-
cacion tambien tiene codigo adicional en la seccion de inicializacion (concreta-
mente codigo de registro) para permitir que la aplicacion sepa c u d es la funcion
de la pagina y su comportamiento.
- - -- - - -- -- - ---- - ..-
A

TRUCO:A1 contrario que en 10s ejempIos de WebBroker de este capitulo,


10s ejemplos de WebSnap se wmpilan cada uno en su propia carpeta. Se
hace asi porque 10s archivos HTML se riecesitan en tiempo de ejecucion y
hemos preferido simplificar el despliegue.

Gestion de varias paginas


La diferencia mas notable entre WebSnap y WebBroker es que, en lugar de
tener un unico modulo de datos con diversas funciones, conectadas finalmente a
componentes productores, WebSnap tiene varios modulos de datos que se corres-
ponden cada uno con una accion y que tienen un componente productor con un
archivo HTML conectado. Podemos aiiadir diversas acciones a una pagina o
modulo, per0 la idea basica es estructurar las aplicaciones en torno a paginas y no
a acciones. A1 igual que con las acciones, el nombre de la pagina se indica en la
ruta de la peticion. Como ejemplo, hemos aiiadido dos paginas mas a la aplicacion
WebSnap (creada con 10s valores predefinidos). Para la primera pagina, en el
dialogo New WebSnap Page Module (vease figura 20.7), hemos escogido un
productor estandar de pagina y lo hemos llamado date. Para la segunda, hemos
preferido un DataSetPageProducer y lo hemos llamado country. Despues de guar-
dar 10s archivos podemos probar la aplicacion. Gracias a algunos guiones, que
veremos mas adelante, cada pagina lista todas las paginas que estan disponibles
(a menos que hayamos desactivado la casilla de verificacion Published del dialo-
go New websnap Page Module).

Figura 20.7. El cuadro de dialogo New WebSnap Page Module.

E
m
Las paginas estaran casi vacias, per0 a1 menos tendremos la estructura ade-
cuada. Para completar la pagina de inicio, simplemente editaremos directamente
el archivo HTML vinculado. Para la pagina date, hemos empleado el mismo
metodo que para una aplicacion de WebBroker, aiiadiendo a1 texto HTML unas
etiquetas personalizadas, como la siguiente:
<p>The t i m e at this site is <#time>.</p>

Tambien hemos aiiadido algo de codigo a1 controlador del evento OnTag del
componente productor para reemplazar esta etiqueta con la hora actual.
Para la segunda pagina, country, hemos modificado el codigo HTML inclu-
yendo etiquetas para 10s diversos campos de la tabla de pais, como en:

Despues conectamos el ClientDataSet a un productor de pagina:


o b j e c t DataSetPageProducer: TDataSetPageProducer
DataSet = cdscountry
end

o b j e c t cdscountry: TClientDataSet
FileName = 'C:\Archivos de programa\Archivos comunes\Borland
Shared\Data\country.cdsl
end

Para abrir este conjunto de datos cuando la pagina se crea por primera vez, y
devolverla a1 primer registro en futuras llamadas, hemos manipulado el evento
OnBe f o r e D i s p a t c h P a g e del modulo de la pagina Web, aiiadiendole este
codigo:

Es bastante importante el hecho de que una pagina WebSnap sea muy similar
a una parte de una aplicacion WebBroker (basicamente una accion ligada a un
productor), ya que nos permite adaptar componentes del codigo WebBroker a esta
nueva arquitectura. Incluso se pueden adaptar 10s componentes DataSetTable-
Producer a esta arquitectura. Tecnicamente, podemos generar una pagina nueva,
quitar su componente productor, sustituirlo por un DataSetTableProducer y co-
nectar este componente a la propiedad P a g e P r o d u c e r del modulo de la pagina
Web. En la practica, este metodo recortara el archivo HTML de la pagina y sus
guiones. En el programa WSnap1 hemos utilizado una tecnica mejor a1 aiiadir a1
archivo HTML una etiqueta personalizada (< # h tml t a b 1 e >). Tambien hemos
utilizado el evento OnTag del productor de la pagina para aiiadir el resultado de
tabla del conjunto de datos a1 HTML:
if Tagstring = ' h t r d t a b l e ' then
ReplaceText : = DataSetTableProducer1.Content;
Guiones de servidor
Cuando tenemos varias paginas en un programa de servidor (cada una de ellas
asociada a un modulo de pagina diferente), la forma de escribir un programa
cambia. Tener a mano 10s guiones de servidor nos permite utilizar un enfoque aun
mas potente. Por ejemplo, 10s guiones estandar del ejemplo WSnap 1 se encargan
de la aplicacion, 10s titulos de las paginas y el indice de las mismas. Este indice lo
genera un cnumerador, es decir, la misma tecnica que se utiliza para recorrer una
lista dentro del codigo de un guion WebSnap:
< t a b l e cellspacing="O" cellpadding="O"><td>
<X e = n e w Enumerator(Pages)
s = "
c = o
for ( ; ! e. atEnd ( ) ; e .moveNext
(
if (e.item() .Published)
(
if ( C > 0 ) s += ' & n b s p ;I & n b s p ; '
if (Page.Name ! = e.item ( ) .Name)
s += ' < a href="' + e.item() .HREF + I " > ' +
e.item() .Title + ' < / a > '
else
s += e.item() .Title
C++
1
1
if ( c > l ) Response .Write (s)
' >
< / td></table>
. . -
NOTA: Generahente, 10s guiones de WebSnap e s t h escritos en JavaScript,
un lenguaje basado en objetos muy comun en la programacion para Internet
ya que es el unico lenguaje de guiones que suele estar disponible en
navegadores (en el cliente). JavaScript (que tecnicamente se llama
ECMAScript) toma prestada la sintaxis bhsica del lenguaje C y no tiene
casi nada que ver con Java. Realmente, el WebSnap utiliza el motor
Activescripting de Microsoft que soporta JScript (una variante de
JavaScript) y VBScript.

Dentro de la unica celda de esta tabla (que no tiene filas, aunque parezca
extraiio)? el guion produce una cadena mediante el metodo Response .Write.
Esta cadena se crea con un bucle f o r sobre un enumerador de paginas de la
aplicacion, guardado en la entidad global Pages. El titulo de cada pagina se
aiiade a la cadena solamente si la pagina se publica. Cada titulo utiliza un
hipervinculo, except0 para la pagina actual. A1 tener este codigo en un guion en
lugar de estar incrustado en un componente Delphi, podemos pasarselo a un buen
diseiiador Web para que lo convierta en algo mas atractivo visualmente.

TRUCX$~Fiqa h a w publicar p a phgina o invertir d i d o estado, no hay


qoe frjarse en ningunapmpledad d d m 6 d d o & hi $hg&i Web. Estc estado
s~icontfolacod nn indici&x d&meto& kdd~izbEf6dule~actc)r~, Ua-
mId.0 cn el &digb de in&&;ic~n del mMbltrld de hp&* Web. Simple-
mente mitntarribE 0n6dicf.mjndicador para ccmseguird efecto deseado.

Como muestra de lo que podemos hacer mediante 10s guiones de lenguajes


interpretados, hemos aiiadido a1 ejemplo WSnap2 (una ampliacion de WSnap l),
una pagina demoscript. El guion de esta pagina puede generar una tabla comple-
ta; llena de valores multiplicados utilizando el siguiente codigo (vease la figura
20.8):
<table border=l cellspacing=O>
<tr>
<th>&nbsp;</th>
<% for (j=l;j<=5;j++) ( %>
<th>Column <%=j %></th>
<% %>
</tr>
< % for (i=l;i<=5;i++) ( %>
< tr>
< td>Line <%=i %></td>
< % for (j=l;j<=5;j++) ( %>
< td>Value= <%=i * j %></td>
<% } B>
</tr>
<B } %>
</table>

En este guion, el simbolo <%=reemplaza a la orden Response .W r i t e .


Otra caracteristica importante de la escritura de guiones de sewidor es la inclu-
sion de unas paginas dentro de otras. Por ejemplo, si queremos modificar el menu,
podemos incluir el codigo HTML relacionado y el guion en un unico archivo, en
lugar de cambiarlo y mantenerlo en varias paginas. Incluir un archivo es tan
sencillo como usar esta sentencia:

En el listado 20.1, podemos encontrar el codigo fuente completo del archivo


incluido para el menu, a1 que se hace referencia desde todos 10s demas archivos
HTML del proyecto.
La figura 20.9, muestra un ejemplo de este menu, que se muestra en la parte
superior de la pagina mediante el guion de generacion de tabla que ya hemos
mencionado.
<hr>
<hl><%= Page. Title %></hl>
<P>

Este guion para el menu utiliza la lista Pages y 10s objetos globales de guion
Page y Application. WebSnap permite disponer de otros objetos globales
tales como EndUser y Session (en caso de que se aiiadan 10s correspondien-
tes adaptadores a la aplicacion), Modules y Producer, que permite acceder a1
componente Producer del modulo de la pagina Web. El guion tambien permite
usar 10s objetos Response y Request del modulo Web.

Adaptadores
Ademas de a estos objetos globales, dentro de un guion tambien podemos
acceder a todos 10s adaptadores que esten disponibles en el modulo de la pagina
Web correspondiente. (Adaptadores de otros modulos, como 10s modulos de datos
Web compartidos, deben de ser referenciados prefijando su nombre con el objeto
Modules y el correspondiente modulo.) La finalidad de 10s adaptadores es pasar
informacion del codigo cornpilado de Delphi al guion interpretado, proporcionan-
do una interfaz que puedan utilizar guiones para la aplicacion Delphi. Los
adaptadores contienen campos que representan datos y albergan funciones que
representan ordenes. Los guiones de servidor pueden acceder a estos valores y
lanzar estas ordenes, pasandoles parametros especificos.
Campos de adaptadores
Para realizar personalizaciones sencillas, simplemente bastara con aiiadir nue-
vos campos a 10s adaptadores especificos. En el ejemplo WSnap2, hemos aiiadido
un campo personalizado al adaptador de la aplicacion. Despues de seleccionar
este componente, podemos optar por abrir su editor Fields (accesible a traves de
su menu local) o trabajar dentro del Object Treeview. Despues de aiiadir un
campo nuevo (Ilamado AppHitCount en el ejemplo), podemos asignarle un
valor en su evento OnGe tvalue. Si queremos contar las solicitudes de cada
pagina de la aplicacion Web, tambien podemos controlar el evento OnBef ore-
PageDispat ch del componente PageDispatcher global para incrementar el valor
de un campo local, HitCount. Este es el codigo para 10s dos metodos:
procedure Thome.PageDispatcherBeforeDispatchPage(Sender:
TObject;
const PageName: String; var Handled: Boolean);
begin
Inc (HitCount);
end;

procedure Thome.CountGetValue(Sender: TObject; var Value:


Variant) ;
begin
Value : = Hitcount;
end;

Tambien podriamos usar el nombre de la pagina para el recuento de 10s acce-


sos a cada una de ellas (y podriamos aiiadir soporte para permanencia, ya que el
contador se pone a cero cada vez que ejecutamos una nueva instancia de la aplica-
cion). Ahora que hemos aiiadido un campo personalizado a un adaptador ya exis-
tente, (correspondiente a1 objeto de guion Application), podemos acceder a1
mismo desde cualquier guion de la siguiente manera:
<p>Application hits since last activation:
< % = Application. C o u n t . V a l u e % X / p >

Componentes de adaptadores
Del mismo modo, podemos aiiadir tambien adaptadores personalizados a pagi-
nas especificas. Si lo que necesitamos es pasar solamente unos cuantos campos,
es mejor utilizar el componente Adapter generico. Otros adaptadores personalizados
(ademas del ApplicationAdapter global que ya hemos utilizado) son:
El componente PagedAdapter: Tiene soporte interno para mostrar su
contenido en diversas paginas.
El componente DataSetAdapter: Se utiliza para acceder desde un guion
a un conjunto de datos de Delphi y que veremos dentro de poco.
El StringValuesList: Contiene una lista de pares nombrelvalor, en forma
de lista de cadenas, que puede utilizarse directamente o para proporcionar
una lista de valores a un campo de adaptador. El adaptador DataSetValue-
List heredado juega el mismo papel per0 obtiene la lista de pares nombrel
valor de un conjunto de datos, proporcionando soporte para busquedas y
otras selecciones.
Los adaptadores relacionados con el usuario, como 10s adaptadores EndUser,
Endusersession y LoginForm, que se utilizan para acceder a informacion
de sesion y usuario, y para crear el formulario de entrada para la aplica-
cion, el cual esta ligado automaticamente con la lista de usuarios. Hablare-
mos de estos adaptadores tambien mas adelante.

Uso del Adapterpageproducer


La mayoria de estos componentes se utilizan junto con un componente
Adapter Page Producer. ~ s t puede e generar partes de un gui6n despuks de
que hayamos diseiiado visualmente el resultado deseado. A mod0 de ejemplo,
hemos aiiadido la pagina inout a la aplicacion WSnap2, que contiene un adapta-
dor con dos campos, uno estandar y otro booleano:
object Adapterl: TAdapter
OnBeforeExecuteAction = AdapterlBeforeExecuteAction
o b j e c t TAdapterActions
o b j e c t AddPlus: TAdapterAction
OnExecute = AddPlusExecute
end
o b j e c t Post: TAdapterAction
OnExecute = PostExecute
end
end
o b j e c t TAdapterFields
o b j e c t Text: TAdapterField
OnGetValue = TextGetValue
end
object Auto: TAdapterBooleanField
OnGetValue = AutoGetValue
end
end
end

El adaptador tiene tambien un par de acciones utilizadas para enviar la entrada


del usuario actual y para aiiadir un signo + a1 texto. El mismo signo se aiiade
cuando activamos el campo Auto. Si utilizamos un codigo HTML basico, el
desarrollo de la interfaz de usuario para este formulario y el guion relacionado
con ella llevaria bastante tiempo. Pero el componente AdapterPageProducer (uti-
lizado en esta pagina) tiene un diseiiador HTML integrado, que Borland llama
Web Surface Designer. A1 utilizar esta herramienta, podemos aiiadir visualmente
un formulario a la pagina HTML y aiiadirle un AdapterFieldGroup. Hay que
conectar este grupo de campo a1 adaptador para que aparezcan automaticamente
10s editores para 10s dos campos. Despues podemos aiiadir un Adapter-
ComrnandGroup y conectarlo a1 AdapterFieldGroup para conseguir botones para
todas las acciones del adaptador. La figura 20.9 muestra un ejemplo de este
diseiiador:
Es decir, para ser mas precisos, 10s campos y 10s botones se muestran
automaticamente si activamos las propiedades Add De f a u 1 t Fie 1 d s y
AddDef aul tcommands de 10s grupos de campos y de ordenes. El efecto de
estas operaciones visuales para construir este formulario queda resumido en el
siguiente fragment0 de codigo DFM:
object AdapterPageProducer: TAdapterPageProducer
object AdapterForml: TAdapterForm
object AdapterFieldGroupl: TAdapterFieldGroup
Adapter = Adapter1
object FldText: TAdapterDisplayField
FieldName = ' Text '
end
object FldAuto: TAdapterDisplayField
FieldName = 'Auto'
end
end
object AdapterCornrnandGroupl: TAdapterCommandGroup
Displaycomponent = AdapterFieldGroupl
object CmdPost: TAdapterActionButton
ActionName = ' P o s t '
end
object CmdAddPlus: TAdapterActionButton
Act ionName = ' A d d P l u s '
end
end
end
end

Figura 20.9. El Web Surface Designer para la pagina inout del ejernplo WSnap2, en
tiernpo de disefio.

Ahora que tenemos una pagina HTML con algunos guiones para mover 10s
datos a nuestro antojo y enviar ordenes, v e a m s el codigo fuente necesario para
que funcione este ejemplo. En primer lugar, tenemos que aiiadir a la clase dos
campos locales para almacenar 10s campos del adaptador y poder manipularlos.
Tambien necesitamos implementar el evento OnGetValue para ambos, devol-
viendo 10s valores de campo. Cada vez que se hace clic sobre uno de 10s botones,
tenemos que conseguir el texto que se ha pasado a1 usuario, que no se copia
automaticamente en el campo correspondiente del adaptador. Podemos conseguir
este efecto si nos fijamos en la propiedad Actionvalue de estos campos, la
cual solamente se establece si escribimos algo (es por esto que, si no lo hacemos,
tenemos que establecer el campo booleano como False). Para evitar la repeti-
cion de este codigo para ambas acciones, lo colocaremos en el evento
OnBeforeExecuteAction del modulo de la pagina Web.
procedure Tinout.AdapterlBeforeExecuteAction(Sender, Action:
TOb j ect ;
Params: TStrings; var Handled: Boolean);
begin
i f Assigned (Text.ActionValue) then
fText : = Text.ActionValue.Va1ues [O];
fAuto : = Assigned (Auto-Actionvalue);
end;

Fijese en que cada accion puede tener varios valores (en caso de que 10s compo-
nentes permitan selection multiple); aunque como no es este el caso, simplemente
tomaremos el primer elemento. Por ultimo, hemos escrito el siguiente codigo para
10s eventos O n E x e c u t e de las dos acciones:
procedure Tinout.AddPlusExecute(Sender: TObject; Params:
TStrings) ;
begin
fText : = £Text + ' + ' ;
end:

procedure Tinout.PostExecute(Sender: TObject; Params:


TStrings) ;
begin
i f £Auto then
AddPlusExecute (Self, nil) ;
end;

Como alternativa, 10s campos de 10s adaptadores tienen una propiedad


EchoAc t ion Fie l d V a l u e publica que podemos establecer para obtener el
valor introducido por el usuario y colocarlo en el formulario resultante. Esta
tecnica se utiliza tipicamente en caso de errores, para permitir que el usuario
modifique la entrada, partiendo de 10s valores ya introducidos.

I t y l e CSS). ~ e + p u e ddefinir
hojas de estilo dn cascada ( ~ a s c a d i n ~ ~ Sheet,
la CSS para una pagina utilizando ya sea ia propiedad S t y l e s F i l e o la
lista cie cadena s t y l e s . Cualquier elemento del editor de 10s elementos
e

del productor puede definir un estilo basico o escoger un estilo del CSS
enlazado. Se realiza esta uItima operation (que es el enfoque que se sugie-
re) utilizando la propiedad S t y l e R u l e ) .

Guiones en lugar de codigo


Incluso ese ejemplo del uso combinado de un adaptador y un productor de
pagina de adaptador, con su diseiiador visual, muestra la potencia de esta arqui-
tectura.
Sin embargo, este enfoque solo tiene un inconveniente: al permitir que 10s
componentes generen el guion (en el codigo HTML solo tenemos la etiqueta
< #SERVE RSCR I PT>), nos ahorramos mucho tiempo de desarrollo, per0 acaba-
remos mezclando el guion con el codigo, de manera que 10s cambios en la interfaz
de usuario requeriran actualizar el programa. Se pierde el reparto de responsabi-
lidades entre el desarrollador de la aplicacion Delphi y el diseiiador de HTML y
guiones. E, ironicamente, se acabara necesitando la ejecucion de un guion para
realizar algo que el programa Delphi podria haber hecho de manera correcta y
posiblemente a mayor velocidad.
WebSnap es una arquitectura potente y un gran paso adelante con respecto a
WebBroker, per0 deberia utilizarse con cuidado para evitar el ma1 uso de algunas
de estas tecnologias ya que son simples y potentes. Por ejemplo, podria merecer la
pena utilizar el diseiiador Adapterpageproducer para generar la primera version
de una pagina, y despues coger el guion generado y copiarlo en el codigo HTML
de un simple Pageproducer, de manera que un diseiiador Web pueda modificar el
guion con una herramienta especifica.
Para aplicaciones mas complicadas, es preferible usar las posibilidades que
ofrecen XML y XSL, que se encuentran disponibles en esta arquitectura aunque
no tengan un papel central. En el capitulo 22 hablaremos mas sobre este tema.

Encontrar archivos
Cuando se ha escrito una aplicacion como la que acabamos de describir, hay
que desplegarla como un CGI o como una biblioteca dinamica. En lugar de colo-
car las plantillas en 10s archivos incluidos en la misma carpeta que el archivo
ejecutable, puede dedicarse una subcarpeta o carpeta personalizada para albergar
todos 10s archivos. El componente LocateFileService se encarga de esta tarea.
El componente no resulta intuitivo. En lugar de tener que especificar una car-
peta destino como una propiedad, el sistema lanza uno de 10s eventos de este
componente cada vez que tiene que encontrar un archivo. (Este enfoque es mucho
mas potente.) Existen tres eventos: OnFindIncludeFile,OnFindStream
y OnFindTempla te File.El primer y el ultimo evento devuelven el nombre
del archivo a utilizar en un parametro var.El evento On Findst ream permite
incluso proporcionar directamente un flujo, empleando uno de 10s que ya se en-
cuentran en memoria o que se ha creado a1 vuelo, extraido de una base de datos,
conseguido mediante una conexion HTTP o de cualquier otra manera que pueda
pensarse.
En el caso mas simple del evento OnFind Include File,se puede escribir
un codigo como el siguiente:
procedure TPageProducerPage2.LocateFileServicelFindIncludeFile(
ASender: TObject; AComponent: TComponent; const AFileName:
String;
var AFoundFile: String; var AHandled: Boolean);
begin
AFoundFile : = DefaultFolder + AFileName;
AHandled := True;
end ;
WebSnap y bases de datos
Una de las areas en las que mas destaca Delphi es en la programacion de bases
de datos. Por ello, no es sorprendente que disponga de un amplio soporte para el
manejo de conjuntos de datos dentro del marco WebSnap. Concretamente, pode-
mos utilizar el componente DataSetAdapter para conectar con un conjunto de
datos y mostrar sus valores en un fonnulario o tabla empleando el editor visual
del componente AdapterPageProducer.

Un modulo de datos WebSnap


Como ejemplo, hemos creado una nueva aplicacion WebSnap (llamada
WSnapTable) con un AdapterPageProducer como pagina principal para que mues-
tre una tabla y otro AdapterPageProducer en una pagina secundaria para mostrar
un fonnulario con un unico registro. Tambien hemos aiiadido a la aplicacion un
WebSnap Data Module como un contenedor de 10s componentes de conjunto de
datos. El modulo de datos tiene un ClientDataSet enlazado a un conjunto de datos
de dbExpress a traves de un proveedor y basado en una conexion InterBase, como
se muestra a continuacion:
o b j e c t ClientDataSetl: TClientDataSet
Active = True
ProviderName = ' D ataSetProviderll
end
o b j e c t SQLConnectionl: TSQLConnection
Connected = True
ConnectionName = ' IBLocal'
LoginPrompt = False
end
o b j e c t SQLDataSet 1: TSQLDataSet
SQLConnection = SQLConnectionl
CommandText =
' select CUST-NO, CUSTOMER, ADDRESS-LINEI, CITY,
STATE-PROVINCE, ' +
' COUNTRY from CUSTOMER'
end
o b j e c t DataSetProviderl: TDataSetProvider
DataSet = SQLDataSetl
end

El DataSetAdapter
Ahora que tenemos disponible un conjunto de datos, podemos aiiadir a la pri-
mera pagina un DataSetAdapter y conectarlo a1 ClientDataSet del modulo Web.
Automaticamente, el adaptador hace que todos 10s campos del conjunto de datos y
diversas acciones predetenninadas para operar con el conjunto (corno Delete,
Edit y Apply) esten disponibles. Podemos aiiadirlos explicitamente a las coleccio-
nes A c t i o n s y F i e l d s para excluir algunos y personalizar su comportamien-
to, per0 no siempre es necesario. A1 igual que el PagedAdapter, el DataSetAdapter
tiene una propiedad P a g e s i z e que podemos utilizar para indicar el numero de
elementos que queremos mostrar en una pagina. El componente tambien dispone
de metodos que podemos utilizar para recorrer las paginas. Este enfoque es muy
aconsejable para visualizar un gran conjunto de datos dentro de una cuadricula. A
continuacion veremos 10s valores del adaptador para la pagina principal del ejem-
plo WSnapTable:
object DataSetAdapterl: TDataSetAdapter
DataSet = WebDataModulel.ClientDataSet1
Pagesize = 6
end

El productor de pagina correspondiente tiene un formulario que contiene dos


grupos de ordenes y una cuadricula. El primer grupo (que se muestra por encima
de la cuadricula) tiene las siguientes ordenes predefinidas para manipular pagi-
nas: CmdPrevPage, CmdNextPage y CmdGotoPage. Esta ultima genera
una lista de numeros para las paginas, para que de esta forma el usuario pueda
acceder directamente a cada una de ellas. El componente AdapterGrid contiene
las columnas predeteminadas y una mas que alberga 10s ordenes de edicion ( E d i t )
y borrado ( D e l e t e ) . El grupo de ordenes inferior tiene un boton que se utiliza
para crear un nuevo registro. La figura 20.10 muestra un ejemplo del aspect0 de
la tabla y la configuracion completa del AdapterPageProducer se muestra en el
listado 20.2.

Listado 20.2. La configuracion del AdapterPageProducer para la pagina principal de


WSnapTable.

o b j e c t AdapterPageProducer: TAdapterPageProducer
object AdapterForml: TAdapterForm
object AdapterCommandGroupl: TAdapterCommandGroup
Displaycomponent = AdapterGridl
object CmdPrevPage: TAdapterActionButton
ActionName = ' P r e v P a g e '
Caption = ' P r e v i o u s P a g e '
end
object CmdGotoPage: TAdapterActionButton ...
object CmdNextPage: TAdapterActionButton ...
Act ionName = ' N e x t P a g e '
Caption = ' N e x t P a g e '
end
end
object AdapterGridl: TAdapterGrid
TableAttributes.Cel1Spacing = 0
TableAttributes.Cel1Padding = 3
Adapter = DataSetAdapterl
AdapterMode = ' B r o w s e '
object ColCUST-NO: TAdapterDisplayColumn
...
object AdapterCommandColumnl: TAdapterCommandColumn
Caption = 'COMMANDS'
object CmdEditRow: TAdapterActionButton
ActionName = 'EditRow'
Caption = 'Edlt'
PageName = 'formview'
DisplayType = ctAnchor
end
object CrndDeleteRow: TAdapterActionButton
ActionName = 'DeleteRowt
Caption = 'Delete'
DisplayType = ctAnchor
end
end
end
object AdapterCommandGroup2: TAdapterCommandGroup
Displaycomponent = AdapterGridl
object CmdNewRow: TAdapterActionButton
Act ionName = ' NewRow'
Caption = 'New'
PageName = ' formview'
end
end
end
end

\\'SnapTa ble
table

Prenous Page 1 2 3 Ntxt Pme

L
Figura 20.10. La pagma que rnuestra el ejemplo WSnapTable al inicio incluye la
parte in~cialde una tabla paginada.
En este listado hay algunas cosas que debemos de tener en cuenta. La primera
es que la cuadricula tiene la propiedad AdapterMode establecida como Browse.
Otras posibilidades podrian ser Edit, Insert y Query. Este mod0 de repre-
sentacion del conjunto de datos para adaptadores determina el tipo de interfaz de
usuario (texto, cuadros de dialog0 y otros controles de entrada) y la visibilidad de
otros botones (como por ejemplo 10s botones Apply y Cancel que solo estan
presentes en la vista de edicion; lo contrario sucede con la orden Edit).
-. ----
NOTA: ~ambi6npucdc modificarse el m o d ~del adaptador mediante gguio-
nes de senridor y accediendo a Adapter o ode .

En segundo lugar, hemos modificado la forma de representar las ordenes en la


cuadricula usando el valor c tAnchor para la propiedad Di splayT ype, en
lugar de utilizar el boton de estilo predeterminado. En la mayoria de 10s compo-
nentes de esta arquitectura encontraremos propiedades similares, que nos permi-
ten ajustar el codigo HTML que producen.

Edicion de 10s datos en un formulario


Algunas de las ordenes estan conectadas a distintas paginas, que se mostrara
cuando se invoquen esas ordenes. Por ejemplo, la propiedad PageName de la
orden edit esta establecida como formview. Esta segunda pagina de la apli-
cacion dispone de un AdapterPageProducer con componentes enganchados a1
DataSetAdapter de la otra tabla, de forma que todas las peticiones se sincronicen
automaticamente. De hecho, si hacemos clic sobre la orden Edit, el programa
abrira una pagina secundaria mostrando 10s datos del registro correspondiente a
la orden.
El listado 20.3 nos muestra 10s detalles del productor de la segunda pagina del
programa. Crear visualmente el formulario HTML, utilizando el diseiiador espe-
cifico de Delphi (vease la figura 20.1 l), hace que esta operacion sea muy rapida.

Listado 20.3. La configuracidn del AdapterPageProducer para la phgina formview.

object AdapterPageProducer: TAdapterPageProducer


object AdapterForml: TAdapterForm
object AdapterErrorListl: TAdapterErrorList
Adapter = table.DataSetAdapter1
end
object AdapterCommandGroupl: TAdapterCommandGroup
Displaycomponent = AdapterFieldGroupl
object CmdApply: TAdapterActionButton
ActionName = ' A p p l y 1
PageName = ' table1
end
object CmdCancel: TAdapterActionButton
ActionName = ' C a n c e l '
PageName = ' t a b l e '
end
object CmdDeleteRow: TAdapterActionButton
ActionName = ' D e l e t e R o w l
Caption = ' D e l e t e '
PageName = ' t a b l e 1
end
end
object AdapterFieldGroupl: TAdapterFieldGroup
Adapter = table.DataSetAdapter1
AdapterMode = ' E d i t '
object FldCUST-NO: TAdapterDisplayField
Displaywidth = 10
FieldName = ' CUST-NO'
end
object FldCUSTOMER: TAdapterDisplayField

end
end
end

-iJp1
- -- - ---- - -- .-- - --
+

- - - - -- - -. --
AdaplmPagcRoduar AdaptetForrnl
AdaplerFolrnl

E L
AdaplerCornmar
AdapIetFreldGro

I 1
@fowsw HTML Scrlpl I
A

Apply I Cancel I Delete (


CUST-NO pE7-
CXTsTOhE.R S~gnatureDes~gn

ADDRESS-LINE1 15500 P a c ~ f He~ghts


~c Bivd
CFY San D~ego

STATE_PRO\ WCE CA Q
COUNTRY USA

2l
Figura 20.11. La pag~naforrnv~ewmostrada por el ejernplo WSnapTable en t~ernpo
de ejecuc~on,en el Web Surface Des~gner(o el ed~torde Adapterpageproducer).

En el listado, puede verse que todas las operaciones envian el usuario de vuelta
a la pagina principal y que el AdapterMode se establece como Edit, a menos
que haya conflictos o errores en la actualizacion. En este caso se vuelve a mostrar
la misma pagina, con una descripcion de 10s errores a1 afiadir un componente
A d a p t e r E r r o r L i s t en la parte superior de la lista.
La segunda pagina no se publica, porque seleccionarla sin hacer referencia a
un registro especifico no tendria sentido. Para no publicar la pagina, hay que
comentar el indicador correspondiente en el codigo de inicializacion. Finalmente,
para hacer que 10s cambios en el conjunto de datos Sean persistentes, podemos
llamar a1 metodo A p p l y u p d a t e s en 10s eventos O n A f t e r P o s t y
O n A f t e r D e l e t e del componente C l i e n t D a t a S e t que esta en el modulo de
datos. Otro problema (el cual no hemos arreglado) tiene que ver con el hecho de
que el servidor SQL asigne un ID a cada cliente, de manera que cada vez que
introducimos un nuevo registro, no se alinean 10s datos en el ClientDataSet y en la
base de datos. Esto puede causar errores de tipo "Record Not Found" que indican
que no se encuentra un registro, por este problema de desalineamiento.

El componente DataSetAdapter tiene un soporte especifico para las relaciones


maestroldetalle entre 10s conjuntos de datos. Despues de crear la relacion entre
10s conjuntos de datos, como siempre, definimos un adaptador para cada uno de
ellos y, a continuation, conectamos la propiedad M a s t e r A d a p t e r del adapta-
dor del conjunto de 10s datos de detalle. A1 establecer la relacion maestroldetalle
entre 10s adaptadores hacemos que estos trabajen de una forma mas fluida. Por
ejemplo, cuando cambiamos el mod0 de trabajo del conjunto maestro, o introduci-
mos nuevos registros, el conjunto de detalle pasa automaticamente a1 mod0 de
edicion o se actualiza.
El ejemplo WSnapMD utiliza un modulo de datos para definir una relacion de
ese tipo. Incluye dos componentes c 1i e n t D a t ase t ,cada uno de ellos conectado
a un SQLDataSet mediante un proveedor. Cada componente de acceso a datos se
refiere a una tabla, y 10s componentes C l i e n t D a t aSe t definen una relacion
maestroldetalle. El mismo modulo de datos contiene dos adaptadores de conjunto de
datos que se refieren a 10s dos conjuntos y siguen definiendo dicha relacion:
object dsaDepartment: TDataSetAdapter
DataSet = cdsDepartment
end
object dsaEmployee: TDataSetAdapter
DataSet = cdsEmployee
MasterAdapter = dsaDepartment
end
fallo cierra el conjunto de datos en cada interaction, con lo que se pierde
information de estado.

La unica pagina de esta aplicacion WebSnap tiene un componente


AdapterPageProducer conectado a ambos adaptadores. El formulario de esta pa-
gina tiene un grupo de campos enganchado a1 maestro y una cuadricula conectada
con el detalle. A diferencia de otros e.jemplos, hemos tratado de me.jorar la interfaz
de usuario aiiadiendo atributos personalizados para diversos elementos. Hemos
usado un fondo gris, mostrado algunos bordes de cuadricula (Web Surface Designer
suele usar cuadriculas HTML), centrado la mayoria de 10s elementos y aiiadido
espacios. Fijese en que hemos aiiadido espacios adicionales a 10s titulos de boton
para impedir que Sean demasiado pequeiios. Puede verse el codigo relacionado en
el siguiente fragment0 detallado y su efecto en la figura 20.12:
object AdapterPageProducer: TAdapterPageProducer
object AdapterForrnl: TAdapterForrn
Custom = 'Border="lU CellSpacing="OrrCellPadding="lO" ' +
' BgColor="Silver" align="center"'
object AdapterCommandGroupl: TAdapterCommandGroup
Displaycomponent = AdapterFieldGroupl
Custom = 'Align="Center"'
object CmdFirstRow: TAdapterActionButton . . .
object CmdPrevRow: TAdapterActionButton ...
object CmdNextRow: TAdapterActionButton . . .
object CmdLastRow: TAdapterActionButton ...
end
object AdapterFieldGroupl: TAdapterFieldGroup
Custom = ' BgColor="Silver " '
Adapter = WDataMod.dsaDepartment
AdapterMode = ' Browse'
end
object AdapterGridl: TAdapterGrid
TableAttributes.BgCo1or = 'Silver'
TableAttributes.CellSpacing = 0
TableAttributes.CellPadding = 3
HeadingAttributes. BgColor = ' Gray'
Adapter = WDataMod.dsaEmployee
AdapterMode = 'Browse'
object ColEMP-NO: TAdapterDisplayColumn ...
object ColFIRST-NAME: TAdapterDisplayColumn ...
object ColLAST-NAME: TAdapterDisplayColumn ...
object ColDEPT-NO: TAdapterDisplayColumn . . .
object ColJOB-CODE: TAdapterDisplayColumn ...
object ColJOB-COUNTRY: TAdapterDisplayColumn ...
object ColSALARY: TAdapterDisplayColumn ...
end
end
end
First ( Revtous ( Nexl ( Lns! I
DEPARTMENT Corporate Headquarters
DEPT-NO 000
m - D m
LOCATION M a t m y
BUDGET 1000000

Tm Lee 000 ' A h USA 53733


I
Bender 000 CEO /USA ,212850

1&3'sf0 -d'.
q
.
Figura 20.12. El ejemplo WSnapMD muestra una estructura maestroldetalle y tiene
una representacion personalizada.

Sesiones, usuarios y permisos


Otra interesante caracteristica de la arquitectura WebSnap es su soporte para
sesiones y usuarios. Las sesiones se soportan utilizando un enfoque clasico: cookres
temporales. Estas cookies se envian a1 navegador para que sucesivas peticiones
del mismo usuario puedan ser reconocidas por el sistema. A1 aiiadir datos a una
sesion en lugar de a1 adaptador de una aplicacion, se puede disponer de datos que
dependan de la sesion o el usuario especifico (aunque un usuario puede ejecutar
varias sesiones abriendo multiples ventanas de navegacion en el mismo ordena-
dor). Para soportar las sesiones, la aplicacion mantiene 10s datos en memoria, por
lo que esta caracteristica no esta disponible en programas CGI.

Uso de sesiones
Para subrayar la importancia de este tip0 de soporte, hemos creado una aplica-
cion de WebSnap con una sola pagina que muestra tanto el numero total de visitas
como el numero total de visitas para cada sesion. El programa tiene un compo-
nente Sessionservice con valores predeterminados para sus propiedades
MaxSess ions y Def aultTimeout . Para cada nueva peticion, el programa
incrementa tanto su campo privado nHits del modulo de pagina como el valor
SessionHits de la sesion actual:
procedure
TSessionDemo.WebAppPageModuleBeforeDispatchPage(Sender:
TOb j ect;
const PageName: String; var Handled: Boolean) ;
begin
// incrementa las visitas d e aplicacion y sesion
Inc (nHits);
WebContext.Session.Values ['SessionHitsl] : =
Integer ( ~ e b ~ ~ n t.Session.Values
ext [ 'SessionHits '1 ) + 1 ;
end:

NOTA: El objeto Webcontext (de tipo ~ ~ e b ~ o n t e x estuna


) varia-
ble de hebra creada por Websnap para cada'peticih. Proporciona un a c a -
so seguro fiente a hebras a otras d k s globales usadas por el programa.

El codigo HTML asociado muestra inforrnacion de estado utilizando tanto


etiquetas personalizadas evaluadas por el evento OnTag del productor de pagina
como el guion evaluado por el motor. Esta es la parte mas importante del archivo
HTML:
<h3>Plain Tags</h3>
<p>Session id: <#SessionID>
<br>Session hits : < # S e s s i o n ~ its></p>
<h3>Script</h3>
<p>Session hits (via application) :
<%=Application. SessionHi ts. Val ue%>
<br>Application hits: <%=Application.Hits.Value%></p>

Los parametros de la salida 10s proporciona el controlador del evento OnTag


y 10s eventos OnGetValue de 10s campos:
procedure TSessionDemo.PageProducerHTMLTag(Sender: TObject;
Tag: TTag;
const TagString: String; Tagparams: TStrings; var
ReplaceText : String) ;
begin
if TagString = 'SessionID' then
ReplaceText : = WebContext.Session.Session1D
else if TagString = 'SessionHitsl then
ReplaceText : = WebContext.Session.Va1ues ['SessionHits']
end;

procedure TSessionDemo.HitsGetValue(Sender: TObject; var Value:


Variant) ;
begin
Value : = nHits;
end;

procedure TSessionDemo.SessionHitsGetValue(Sender: TObject; var


Value : Variant) ;
begin
Value : = I n t e g e r (WebContext.Session.Va1ues
[ 'SessionHit s ' 1 ) ;
end;

El efecto de este programa se muestra en la figura 20.13, donde hemos activa-


do dos sesiones en dos navegadores distintos.
- . -

TRUCO:En este ejemplo, hemos usado la sustitucih de etiquetas tradi-


cional de WebBroker y 10s nuevos campos de adaptador y guiones de
WebSnap, para que se puedan comparar 10s dos enfoques. Hay que tener
presente que arnbos se encuentran disponibles en una aplicacion de WebSnap.

P I a h TRy

Scrr:an td CY[ZuGcM3zRx85F
Scmon h r 6

Script

Snnonhts (ma npphcannn) 6


Appbcabon hllr 12

Figura 20.13. Dos instancias del navegador funcionan con dos sesiones distintas de
la misrna aplicacion de WebSnap.

Peticion de entrada en el sistema


Ademas de sesiones genericas, WebSnap tambien tiene un soporte especifico
para usuarios y sesiones de autorizacion basada en entradas en el sistema. Se
puede afiadir a una aplicacion una lista de usuarios (con el componente
WebUserList), cada uno con un nombre y una contraseiia. Este componente es
bastante basico en cuanto a 10s datos que puede contener. No obstante, en lugar
de rellenarlo con una lista de usuarios, se puede mantener la lista en una tabla de
base de datos (o en otro formato propietario) y utilizar 10s eventos del componen-
te WebUserList para recuperar 10s datos personalizados de usuario .p comprobar
sus contraseiias.
En general tambien se aiiadiran a la aplicacion 10s componentes SessionService
y EndUserSessionAdapter. En este punto, puede pedirse a1 usuario que se regis-
tre, indicando para cada pagina si puede ser vista por cualquiera o solo por usua-
rios registrados. Esto se lleva a cab0 activando el indicador wpLoginRequired
en el constructor de las clases TWebPageModuleFactory y TWebAppPa-
geModuleFactory en el codigo de inicializacion de la unidad de la pagina
Web.
- -- . .

NOTA: Los derechos y la publicacion de information se incluyen en la


fabric:a en lugar de en el WebPageModule ya que el programa puede corn-
u 10s derechos de acceso y listar las paginas sin cargar siquiera el
I-

Cuando un usuario trata de ver una pagina que requiera la identificacion del
usuario. aparecera la pagina de entrada en el sistema indicada en el componente
EndUserSessionAdapter. Se puede crear una pagina de este tip0 facilmente, creando
un nuevo modulo de pagina Web basado en un Adapterpageproducer y aiiadien-
dole el LoginFormAdapter.
En el editor de la pagina, se aiiade un grupo de campos dentro de un formula-
rio, se conecta el grupo de campos a1 LoginFormAdapter, y se aiiade un grupo de
comandos con el boton predeterminado Login. El formulario de entrada resultan-
te tendra campos para el nombre de usuario y su contraseiia, y tambien para la
pagina solicitada.
Este ultimo valor se rellena automaticamente con la pagina solicitada, en caso
de que la pagina necesitara autorizacion y el usuario no haya entrado en el siste-
ma aun. De este modo, un usuario puede alcanzar inmediatamente la pagina soli-
citada sin tener que volver a1 menu general.
El formulario de entrada tipicamente es no publicado, porque la orden Login
correspondiente ya esta disponible cuando el usuario no se encuentra dentro del
sistema; cuando entra el usuario, se sustituye con un comando Logout (de sali-
da del sistema). Este comando lo obtiene el guion estandar del modulo de la
pagina Web, en particular con el siguiente codigo:
i f ( E n d U s e r . L o g o u t != n u l l ) ( %>
i f ( E n d U s e r . D i s p l a y N a m e != ' ') ( %>
<hl>Welcome <%=EndUser.DisplayName :></hl>
} %>
.
i f ( E n d U s e r . L o g o ~ r tE n a b l e d ) ( %>
< a href="<%=EndUser.Logout.AsHREF%>">Logout</a>
I %>
i f ( E n d U s e r . LoginPorrn. E n a b l e d ) ( %>
< a href="<%=EndUser.LoginForm.As~R~FG>">Login</a>
} %>
} %>

No hay mucho mas que decir sobre la aplicacion WSnapUsers, ya que casi no
tiene codigo ni valores personalizados. Este guion para la plantilla esthdar muestra
como se realiza el acceso a 10s datos de usuario.
Derechos de acceso a una unica pagina
Ademas de hacer que las paginas necesiten entrar en el sistema para acceder a
ellas, se puede dar a usuarios especificos el derecho de ver mas paginas que otros.
Cualquier usuario tiene un conjunto de derechos separados por punto y coma, o
comas. El usuario debe tener definidos todos 10s derechos para la pagina solicita-
da. Estos derechos, que en general se listan en las propiedades ViewAccess y
Modif yAccess de 10s adaptadores, indican respectivamente si el usuario pue-
de ver 10s elementos dados mientras navega o si puede editarlos. Estas configura-
ciones son muy granulares y pueden aplicarse a adaptadores completos o a campos
especificos de adaptadores (fijese en que me estoy refiriendo a campos de
adaptadores, no a 10s componentes de la interfaz de usuario del diseiiador). Por
ejemplo, pueden esconderse algunas columnas de una tabla para unos usuarios
escondiendo 10s campos correspondientes (y tambien en otros casos, tal y como
especifique la propiedad HideOptions).
El componente global PageDispatcher tambien tiene eventos oncanviewpage
y OnPageAcce ssdenied, que puede utilizarse para controlar el acceso a
varias paginas de un programa dentro del codigo del programa, proporcionando
un control mucho mayor.
Programacion
Web con

Desde 10s dias de Delphi 2, Chad Z . Hower se ha encargado de la creacion de


una arquitectura Delphi que simplifique el desarrollo de aplicaciones Web, con la
idea de hacer que la programacion Web tan simple y visual como la programacion
de formularios Delphi estandar. Algunos programadores estan completamente
acostumbrados a las tecnologias HTML, JavaScript, hojas de estilo en cascada y
las mas recientes tecnologias de Internet. Otros programadores simplemente quie-
ren crear aplicaciones Web en Delphi del mismo mod0 en que crean aplicaciones
VCL 0 CLX.
IntraWeb esta pensada para este segundo tipo de desarrolladores, aunque es
tan potente que, incluso programadores expertos en Web pueden sacar partido de
su utilizacion. Segun Chad, IntraWeb esta pensado desarrollar aplicaciones Web,
no sitios Web. Aun mas, 10s componentes de IntraWeb pueden usarse en una
aplicacion especifica o pueden utilizarse en un programa de WebBroker o
WebSnap.
En este capitulo no hablaremos en detalle de IntraWeb, ya que es una bibliote-
ca muy grande, como 50 componentes instalados en la paleta de Delphi y varios
diseiiadores de modulos. Lo que vamos a hacer es comentar su base, de manera
que pueda sopesarse su uso para futuros proyectos o para partes de estos proyec-
tos, lo que resulte mas adecuado.
'
II \ - I)

TRUCO: Si se desea conseguir documentation sobre IntraWeb, se pueden


consultar 10s manuales PDF que se encuentran en el Companion CD de
Delphi 7. Si no pueden encontrarse aqui, tambidn se pueden descargar des-
de el sitio Web de Atozed Software. Para soporte sobre IntraWeb, es con-
veniente acudir a 10s grupos de noticias de Borland.

-
NOTA: Este capitulo ha sido especialmente revisado por Chad Z. Hower
(tambien conocido como "Kudzu", el autor y coordinador de proyecto origi-
nal de Internet Direct (Indy) y autor de IntraWeb. Entre las especialidades
de Chad se incluyen las redes y programacion TCPIIP, la comunicacion
entre procesos, la programacion distribuida, 10s protocolos de Lnternet y la
programacion orientada a objetos. Cuando no esta programando, tambiin
le gusta montar en bicicleta, kayak, escalar, descender en ski, conducir y
hacer casi cualquier cosa a1 aire libre. Chad tambien publica articulos,
programas y herramientas gratuitas (y otras curiosidades) en Kudzu World
en el URL http:llwww.Hower.o~Kudrul. Chad es un estadounidense ex-
patriado que pasa sus veranos en San Petersburgo (Rusia) y sus inviernos
en Limassol (Chipre). Se puede contactar con Chad a traves de
cpub@Hower.org.

En este capitulo trataremos 10s siguientes temas:


IntraWeb, aplicaciones Web y sitios Web.
Uso de componentes IntraWeb.
lntegracion con WebBroker y WebSnap.
Aplicaciones Web de bases de datos.
Uso de componentes de cliente.

lntroduccion a IntraWeb
lntraWeb es una biblioteca de componentes creada por Atozed Software
(www.atozedsoftware.com).En las ediciones Professional y Enterprise de Delphi
7, sc puede encontrar la version correspondiente de IntraWeb. La version
Professional solo puede usarse en mod0 de pagina, como veremos mas adelante.
Aunque Delphi 7 es la primera version del IDE de Borland en incluir este con-jun-
to de componentes, IntraWeb lleva existiendo ya varios aiios, ha sido muy bien
recibida y apoyada, con la disponibilidad afiadida de varios componentes de ter-
ceras partes.
- I

TRUC0 :Entre 10s componentes de terceras partes parar IntraWeb se inclu


-
-
yen IW'Char de Steema (10s creaclores de Teechart), l7N Bold de CentillelY
.. - - -. -- - - - -
(para la 1ntegraci6n con Bold), IWOpenSource, IWTranslator, IWD~alogs,
IWDataModulePool d e Arcana, IW Component Pack d e T M S y
IWGranPrimo de GranPrimo. Se puede encontrar una lista actualizada de
este tip0 de componentes en www.atozedsoftware.com.

Aunquc no se dispone del codigo fuente para la biblioteca central (disponible


bajo solicitud y previo pago), la arquitectura IntraWeb es bastante abierta, y el
codigo fuente completo dc 10s componentes esta plenamente disponible. IntraWeb
forma ahora parte de la instalacion estandar de Delphi, per0 tambien esta disponi-
ble para Kylis. Si se escriben con cuidado, las aplicaciones IntraWeb pueden ser
completamente multiplataforma.

siones para C++ Builder y Java. Se esta trabajando en una version .NET y
probablemente estara disponible junto con una futura version de Delphi

Como propietario de Delphi 7, se puede recibir la primera publicacion de una


actualizacion significativa (la vcrsion 5. I) y se pucde actualizar la licencia a una
version completa de la edicion Enterprise de IntraWeb que incluye actualizacio-
nes y soporte de Atozed Software (como puede comprobarse en su sitio Web). Si
se desea una documentacion mas completa (archives de ayuda y PDF), es reco-
mendable acceder a esta actualizacion a la version 5.1.

De sitios Web a aplicaciones Web


Como ya se ha comentado, la idea que esconde IntraWeb es crcar aplicaciones
Web en lugar de sitios Web. Cuando se trabaja con WebBroker o WebSnap (de
10s que se hablaba en el capitulo anterior), se puede pensar en terminos de paginas
Web y productores de paginas, y trabajar muy cerca del nivel de la generation de
paginas HTML. Cuando se trabaja con IntraWeb hay que pensar en terminos de
componentes, sus propiedades y sus controladores de eventos, a1 igual que en el
desarrollo visual con Delphi. Por ejemplo, se crea una nueva aplicacion IntraWeb
mediante el menu File>New>Other, accediendo a la pagina IntraWeb del cuadro
de dialogo New Items y escogiendo la opcion Stand Alone Application. En el
cuadro de dialogo siguiente (que forma parte de Delphi, no del asistente de
IntraWeb) se puede escoger una carpeta ya esistente o escribir el nombre de una
que se creara nueva (lo comentamos porque no quedara muy claro en el dialogo).
El programa resultante tiene un archivo de proyecto y dos unidades distintas.
Por el momento, creemos un ejemplo (llamado IWSimpleApp en el codigo
fuente del libro). Para construirlo, habra que seguir estos pasos:
1. Accedemos a1 formulario principal del programa y le aiiadimos un boton,
un cuadro de edicion y un cuadro de lista desde la pagina IW Standard de
la paleta de componentes, Es decir, no aiiadimos 10s componentes VCL de
la pagina Standard de la paleta, sino que utilizaremos 10s correspondien-
tes componentes IntraWeb: IWButton, IWEdit y IWListbox.
2. Modificaremos ligeramente sus propiedades de esta manera:
object IWButtonl: TIWButton
Caption = 'Add I t e m '
end
object IWEditl: TIWEdit
Text = ' f o u r '
end
object IWListboxl: TIWListbox
1tems.Strings = (
'one '
'two'
' t h r e e ')
end

3. Controlamos el evento O n c l i c k del boton haciendo doble clic sobre el


componente en tiempo de diseiio (como siempre) y escribiendo este codigo
tan familiar:
procedure TforrnMain.IWButtonlClick(Sender: TObject);
begin
1WListBoxl.Items.Add (1WEditl.Text);
end;

Esto es todo lo que se necesita para crear una aplicacion basada en Web capaz
de aiiadir texto a un cuadro de lista, como muestra la figura 2 1.1 (que muestra la
version final del programa, con un par mas de botones). Lo que es importante
tener en cuenta es cuando se ejecuta este programa es que cada vez que se hace
clic sobre el boton, el navegador envia una peticion nueva a la aplicacion, que
ejecuta el controlador de evento de Delphi y produce una nueva pagina HTML
basada en el nuevo estado de 10s componentes del formulario.
Cuando se ejecute la aplicacion, no se vera la salida del programa en el
navegador, sino en el formulario del controlador de IntraWeb que muestra la
figura 21.2. Una aplicacion IntraWeb independiente es un servidor HTTP com-
pleto, como se vera a continuacion. El formulario que se puede ver esta controla-
do por la llamada IWRun en el archivo de proyecto creado de manera predefinida
en cada aplicacion IntraWeb independiente. El formulario de depuracion permite
escoger un navegador y ejecutar la aplicacion a traves de el o copiar el URL de la
aplicacion en el Portapapeles, para que se pueda pegar despues dentro del
navegador. Es importante saber que la aplicacion utilizara de manera predetermi-
nada un numero de puerto aleatorio, que es distinto para cada ejecucion. Por eso
habra que utiliza un URL distinta cada vez. Se puede modificar este comporta-
miento si se selecciona el diseiiador del controlador del servidor (que es parecido
a un modulo de datos) y se fija la propiedad port. En el ejemplo hemos usado
8080 (uno de 10s puertos HTTP habituales), per0 otros valores tambien pueden
funcionar.

one
hvo
lhree
lour
four

Figura 21.1. El programa IWSimpleApp en un navegador

Fie Run I.ldp

3wmi- 1 -Ia
lnlraweb Version. 5 0 43
HTTP Pmt.8080
A
Packaged Enterprise
L~censeNumber 0
.-. ...............
.-.

-- -
-
- II
,,,

Figura 21.2. El formulario de controlador de una aplicacion IntraWeb


independiente.
El codigo IntraWeb es bbicamente codigo de servidor, pero IntraWeb tambien
puede generar JavaScript para controlar algunas de las caracteristicas de la apli-
cacion. Tambien puedc e.jecutarse codigo adicional en el lado del cliente. Para
haccr esto se utilizan componente especificos de cliente de IntraWeb o se escribe
un codigo JavaScript personalizado. Como comparacion, 10s dos botones de la
parte inferior del formulario en el ejemplo IWSimpleApp muestran un cuadro de
mensaje utilizando dos tecnicas distintas.
El primer0 de 10s dos botones (IWButton2) muestra un mensa.je mediante un
cvento de servidor, con este codigo Delphi:
procedure T f o r m M a i n . 1 ~ B u t t o n 2 C l i c k ( S e n d e r : TObject);
var
nItem: Integer;
begin
nItem : = 1WListboxl.ItemIndex;
if nItem >= 0 then
WebApp1ication.ShowMessage (1WListBoxl.Items [nItem])
else
WebApplication.ShowMessage ('No ltem s e l e c t e d ' ) ;
end;

El segundo de estos dos botones (IWButton3) utiliza JavaScript, que se


inserta cn el programa Delphi preparando el controlador de eventos JavaScript
apropjado en cl cditor especial de propiedades para la propiedad S c r i p t E v e n t s :

Un primer vistazo interior


Ya se ha visto que crear una aplicacion IntraWeb es tan sencillo como crear un
programa Delphi basado en formularies: se colocan componentes en un formula-
rio y se controlan sus eventos.
El efecto es bastante distinto, por supuesto, ya que la aplicacion se ejecuta en
un navegador. Para comprender mejor lo que sucede, vamos a analizar el compor-
tamiento interno de este programa tan sencillo. Esto deberia ayudar a comprender
el efecto de establecer las propiedades de componente y trabajar con ellas en
general.
Se trata de un programa basado en un navegador, por lo que no hay mod0
mejor de comprenderlo que fijarse en el codigo HTML que envia a dicho navegador.
Si abrimos el codigo de la pagina del programa IWSimpleApp (que no se muestra
aqui, porque ocuparia un espacio excesivo), se podra ver que esta dividido en tres
secciones principales.
La primera es una lista de estilos (basados en la etiqueta HTTP style) con
lineas como la siguiente:

IntraWeb utiliza estilos para determinar no solo la apariencia visual de cada


componente, como su fuente y color, sin0 tambien la posicion del componente,
mediante el posicionamiento absoluto predetern~inado.Cada estilo se ve afectado
por un cierto ni~merode propiedades de 10s componentes IntraWeb, asi que se
puede experimentar sin problemas si se sabe algo de ho-jas de estilo. Si no se esta
familiarizado con las ho-ias de estilo, lo mas facil es utilizar simplemente las
propiedades y confiar en que IntraWeb hara lo mejor que pucda para representar
10s componentes en la pagina Web.
El segundo bloque consiste cn codigo de guiones JavaScript. El bloque de
guiones principal contiene el codigo de inicializacion y el codigo de 10s
controladores de evcntos de cliente para 10s componentes, como el estracto si-
guiente:
function IWBUTTONl-OnClick(ASender) (
r e t u r n SubmitClickConf irm( 'IWBUTTONI ', ' ', true, ' ' ) ;
)

Este controlador llama a1 correspondiente codigo de servidor. Si se ha propor-


cionado directamente el codigo JavaScript en la aplicacion IntraWeb, como ya
hemos comentado, se vera este codigo:
f u n c t i o n IWBUTTON3_onClick(ASender) (
window. alert (ASender.value) :
)

La seccion de guiones de la pagina tambien hacer referencia a otros archivos


necesarios para el navegador y que IntraWeb pone a su disposicion. Algunos de
estos archivos son genericos: otros estan enlazados con un navegador especifico:
IntraWeb detecta el navegador que se esta utilizando y devuelve un codigo
JavaScript y archivos basicos JavaScript distintos.
. - - - . - . --
- -- ..- - ..

NOTA: Ya que el lenguaje JavaScript no es idkntico para todos 10s


navegadores, IntraWeb soporta solo algunos de ellos, como el de las ulti-
mas versiones de Microsoft Internet Explorer, Netscape Navigator y Mozilla
(un proyecto de codigo abierto usado durante la elaboration de este capitu-
lo). Opera ofiece un soporte mas limitado de JavaScript, por lo que, de
manera predeterminada, IntraWeb emitira un error si lo reconoce (de acuerdo
con la propiedad SupportedBrowsers del controlador). Opera puede
por la version 5.1 de IntraWeb. Hay que tener presente que un navegador
puede simular su identidad: Por ejemplo, es habitual que Opera este confi-
gurado para identificarse como Internet Explorer, lo que irnpedira una iden-
tificacion correcta para posibilitar el uso de sitios restringidos a otros
navegadores, pero posiblemente llevara a errores en tiempo de ejecucion o
inconsistencias.

La tercera parte del HTML generado es la definition de la estructura de pagi-


na. Dentro de la etiqueta body se encuentra una etiqueta f o r m (en la misma
linea) con la siguiente accion de ejecucion:

La etiqueta f o r m contiene componentes especificos de la interfaz de usuario,


como botones y cuadros de edicion:
<input type="TEXTV name="IWEDITl" size="17" value="fourl'
id="IWEDITlW class="IWEDITlCSS">
< i n p u t v a l u e = " A d d I t e m " name=" I W B U T T O N l " t y p e = " b u t t o n "
o n c l i c k = " r e t u r n IWBUTTONl-OnClick(this);"
id="IWBUTTONl" class="IWBUTTONlCSS">

El formulario tiene tambien algunos componentes ocultos que IntraWeb utiliza


para llevar informacion entre el cliente y el servidor, Sin embargo, el URL es el
mod0 mas importante de pasar informacion en IntraWeb; en el programa tendra
este aspecto:

La primera parte es la direccion IP y el puerto que suelen utilizarse para la


aplicacion IntraWeb independiente (cambia cuando se usa una arquitectura dis-
tinta para desplegar el programa), seguida del comando EXEC, un numero de
creciente peticion y un identificador de sesion. Ya hablaremos mas tarde de la
sesion, pero por ahora bastara con saber que IntraWeb utiliza un elemento del
URL en lugar de cookies para permitir el acceso a sus aplicaciones a pesar de las
posibles configuraciones de 10s navegadores. Si se prefiere, se pueden utilizar
cookies en lugar del URL, modificando la propiedad TrackMode en el controla-
dor del servidor.
Arquitecturas IntraWeb
Antes de escribir mas ejemplos para mostrar el uso de otros componentes
IntraWeb disponibles en Delphi 7, vamos a analizar otro elemento clave de
IntraWeb: las distintas arquitecturas que pueden usarse para crear y desplegar
aplicaciones basadas en esta biblioteca. Se pueden crear proyectos IntraWeb en el
mod0 Application (donde son aplicables todas las caracteristicas de IntraWeb o
en el mod0 Page (que es una version simplificada que puede aiiadirse a aplicacio-
nes WebBroker o WebSnap ya existentes). Las aplicaciones que utilizan el mod0
Application pueden desplegarse como bibliotecas ISAPI, modulos de Apache o
utilizando el mod0 IntraWeb Standalone (una variante de la arquitectura del mod0
Application).
Los programas en mod0 Page pueden desplegarse como cualquier otra aplica-
cion WebBroker (ISAPI, modulo de Apache, CGI, etc.. .). IntraWeb usa tres ar-
quitecturas distintas que se solapan en parte:

Modo Standalone: Proporciona un servidor Web personalizado, como en


el primer ejemplo creado. Resulta practico para depurar la aplicacion (ya
que puede ejecutarse desde el IDE de Delphi y situar puntos de ruptura en
cualquier parte del codigo). Tambien se puede usar este mod0 para desple-
gar aplicaciones en redes internas (intranets) y para permitir que 10s usua-
rios trabajen en mod0 desconectado en sus propios ordenadores, con una
interfaz Web. Si se ejecuta un programa IntraWeb independiente con el
indicador -inst a l l , se ejecutara como servicio y no aparecera el cua-
dro de dialogo. El mod0 Standalone ofrece un mod0 de desplegar un pro-
grams IntraWeb de mod0 Application mediante la propia IntraWeb como
servidor Web.
Modo Application: Permite desplegar una aplicacion IntraWeb en un ser-
vidor comercial, construido como modulo Apache o biblioteca ISS. El
mod0 Application incluye gestion de sesiones y todas las caracteristicas de
IntraWeb, y es el mod0 preferido de desplegar una aplicacion escalable
para su uso en la Web. Para ser mas precisos, 10s programas IntraWeb en
mod0 Application pueden desplegarse como programas independientes,
bibliotecas ISAPI o modulos de Apache.
Modo Page: Abre una via a la integracion de paginas IntraWeb en aplica-
ciones WebBroker y WebSnap. Se pueden aiiadir caracteristicas a progra-
mas ya existentes o confiar en otras tecnologias para partes de un sitio
dinamico basado en paginas Web, mientras que se gestionan mediante
IntraWeb las partes interactivas. El mod0 Page es la unica opcion para
utilizar IntraWeb en aplicaciones CGI, per0 carece de las caracteristicas
de gestion de sesiones. Los servidores IntraWeb independientes no soporta
el mod0 Page.
En 10s ejemplos que apareceran en el resto del capitulo utilizaremos por sim-
plicidad y un proceso de depuracion mas sencillo el mod0 Standalone, per0 tam-
bien hablaremos del soporte del mod0 Page.

Creacion del aplicaciones IntraWeb


Cuando se crea una aplicacion IntraWeb, se dispone de un gran numero de
componentes. Por ejemplo, si nos fijamos en la pagina IW Standard de la
Component Palette de Delphi, se vera una lista impresionante de componentes
importantes, desde el boton, casilla de verificacion, boton de radio, cuadro de
edicion, cuadro de lista, campo de memo, y demas hasta 10s interesantes compo-
nentes de vista en arbol, menu, temporizador, cuadricula y vinculo. No vamos a
mostrar una lista de todos 10s componentes y describir su uso con un ejemplo (en
lugar de eso usaremos algunos de ellos en unos cuantos programas de demostra-
cion y remarcaremos la arquitectura de IntraWeb en lugar de 10s detalles especi-
ficos).
Hemos creado un ejemplo (llamado IWTree) en el que se utilizan 10s compo-
nentes de menu y de vista en arbol de IntraWeb per0 tambien se crea un compo-
nente en tiempo de ejecucion. Este componente tan practico permite acceder en un
menu dinamico a1 contenido de un menu estandar de Delphi, haciendo referencia a
su propiedad At tachedMenu y a un componente TMenu.
o b j e c t MainMenul : TMainMenu
o b j e c t Treel: TMenuItem
o b j e c t ExpandAlll: TMenuItem
o b j e c t CollapseAlll: TMenuItem
o b j e c t N1: TMenuItem
o b j e c t EnlargeFontl: TMenuItem
o b j e c t ReduceFontl: TMenuItem
end
o b j e c t Aboutl: TMenuItem
o b j e c t Applicationl: TMenuItem
o b j e c t TreeContentsl: TMenuItem
end
end
o b j e c t IWMenul : TIWMenu
AttachedMenu = MainMenul
Orientation = iwOHorizonta1
end

Si 10s elementos del menu controlan el evento Onclick en el codigo, se


convertiran en enlaces en tiempo de ejecucion. Se puede ver un ejemplo de un
menu en la figura 2 1.3. El segundo componente del ejemplo es una vista en arbol
con un conjunto de nodos predefinidos. Este componente utiliza mucho codigo
JavaScript para permitir la expansion y colapso de 10s nodos directamente en el
navegador (sin tener que volver a acceder a1 servidor). A1 mismo tiempo, 10s
elementos del menu permiten que el programa trabaje con el menu expandiendo o
colapsando 10s nodos y modificando la fuente. Este es el codigo para un par de
controladores de eventos:
procedure TformTree.ExpandAlllClick(Sender: TObject);
var
i: Integer;
begin
for i : = 0 to 1WTreeViewl.Items.Count - 1 do
1WTreeViewl.Item.s [i].Expanded : = True;
end;

procedure TformTree.EnlargeFontlClick(Sender: TObject);


begin
1WTreeViewl.Font.Size : = 1WTreeViewl.Font.Size + 2;
end;

Gracias a1 parecido de 10s componentes de IntraWeb con 10s componentes


estandar de la VCL de Delphi, es facil leer y comprender este codigo.

Figura 21.3. El ejemplo IWTree utiliza un menti, una vista en arbol y la creacion
dinamica de un componente de memo.

El menu tiene dos submenus, que son bastante mas complejos. El primer0
muestra el identificador de la aplicacion, que es un identificador de la ejecucion y
sesion de la aplicacion. Este identificador esta disponible mediante la propiedad
AppI D del objeto global WebApp 1i c a t i o n . El segundo submenu, Tree
Contents, muestra una lista de 10s tres primeros nodos del nivel principal junto
con el numero de subnodos directos. Aim asi, lo que es interesante es que la
informacion se muestra en un componente de memo creado en tiempo de ejecucion
(como muestra la anterior figura 2 1.3.), esactamente del mismo mod0 que en una
aplicacion VCL:
procedure TformTree.TreeContentslClick(Sender: TObject);
var
i: Integer;
begin
w i t h T I W M e m o - C r e a t e (Self) d o
begin
Parent : = Self;
A l i g n : = alBottom;
f o r i : = 0 t o 1WTreeViewl.Items.Count - 1 d o
Lines .Add ( IWTreeViewl. Items [i].Caption + ' ( ' +
IntToStr ( IWTreeViewl. Items [i] .SubItems .Count) +
') '1;
end;
end;

--

TRUCO:Fijese en que el alineamiento en ~ n t r a ~ funcionaeb de un m o d ~


parecido a su homologo VCL. Por ejemplo, el menu de este programa tiene
un alineamiento a l T o p , la vista en h b o l se alinea como a l C 1 i e n t y el
campo d i n h i c o de memo se crea con el alineamiento a l B o t tom. Como
alternativa, se pueden usar anclajes (que b c i o n a n como en la VCL): Se
pueden crear botones colocados en la esquina inferior derecha o componen-
tes en el centro de la pagina, fijando 10s cuatro anclajes. En 10s siguientes
programas de muestras se pueden ver ejemplos de esta tecnica.

Escritura de aplicaciones de varias paginas


Todos 10s programas que se han creado hasta ahora tenian una sola pagina. Es
hora ya de crear una aplicacion IntraWeb con una segunda pagina. Como se vera,
incluso en este caso, el desarrollo mediante IntraWeb se parece a1 desarrollo
estandar en Delphi o Kylis, y es diferente de la mayoria del resto de las bibliote-
cas de desarrollo para Internet. Este ejemplo t a m b i h servira como excusa para
profundizar en parte del codigo fuente generado automaticamente mediante el
asistente de aplicacion IntraWeb.
Comencemos desde el principio. El formulario principal del ejemplo
IWTwoForms utiliza una cuadricula de IntraWeb. Este potente componente per-
mite situar en una cuadricula HTML tanto texto como otros componentes. En el
ejemplo, el contenido de la cuadricula queda determinado durante el arranque (en
el controlador del evento o n c r e a t e del formulario principal):
procedure TformMain.IWAppFormCreate(Sender: TObject);
var
i: Integer;
link: TIWURL;
begin
// fija 10s titulos de la cuadricula
IWGridl.Cell [0, 0] .Text := 'Row';
IWGridl .Cell [0, 11 .Text : = 'Owner';
IWGridl .Cell [0, 2 ] .Text := 'Web Site';
/ / fija el contenido de las celdas
for i : = 1 to 1WGridl.RowCount - 1 do
begin
IWGridl .Cell [i,01 .Text : = 'Row ' + IntToStr (i+l);
1WGridl.Cell [i,l] .Text : = 'IWTwoForms by Marc0 Cantu';
link := TIWURL.Create (Self);
link.Text := 'Click here ';
link .URL : = 'http://www.marcocantu. corn';
.
IWGridl Cell [i,2 ] .Control : = link;
end ;
end ;

El efecto de este codigo se muestra en la figura 2 1.4. Ademas de la salida, hay


que fijarse en unos cuantos detalles interesantes. En primer lugar, el componente
de cuadricula utiliza 10s anclajes de Delphi (a False) para generar el codigo que
lo mantiene centrado en la pagina, incluso aunque un usuario ajuste el tamaiio de
la ventana del navegador.
En segundo lugar, hemos aiiadido un componente IWURL a la tercera colum-
na, per0 podria aiiadirse cualquier otro componente (incluidos botones y cuadros
de edicion) a la cuadricula.
La tercera (y mas importante) cuestion es que un IWGrid se traduce en una
cuadricula HTML, con o sin marco alrededor. Este es un fragment0 del codigo
HTML generado para una de las filas de la cuadricula:
<tr>
<td valign="middle" align="leftW NOWRAP>
<font style="font-size:lOpt;">Row 2</font>
< / td>
<td valign="middle" align="leftW NOWRAP>
<font style="font-size: 10pt;">IWTwoForms by Marco Cantu</
font>
</t&
<td valign="middle" align="leftW NOWRAP>
<font style="font-size: l0pt; "></font>
<a href="#" onclick="parent .LoadURL ( 'http:/ /
www.marcocantu.com')"
id="TIWURLlr' name="TIWURLl"
style="z-index:lOO;font-sty1e:normal;font-
size:lOpt;text-decoration:none;">
Click here</a>
< / td>
< /tr>
kowl ~ W ~ ' W O by
FMO a~ m Canhl blick here 1
bow 3 bWwoFoms by Mawo Canhl
\
Flick here I

Figura 21.4. El ejemplo IWTwoForms usa un componente IWGrid, texto incrustado y


componentes IWURL.

TRUCO:En el listado anterior, hay que fijarse en que el UIU. vinculado se


activa mediante JavaScript, y no directamente. Se hace asi porque todas las
acciones de IntraWeb permiten operaciones adicionales de cliente, como
validaciones, comprobaciones y envios. Por ejemplo, si se establece la pro-
piedad Required de un componente, si el campo estiz vacio no se envia-
-- o--r-r - - - - - - - a:--1-1-
I--
ran 10s
-1..
aaws, -.
y se
> - A _ - 1- 3- 3-
Vera un mensaje ae error ae Javaacrlpr
1 T
wersonallzaole /

mediante la propiedad descriptiva F r iendlyName).

La caracteristica principal del programa es su capacidad de mostrar una se-


gunda pagina. Para realizar esto, en primer lugar se necesita aiiadir una nueva
pagina IntraWeb a la aplicacion, mediante la opcion ApplicationForm de la
pagina IntraWeb del cuadro de dialogo New Items de Delphi (File>New>Other).
Aiiadimos a esta pagina algunos componentes IntraWeb, como siempre, y des-
pues la aiiadiremos un boton u otro control a1 formulario principal que podamos
usar para mostrar el formulario secundario (con la referencia a n o t h e r f orm
almacenada en un campo del formulario principal):
procedure TformMain.btnShowGraphicClick(Sender: TObject);
begin
anotherform : = TAnotherForm.Create(WebApp1ication);
anotherform.Show;
end;

Incluso aunque el programa llame a1 metodo Show, puede considerarse como


una llamada a ShowModal, ya que IntraWeb considera las paginas visibles
como una pila. La ultima pagina que se muestra esta en la parte superior de la pila
y es la que muestra el navegador. A1 cerrar esta pagina (escondiendola o destru-
yendola), se vuelve a mostrar la pagina anterior. En el programa, las paginas
secundarias se pueden cerrar cuando se llama a1 metodo R e l e a s e , que es (corno
en la VCL) el mod0 correct0 de deshacerse de un formulario que se ejecuta en ese
instante. Tambien se puede esconder el formulario secundario y volverlo a mos-
trar mas tarde, evitando volver a crearlo cada vez (en particular si hacer esto
implica perder las operaciones de edicion del usuario).
- - -. - -- - -

ADVERTENCIA: Hemos &dido en el pronrama - - un b o t h CIese en el


- en SU
formulario principal. No deberia llamar a Release, she invocar
lugar a1 mCtodo Terminate del objeto WebApplication, pashdlola
I
.. . . . 1 .
el mensaje de salida, como en WebApplication .Terminat e
, .- . .-.. .. ..
[ ' tiooaoye ! ' ) . La aemostracion urillza una llamaaa alternauva:
. I .I 4 '

TerminateAndRedirect.

Ahora que se ha visto como crear una aplicacion IntraWeb con dos formula-
rios, vamos a analizar brevemente el mod0 que se IntraWeb crea el formulario
principal. El codigo relevante, generado por el asistente de IntraWeb cuando se
crea un programa nuevo, esta en el archivo de proyecto:
begin
IWRun(TFormMain, TIWServerController);

Es algo distinto del archivo de proyecto estandar de Delphi, porque llama a


una funcion global en lugar de aplicar un metodo a un objeto global que represen-
te a la aplicacion.
No obstante, el efecto es bastante parecido. Los dos parametros son las clases
del formulario principal y del controlador IntraWeb, que maneja sesiones y otras
caracteristicas, como veremos en breve.
El formulario secundario del ejemplo IWTwoForms muestra otra caracteris-
tica interesante de IntraWeb: su extenso soporte de graficos. El formulario tiene
un componente grafico con la clasica imagen de Atenea de Delphi. Se consigue
esto a1 cargar un mapa de bits en un componente IWImage: IntraWeb convierte
el mapa de bits en un archivo JPEG, lo guarda en una carpeta cache creada dentro
de la carpeta de la aplicacion y devuelve una referencia a ese archivo, con el
siguiente codigo HTML:
La caracteristica adicional proporcionada por IntraWeb y aprovechada por el
programa es que un usuario puede hacer clic sobre la imagen con el raton para
modificar la imagen a1 ordenar la ejecucion de codigo de servidor. En este progra-
ma, el efecto es dibujar pequeiios circulos verdes.

Este efecto se consigue con el codigo siguiente:


procedure Tanotherform.IWImagelMouseDown(ASender: TObject;
const AX, AY: Integer);
var
aCanvas: TCanvas;
begin
aCanvas : = 1WImagel.Picture.Bitmap.Canvas;
aCanvas.Pen.Width := 8;
aCanvas.Pen.Color := clGreen;
aCanvas. Ellipse (Ax - 10, A y - 10, Ax + 10, A y + 10) ;
end;

rnapa de bits. No hay que intentar utilizar el lienzo &age (como se hacia
con el companente TImage de la VCL) y no hay que tratar de war un JPEG ,
en primer Ingar, o no se verh nin& efecto o aparecera un error en tiempo
de e j m c i h .

Gestion de sesiones
Si se ha realizado algo de programacion Web, ya se sabe que la gestion de
sesiones es un asunto bastante complejo. IntraWeb proporciona un sistema de
gestion de sesiones predefinido y simplifica el mod0 en que se trabaja con sesio-
nes. Si se necesita una sesion de datos para un formulario especifico, todo lo que
hay que hacer es afiadir un campo a ese formulario. Los formularies IntraWeb y
sus componentes tienen una instancia para cada sesion de usuario. Por ejemplo,
en IWSession hemos aiiadido al formulario un campo llamado Formcount.Por
contra, tambitn hemos declarado una variable de unidad global llamada
Globalcount, que comparten todas las instancias (o sesiones) de la aplica-
cion.
Para aumentar el control sobre 10s datos de sesion y permitir que varios formu-
larios la compartan, se puede personalizar la clase TUserSession que coloca
el IntraWeb Wizard en la unidad ServerController. En el ejemplo WSession,
hemos particularizado la clase de esta forma:
type
TUserSession = class
public
Usercount : Integer;
end ;

IntraWeb crea una instancia de este objeto para cada nueva sesion, como pue-
de verse en el metodo IWServerControllerBaseNewSession de la cla-
se TIWServerController en la unidad ServerController predefinida.
procedure TIWServerController.IWServerControllerBaseNewSession(
ASession: TIWApplication; v a r VMainForm: TIWAppForm);
begin
ASession.Data := TUserSession-Create;
end;

En el codigo de una aplicacion, se puede hacer referencia al objeto de sesion


accediendo al campo Data de la variable global RWebApplication,utiliza-
da para acceder a la sesion de usuario actual.

la unidad IWInit. Ofrece acceso a 10s datos de sesibn dc un mod0 seguro


con respecto a 10s hilos: hay que tener un cuidado especial para acceder a
ella incluso en un entorno rnultihilo. Esta variable puede utilizarse fhera de
un formulario o control (que se basan de manera nativa en sesiones), que es
por lo que se usa sobre todo en modulos de datos, rutinas globales y clases
que no Sean de LntraWeb.

Una vez mas, la unidad ServerController predeterminada ofrece una funcion


auxiliar que puede utilizarse:
function UserSession: TUserSession;
begin
Result := TUserSession(RWebApp1ication.Data);
end;
Ya que la mayor parte del codigo se genera automaticamente, despues de aiia-
dir datos a la clase TUser ses s ion simplemente hay que usarla mediante la
funcion User ses s ion, como en el codigo siguiente, extraido del ejemplo
IWSession. Cuando se hace clic sobre un boton, el programa incrementa varios
contadores (uno global y dos especificos de sesion) y muestra sus valores en
etiquetas:
procedure TformMain.IWButtonlClick(Sender: TObject);
begin
InterlockedIncrement (GlobalCount);
Inc (FormCount);

Inc (UserSession.UserCount);

IWLabell-Text : = 'Global: ' + IntToStr (GlobalCount);


IWLabel2 .Text : = 'Form: ' + IntToStr (FormCount);
IWLabel3 .Text : = 'User: ' + IntToStr
(UserSession.UserCount);
end;

Fijese en que el programa utiliza la llamada Inter loc kedI ncrement de


Windows para evitar el acceso concurrente a la variable global compartida por
varios hilos. Entre las tecnicas alternativas se incluye el uso una seccion critica o
de T i d T h r e a d S a f e I n t e g e r de Indy (que se encuentra en la unidad
IdTrheadsafe).
La figura 2 1.5 muestra el resultado del programa (con dos sesiones en ejecu-
cion en dos navegadores distintos). El programa tambien tiene una casilla de
verificacion que activa un temporizador. Aunque suene extraiio, en una aplica-
cion IntraWeb, 10s temporizadores funcionan casi del mismo mod0 que en Windows.
Cuando expira el plazo del temporizador, se ejecuta un cierto codigo. En la Web
esto significa refrescar la pagina lanzando una orden de refresco en el codigo
JavaScript :
IWTIMERl=setTimeout ( ' S ~ b r n i t C l i c k ( ~ ~ ~ W T I M"",
E R l ~false)
~, ' ,5000);

Integracion con WebBroker (y WebSnap)


Hasta ahora, hemos construido aplicaciones IntraWeb independientes. Cuando
se crea una aplicacion IntraWeb en una biblioteca para desplegarla en ISS o
Apache, nos encontramos basicamente en la misma situacion. Sin embargo, si se
quiere usar el mod0 Page de IntraWeb, las cosas cambian de un mod0 importante.
Se trata de integrar una pagina de IntraWeb en una aplicacion Delphi WebBroker
(o WebSnap).
El puente entre 10s dos mundos es el componente IWPageProducer.Este
componente se conecta a una accion de WebBroker como cualquier otro compo-
nente productor de paginas y tiene un evento especial que puede usarse para crear
y devolver un formulario IntraWeb:
procedure TWebModulel.IWPageProducerlGetForm(ASender:
TIWPageProducer;
AWebApplication: TIWApplication; var VForm: TIWPageForm);
begin
VForm : = TformMain.Create(AWebApplication);
end;

Con esta sencilla linea de codigo (ademas de un componente IWModuleCon-troller


en el modulo Web), la aplicacion WebBroker puede incrustarse en una pagina IntraWeb,
como hace el programa CgiIntra. El componente IWModuleController propor-
ciona servicios centrales para el soporte de IntraWeb. Debe existir un componente de
este tip0 para que cada proyecto de IntraWeb funcione correctamente.

Global 24

Form 14

User. 14

Figura 21.5. La aplicacion IWSession tiene contadores globales y especlficos de


sesion, como puede verse ejecutando dos sesiones en dos navegadores distintos (o
incluso en el mismo navegador).
- - . -
ADVERTENCIA: La versibn que se incluye con Delphi 7 tiene un proble-
ma con el Web App Debugger de Delphi y el componente IWModuleCon-
troller. Ya se ha solucionado este problema y existe una actualizacion ,

gratuita.

Este es un resumen del archivo DFM del mbdulo Web del programa de ejemplo:
object WebModulel: TWebModulel
Actions = <
item
Default = True
Name = ' W e b A c t i o n I t e m l r
PathInfo = ' / s h o w '
OnAction = WebModulelWebActionItemlAction
end
item
Name = ' W e b A c t i o n I t e m . 2 '
PathInfo = ' / i w d e m o r
Producer = IWPageProducerl
end>
object IWModuleControllerl: TIWModuleController
object IWPageProducerl: TIWPageProducer
OnGetForm = IWPageProducerlGetForm
end
end

Ya que esta es una aplicacion CGI en mod0 Page, no hay ninguna gestion de
sesiones. Aun mas, el estado de 10s componentes de una pagina no se actualiza
automaticamente escribiendo controladores de eventos, como en un programa
IntraWeb estandar. Para conseguir el mismo efecto se necesita escribir codigo
especifico para manejar mas parametros de la peticion HTTP. Deberia quedar
claro incluso mediante este ejemplo tan sencillo que el mod0 Page hace menos
cosas de mod0 automatic0 que el mod0 Application, pero que es mas flexible. En
particular, el mod0 Page de IntraWeb permite aiiadir prestaciones de diseiio RAD
visual a las aplicaciones WebBroker y WebSnap.

Control de la estructura
El programa CgiIntra utiliza otra interesante tecnologia disponible en IntraWeb:
la definition de una estructura personalizada basada en HTML. (Este tema no
tiene realmente relacion, ya que las estructuras HTML tambien funcionan en
mod0 Application, pero, simplemente, se han usado estas dos tecnicas en un unico
ejemplo.) En 10s programas creados h a s h este momento, la pagina resultante es la
proyeccion de una serie de componentes colocados en tiempo de diseiio en un
formulario, en el que se pueden usar propiedades para modificar el codigo HTML
resultante. i Q u l es lo que sucederia si se deseara incrustar un formulario de
entrada de datos en una pagina HTML compleja? Es extrafio construir todo el
contenido de una pagina mediante componentes IntraWeb, incluso aunque se pue-
da usar el componente IWText para incrustar una porcion de HTML personaliza-
do en una pagina IntraWeb. La tecnica alternativa implica el uso de gestores de
estructura de IntraWeb. En IntraWeb se usa de manera invariable un gestor de
estructura; el predeterminado es el componente IWLayoutMgrForm. Las otras
dos alternativas son componentes IWTemplateProcessorHTML para trabajar con
un archivo de plantilla HTML externo e IWLayoutMgrHTML para trabajar con
HTML interno. Este segundo componente incluye un potente editor HTML que
puede usarse para preparar el HTML generic0 al igual que incrustar 10s compo-
nentes IntraWeb necesarios (algo que en ocasiones habra que hacer manualmente
con un editor HTML esterno). Aun mas, cuando se selecciona un componente
IntraWeb desde este editor (que se activa haciendo doble clic sobre un componen-
te IWLayoutMgrHTML), se podra utilizar el Object Inspector de Delphi para
personalizar las propiedades del componente. Como puede verse en la figura 2 1.6,
el HTML Layout Editor disponible en IntraWeb es un potente editor HTML
visual; el texto HTML que genera esta disponible en una pagina aparte. (El editor
HTML se mejorara en una proxima actualization, y se arreglaran unos cuantos
detalles.)

Html Example code


Font test

More text goes here, and you can type it directly.

j~nd
finally we have some text and a combo box within a grid..
1-

Figura 21.6. El HTML Layout Editor de IntraWeb es un cornpleto editor HTML visual.

En el HTML generado, el HTML define la estructura de Ia pagina. Los com-


ponentes solo se marcan con una etiqueta especial basada en Ilaves, como en el
ejemplo siguiente:
TRUCQ:Fijese mqw cuando se u w w , los c&p.ona@s nn otilizan
el posicioI3ami~& m ~ u t os b que ae ;listrihuyende acukdo con el IfIhlL.
Por eso, el form'dari~sc convierte hnipmente en up contenedor de compo-
nentes, porque se ignofa la posicib d o de 10s companentes del for-
mulario.
-
No hace falta decir que el HTML que se ve en el diseiiador visual del HTML
Layout Editor se corresponde de manera casi perfecta con el HTML que se
puede ver a1 ejecutar el programa en un navegador.

Aplicaciones Web de bases de datos


Como en las bibliotecas de Delphi, una parte importante de 10s controles dis-
ponibles en IntraWeb tienen que ver con el desarrollo de aplicaciones de bases de
datos. El Application Wizard de IntraWeb tiene una version que permite crear
una aplicacion con un modulo de datos (un buen punto de partida para el desarro-
110 de una aplicacion de bases de datos). En este caso, el codigo predefinido de la
aplicacion crea una instancia del modulo de datos para cada sesion, guardandolo
en 10s datos de sesion. Esta es la clase TUserSession predefinida (y su cons-
tructor) para una aplicacion IntraWeb con un modulo de datos:
type
TUserSession = class(TComponent)
public
DataModulel: TDataModulel;
constructor Create (AOwner: TComponent) ; override;
end;

constructor TUserSession.Create(AOwner: TComponent);


begin
inherited;
Datamodulel : = TDatamodulel.Create(AOwner);
end:

La unidad del modulo de datos no tiene asignada una variable global; si fuera
asi, todos 10s datos se compartirian entre todas las sesiones, con una gran posibi-
lidad de problemas en caso de peticiones concurrentes desde varios hilos. Sin
embargo, el modulo de datos ya expone una funcion global que tiene el mismo
nombre que la variable global que utilizaria Delphi, y que accede a1 modulo de
datos de la sesion actual:
function DataModulel: TDataModulel;
begin
Result : = TUserSession(RWebApp1ication.Data) .Datamodulel;
end;
Esto significa que se puede escribir un codigo como el siguiente:

Pero en lugar de acceder a un modulo de datos global, se utiliza el modulo de


datos de la sesion actual.
En el primer programa de ejemplo en el que se incluyen datos de una base de
datos, llamado IWScrollData, hemos afiadido a1 modulo de datos un componente
S i m p l e D a t a S e t y a1 formulario principal un componente IWDBGrid con la
siguiente configuracion:
o b j e c t IWDBGridl: TIWDBGrid
Anchors = [akLeft, akTop, akRight, akBottom]
Bordersize = 1
Cellpadding = 0
CellSpacing = 0
Lines = t l R o w s
UseFrame = False
DataSource = DataSourcel
FromStart = False
Options = [dgShowTitles]
RowAlternateColor = clSilver
RowLimit = 10
RowCurrentColor = clTeal
end

La configuracion mas importante es la eliminacion de un marco que albergue


el control con sus propias barras de desplazamiento (la propiedad ~ s e ~ r a m e ) ,
el hecho de que 10s datos se muestren a partir de la posicion del conjunto de datos
actual (la propiedad F r o m S t a r t ) y el numero de filas que se mostraran en el
navegador (la propiedad RowLimi t ) .
En la interfaz de usuario, hemos eliminado las lineas verticales y dado color a
filas salteadas. Tambien hemos especificado un color para la fila actual (la pro-
piedad R o w C u r r e n t C o l o r ) ; de no ser asi, 10s colores salteados no aparece-
rian correctamente, ya que la fila actual tiene el mismo color que las filas del
fondo, sin importar su posicion (si se fija la propiedad R o w C u r r e n t C o l o r
como c l N o n e se podra ver lo que queremos decir). Estos parametros producen
el efecto que muestra la figura 2 1.7. Tambien puede verse si se ejecuta el ejemplo
IWScrollData.
El programa abre el conjunto de datos cuando se crea el formulario, utilizando
el conjunto de datos enlazado con la fuente de datos actual.
procedure TformMain.IWAppFormCreate(Sender: TObject);
begin
DataSourcel.DataSet.0pen;
end;

El codigo relevante del ejemplo esta en el codigo del boton, que puede usarse
para recorrer 10s datos mostrando la pagina siguiente o volviendo a la anterior.
Este cs el codigo para uno de 10s dos metodos (el otro no se presenta porque es
muy parecido):
procedure TformMain.btnNextClick(Sender: TObject);
var
i: Integer;
begin
n P o s : = n P o s + 10;
if n P o s > DataSourcel.DataSet.RecordCount - 10 then
n P o s : = DataSourcel.DataSet.RecordCount - 10;
DataSourcel.DataSet.First;
for i : = 0 to nPos do
DataSource1.DataSet.Next;
end;

Figura 21.7. La cuadricula data-aware del ejemplo IWScrollData.

Enlaces con detalles


La cuadricula del ejemplo IWScrollData muestra una unica pagina de una
tabla de datos; 10s botones permiten desplazarse hacia arriba y abajo por las
paginas. Un estilo de cuadricula alternativo en IntraWeb es el que ofrecen las
cuadriculas con marcos, que pueden mover cantidades aun mas grandes de datos
hacia el navegador Web dentro de un area de pantalla de un tamaiio fijo utilizando
un marco y una barra de desplazamiento interna, como haria un control ScrollBox
de Delphi. Esto se demuestra en el ejemplo IWGridDemo.
El ejemplo personaliza la cuadricula de un segundo mod0 muy potente: esta-
blece la propiedad de conjunto c o l u m n s de la cuadricula. Este parametro per-
mite ajustar con precision el aspecto y el comportamiento de columnas especificas,
mostrando por ejemplo hipervinculo o controlando 10s clics sobre celdas de ele-
mentos o titulos. En el ejemplo IWGridDemo, una de las columnas (la del apelli-
do) se ha convertido en un hipervinculo; se pasa el numero de empleado como
parametro a1 comando de continuar, como muestra la figura 2 1.8.

Figura 21.8. El formulario principal del ejemplo IWGridDemo utiliza una cuadricula
con marco con hipervinculos hacia el formulario secundario.

El listado 2 1 . 1 , muestra un resumen de las propiedades clave de la cuadricula.


Fijese en particular en la columna del apellido, que tiene un campo enlazado (lo
que convierte a1 texto de la celda en un hipervinculo) y un controlador de evento
que responde a su seleccion. En este metodo; el programa crea un formulario
secundario mediante el cual el usuario puede editar 10s datos:
p r o c e d u r e TGridForm.IWDBGridlColurnns1C~ick(ASender: TObject;
c o n s t AValue: String) ;
begin
w i t h TRecordForm.Create (WebApplication) d o
begin
S t a r t I D : = AValue;
Show;
end;
end:

Listado 21.1. Propiedades de IWDBGrid en el ejemplo IWGridDemo.

o b j e c t IWDBGridl: TIWDBGrid
A n c h o r s = [akLeft, akTop, akRight, akBottom]
UseFrame = True
Usewidth = True
Columns = <
item
Alignment = taLeftJustify
BGColor = clNone
DoSubmitValidation = True
Font-Color = clNone
Font-Enabled = True
Font.Size = 10
Font-Style = [ I
Header = False
Height = '0'
VAlign = vaMiddle
Visible = True
Width = ' 0 '
Wrap = False
BlobCharLimit = 0
CompareHighlight = hcNone
DataField = ' F I R S T - N A M E '
Title.Alignment = taCenter
Title.BGColor = clNone
Title.DoSubmitVa1idation = True
Title.Font.Color = clNone
Tit1e.Font.Enabled = True
Title.Font.Size = 10
Title.Font.Style = [ I
Title.Header = False
Title-Height = '0'
Title.Text = ' F I R S T - N A M E '
Title.VAlign = vaMiddle
Title.Visible = True
Title.Width = '0'
Title.Wrap = False
end
item
DataField = 'LAST-NAME'
LinkField = 'EMP-NO '
OnClick = IWDBGridlColumnslClick
end
item
DataField = ' H I R E - D A T E '
end
item
DataField = ' JOB-CODE'
end
item
DataField = ' JOB-COUNTRY'
end
item
DataField = ' JOB-GRADE'
end
item
DataField = 'PHONE-EXT'
end>
Datasource = DataSourcel
Options = [dgShowTitles]
end

A1 establecer la propiedad Start I D del segundo formulario, se puede encon-


trar el registro apropiado:
procedure TRecordForm.SetStartID(const Value: string);
begin
FStartID : = Value;
DataSourcel .Dataset .Locate ( 'EMP-NO', Value, [ I ) ;
end;

otras operaciones sobre la columna.

El formulario secundario esta enlazado con el mismo modulo de datos que el


formulario principal. Por eso; despues de actualizar 10s datos de la base de datos,
se pueden ver en la cuadricula (pero las actualizaciones se guardan solo en memo-
ria. porque el programa no realiza una llamada a ApplyUpdates). El formula-
rio secundario utiliza unos cuantos controles de edicion y un navegador,
proporcionado por IntraWeb. La figura 2 1.9 muestra este formulario en tiempo
de ejecucion.

ma- - u

Last Name estan

Hire 1111 711 990

Figura 21.9. El formulario secundario del ejemplo IWGridDemo permite que un


usuario edite 10s datos y explore 10s registros.
Transporte de datos al cliente
Sin tener en cuenta como se utilice, el componente IWDBGrid produce HTML
con 10s datos de la base de datos incrustados en las celdas, per0 no puede trabajar
con 10s datos en el lado del cliente. Un componente distinto (o un conjunto de
componentes de IntraWeb) permite utilizar un modelo distinto. Los datos se en-
vian a1 navegador en un formato personalizado, y el codigo JavaScript del
navegador rellena la cuadricula y trabaja con 10s datos, pasando de un registro a
otro sin solicitar mas datos a1 servidor.
Se pueden usar varios componentes IntraWeb para una aplicacion cliente,
per0 estos son algunos de 10s mas importantes:
IWClientSideDataSet: Un conjunto de datos en memoria que se define
fijando las propiedades ColumnName y Data en el codigo del programa.
En futuras actualizaciones se podra editar datos en el cliente, ordenarlos,
filtrarlos, definir estructuras maestro-detalle y muchas cosas mas.
IWClientSideDataSetDBLink: Un proveedor de datos que puede conec-
tarse a cualquier conjunto de datos de Delphi, conectandolo con la propie-
dad Datasource.
IWDynGrid: Un componente de cuadricula dinamica conectado con uno
de 10s dos componentes anteriores mediante la propiedad D a t a . Este com-
ponente lleva todos 10s datos a1 navegador y puede trabajar con ellos en el
cliente mediante JavaScript.
Existen otros componentes de cliente en IntraWeb, como IWCSLabel,
IWCSNavigator e IWDynamicChart (que solo funciona con Internet Explorer).
Como un ejemplo del uso de esta tecnica, hemos construido el ejemplo
IWClientGrid. El programa tiene poco codigo, per0 que hay mucho preparado
para su uso en 10s componentes. Estos son 10s elementos centrales de su formula-
rio principal:
object formMain: TformMain
SupportedBrowsers = [brIE, brNetscape61
OnCreate = IWAppFormCreate
object IWDynGridl: TIWDynGrid
Align = alClient
Data = IWClientSideDatasetDBLinkl
end
object DataSourcel: TDataSource
Left = 72
Top = 8 8
end
object IWClientSideDatasetDBLinkl: TIWClientSideDatasetDBLink
Datasource = DataSourcel
end
end
El conjunto de datos procedente del modulo de datos se conecta con el
Datasource cuando se crea el formulario. La cuadricula resultante, que muestra
la figura 2 1.10, permite ordenar 10s datos en cualquier celda (mediante la peque-
iia flecha que se encuentra despues del titulo de cada columna) y filtrar 10s datos
mostrados seglin uno de 10s valores posibles del campo. Por ejernplo. en la figura
se pueden ordenar 10s datos de empleado de acuerdo con el apellido y filtrarlos
por pais y categoria laboral.

lC' LI I
TJ Green 4 USA
Tcm Lcc 4 USA
Luke Leung 4 USA
Carol Nordskom 4 USA
Mary pas 4 USA
Leshe Phong 4 USA
K J Wcrton 4 USA
Randy Wdhams 4 USA
Mchael Yanowslu 4 USA

Figura 21.10. La cuadricula del ejernplo IWClientGrid soporta la ordenacion y filtrado


personalizados sin tener que volver a traer 10s datos desde el servidor Web.

Esta caracteristica es posible porque 10s datos se llevan a1 navegador dentro


del codigo JavaScript. Este es un fragmente de uno de 10s guiones incrustados en
el codigo HTML de la pagina:
< s c r i p t language="Javascriptl.Z">
var IWDYNGRIDl-Titlecaptions =
[ "EMP-NO", "FIRST-NAME ","LAS T-NAME", "PHONE-EXT",
"DEPT-NO " ,"JOB- CODE " ,"JOB-GRADE " ,"JOB- COUNTRY "1 ;
var IWDYNGRIDl-Cellvalues = new Array();
IWDYNGRID1-CellValues[O] =
[ Z , 'Robert', 'Nelson', '332', '600', 'VP',21 'USA'] ;
IWDYNGRID1-CellValues[l] =
[ 4 , 'Bruce', 'Young', '233', '621 ', ' E n g ' , 2 1' U S A ' ] ;
IWDYNGRIDl-CellValues[Z] =
[ 5 , 'Kim', 'Lambert ', '22', ' 1 3 O 1 ,' E n g 1 , 2 ,'USA'];
IWDYNGRID1-Cellvalues [3] =
[8, 'Leslie', 'Johnson', '410', ' 1 8 0 r ,'Mktg',3, 'USA'] ;
IWDYNGRID1-CellValues[4] =
[ 9 , 'Phil', 'Forest', '229', ' 6 2 Z 1 ,' M n g r ' , 3 1' U S A ' ] ;

El motivo para utilizar este enfoque basado en JavaScript en lugar de un enfo-


que basado en XML como el utilizado en otras tecnologias parecidas, es que solo
Internet Explorer ofrecer soporte para islas de datos XML. Mozilla y Netscape
carecen de esta caracteristica y tienen un soporte muy limitado de XML.
tecnolog~as
XML

Crear aplicaciones para Internet significa usar protocolos y crear interfaces de


usuario basadas en navegadores, como en 10s dos capitulos anteriores, per0 tam-
bien abre una oportunidad para el intercambio de documentos de negocio
electronicamente. Los estandares que surgen para este tip0 de actividad se cen-
tran en el formato de documento XML e incluyen el protocolo de transmision
SOAP, 10s esquemas XML para la validacion de documentos y XSL para repre-
sentar documentos como HTML.
En este capitulo, comentaremos las principales tecnologias XML y el amplio
soporte que Delphi les ofrece desde su version 6. Ya que el conocimiento sobre
XML no esta muy extendido, vamos a ofrecer una pequeiia presentacion sobre
cada tecnologia, per0 deberian consultarse libros dedicados especialmente a estas
tecnologias para aprender mas. En el capitulo 23 nos centraremos de manera
especifica en 10s servicios Web y SOAP.
En este capitulo se tratan 10s siguientes temas:
Presentacion de XML: Extensible Markup Language
Trabajo con un DOM XML.
Delphi y XML: interfaces y proyeccion.
Procesamiento de XML con SAX.
Internet Express.
Uso de XSLT.
XSL en WebSnap.

Presentacion de XML
El lenguaje extensible de marcas (extens~bleMarkup Language, XML) es una
version simplificada de SGML y recibe mucha atencion en el mundo de las tecno-
logias de la informacion. XML es un lenguaje de marcas, que quiere decir que
utiliza simbolos para describir su propio contenido (en este caso, etiquetas que
consistente en un texto definido de manera especial, encerrado entre 10s caracte-
res < y >). Es extensible porque permite usar marcas libres (en contraste con, por
ejemplo, HTML, que tiene marcas predefinidas). El lenguaje XML es un estandar
promocionado por el World Wide Web Consortium (W3C). La recomendacion
XML puede encontrarse en www.w3.org/TR/REC-xml.
Se ha llamado a XML el ASCI del aiio 2000, para indicar que es una tecnolo-
gia simple y muy extendida y tambien que un documento XML es un archivo de
texto plano (de manera opcional con caracteres Unicode en lugar de simple texto
ASCII). La caracteristica mas importante de XML es que es descriptivo, ya que
cada etiqueta tiene un nombre casi legible para un humano. Este es un ejemplo, en
caso de que jamas se haya visto un documento XML:
<book>
<title>La biblia de Delphi 7</title>
<author>Cantu</author>
<publisher>Anaya</publisher>
</book>

XML presenta unas cuantas desventajas que estaria bien resaltar desde el
principio. La mas importante es que sin una descripcion formal, un documento
vale de poco. Si se quiere intercambiar documentos con otra empresa, hay que
llegar a un acuerdo sobre lo que significa cada etiqueta y tambien sobre el signi-
ficado semantic0 del contenido. (Por ejemplo, cuando se tiene una cantidad, hay
que acordar el sistema de medida o incluirlo en el documento.) Otra desventaja es
que 10s documentos XML son mucho mayores que otros formatos. Por ejemplo,
usar cadenas para 10s numeros no es nada eficiente, y las etiquetas de apertura y
cierre ocupan mucho espacio. Lo bueno es que XML se comprime muy bien, por
el mismo motivo.

Sintaxis XML basica


Merece la pena conocer algunos elementos tecnicos de XML antes de analizar
su uso en Delphi. Este es un resumen de 10s elementos clave de la sintaxis XML:
Los espacios en blanco (corno el caracter de espacio, el retorno de carro, el
salto de linea y 10s tabuladores) generalmente se ignoran (corno en un
documento HTML). Es importante dar formato a un documento XML para
que resulte legible, per0 a 10s programas no les importara demasiado.
Se pueden aiiadir comentarios dentro de las marcas < ! -- y -->, que, en
esencia, ignoran 10s procesadores de XML. Existente tambien directivas e
instrucciones de proceso, encerradas entre las marcas < ? y ? > .
Existen unos pocos caracteres especiales o reservados que no pueden usar-
se en el texto. Los dos unicos simbolos que no pueden usarse jamas son el
caracter menor que (<, usado para delimitar una marca), que se sustituye
por & 1t ; y el caracter ampersand (&), que se sustituye por &amp ; (y es
la evolucion grafica del et latino). Otros caracteres especiales optativos
son & g t ; para el simbolo mayor que (>), & apo s ; para la comilla simple
( ' ) y &quot para la comilla doble (").
Para aiiadir contenido que no sea XML (por ejemplo, informacion binaria
o un guion), se puede usar una seccion CDATA, delimitada por
< ! [CDATA[ y ] I > .
Todas las etiquetas se encuentran entre 10s simbolos menor y mayor que, <
y >. Las marcas son sensibles a las mayusculas (no como en HTML).
Por cada marca de apertura, debe existir una marca de cierre correspon-
diente, indicada por un caracter inicial de barra inclinada:

Las marcas no pueden solaparse: deben anidarse correctamente, como en


la primera linea que se muestra (la segunda linea no es correcta):
<node>xx <nested> yy</nested> </node> / / correct0
<node>xx <nested> yy</node> </nested> / / erroneo

Si una marca no tiene contenido (pero su presencia resulta importante),


pueden sustituirse las marcas de apertura y cierre por una marca unica que
incluye una barra inclinada final: <node / >.
Las marcas pueden tener atributos, usando varios nombres de atributos
seguidos de un valor encerrado entre comillas:

Cualquier nodo XML puede tener varios atributos, varias etiquetas incrus-
tadas y un unico bloque de texto que representa el valor del nodo. Es
habitual que 10s nodos XML tengan un valor textual o etiquetas incrusta-
das, y no ambas variantes. Este es un ejemplo de la sintaxis completa de un
nodo:
Un nodo puede tener varios nodos hijo con la misma etiqueta (las etiquetas
no tiene por que ser unicas). Los nombres de atributos son unicos para
cada nodo.

XML bien formado


Los elementos comentados en la seccion anterior definen la sintaxis de un
documento XML, per0 no bastan. Un documento XML se considera correcto
sintacticamente, o bien formato, si sigue unas cuantas reglas adicionales. Fijese
en que este tipo de comprobacion no garantiza que el contenido del documento sea
significative, solo que las etiquetas esten bien dispuestas.
Cada documento deberia tener un prologo que indica que es de hecho un docu-
mento XML, q u i version de XML cumple y posiblemente el tip0 de codificacion
de 10s caracteres. Este es un ejemplo:

Entre las codificaciones posibles hay conjuntos de caracteres Unicode (como


UTF-8, UTF- 16 y UTF-32) y algunas codificaciones I S 0 (como ISO- 10646-xxx
o 1SO-8859-xss). El prologo tambien puede incluir declaraciones externas, el
esquema usado para validar el documento, declaraciones de espacios de nombre,
un archivo XSL asociado y algunas declaraciones de entidades internas. Consulte
documentacion o libros sobre XML para conseguir mas informacion sobre estos
temas. Un documento XML esta bien formado si tiene un prologo, tiene una
sintaxis correcta (segun las reglas de la seccion anterior) y tiene un arb01 de nodos
dentro de una raiz unica. La mayoria de las herramientas (como Internet Explorer)
comprueban si un documento esta bien formado a1 cargarlo.

NOTA: XML es miis formal y precis0 que HTML.El W3C trabaja en un


estandar XHTML que hara que los docurnentos HTML sean confonnes con
XML, para que las herramientas XML 10s prowsen mejor. Esta implica
muchos carnbios en un docurnento HTML tipico, d o evitar 10s atributos
sin valores, a f d i r todas las marcas de cierie (wmo m </p> y </li>),
ahdir la barra invertida para marcas independientes (cam0 <hr / > y €br/
>), un anidado correcto y muchas cosas mais. El sitio Web de W3C alberga
u n cnnvercnr de HTMT, a XHTMT. Ilnmdn HTMI. Tidv en www w 3 mnl
Trabajo con XML
Para acostumbrarse al formato de XML, se puede usar uno de 10s editores
XML disponibles en el mercado (incluidos Delphi y Context, un editor para pro-
gramadores escrito en Delphi). Cuando se carga un documento XML en Internet
Explorer, se puede ver si es correct0 y, en ese caso, visualizarlo en el navegador
con una estructura en arbol. (En el momento de escribir esto, otros navegadores
tienen un soporte XML mas limitado.)
Para acelerar este tipo de operacion, hemos creado el editor XML mas simple
posible, basicamente un campo de memo con comprobacion de sintaxis XML y un
navegador conectado. El ejemplo XmlEditOne tiene un Pagecontrol con tres pa-
ginas. La primera pagina, Settings, contiene un par de componentes en 10s que se
puede escribir la ruta y el nombre del archivo con el que se quiere trabajar. (El
motivo de no utilizar un dialog0 estandar quedara claro cuando mostremos una
ampliacion del programa.) El cuadro de edicion que contiene el nombre completo
del archivo se actualiza automaticamente con la ruta y el nombre de archivo, si
esta seleccionado el cuadro de verificacion Autoupdate.
La segunda pagina contiene un control de memo; se carga y se guarda el texto
del archivo XML haciendo clic sobre 10s dos botones de la barra de herramientas.
En cuanto se carga el archivo, o cada vez que se modifica su texto, su contenido
se carga en un DOM para que un analizador sintactico o parser compruebe su
correccion (algo que seria complejo hacer con codigo propio). Para procesar el
codigo, hemos usado el componente XMLDocument disponible en Delphi, que es
basicamente un envoltorio de un DOM disponible en el ordenador e indicado
mediante su propiedad ~ b ~ v e n d oComentaremos
r. el uso de este componente
con mayor detalle en la siguiente seccion. Por el momento, baste con decir que se
puede asignar una lista de cadena a esta propiedad XML y activarla para permitir
que procese el texto XML e informe tal vez de un error mediante una excepcion.
Para este ejemplo, este comportamiento dista de ser bueno, ya que mientras se
escribe el codigo XML se tendra codigo XML temporalmente incorrecto. Aun asi,
hemos preferido no pedir a1 usuario que haga clic sobre un boton para realizar la
validacion, sino ejecutarla de manera continua. Ya que no es posible inhabilitar la
excepcion de proceso lanzada por el componente XMLDocument, hemos tenido
que trabajar a un nivel mas bajo, extrayendo la propiedad DOMPersist (que
hace referencia a la interfaz de permanencia de DOM) despues de extraer la
interfaz IXMLDo cumentAccess del componente XMLDocument, llamado
XmlDoc en este codigo. Tambien puede extraerse la interfaz I DOMPars eError
del componente del documento, para mostrar cualquier mensaje de error en la
barra de estado:
procedure TFormXmlEdit.MemoXmlChange(Sender: TObject);
var
eParse: IDOMParseError;
begin
XmlDoc.Active : = True;
xmlBar. Panels [l] .Text := 'OK';
.
xmlBar Panels [2] .Text : = ' ';
.
(XmlDoc as IXMLDocumentAccess) .DOMPersist loadxml (MemoXml .Text) ;
eParse := (XmlDoc.DOMDocument as IDOMParseError) ;
i f eParse. errorcode <> 0 then
with eParse do
begin
xmlBar. Panels [1] .Text : = 'Error in: ' + IntToStr
(Line) + '. ' + IntToStr (LinePos);
xmlBar. Panels [2] .Text : = SrcText + ': ' + Reason;
end;
end;

La figura 22.1 muestra un ejemplo de la salida del programa, junto con la vista
en arb01 XML que ofrece la tercera pagina (para un documento correcto). La
tercera pagina del programa se construyo mediante el componente WebBrowser,
que incluye un control ActiveX de Internet Explorer. Lamentablemente, no esiste
un mod0 direct0 de asignar una cadena con testo XML a este control, por lo
quc habra que guardar el archivo en primer lugar para luego pasar a esta pagina
para iniciar la carga del XML en el navegador (despues de hacer clic a mano
sobre el boton Refresh a1 menos una vez).

Figura 22.1. El ejemplo XmlEditOne permite escribir texto XML en un componente de


memo, indicando 10s errores durante la escritura y mostrando el resultado en el
navegador incluido.

- . - -- .- - -

NOTA: Hemos utilizado este codigo como punto de partida para crear un
editor XML cornpleto llamado XrnlTypist. Incluye resaltado de sintaxis,
soporte XSLT y unas cuantas caracteristicas adicionales. En el apCndice A
se puede consultar la disponibilidad de este editor XML gratuito.

Manejo de documentos XML en Delphi


Ahora que ya se conocen 10s elementos principales de XML, podemos comen-
zar a analizar como se manejan 10s documentos XML en programas Delphi (o en
programas en general, ya que algunas de las tecnicas que vamos a ver van mas
alla del lenguaje que se utilice). Hay dos tecnicas basicas para manipular docu-
mentos XML: utilizar una interfaz de modelo de objeto de documento (Document
Object Model, DOM) o utilizar una API para XML sencilla (Simple API for
XML, SAX). Los dos enfoques son bastante distintos:
DOM: Carga un documento completo en un arbol jerarquico de nodos, lo
que nos permite leerlos y manipularlos para modificar el documento. Por
ello, el DOM es aconsejable para navegar por la estructura XML en me-
moria, editarla e incluso para crear documentos completamente nuevos.
SAX: Analiza sintacticamente el documento, lanzando un evento para cada
elemento del documento sin crear ninguna estructura en memoria. Despues
de que SAX haya analizado el documento, este se pierde, per0 este mod0
de funcionamiento suele ser mucho mas rapido que crear el arbol DOM.
Usar SAX esta bien si el documento se va a leer de una vez, por ejemplo, si
se busca una parte de sus datos.
Existe una tercera posibilidad para manipular (y en concreto para crear) docu-
mentos XML: el manejo de cadenas. Crear un documento aiiadiendo cadenas es,
sin duda alguna, la operacion mas rapida si podemos dar una sola pasada (y no
necesitamos modificar 10s nodos ya generados). Incluso la lectura de documentos
por medio de funciones de cadenas es muy rapida, per0 puede complicarse para
estructuras complejas.
Ademas de estos enfoques clasicos del procesamiento de XML, que tambien
estan disponibles para otros lenguajes de programacion, Delphi 6 proporciona
dos tecnicas mas que deberiamos tener en cuenta. La primera es la definicion de
interfaces que proyectan la estructura del documento y que se utilizan para acce-
der a1 mismo en lugar de hacerlo a traves de la interfaz generica de DOM. Como
veremos, este metodo contribuye a una codification mas rapida y aplicaciones
mas solidas. La segunda tecnica es el desarrollo de transformaciones que nos
permitan leer un documento XML generic0 dentro de un componente ClientDataSet
o guardar el conjunto de datos en un archivo XML con una estructura dada (no en
la estructura XML especifica que soporta nativamente el ClientDataSet o MyBase).
No vamos a tratar de decidir que opcion es la que mejor se adapta a cada tip0
de documento y manipulacion, per0 resaltaremos algunas de las ventajas e incon-
venientes mientras analizamos ejemplos de cada enfoque en las secciones siguien-
tes. A1 final del capitulo, analizaremos la velocidad relativa de las tecnicas para el
procesamiento de grandes archivos.

Programacion con DOM


Ya que un documento XML tiene una estructura parecida a un arbol, cargar un
documento XML en un arbol en memoria es una operacion bastante natural. Esto
es lo que hace DOM. DOM es una interfaz estandar, por lo que cuando se ha
escrito codigo que utiliza un arb01 DOM, podemos cambiar de implementacion de
DOM sin alterar el codigo fuente (a1 menos si no hemos utilizado extensiones
personalizadas).
En Delphi se pueden instalar varias implementaciones de DOM, disponibles
como servidores COM, y utilizar sus interfaces. Uno de 10s motores DOM mas
utilizados en Windows es el que proporciona Microsoft como parte del MSXML
SDK, per0 que tambien instala Internet Explorer (y por ello todas las versiones
recientcs de Windows) y muchas otras aplicaciones de Microsoft. (Con el MSXML
SDK cornpleto tambien se incluye documentacion y ejemplos bastante detallados
que no se conseguiran en otras instalaciones de la misma biblioteca incluidas con
otras aplicaciones.) Otros motores DOM disponibles directamente en Delphi 7
son Xerces, de la fundacion Apache y OpenXML, de codigo abierto.

TRUCO: OpenXML es un motor DOM nativo en Object Pascal disponible


en www.philo.de/xml. Otro motor DOM nativo en Delphi lo ofrece
.
Turbopower. Estas soluciones tienen dos ventajas. No necesitan una bi-
.. . . . a I
ouoteca externa para que se ejecure el programs, ya que el componente
DOM se compila con la aplicacion; y son multiplataforma.

Delphi incluye las implementaciones DOM en un componente envoltorio Ila-


mado XMLDocument. Hemos usado este componente en el ejemplo anterior, per0
esaminaremos su papel en un aspect0 mas general. La idea de usar este compo-
nente en lugar de la interfaz DOM es permanecer independientes de las
implementaciones y poder trabajar con metodos simplificados, o auxiliares.
El uso de la interfaz DOM es bastante complejo. Un documento es un conjunto
de nodos, cada uno con un nombre, un elemento de texto, un conjunto de atributos
y un conjunto de nodos hijo. Cada conjunto de nodos permite el acceso a 10s
elementos a traves de su posicion o buscandolos por nombre.
Observese que el texto que se encuentra dentro de las etiquetas de un nodo, si
hay alguno, se representa como un hijo como del nodo y se listara en el conjunto
de nodos hijo. El nodo raiz tiene algunos metodos adicionales para crear nuevos
nodos, valores o atributos. Con el XMLDocument de Delphi podemos trabajar a
dos niveles:
A un nivel inferior, podemos utilizar la propiedad DOMDocument (del
tip0 de interfaz ~DOMDocument)para acceder a la interfaz estandar
W3C Document Object Model. La interfaz DOM oficial se define en la
unidad xmldom e incluye interfaces como IDOMNode, IDOMNodeList,
IDOMAttr, IDOMElement e IDOMText. Con las interfaces DOM oficia-
les, Delphi soporta un modelo de programacion estandar per0 de bajo ni-
vel. La implementacion de DOM la indica el componente XMLDocument
en la propiedad DOMVendor.
A un nivel superior, el componente XMLDocument implementa tambien la
interfaz IXMLDocument. Se trata de una API personalizada del tip0 de
DOM definida por Borland en la unidad XMLIntf y que incluye interfaces
como IXMLNode, IXMLNodeList e IXMLNodeCollection. Esta interfaz
de Borland simplifica algunas de las operaciones de DOM sustituyendo
varias llamadas a metodos, que suelen repetirse a mod0 de secuencia, por
una sola propiedad o metodo.
En 10s siguientes ejemplos (sobre todo en el ejemplo DomCreate), utilizaremos
ambos enfoques para dar una mejor idea de las diferencias practicas entre ambos.

Un documento XML en una TreeView


Normalmente, el punto de partida consiste en cargar un documento desde un
archivo o crearlo a partir de una cadena, per0 tambien podemos empezar con un
documento completamente nuevo. Como primer ejemplo de la utilizacion de DOM,
hemos creado un programa que carga un documento XML en un DOM y muestra
su estructura en un control TreeView. Tambien hemos aiiadido a1 programa
XmlDomTree unos botones con codigo de muestra usados para acceder a 10s
elementos de un archivo de prueba, como un ejemplo del acceso a 10s datos DOM.
Cargar el documento es sencillo, per0 mostrarlo en un arb01 requiere una funcion
recursiva que recorra 10s nodos y subnodos. Este es el codigo para 10s dos meto-
dos :
p r o c e d u r e TFormXmlTree.btnLoadClick(Sender: TObject);
begin
0penDialogl.InitialDir : = ExtractFilePath
( A p p l i c a t i o n .ExeName) ;
i f 0penDialogl.Execute t h e n
begin
XMLDocumentl.LoadFromFile(OpenDialogl.Fi1eName);
Treeviewl.1tems.Clear;
DomToTree (XMLDocumentl.DocumentElement, nil);
TreeViewl-FullExpand;
end;
end:

p r o c e d u r e TFormXmlTree.DomToTree (XmlNode: IXMLNode; TreeNode:


TTreeNode) ;
var
I: Integer;
NewTreeNode: TTreeNode;
NodeText: string;
AttrNode: IXMLNode;
begin
// omite nodos d e texto y otros casos especiales
i f (XmlNode.NodeType <> ntElement) t h e n
Exit;
// afiade e l p r o p i o nodo
NodeText : = XmlNode.NodeName;
i f XmlNode.1sTextElement then
NodeText : = NodeText + ' = ' + XmlNode.NodeValue;
NewTreeNode : = TreeViewl.Items.AddChild(TreeNode, NodeText);
// a t i a d e s u s a t r i b u t o s
f o r I := 0 t o xmlNode.AttributeNodes.Count - 1 d o
begin
AttrNode : = xmlNode.AttributeNodes.Nodes[I];
TreeViewl.Items.AddChild(NewTreeNode,
' I ' + AttrNode.NodeName + ' = " ' + AttrNode.Text + " ' 1 ' ) ;
end;
// afiade cada nodo h i j o
i f XmlNode.HasChildNodes then
f o r I : = 0 t o xmlNode.ChildNodes.Count - 1 d o
DomToTree (xmlNode.Chi1dNodes.Nodes [I], NewTreeNode);
end;

Este codigo es bastante interesante ya que resalta algunas de las operaciones


que podemos realizar con un DOM. En primer lugar, cada nodo tiene una propie-
dad NodeType que podemos usar para determinar si el nodo es un elemento, un
atributo, un nodo de testo o una entidad especial (como CDATA y otras). Ade-
mas, no podemos acceder a la representacion textual del nodo, su Nodevalue, a
menos que tenga un elemento de testo (el nodo de texto se omitira, como compro-
bacion inicial). Despues de mostrar el nombre del elemento y el valor del testo, si
esta disponible, el programa muestra directamente el contenido de cada atributo y
de cada subnodo llamando de manera recursiva a1 metodo DomToTree (vease
figura 22.2).

a u h r = Canlu
i
3 book
title = Delphi Devdoper'sHandbook
aulhol = Canlu
aulhor = Gaoch
r3boak
litle = MarletingDelph~6
aulho~= Canlu
@i:book
E book
El ebwk
l i b = EssenlidPascd
utl = hllp:Nwww.marwcantucorn
wthm = Canlu
8 ebmk
title = Thinking in Java
url = hllp:llwww,mindview.com
aulhu = Eckel

Figura 22.2. El ejernplo XmlDomTree puede abrir un documento XML generic0 y


mostrarlo dentro de un control TreeView cornun.
Una vez que hayamos cargado el documento de muestra que acompaiia a1
programa XmlDomTree (mostrado en el listado 22.1) en el componente
XMLDocument, podemos utilizar diversos metodos para acceder a nodos generi-
cos, como en el anterior codigo de construccion del arbol, o buscar elementos
especificos. Por ejemplo podemos obtener el valor del atributo t e x t del nodo raiz
si escribimos:
XMLDocumentl.DocumentElement.Attributes ['text']

Hay que tener en cuenta que si no hay ningun atributo llamado t e x t , la llamada
fallara con un mensaje de error generico: "Invalid variant type conversion" (con-
version de tip0 variante invalida). Si necesitamos acceder a1 primer atributo de la
raiz y no conocemos su nombre, podemos utilizar el siguiente codigo:

Para acceder a 10s nodos, utilizamos una tecnica similar, aprovechandonos


posiblemente de la matriz ChildValues.Se trata de una extension de Delphi a
DOM, que nos permite pasar como parametro el nombre del elemento o su posi-
cion numerica:

Este codigo consigue el (primer) autor del segundo libro. No podemos utilizar
la expresion Chi 1 dVa lues [ ' book ' ] , ya que hay varios nodos con el mismo
nombre bajo el nodo raiz.

Listado 22.1. El docurnento XML de muestra utilizado en 10s ejernplos de este


capitulo.

<?xml version="l.OW encoding="UTF-8"?>


<books t e x t = " B o o k s W >
<book>
<title>La biblia de Delphi 7</title>
<author>Cantu</author>
</book>
<book>
<title>Delphi Developer's Handbook</title>
<author>Cantu</author>
<author>Gooch</author>
</book>
<book>
<title>Delphi COM Programming</title>
<author>Harmon</author>
</book>
<book>
<title>Thinking i n C++</title>
<author>Eckel</author>
</book>
Creacion de documentos utilizando DOM
Aunque hemos mencionado anteriormente que se puede crear un documento
XML agrupando cadenas, Csta tecnica no es la mas robusta. Usar DOM para
crear un documento garantiza que el XML estara bien formado. Ademas, si a1
DOM se el adjunta una definition de esquema, podemos validar la estructura del
documento mientras le aiiadimos datos.
Para resaltar 10s diferentes casos de creacion de un documento, hemos cons-
truido el ejemplo DomCreate. Este programa puede crear documentos XML
dentro del DOM, mostrando su texto en un campo de memo y, opcionalmente, en
una TreeView

para mejorar la salida al memo del texto XML, fo&tehdolo m$or. Pode-
mos escoger el- tip0- de.-
sangrado estqbleciendo
--d
la
-.
propie* ..$ Node-
*
nco, tamb16 podemos
.blecidos dos espacios
,no hay forma alguna

El primer boton del formulario, Simple, crea un texto XML sencillo utilizando
las interfaces oficiales de bajo nivel de DOM. El programa llama a1 metodo
creat eElement del documento para cada nodo, aiiadiendolos como hijos de
otros nodos:
procedure TForml.btnSimpleClick(Sender: TObject);
var
iXml: IDOMDocument;
iRoot, iNode, iNode2, iChild, iAttribute: IDOMNode;
begin
/ / v a c i a e l docurnento
XMLDoc.Active : = False;
XMLDoc. XML-Text := ";
XMLDoc.Active : = True;
/ / raiz
iXml : = XmlDoc.DOMDocument;
iRoot : = iXml.appendChild (iXml.createElement ( ' x m l ' ) ) ;
/ / nodo "test"
iNode : = iRoot.appendChild (iXml.createElement ('test'));
iNode.appendChild (iXml.createElement ('test2'));
iChild : = iNode.appendChild (iXml.createElement ('test3'));
iChild. appendchild (iXml.createTextNode ( 'simple value ' ) ) ;
iNode.insertBefore (iXml.createElement ('testd'), iChild);

/ / replica de nodo
iNode2 : = iNode. cloneNode (True);
iRoot.appendChild (iNode2);

/ / adade un atrlbuto
iAttribute .nodevalue : = 'red ';
iNode2.attributes.setNamedItem (iAttribute);

/ / muestra XML en el memo


Memol.Lines.Text : = FormatXMLData (XMLDoc.XML.Text);
end;

Fijese en que 10s textos de 10s nodos se aiiaden explicitamente, que 10s atribu-
tos se crean con una llamada de creacion especifica y que el codigo utiliza
cloneNode para hacer una replica de una rama entera del arbol. Globalmente,
la escritura del codigo es un poco engorrosa, per0 se acostumbrara a1 estilo. El
efecto del programa se muestra en la figura 22.3 (con formato en el memo y en el
arbol).

9x d
E test
test2
test4
test3 = s~mplevalue
E test
[color ="red"]
test2
test4
lest3 = s~molevalue

Figura 22.3. El ejemplo DomCreate puede generar diferentes tipos de documentos


XML utilizando un DOM.
El segundo ejemplo de creacion de DOM tiene que ver con un conjunto de
datos. Hemos aiiadido a1 formulario un componente de conjunto de datos dbExpress
(pero habria semido cualquier otro conjunto de datos) y agregado tambien la
llamada a1 procedimiento personalizado DataSetToDOM a un boton, de la si-
guiente manera:
DataSetToDOM ('customers', ' c u s t o m e r ' , XMLDoc, SQLDataSetl);

El procedimiento Da t aSet ToDOM crea el nodo raiz con el texto del primer
parametro, coge cada registro del conjunto de datos, define un nodo con el segun-
do parametro y agrega un subnodo para cada campo del registro utilizando un
codigo extremadamente generico:
p r o c e d u r e DataSetToDOM (RootName, RecordName: string; XMLDoc:
TXMLDocument;
DataSet: TDataSet) ;
var
iNode, iChild: IXMLNode;
i: Integer;
begin
DataSet.Open;
Dataset-First;

// r a i z
XMLDoc.DocumentElement := XMLDoc.CreateNode (RootName);

// afiade d a t o s d e t a b l a
w h i l e n o t DataSet .EOF d o
begin
// a d a d e u n n o d o p a r a c a d a r e g i s t r o
iNode : = XMLDoc.DocumentElement.AddChild (RecordName);
f o r I : = 0 t o DataSet.FieldCount - 1 d o
begin
// a f i a d e u n e l e m e n t o p a r a c a d a c a m p o
iChild : = iNode.AddChild (DataSet.Fields[i].FieldName);
iChild.Text : = DataSet.Fields[i].AsString;
end;
DataSet.Next;
end;
DataSet.Close;
end;

El codigo anterior utiliza las interfaces de acceso simplificado de DOM que


proporciona Borland, que incluyen un nodo AddChi ld que crea el subnodo, y el
acceso direct0 a la propiedad Text para definir un nodo hijo con contenido
textual. Esta rutina extrae una representacion XML del conjunto de datos, ofre-
ciendo muchas posibilidades para la publicacion Web, como veremos en la sec-
cion sobre XSL.
Otra interesante posibilidad es la generacion de documentos XML que descri-
ban objetos Delphi. El programa DomCreate tiene un boton que se utiliza para
describir algunas propiedades de un objeto usando, una vez mas, el DOM de bajo
nivel :
procedure AddAttr (iNode: IDOMNode; Name, Value : string) ;
var
iAttr: IDOMNode;
begin
iAttr : = iNode.ownerDocument.createAttribute (name);
iAttr.nodeValue : = Value;
iNode.attributes.setNamed1tem (iAttr);
end;

procedure TForml.btnObjectClick(Sender: TObject);


var
iXml: IDOMDocument ;
iRoot: IDOMNode;
begin
// v a c i a e l documento
XMLDoc.Active : = False;
XMLDoc. XML.Text : = ' ';
XMLDoc-Active : = True;

// r a i z
iXml := XmlDoc. DOMDocument ;
iRoot : = iXml.appendChild (iXm1-createElement ( ' B u t t o n l ' ) ) ;

/ / a l g u n a s p r o p i e d a d e s como a t r i b u t o s ( t a m b i e n p o d r i a n s e r
// n o d o s )
AddAttr (iRoot, ' N a m e ' , Buttonl-Name);
AddAttr (iRoot, ' C a p t i o n ' , Buttonl .Caption);
AddAttr (iRoot, ' F o n t .Name ', Buttonl.Font.Name) ;
AddAttr (iRoot, ' L e f t ', IntToStr (Buttonl.Left)) ;
AddAttr (iRoot, ' H i n t ', Buttonl .Hint);

/ / m u e s t r a XML e n u n memo
Memol.Lines : = XmlDoc.XML;
end;

Desde luego, seria mas interesante disponer de una tecnica generica capaz de
guardar las propiedades de cada componente de Delphi (u objeto permanente,
para ser mas precisos), recorriendo de manera recursiva 10s subobjetos perma-
nentes e indicando 10s nombres de 10s componentes a 10s que se hace referencia.
Esto es lo que hace el procedimiento C o m p o n e n t T o D O M , que utiliza la informa-
cion RTTI bajo nivel proporcionada por la unidad TypInfo e incluye la extraccion
de la lista de propiedades de componentes. Una vez mas, el programa utiliza las
interfaces XML simplificadas de Delphi:
procedure ComponentToDOM (iNode: IXmlNode; Comp: TPersistent);
var
nProps, i: Integer;
PropList: PPropList;
Value : Variant ;
newNode: IXmlNode;
begin
// o b t i e n e l a lista d e p r o p i e d a d e s
nProps : = GetTypeData ( C ~ m p . C l a s s I n f o ) ~ . P r o p C o u n t ;
GetMem (PropList, nProps * SizeOf (Pointer)) ;
try
GetPropInfos (Comp.ClassInfo, PropList) ;
for i : = 0 to nProps - 1 do
begin
Value : = GetPropValue (Comp, PropList [i] .Name) ;
NewNode := iNode .Addchild (PropList [i] .Name) ;
NewNode.Text : = Value;
i f (PropList [i] . PropTypeA .Kind = tkclass) and (Value
<> 0 ) then
i f TObject (Integer (Value)) is TComponent then
NewNode .Text : = TComponent (Integer (Value)) .Name
else
/ / TPersistent p e r 0 n o TComponent: recursive
ComponentToDOM (newNode, TOb ject
(Integer(Value)) as TPersistent) ;
end ;
finally
FreeMem (PropList);
end;
end;

Las siguientes dos lineas de codigo, disparan la creacion del documento XML
(que se muestra en la figura 22.4):
XMLDoc.DocumentE1ement : = XMLDoc.CreateNode(SelffC1assName);
ComponentToDOM (XMLDoc.DocumentElement, Self) ;

Interfaces de enlace de datos XML


Trabajar con DOM para acceder o generar un documento es bastante tedioso,
ya que en lugar de utilizar un acceso logico a 10s datos se nos obliga a utilizar la
informacion de posicion. Ademas, manipular series de nodos repetidos de distin-
tos tipos posibles, no es nada sencillo (como en el ejemplo XML del listado 22.1,
que describe libros). Ademas, utilizando el DOM podemos crear cualquier docu-
mento bien formado, per0 (a menos que utilicemos un DOM con validacion) po-
demos aiiadir subnodos a cualquier nodo, acabando con documentos casi inutiles,
ya que el resto del mundo sera incapaz de manejarlos.
Para solucionar estos problemas, Borland ha aiiadido a Delphi un XML Data
Binding Wizard, que es capaz de examinar un documento XML o una definicion
de un documento (un esquema, un DTD [definicion de tipo de documento] u otro
tipo de definicion) y generar un conjunto de interfaces para manipular el docu-
mento. Estas interfaces son especificas a1 documento y su estructura y nos permi-
ten disponer de un codigo mas legible, pero son bastante menos genericas en
cuanto a 10s tipos de documentos que podemos manipular con ellas (y esto es mas
positivo de lo que podria parecer en primera instancia).

1 TFolml
Name = Fmml
Lell = 192
Top = 107
Wdh= 5n
He~gh!= 412
H HorzScrollBar
Range 97 -
VertScrollBar = 20260720
Act~veConlrol=btnRTTl
B~DlMode= bdLellToR~ghl
Capl~on= DomCrealc
Cl~enlHerghl = 385
CfienlWdh = 563
Color = -16777201
-
Cnnslranls = 20255360
a FOIM
Charset = 1
Color = 16777208
He~ght= 11 '1
., .... ,,"".-.*-A -
Figura 22.4. El XML generado para describir el formulario del programa DomCreate.
Fijese en que las propiedades de 10s tipos de clase estan mas expandidas.

El asistente XML Data Binding Wizard se activa utilizando el icono corres-


pondiente de la primera pagina del cuadro de dialogo New Items del IDE, o
haciendo doble clic directamente sobre el componente XMLDocument. (Es extra-
fio que el comando correspondiente no este en el menu local del componente).
Despues de una pagina en la que seleccionaremos un archivo de entrada, este
asistente muestra graficamente la estructura del documento, como se puede ver en
la figura 22.5 para el archivo XML de muestra del listado 22.1. En esta pagina es
donde nombramos cada entidad de las interfaces generadas, en caso de que no nos
gusten 10s que el asistente proporciona de manera predeterminada. Incluso pode-
mos cambiar las reglas utilizadas por el asistente para generar 10s nombres (una
flexibilidad especial que no estaria ma1 en otras partes del IDE de Delphi). La
pagina final nos ofrece una vista previa de las interfaces generadas y ofrece op-
ciones para generar 10s esquemas y otros archivos de definicion.
Para el archivo XML de muestra con 10s nombres de autores, el XML Data
Binding Wizard genera una interfaz para el nodo raiz, dos interfaces para las
listas de elementos de 10s dos tipos distintos de nodos (libros y libros electroni-
cos), y dos interfaces mas para 10s elementos de cada uno de estos tipos.
6~ booksType
texi
U Q book
O tale
-. . --

P Generate B i i

Figura 22.5. t l aslstente XML Data Blndlng Wlzard de Delphi puede anallzar la estructura
de un documento o un esquema (u otra definicion de documento) para crear un conjunto
de interfaces para un acceso mas simple y direct0 a 10s datos DOM.

Veamos a continuacion unos fragmentos del codigo generado, disponible en la


unidad XmlIntfDefinition del ejemplo Xml I n t e r face:
tYPe
IXMLBooksType = interface (IXMLNode)
[ ' {C9A9PB63-47ED-dP27-8ABA-E71P30BA7Pll) ' 1
( Property Accessors )
function Get-Text: WideString;
function Get-Book: IXMLBookTypeList;
function Get-Ebook: IXMLEbookTypeList;
procedure Set-Text(Va1ue: Widestring);
( Methods 6 Properties )
property Text: WideString read Get-Text write Set-Text;
property Book: IXMLBookTypeList read Get-Book;
property Ebook: IXMLEbookTypeList read Get-Ebook;
end;

IXMLBookTypeList = interface(IXMLNodeCo11ection)
[ ' {3449E8C4-3222-47B8-B2B2-38EE504 79OB6) ' 1
( Methods 6 Properties )
function Add: IXMLBookType;
function Insert (const Index: Integer) : IXMLBookType;
function Get-Item(1ndex: Integer): IXMLBookType;
property Items [Index: Integer] : IXMLBookType read
Get-Item; default;
end;

IXMLBookType = interface ( IXMLNode)


[ ' {26BF5CS1-9247-4DlA-8584-24AE68969935) 'J
( Property Accessors )
function Get-Title: WideString;
f u n c t i o n Get-Author: IXMLString-List;
p r o c e d u r e Set-Title (Value: WideString) ;
{ Methods & Properties }
p r o p e r t y Title: WideString r e a d Get-Title w r i t e Set-Title;
property Author: IXMLString-List r e a d G e t A u t h o r ;
end:

Para cada interfaz, el XML Data Binding Wizard genera tambien una clase de
implementacion que proporciona el codigo para 10s metodos de la interfaz, con-
virtiendo 10s consultas en llamadas DOM. La unidad incluye tres funciones de
inicializacion, que pueden devolver la interfaz del nodo raiz desde un documento
cargado en un componente XMLDocument (o un componente que proporcione
una interfaz IXMLDocument generica), o devolverla desde un archivo, o crear
un DOM completamente nuevo:
f u n c t i o n Getbooks(Doc: IXMLDocument) : IXMLBooksType;
f u n c t i o n Loadbooks(const FileName: WideString): IXMLBooksType;
f u n c t i o n Newbooks: IXMLBooksType;

Despues de generar estas interfaces utilizando el asistente en el ejemplo


Xml Interface,hemos repetido el codigo de acceso a1 documento XML que es
similar a1 del ejemplo XmlDomTree per0 mas facil de escribir (y leer). Por
ejemplo, podemos obtener el atributo del nodo raiz escribiendo simplemente:
procedure TForml .btnAttrClick (Sender: TObject) ;
var
Books: IXMLBooksType;
begin
Books : = Getbooks (XmlDocumentl) ;
ShowMessage (Books.Text) ;
end;

Puede ser incluso mas sencillo si recuerda que mientras se escribe este codigo,
la funcion Code Insight de Delphi puede ayudar listando las propiedades disponi-
bles de cada nodo, gracias a que el analizador sintactico puede leer las definicio-
nes de la interfaz (aunque no entienda el formato de un documento XML generico).
Para acceder a un nodo de una de estas sublistas, escribiremos una de las siguien-
tes sentencias (posiblemente la segunda, con la propiedad de matriz predetermi-
nada) :
Books.Book. Items [I] .Title // completo
Books .Book [l] .Title // mds simple

Podemos utilizar un codigo igualmente simplificado para generar nuevos do-


cumentos o aiiadir elementos nuevos, gracias a1 metodo personalizado ~ d dque
,
esta disponible en cada interfaz basada en una lista. Si no disponemos de una
estructura predefinida para el documento XML, como en 10s ejemplos basados en
un conjunto de datos y RTTI de la demostracion anterior, no podremos utilizar
este enfoque.
Validation y esquemas
El asistente XML Data Binding Wizard puede trabajar a partir de esquemas ya
existentes o generar un esquema para un documento XML (e incluso guardarlo en
un archivo con la extension .XDB). Un documento XML describe algunos datos,
per0 para compartir estos datos entre empresas, tiene que adherirse a alguna
estructura previamente acordada. Un esquema es una definicion de documento
contra la que se puede comprobar la correccion de un documento, una operacion
que suele llamarse validacion.
El primer (y mas difundido) tipo de validacion disponible para XML usaba las
definiciones de tipo de documento (Document Type Definitions, DTD). Estos
documentos describen la estructura del XML per0 no pueden definir 10s posibles
contenidos de cada nodo. Ademas, 10s DTD no son documentos XML ellos mis-
mo, sino que usan una notacion diferente y algo extraiia.
A finales del aiio 2000, el W3c aprobo el primer borrador oficial de 10s esque-
mas XML ya disponibles en una version incompatible llamada XML-Data dentro
del DOM de Microsoft). Un esquema XML es un documento XML que puede
validar tanto la estructura del arb01 XML como el contenido de 10s nodos. Un
esquema se basa en el uso y la definicion de tipos de datos simples y complejos, de
un mod0 parecido a un lenguaje orientado a objetos.
Un esquema define tipos complejos, indicando cada uno de 10s nodos posibles,
su secuencia opcional ( s e q u e n c e , a l l ) , el numero de ocurrencias de cada
subnodo ( m i n o c c u r s , m a x 0 c c u r s ) y el tipo de datos de cada elemento espe-
cifico. Este es el esquema definido por el XML Data Binding Wizard para el
archivo de libros de muestra:
Los motores DOM de Microsoft y Apache tienen un buen soporte para 10s
esquemas. Otra herramienta que hemos usado para la validation es XML Schema
Validator (XSV), un intento de codigo abierto de conseguir un procesador confor-
me con 10s esquemas, que puede usarse bien directamente a traves de la Web o
despues de descargar un ejecutable en linea de comandos (en las paginas sobre
XML Schema del W3C se encuentra el enlace a1 sitio Web actual de esta herra-

- - -.- - - -- -
NOTA: El editor de Delphi soporte la completitud de cbchgo para archives
XML gracias a 10s DTD. Si se coloca un ar&vo DTD en el dir&to';io bin
de Delphi y se hace referencia a 61 mediante una etiqaeta DOCTYPE, se
habilitarh esta caracteristica, que brland no sa~ortadc h n a dfidd.

Uso de la API de SAX


La Simple API for XML, o SAX, no crea un arbol para 10s nodos XML, sino
que analiza sintacticamente el nodo, disparando eventos para cada nodo, atributo,
valor, etc ... Puesto que el documento no se guarda en memoria, la utilizacion del
SAX nos permite manejar documentos mucho mas grandes. Este enfoque tambien
es muy util para examinar una sola vez un documento o para recuperar informa-
cion especifica. Veamos una lista de 10s eventos activados por SAX:
StartDocument y EndDocument para el documento complete.
StarElement y EndElement para cada nodo.
Charact er s para el testo contenido en 10s nodos
Es bastante comun utilizar una pila para manejar la ruta actual dentro del
arbol de nodos, y meter y sacar elementos enly desde la misma para cada evento
Start Element y EndElement . Delphi no incluye soporte especifico para la
interfaz SAX per0 se puede conseguir facilmente importando el soporte XML de
Microsoft (la biblioteca MSXML). En particular, para el ejemplo SasDemol,
hemos utilizado la version 2 de MSXML, ya que se encuentra muy difundida.
Hemos generado una unidad de importacion de biblioteca de tipos de Pascal para
la biblioteca de tipos, y la unidad de importacion esta disponible dentro del codigo
fuente del programa, per0 necesitamos que la biblioteca COM este registrada en
nuestro ordenador para ejecutar el programa con exito.

I NOTA:Otro ejcmploVhaciael final delcapituli(Lpgc~rnl) muestra, entis I


- otras cosas, el uso de la API de SAX, incluyendselmotor OpenXml.

Para utilizar SAX, tenemos que instalar un controlador de eventos de SAX


dentro de un lector SAX, y despues cargar un archivo y analizarlo sinticticamente.
Hemos utilizado la interfaz de lectura de SAX proporcionada por MSXML para
programadores de VB. La interfaz oficial (C++) tenia unos cuantos errores en su
biblioteca de tipos que impedia que Delphi la pudiera importar de forma correcta.
En el formulario principal del ejemplo SaxDemo 1 se declara:
sax: IVBSAXXMLReader;

En el metodo Formcreate, la variable sax se inicializa con el objeto COM:


sax : = CoSAXXMLReader.Create;
sax.ErrorHandler := TMySaxErrorHand1er.Create;

El codigo tambien define un controlador de errores, que es una clase que


implementa una interfaz especifica (IVBSAXErrorHandler)con tres meto-
dos a 10s que se llama dependiendo de la gravedad del problema: error,
fatalError e ignorablewarning.
Simplificando un poco el codigo, el analizador sintactico SAX se activa al
llamar a1 metodo parseURL despues de asignarle un controlador de contenido:
s a x - C o n t e n t H a n d l e r := TMySaxHandler-Create;
s a x .parseURL (filename)

A1 final el codigo se encuentra en la clase TMySaxHandler, que es la que


contiene 10s eventos SAX. Ya que en el ejemplo tenemos varios controladores de
contenido SAX, hemos escrito una clase basica con el codigo principal y unas
cuantas versiones especializadas para el procesamiento especifico. A continua-
cion veremos el codigo de la clase basica, que implementa l a interfaz
I V B S A X C o n t e n t H a n d l e r y la interfaz I D i s p a t c h en que se basa
IVBSAXContentHandler:

type
TMySaxHandler =class (TInterfacedObject, IVBSAXContentHandler)
protected
stack: TStringList;
public
constructor Create;
destructor Destroy; override;
/ / IDispa tch
function GetTypeInfoCount(out Count: Integer): HResult;
stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo):
HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
Namecount, LocaleID: Integer; DispIDs: Pointer) :
HResult; stdcall;
function Invoke(Disp1D: Integer; const IID: TGUID;
LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr:
Pointer) :
HResult; stdcall;
/ / IVBSAXContentHandler
procedure S e t ~ d o c u m e n t L o c a t o r ( c o n s tParaml: IVBSAXLocator);
virtual; safecall;
procedure startDocument; virtual; safecall;
procedure endDocument; virtual; safecall;
procedure startPrefixMapping(var strprefix: WideString;
var strURI : Widestring) ; virtual; safecall;
procedure endPrefixMapping(var strprefix: Widestring);
virtual; safecall;
procedure startElement(var strNamespaceUR1: WideString;
var strLocalName: WideString; var strQName: WideString;
const ~Attributes: IVBSAXAttributes); virtual; safecall;
procedure endElement(var strNamespaceUR1: WideString;
var strLocalName: WideString; var strQName: WideString);
virtual; safecall;
procedure characters(var strchars: WideString); virtual;
safecall ;
procedure ignorableWhitespace(var strchars: WideString);
virtual; safecall;
procedure processingInstruction(var strTarget: WideString;
var strData: WideString); virtual; safecall;
procedure skippedEntity(var strName: WideString);
virtual; safecall;
end :

La parte mas interesante es la lista final de 10s eventos SAX. Todo lo que hace
esta clase basica es enviar la informacion a un registro cuando el analizador
sintactico empieza (startDocument) y finaliza (endDocument) y hace el
seguimiento de 10s nodos actuales y 10s nodos padre con una pila:
/ / TMySaxHandler. s tartElement
stack .Add (strLocalName);
// TMySaxHandler. endEl ement
stack .Delete (stack.Count - 1) ;
La clase TMyS impleSaxHand ler proporciona una implementation real
que sobrescribe el evento st art E leme nt lanzado por cualquier nuevo nodo
para enviar la posicion actual en el arbol, con la siguiente sentencia:
Log.Add (strLocalName + ' ( ' + stack.ComrnaText + ') ') ;

El segundo metodo de la clase es el evento characters, que se provoca


cuando se encuentra un valor de nodo (o un nodo de texto) y envia su contenido
(como muestra la figura 22.6):
p r o c e d u r e TMySimpleSaxHandler.characters(var strchars:
WideString) ;
var
str: WideString;
begin
inherited;
s t r : = Removewhites (strchars);
if (str <> " ) then
L o g - A d d ('Text: ' + str);
end;

pam~w ( I- s ~ a l ~ ~ o c l m---
bwkslbooksl
ent

' List ' book(books.book]


lkle(books.book.tille]
Texl: La B~bhade Delphi 7
Paw TkJw 1 author~books.book.auIhar1

booklbooks,book]
blle(books,book,liUe]
Text DelphlDeveloper's Handbook
author(books,bo&.author]
Ten!: Canlu
au(hor(books.book.au(hor1
Text: Gooch
bwk[books.book]
!~tle[books,book,l~Ile]
Texl. La Bibl~ade Delphi 6
aulhor(books,book,aulhor]
Text. Canlu
bmk[books,bwk]
Mle(bwks.book,lillej
Ted: DelphiCOM Programrring
aulho@ooks.book,auIhor]
Texl Herrnon
bwk(books,book]
title(books.book.lille)
Text Thlnkmg in Ctt
aulhor[bwks.bodLw(hal
Text: Edtel

Figura 22.6. El registro generado por la lectura de un documento XML con SAX en el
ejernplo SaxDemol.

Se trata de una operacion de analisis sintactico generica que afecta a todo el


archivo XML. El segundo controlador de contenido SAX derivado se refiere a la
estructura especifica del documento XML, extrayendo solamente nodos de un
tip0 determinado. Concretamente, el programa busca 10s nodos de tipo title. Cuando
un nodo es de este tipo (en s t a r t E l e m e n t ) , la clase activa la variable booleana
i s b o o k . El valor de texto del nodo se tiene en cuenta solo inmediatamente des-
pues de encontrar un nodo de este tipo:
procedure TMyBooksListSaxHandler.startElement(var
strNamespaceUR1,
strLocalName, strQName: Widestring; const ~ A t t r i b u t e s :
IVBSAXAttributes) ;
begin
inherited;
isbook := (strLocalName = 'title ') ;
end :

procedure T ~ y B o o k s L i s t S a x H a n d l e r . c h a r a c t e r s ( v a r strchars:
Widestring) ;
var
str: string;
begin
inherited;
if isbook then
begin
s t r : = Removewhites (strchars);
if (str <> 'I) then
Log.Add (stack.CommaText + ': ' + s t r ) ;
end ;
end ;

Proyeccion de XML con transformaciones


Hay una tecnica mas en Delphi que podemos utilizar para manejar algunos
documentos XML: podemos crear una transformacion, para traducir el XML de un
documento generico a1 formato nativo que utiliza un componenteC l i e n t D a t a S e t
cuando guarda datos en un archivo XML de MyBase. De la misma manera, y en
sentido inverso, otra transformacion puede convertir un conjunto de datos disponi-
ble en un ClientDataSet (a traves del componente D a t a s e t p r o v i d e r ) , en un
archivo XML con un formato (o esquema) determinado.
Delphi incluye un asistente que genera estas transformaciones, llamado XML
Mapping Tool o XML Mapper, a1 que se accede desde el menu Tools del IDE o
que puede ejecutarse como una aplicacion independiente. El XML Mapper, que
muestra la figura 22.7, es un asistente en tiempo de diseiio que nos ayuda con la
definicion de las reglas de transformacion entre 10s nodos de un documento XML
generico y 10s campos del paquete de datos del ClientDataSet.
La ventana del XML Mapper tiene tres zonas:
A la izquierda esta la seccion del documento XML: Muestra la informa-
cion sobre la estructura del documento XML (y sus datos, si la casilla de
activacion correspondiente esta activada) en la Document View o en un
esquema XML en la Schema View, segun la solapa que seleccionemos.
Tmrh-mmon E w ~ ~ o ~ m d c v ~ u ~ l - ~ B d r ( , d a a
lrri~d
J
Figura 22.7. El XML Mapper muestra 10s dos extremos de una transforrnac~onpara
definir la proyeccion entre ellos (con las reglas indicadas en la parte central).

A la derecha se encuentra la seccion del paquete de datos: Muestra la


informacion acerca de 10s metadatos en el paquete de datos, bien en la
Field View (indicando la estructura del conjunto de datos) o en la
Datapacket View (dandonos informacion sobre la estructura XML). Fi-
.jese en que el XML Mapper tambien puede abrir archivos en el formato
original de ClientDataSet.
La parte central d e la ventana se utiliza para la proyeccion: Contiene a
su vez dos paginas: Mapping, donde podemos ver las correspondencias
entre 10s elementos seleccionados en ambos lados que formaran parte de la
proyeccion; y Node Properties, donde podemos modificar 10s tipos de
datos y otros detalles de cada posible proyeccion.
La pagina Mapping del panel central alberga tambien el menu de metodo
abreviado que se utiliza para generar la transformacion. El resto de 10s paneles y
vistas tienen menus locales especificos, utilizados para realizar diversas acciones
(ademas de unos cuantos comandos en el menu principal). Podemos utilizar XML
Mapper para proyectar un esquema existente (o estraerlo a partir de un documen-
to) sobre un paquete de datos nuevo, de un paquete de datos existente a un nuevo
esquema o documento, o de un paquete de datos ekistente a un documento XML
ya existente (si es razonable una cierta correspondencia). Ademas de convertir 10s
datos de un archivo XML a un paquete de datos, tambien podemos convertirlos en
un paquete delta del ClientDataSet. Esta tecnica es muy util para fusionar un
documento con una tabla, como si un usuario hubiera insertado 10s registros
modificados de la tabla. Concretamente, podemos transformar un documento XML
en un paquete delta para modificar, borrar o insertar registros.
El resultado de usar el XML Mapper es uno o mas archivos de transformacion,
cada uno de 10s cuales representa una conversion en sentido unico (necesitamos a1
menos dos archivos para hacer la conversion en ambos sentidos). Estos archivos
de transformacion se utilizan en tiempo de diseiio y de ejecucion por 10s compo-
nentes XMLTransform, XMLTransformProvider y XMLTransformClient.
A mod0 de ejemplo, hemos intentado abrir el documento XML de 10s libros,
que tiene una estructura que no se corresponde facilmente con una tabla, ya que
contiene dos listas de valores de distintos tipos. Despues de abrir el archivo
sample. xml en la seccion XML Document, hemos utilizado su menu local
para seleccionar todos sus elementos (Select All) y para crear el paquete de datos
(Create Datapacket From XML). Esta operacion hace que el panel derecho se
rellene automaticamente con el paquete de datos y la parte central con la transfor-
macion propuesta. Tambien podemos ver inmediatamente su efecto en un progra-
ma de muestra haciendo clic sobre el boton Create and Test Transformation.
Esto abre una aplicacion generica que permite cargar un documento en el conjun-
to de datos usando la transformacion creada.
En este caso en concreto, podemos ver que el XML Mapper genera una tabla
con dos campos de conjunto de datos: uno para cada una de las posibles listas de
subelementos. Esta era la unica solucion estandar posible ya que las dos sublistas
tienen estructuras diferentes, y es la unica solucion que permite editar 10s datos en
la DBGrid conectada a1 ClientDataSet y guardarlos de nuevo en un archivo XML,
tal y como se muestra en el ejemplo XmlMapping. Basicamente, este programa es
un editor basado en Windows para un documento XML complejo.
El ejemplo utiliza un componente TransformProvider con dos archivos de trans-
formation aiiadidos para leer un documento XML y para que el ClientDataSet
pueda disponer de el. Como sugiere su nombre, este componente es un proveedor
de conjuntos de datos. Para construir la interfaz de usuario, no hemos conectado
directamente el ClientDataSet a una cuadricula, ya que contiene un unico registro
con un campo de texto y dos conjuntos de datos detallados. Por ello, hemos aiia-
dido a1 programa dos componentes ClientDataSet mas enlazados con 10s campos
del conjunto de datos y conectados con 10s dos controles DBGrid. Probablemente
sea mas facil entender esto si echamos un vistazo a la definicion de 10s componen-
tes no visuales en el codigo fuente DFM en el siguiente fragmento, y a su salida en
la figura 22.8.
object XMLTransformProviderl: TXMLTransformProvider
TransforrnRead.TransformationFile = ' B o o k s D e f a u l t . x t r f
TransformWrite.TransformationFile = 'BooksDefau1tToXml.xtr'
XMLDataFile = 'Sanple.xml '
end
object ClientDataSetl: TClientDataSet
ProviderName = 'XMLTransformProviderl '
object ClientDataSetltext: TStringField
object ClientDataSetlbook: TDataSetField
object ClientDataSetlebook: TDataSetField
end
o b j e c t ClientDataSet2: TClientDataSet
DataSetField = ClientDataSetlbook
end
o b j e c t ClientDataSet3: TClientDataSet
DataSetField = ClientDataSetlebook
end

Delplw COM Pcgfarrunmg Harmon


Thinkinp m C t t Btuce
La Bbl~ade Delph 7 Can(u

hltg//ww.ma~cocantu.~m Cantu
II

Figura 22.8. El ejemplo XmlMapping utiliza un componente TransformProvider para


permitir la edicion de un documento XML complejo dentro de varios componentes
ClientDataSet.

Este programa no solo permite editar 10s datos de las diferentes sublistas de
nodos dentro de las cuadriculas, sin0 tambien modificarlos, borrarlos o aiiadir
nuevos registros. Cuando aplicamos 10s cambios al conjunto de datos (haciendo
clic sobre el boton Save, que llama a ~pplyupdates),el proveedor de trans-
formaciones guarda una version actualizada del archivo en el disco. Como meto-
do alternativo, tambien podemos crear transformaciones que proyecten solo
determinadas partes del documento XML sobre un conjunto de datos. Como ejem-
plo, puede consultarse el archivo Booksonly .x t r que se encuentra en la car-
peta del ejemplo XmlMapping. El documento XML modificado que generara tendra
una estructura y contenido distintos del original, incluyendo solo la parte que se
ha seleccionado. Por eso, puede ser util para ver 10s datos per0 no para editarlos.

Una transformacion puede utilizarse para coger una tabla de una base de datos
o el resultado de una consulta y producir un archivo XML con un formato mas
legible que el que nos proporciona por defect0 el mecanismo de permanencia de
ClientDataSet. Para construir el ejemplo MapTable, hemos colocado un compo-
nente SimpleDataSet de dbExpress en un formulario y le hemos conectado un
DataSetProvider y un ClientDataSet a1 proveedor. Despues de abrir la tabla y el
conjunto de datos de cliente, hemos guardado su contenido en un archivo XML.
El siguiente paso ha sido abrir el XML Mapper, cargar el archivo del paquete
de datos en el, seleccionar todos 10s nodos del paquete de datos (con el comando
Select All de su menu local) y llamar a1 comando Create XML From
Datapacket.
En el siguiente cuadro de dialogo, aceptamos las proyecciones predetermina-
das de 10s nombres para 10s campos y solo cambiamos el nombre sugerido para
10s nodos de registro (ROW) por algo mas legible (Customer). Si probamos ahora
la transformacion, el XML Mapper mostrara el contenido del documento XML
resultante en una vista de arb01 personalizada
Una vez que hemos guardado el archivo de transformacion, podemos reanudar
el desarrollo del programa, eliminando el ClientDataSet y aiiadiendo un Datasource
y una DBGrid (para que un usuario pueda editar 10s datos en una DBGrid antes
de transformarlos), y un componente XMLTransformClient. Este componente
tiene conectado el archivo de transformacion, per0 no un archivo XML. En lugar
de eso, hace referencia a 10s datos a traves del proveedor. A1 hacer clic sobre el
boton, veremos el documento XML dentro de un campo de memo (despues de
darle formato) en lugar de guardarlo en un archivo, algo que podemos hacer
llamando a1 metodo GetDataAsXml (aunque el archivo de ayuda no resulta
muy claro sobre el uso de este metodo):
procedure TForrnl.btnMapClick(Sender: TObject);
begin
Mernol.Lines.Text : =
ForrnatXmlData(XMLTransf0rmC1ientl.GetDataAsXml("));
end;

Este es el unico codigo del programa que podemos ver en tiempo de ejecucion
en la figura 22.9. El conjunto de datos original puede verse en la DBGrid, y el
documento XML resultante en el control de memo que se encuentra bajo la cua-
dricula. La aplicacion dispone de un codigo mucho mas sencillo que el que hemos
utilizado en el ejemplo DomCreate para generar un documento XML parecido,
per0 requiere la definition de la transformacion en tiempo de diseiio. El ejemplo
DomCreate podria trabajar en tiempo de ejecucion sobre cualquier conjunto de
datos, sin necesidad de una conexion a una tabla especifica ya es un codigo
bastante generico.
En teoria, podemos producir proyecciones dinamicas similares utilizando 10s
eventos del componente generico XMLTransform, per0 parece mas sencillo usar
el enfoque basado en DOM ya comentado. Ademas, la llamada a FormatXml-
Data produce una salida mas agradable per0 ralentiza el programa, ya que im-
plica la carga del XML en un DOM.
I
1231 Unirco PO BoxZ-547
1351 S~ghlD i w 1 Neptune Lane
1354 Cayman Diverr World Llnhded PO Box 541
1356 Tom Swyer D i n g Cedre 632.1 T hid Frydenhq
1 3 0 Blw Jack Aqua Center 23-73 PaddnglonL a m Suhe 310
1384 VIP Divers Club 32 Main St.

.............. . .. . .......
......... . .-... ........,...Map ... -, .......... .- . .... .. - -:

O x m l vefs~on="l0"b
< Docwnent~
<Cuttomef>
~Custl~lo>t221 ;ICustNo)
<Compary)Kaua~D~veShoppe</Cornpany>
cAddrl>4.976 Sugarloaf Hwyc/Addrl>
cAd&2>Smte 103</Addr2>

Figura 22.9. El ejemplo MapTable genera un documento XML a partir de una tabla de
base de datos rnediante un archivo de transforrnacion personalizado.

XML e lnternet Express


Una vez que hemos definido la estructura de un documento XML, podemos
desear permitir que 10s usuarios vean y editen 10s datos en una aplicacion de
Windows o a traves de la Web. Este segundo supuesto es bastante interesante ya
que Delphi proporciona un soporte especifico para ello. Delphi 5 ya incluia una
arquitectura llamada Internet Espress, que ahora forma parte de la plataforma
WebSnap. WebSnap ofrece tambien soporte para XSL, del que hablaremos mas
adelante.
En el capitulo 16, ya hemos hablado del desarrollo de aplicaciones de DataSnap.
Internet Espress proporciona un componente de cliente llamado XMLBroker para
esta arquitectura, que puede utilizarse en lugar de un conjunto de datos para
obtener 10s datos a partir de un programa DataSnap de capa intermedia y ponerlo
a disposicion de un tip0 especifico de productor de pagina llamado
InetXPageProducer. Podemos utilizar estos componentes en una aplicacion
WebBroker estandar o en un programa WebSnap. La idea de Internet Express es
escribir una estension de servidor Web que produzca paginas Web conectadas
con nuestro servidor DataSnap. La aplicacion personalizada actua como un clien-
te DataSnap y produce paginas para un navegador cliente. Internet Espress ofre-
ce 10s servicios necesarios para crear facilmente esta aplicacion personalizada.
Puede que resulte confuso, pero Internet Express es una arquitectura de cuatro
niveles. Estos son: servidor SQL, servidor de aplicacion (el servidor DataSnap),
servidor Web con una aplicacion personalizada y, finalmente, el navegador Web.
Podemos colocar 10s componentes de acceso a la base de datos dentro de la misma
aplicacion que maneja la peticion HTTP y que genera el resultado HTML, como
en una solucion cliente/servidor. Incluso podemos acceder a una base de datos
local o a un archivo XML, con una estructura de dos capas (el programa servidor
y el navegador).
Es decir, Internet Express es una tecnologia para crear clientes basados en un
navegador, lo que nos permite enviar, junto con el HTML, todo el conjunto de
datos a1 ordenador cliente. Tambien enviamos algo de codigo JavaScript para
poder manipular el XML y mostrarlo dentro de la interfaz de usuario definida por
el codigo HTML. El codigo JavaScript es lo que hace posible que el navegador
pueda mostrar 10s datos e incluso manipularlos.

El componente XMLBroker
Internet Express utiliza diversas tecnologias para conseguir este resultado.
Convierte 10s paquetes de datos DataSnap a1 formato XML para que el programa
pueda insertar estos datos en la pagina HTML para su manipulation Web en el
cliente. Realmente, el paquete de datos delta tambien se representa como XML.
El componente XMLBroker lleva a cabo estas operaciones, maneja el XML y
proporciona 10s datos a 10s nuevos componentes JavaScript. A1 igual que el
ClientDataSet, el XMLBroker tiene:
Una propiedad MaxRecords: Sirve para indicar el numero de registros
que se aiiaden a una sola pagina.
Una propiedad Params: Se utiliza para albergar 10s parametros que 10s
componentes reenviaran a la consulta remota a traves del proveedor.
Una propiedad WebDispatch: Sirve para indicar la consulta actualizada
a la que responde el broker.
El InetXPageProducer permite generar visualmente 10s formularios HTML a
partir de 10s conjuntos de datos, de una forma similar a1 desarrollo de una interfaz
de usuario AdapterPageProducer. En realidad, la arquitectura de Internet Express,
las interfaces internas que utiliza y parte de su editor IDE pueden ser considera-
dos como el progenitor de la arquitectura WebSnap. Con la notable diferencia de
generar guiones que se ejecutan del lado del servidor y del cliente, ambos propor-
cionan un editor para colocar componentes visuales y generar estos guiones. Cabe
destacar que el antiguo Internet Express se orienta mas a XML que el mas recien-
te WebSnap.
- - -- -,

I TRUCO: Otra caracteristica comun del InetXPageProducer y el


AdapterPageProducer es el soporte para hojas de estilo en cascada (CSS).
h -.
~ s t o cornponent&~tienen
s las propiedyad8- st yi&-e$ ~ i i para
e '
definir el CSS y cada elemento visual time una propiedad
- - 3t y l e Rule
I que puede usarse para seleccioaar el nombre del &lo. I
Soporte de JavaScript
Para producir potentes operaciones de edicion en el lado del cliente, el
InetXPageProducer utiliza un codigo y unos componentes JavaScript especiales.
Delphi incluye una biblioteca bastante extensa de JavaScript, que el navegador
tiene que descargar. Es un proceso algo fastidioso, per0 es la unica manera de que
la interfaz del navegador (que se basa en codigo HTML dinamico), sea lo sufi-
cientemente buena como para soportar restricciones de campos y otras reglas de
negocio similares.
Esto seria totalmente imposible con el HTML simple. Los archivos de JavaScript
proporcionados por Borland, que deberian hacerse disponibles en la pagina Web
que albergue la aplicacion, son 10s siguientes:

Xmldom. j s Analizador sintactico XML compatible con DOM


(para navegadores que carezcan de soporte nativo
DOM).
I x m l d b . js Clases JavaScript para 10s controles HTML. I
Xm1disp.j~ Clases JavaScript para enlace de datos XML con
controles HTML.
I Xrnlerrdisp js . Clases para arreglar errores. I
Xrn1Show.j~ Funciones JavaScript para mostrar datos y paque-
tes delta (cuya finalidad es la depuraci6n).

Normalmente, las paginas HTML generadas por Internet Express incluyen


referencias a estos archivos JavaScript, como en:

Podemos personalizar el codigo JavaScript aiiadiendo directamente codigo a


las paginas HTML, o creando nuevos componentes de Delphi escritos para enca-
jar con la arquitectura de Internet Express que produce el codigo JavaScript
(posiblemente junto con codigo HTML). Como ejemplo, la clase TPrompt-
QueryButton de muestra de InetXCustom genera el siguiente codigo HTML y
JavaScript:
<script language=javascript type="text/javascript">
function PromptSetField (input, msg) (
v a r v = prompt (msg);
i f ( V == null I I v == " " )
return false;
input. value = v
return true ;
1
var QueryForm3 = document.forms['QueryForm3'];
</script>
<input type=button value="Prornpt ..."
onclick="if (PromptSetField(PromptResult, 'Enter some t e x t \ n r ) )
QueryForm3. submit ( ) ;">

TRUCOt Los mriipooentjq dcionales '&muestia.de ENetXCustom sew


de graa ayuda si tenaaos ktFncibn de u k Inkmet Express. E& c(m-
ponentcn, estiin dirponibles en la Carpeta \ o dho s \ M i d a s \
~ n t e r n e t ~ x ~ r e~ ~ ~ Siga
s st\~ e ttom. u s 1% dew* instrucciones
del ar'chivo readma. t x t para ins'talar estos ~ ~que Borlands .
proporciona sin n&im tipo de sopork pmo que penniten afUadir muchias
wra@terist.icas a Ias aplicaciones &bm&Eqress ccin hpxpdlo esfhrzo
adicid.

Para utilizar esta arquitectura no necesitamos nada especial en el cliente, ya


que puede usarse cualquier navegador que entienda el estandar HTML 4, en cual-
quier sistema operativo. Sin embargo, el servidor Web debe de ser un servidor de
Win32 (esta tecnologia no esta disponible en Kylis) y hay que utilizarlo con
bibliotecas DataSnap.

Creacion de un ejemplo
Para entender mejor de que hablamos, y como forma de comentar algunos deta-
lles mas tecnicos, vamos a probar sencillo ejemplo llamado IeFirst. Para evitar
problemas de configuracion, esta es una aplicacion CGI que accede directamente a
un conjunto de datos (en este caso a una tabla local conseguida mediante un compo-
nente C l i e n t D a t a S e t ) . Mas tarde veremos como convertir un cliente DataSnap
de Windows ya existente en una interfaz que basada en un navegador. Para crear
IeFirst, hemos creado una nueva aplicacion CGI y afiadido a su modulo de datos un
componente C l i e n t Dataset conectado con un archivo .CDS local y un compo-
nente D a t a S e t P r o v i d e r conectado con el conjunto de datos. El paso siguiente
es aiiadir un componente XMLBroker y conectarlo con el proveedor:
object ClientDataSetl: TClientDataSet
FileName = 'C: \Archives d e programa \Archives comunes \Borland
Shared\Data \employee.cds '
end
o b j e c t DataSetProviderl: TDataSetProvider
DataSet = ClientDataSetl
end
o b j e c t XMLBrokerl: TXMLBroker
ProviderName = 'Da taSetProvider1 '
WebDispatch.MethodType = mtAny
WebDispatch. PathInf o = 'XMLBrokerl '
ReconcileProducer = PageProducerl
OnGetResponse = XMLBrokerlGetResponse
end

Se necesita la propiedad ReconcileProducer para mostrar un mensaje


de error adecuado en caso de conflict0 de actualizacion. Uno de 10s programas de
ejemplo de Delphi incluye un codigo personalizado, pero, para este caso, simple-
mente hemos conectado un componente Pageproducer tradicional con un mensaje
de error HTML generico. Despues de preparar el XMLBroker, podemos aiiadir
un InetXPageProducer al modulo de datos Web. Este componente tiene un esque-
leto HTML estandar, que hemos personalizado para aiiadir un titulo, sin modifi-
car las etiquetas especiales:
<HTML><HEAD>
<title>IeFirst</title>
</HEAD><BODY>
<hl>Internet Express First Demo (IeFirst . exe)</hl>
<#INCLUDES><#STYLES><#WARNINGS><#FORMS><#SCRIPT>
</BODY>

Las etiquetas especiales se expanden automaticamente mediante 10s archivos


JavaScript del directorio especificado en la propiedad Include Pat hURL. Es
necesario establecer esta propiedad para que haga referencia al directorio del
servidor Web donde residen estos archivos. Podemos encontrarlos en el directorio
\ D e l p h i 7 \ S o u r c e \ W e b M i d a s . Las cinco etiquetas tiene el siguiente
efecto:

<#INCLUDES> Genera las instrucciones para incluir las bibliote-


cas JavaScript.
<#STYLES> Aiiade la hoja de definicion de estilo incrustada.
<#WARNINGS> Se utiliza en tiempo de diseiio para mostrar 10s
errores en el editor InetXPageProducer.
<#FORMS> Genera el codigo HTML producido por 10s compo-
nentes de la pagina Web.
Aiiade un bloque de JavaScript utilizado para ini-
ciar el guion del lado del cliente.
NOTA: El componente InetXPageProducer tambien maneja unas cuantas
etiquetas internas mas. <#BODYELEMENTS> se corresponde con las cinco
etiauetas ---- de
- .-x - - - - - la r
---
dantilla
----------- aredefinida. <#COMPONENT
~ ..
- - - - - -

nentName> forma parte del codigo HTML generado utilizado para de-
Name=WebCom~o-
-- - - - ~ -- - - ~ -
.-
-

clarar 10s componentes generados visualmente. < D A T A P A C K E T


m --
-.. w -m o .Ke r =-m o .Ke m.- ' .I
a m e > se sustltuye con el coaigo
.
.
A I.
AML ael paque-
.
,
1
'
* 1 1

te de datos.

Para personalizar el HTML resultante dcl InetXPageProducer, podemos utili-


zar su editor. que vuelve a scr parecido a1 editor de guiones de servidor de WebSnap.
Haciendo dobIe clic sobre el componente InetXPageProducer,Delphi abre
una x n t a n a como la que muestra la figura 22.10 (con la configuracion final del
ejemplo).
En este editor podemos crear estructuras complejas partiendo de un formulario
de consulta. un formulario de datos o un grupo generic0 de estructura. En el
formulario de datos de nuestro ejemplo, hemos aiiadido dos componentes
DataGr id y DataNavigator sin personalizarlos (operacion que se puede
llevar a cabo aiiadiendo botones hijo, columnas y otros objetos que sustituyan
completamente a 10s predeterminados).

at?,*

L
-

-
- -
InelXPagePraducerl ErnpFlo
DataForrnl

A
LaslNarne
DalaNavqalor F1r~tNarne
E:
Salary
SlalusColurnnl
- - - -

Internet Express First Demo (IeFirst.exe)

Figura 22.10. El editor de InetXPageProducer nos permite crear visualmente


complejos formularios HTML de una forma parecida al Adapterpageproducer.
El codigo DFM para el InetXPageProducer y sus componentes internos en el
ejemplo se muestra a continuacion. Se pueden ver las configuraciones principa-
les, ademas de algunas limitadas personalizaciones graficas:
object InetXPageProducerl: TInetXPageProducer
IncludePathURL = ' / j ssource/ '
HTMLDoc.Strings = ( . . . )
o b j e c t DataForml: TDataForm
o b j e c t DataNavigatorl: TDataNavigator
XMLComponent = DataGridl
Custom = 'align="center" '
end
o b j e c t DataGridl: TDataGrid
XMLBroker = XMLBrokerl
DisplayRows = 5
TableAttributes.BgCo1or = 'Silver'
TableAttributes.CellSpacing = 0
TableAttributes.Cel1Padding = 2
HeadingAttributes.BgCo1or = 'Aqua'
o b j e c t EmpNo: TTextColumn...
o b j e c t LastName: TTextColumn. . .
o b j e c t FirstName: TTextColumn...
.
o b j e c t PhoneExt : TTextColumn. .
o b j e c t HireDate: TTextColumn. ..
o b j e c t Salary: TTextColumn.. .
o b j e c t StatusColumnl: TStatusColumn...
end
end
end

El valor de estos componentes esta en el codigo HTML (y JavaScript) que


generan y que podemos ver previamente a1 seleccionar la pestaiia H T M L del edi-
tor de InetXPageProducer. Veamos a continuacion un parte de las definiciones en
el HTML para 10s botones, el encabezado de la cuadricula de datos y una de sus
celdas:
/ / botones
<table align="centerV>
<tr><td colspan="2">
<input type="buttonW value=" 1 < "
onclick='if (xml-ready) DataGridl-Disp. first ( ) ; ' >
<input t ype="buttonW value="<< "
onclick='if (xml-ready) DataGridl-Disp.pgup();'>
...
/ / titulo de cuadricula de datos
<tr>
/ / u n a celda d e datos
<td><div>
<input type="textW name="EmpNo" size="lO"
onfocus='if(xml~ready)DataGridlonfocus='ifoDataGridl_Disp.xfoDi~p.~focus(this);'
onkeydown='if (xml-ready) D a
tG
r
i
d
l
o
n
k
e
y
d
o
w
n
=
'i
f
o
D
a
tG
r
i
d
l
_
Ds
p
.
k
D
i
~
p
. keys (this); I >
</div></td> ...
Despues de preparar el generador de HTML, podemos volver a1 modulo de
datos Web, aiiadirle una accion y conectarla con el InetXPageProducer mediante
la propiedad P r o d u c e r . Esto deberia bastar para que el funcione a programs
travts-de un navegador, como muestra la fig& 22.1 1 .

1 Internet Express First Demo (IeFirst.exe)

lelson IRoberio
. - -- -
'oung l~ruce

:uao
Figura 22.11. La aplicacion IeFirst envia al navegador algunos componentes HTML,
un documento XML complete, y codigo JavaScript para mostrar 10s datos en 10s
componentes visuales

Si miramos en el archivo HTML recibido por el navegador, encontraremos la


tabla mencionada en la definition anterior, algo de codigo JavaScript y 10s datos
de la base de datos en el formato XML del paquete de datos. Estos datos 10s
controla el XMLBroker y se 10s pasa a1 componente productor para insertarlos en
el archivo HTML. El numero de registros enviados al cliente depende del
XMLBroker y no del numero de lineas de la cuadricula. Despues de que se envien
10s datos XML a1 navegador, podemos usar 10s botones del componente navegador
para movernos por ellos sin necesidad de acceder a1 servidor para buscar mas.
Esto difiere bastante del comportamiento de WebSnap. No queremos decir que un
enfoque sea mejor que otro, sino que depende del tip0 de aplicacion que vayamos
a construir.
A1 mismo tiempo, las clases JavaScript del sistema permiten que el usuario
introduzca datos nuevos, siguiendo las reglas impuestas por el codigo JavaScript
que conectado a 10s eventos HTML dinamicos. De manera predeterminada, la
cuadricula tiene una columna adicional con un asterisco que indica que registros
se han modificado. Los datos de actualizacion se agrupan en un paquete de datos
XML en el navegador y se envian de vuelta al sewidor cuando el usuario hace clic
sobre el boton Apply Updates. A partir de aqui, el navegador activa la accion
especificada por la propiedad WebDispat h . Pat hInf o del XMLBroker. No
hay necesidad de exportar esta accion desde el modulo de datos Web, ya que es
una operacion automatica (aunque podemos desactivarla si establecemos
WebDispath. Enable como False).
El XMLBroker aplica 10s cambios al servidor, devolviendo el contenido a1
proveedor conectado a la propiedad Re conc i 1e Provider (o lanzando una
excepcion si esta propiedad no esta definida). Si todo funciona bien, el XMLBroker
redirige el control a la pagina principal que contiene 10s datos. Sin embargo,
hemos experimentado algunos problemas con esta tecnica, por lo que el ejemplo
IeFirst controla el evento OnGetResponse, indicando que se trata de una vista
actualizada:
procedure TWebModulel.XMLBrokerlGetResponse(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; v a r Handled:
Boolean) ;
begin
Response .Content : = ' < h l > U p d a t e d < / h l > < p >' +
1netXPageProducerl.Content;
Handled : = True;
end;

Uso de XSLT
Otra posibilidad para generar un codigo HTML partiendo de un documento
XML es usar el lenguaje de hojas de estilo extensible (Extensible Stylesheet
Language, XSL) o, para ser mas precisos, su subconjunto XSL Transformations
(XSLT).
El objetivo de de XSLT es transformar un documento XML en otro documen-
to, generalmente un documento XML. Uno de 10s usos mas frecuentes de la tecno-
logia es convertir un documento XML en un documento XHTML para enviarlo a
un navegador desde un servidor Web. Otra interesante tecnologia relacionada es
XSL-FO (XSL Formatting Objects), que puede usarse para convertir un docu-
mento XML en un documento PDF u otro tipo de documento con formato.
Un documento XSLT es un documento XML bien formado. La estructura de
un archivo XSLT necesita un nodo raiz como el siguiente:
El contenido del archivo XSLT se basa en una o mas plantillas (o reglas o
funciones) que procesara el motor. Su nodo es xsl : template,normalmente
con un atributo match. En el caso mas sencillo, una plantilla funciona sobre
nodos con un nombre determinado; se invoca la plantilla pasandole uno o mas
nodos con una expresion XPath:

El punto de partida para esta operacion es una plantilla que procesa el nodo
raiz, que puede ser la unica plantilla del archivo XSLT. En las plantillas, se puede
encontrar cualquier otro comando, como la extraccion de un valor desde un docu-
mento XML (xsl :value-of select), sentencias de bucle (xsl : for-each),
expresiones condicionales (xs1 : if,xs 1 :choose), peticiones de ordenacion
(xs1 : sort) y peticiones de numeracion (xsl : number) por mencionar solo
algunos comandos XSLT comunes.

Uso de XPath
XSLT usa otras tecnologias XML, sobre todo XPath para identificar partes de
documentos. XPath define un conjunto de reglas para encontrar uno o mas nodos
dentro de un documento. Estas reglas se basan en una estructura de lineas de ruta
del nodo dentro del arb01 XML. De esta manera, la /books/book identifica
cualquier nodo book bajo la raiz de documento books.XPath utiliza unos simbo-
10s especiales para identificar a 10s nodos:
Un asterisco (*) significa cualquier nodo; por ejemplo book/* indica
cualquier subnodo bajo el nodo book.
Un punto ( .) significa el nodo actual.
El simbolo de barra ( I ) significa alternativas como en book I ebook.
Una doble barra inclinada ( / / ) significa cualquier ruta. / /title nos
indica todos 10s nodos title, cualesquiera que sean sus nodos padre y
books/ /author nos indica cualquier nodo author bajo un nodo books
sin tener en cuenta 10s nodos intermedios.
El signo de arroba o at ( @ ) indica un atributo en lugar de un nodo, como
en el caso hipotetico de author/@lastname.
Los corchetes cuadrados ( [ y ] ) pueden usarse para escoger solo 10s nodos
o atributos que contengan un valor dado. Por ejemplo, author [ @name=
"marco"] seleccionaria todos 10s autores con un atributo de nombre
(name) dado (en este caso, marco).
Hay muchos casos mas, per0 esta pequeiia introduccion a las reglas de XPath
ayudaran con el comienzo y a comprender 10s ejemplos que siguen. Un documento
XSLT es un documento XML que trabaja sobre la estructura de un documento
XML origen y que genera como salida otro documento XML, como un documento
XHTML que podemos ver en un navegador Web.

NOTA: Entre 10s procesadores XSLT m h usados se incluyen MS-XML,


Xalan del proyecto Apache XML (xml. apache. org) y Xt basado en
Java de James Clarke. En Delphi tambih se puede usar el motor XSLT,
incluido en XML Partner Pro de Turbopower (www.turbopower.com).

XSLT en la practica
Vamos a analizar un par de ejemplos. Como punto de partida, deberiamos
estudiar el propio estandar XSL y centrarnos en su activacion desde una aplica-
cion Delphi.
Como prueba inicial, podemos conectar directamente un archivo XSL con un
a r c h i ~ ~XML.
o Cuando cargamos en archivo XML en lnternet Explorer veremos
la transformacion en XHTML resultante. La conexion se indica en la cabecera del
documento XML con un comando como el siguiente:

Esto es lo que hemos hecho en el archivo samplelembedded . xml dispo-


nible en la carpeta XslEmbed.El XSL relacionado incluye varios fragmentos de
XSL que no tenemos espacio para comentar en detalle. Por ejemplo, coge la lista
completa de autores o filtra un grupo especifico de ellos con el siguiente codigo:

Se usa codigo mas complejo para extraer nodos solo cuando se encuentra
presente un valor especifico en un subnodo o atributo, sin tener en cuenta 10s
nodos de mayor nivel. El siguiente fragment0 de XSL tambien tiene una sentencia
i f y produce un atributo en el nodo resultante, como un mod0 de crear un
hipervinculo href en el codigo HTML:
< h 3 > M a r c o 1 s works (books + ebooks)</h3>
<ul>
XSLT con WebSnap
Dentro del codigo de un programa, existe la posibilidad de ejecutar el metodo
Transf ormNode de un nodo DOM, pasandole otro DOM que contenga el do-
cumento XSL. Sin embargo, en lugar de utilizar este enfoque de bajo nivel, pode-
mos crear un ejemplo basado en XSL con la ayuda del WebSnap. Podemos crear
una nueva aplicacion de WebSnap (En este caso, hemos creado un programa CGI
llamado XslCust) y escoger un componente XSLPageProducer para su pagi-
na principal, para permitir que Delphi nos ayude a comenzar con el codigo de la
aplicacion. Delphi incluye tambien un archivo XSL esqueleto para manipular un
paquete de datos ClientDataSet y aiiade muchas nuevas vistas a1 editor. El texto
XSL sustituye al archivo HTML; la pagina XML Tree muestra 10s datos, si hay
alguno; la pagina XSL Tree muestra el XSL dentro de un componente ActiveX
de Internet Explorer; la pagina HTML Result muestra el codigo producido por la
transformacion y, finalmente, la pagina Preview muestra lo que el usuario vera
en un navegador.

TRUCO: En Delphi 7, el editor proporciona la completitud de codigo para


XSLT, que hace que la edicion de este tipo de c6digo en el editor sea tan
potente como en algunos sofisticados editores especificos para XML.

Para que funcione este ejemplo, debemos proporcionar algunos datos al com-
ponente XSLPageProducer a traves de su propiedad XMLData. Esta propiedad
puede conectarse a un XMLDocument o directamente a un componente
XMLBroker, como hemos hecho en este caso. El XMLBroker toma 10s datos de
un proveedor conectado a una tabla local, enlazada con el clasico componente de
tabla customer. c d s de DBDEMOS. El efecto es que, con el siguiente XSL
generado con Delphi, se consigue (incluso en tiempo de diseiio), el resultado que
muestra la figura 22.12:
txs1:template match="ROWDATA/ROW">
txs1:variable name="fieldDefs" select="//METADATA/FIELDS"/>
<xsl:variable name="currentRow" select="current()"/>
<tr>
<xsl:for-each select="$fieldDefs/FIELD">
<td>
<xsl:value-of select="$currentRow/
@ *[ n a m e ( ) =current ( ) /@attrname] " / > < b r / >
</td>
</xsl:for-each>
</tr>
</xsl:template>

. - --. - - - .

NOTA: La ~lantillaXSL esthdar se ha &mPIiado desde D e l ~ h6. .


i va aueT .

las versiones originales no tenian en cuenta 10s campos nulos omitidos en el

GXLGLISIULI a 1 c;uu~gu
A ~ uLn g u GLI ~aLUUL Durlanu LUIUGIGIL~;~: y S U ~ ~ S

de sus sugerencias han sido incorporadas a la plantilla.

Este codigo genera una tabla HTML consistente en la expansion de 10s metadatos
de campo y datos de fila. Los campos se usan para generar el encabezado de la
tabla, con una celda < t h > para cada entrada de una sola fila. Los datos de fila se
usan para rellenar el resto de las filas de la tabla. No basta con tomar el valor de
cada atributo (select=" @ * "); ya que 10s atributos pueden no existir. Por este
motivo, la lista de campos y la fila actual se guardan en dos variables: despues,
para cada campo, el codigo XSL extrae el valor de un elemento de fila que tenga
.
un nombre de atributo (@* [name( ) = . .) que se corresponda con el nombre
del campo actual guardado en su atributo attrnnme (eattrname). Este codigo
no resulta nada sencillo, per0 es un mod0 compacto y adaptable para analizar
divcrsas partes de un documento XML a1 mismo tiempo.

j Procedures
.JUser

Tom

--
I 39 1 Insert ~ H T M ~ ~ P ~ Tree,&~~ ~ , $ M. L
~ ~ ~Tree/
'igura 22.12. El resultado de una transforrnacion XSL generada por el cornponente
XSLPageProducer en el ejernplo XslCust.

Transformaciones XSL directas con DOM


Usar el XSLPageProducer puede ser util, per0 generar varias paginas basadas
en 10s mismos datos simplemente para poder manejar estilos XSL posiblemente
distintos con WebSnap, no parece el mejor metodo. Hemos creado una aplicacion
CGI simple, llamada CdsXstl que puede transformar un paquete de datos
ClientDataSet en diferentes tipos de HTML, segun el nombre del archivo XSL
que se haya pasado como parametro. La ventaja es que ahora se pueden modificar
10s archivos XSL existentes y aiiadir nuevos archivos XSL sin tener que volver a
compilar el programa. Para conseguir la transformacion XSL, el programa carga
10s archivos XML y XSL en sendos componentes XMLDocument, llamados
XmlDom y XslDom. Despues invoca el metodo transformNode del docu-
mento XML, pasando el documento XSL como parametro y rellenando un tercer
componente XMLDocument, llamado HtmlDom:
procedure TWebModulel.WebModulelWebActionItemlAction(Sender:
TOb j ect;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean) ;
var
xslfile, xslfolder: string;
attr: IDOMAttr;
begin
// abre el conjunto de datos del cliente y carga su XML en un
// DOM
ClientDataSetl.0pen;
XmlDom.Xml.Text : = ClientDataSetl.XMLData;
XmlDom.Active := True;
// carga el archivo xsl solicitado
xslfile : = Request.QueryFields.Va1ues ['style'];
i f xslfile = " then
xslfile : = 'customer.xsl ';
xslfolder : = ExtractFilePath (ParamStr (0)) + 'xsl\';
i f FileExists (xslfolder + xslfile) then
xslDom.LoadFromFile (xslfolder + xslfile)
else
r a i s e Exception.Create('Missing file: ' + xslfolder +
xslfile) ;
XSLDom.Active : = True;
i f xslfile = 'single.xsl ' then
begin
attr : = xslDom.DOMDocument.createAttribute('select');
attr.value : = '//ROW[@CustNo="' +
Request. QueryFields .Values [ 'id'] + ' " I ';
xslDom.DOMDocument.getElementsByTagName ('xs1:apply-
templates ' ) .
item[O].attributes.setNamedItem(attr);
end;
/ / redliza la transformation
HTMLDom.Active : = True;
xmlDom.DocumentElement.transformNode
(xslDom.DocumentElement, HTMLDom);
Response.Content := HTMLDom.XML.Text;
end:

El codigo usa el DOM para modificar el documento XSL para mostrar un


unico registro, aiiadiendo la sentencia XPath para seleccionar el registro indicado
por el campo de consulta i d . Este i d se aiiade a1 hipervinculo gracias a1 XSL
con la lista de 10s registros, pero no vamos a listar mas archivos XSL, ya que
podemos obtenerlos en la subcarpeta XSL de la carpeta de este ejemplo y anali-
zarlos con mayor detenimiento.

ADVERTENCIA: Para ejecutar este programa, hay que colocar 10s archi-
vos XSL en una carpeta llamada XSL dentro de aquella en que se encuentre
la Carpeta de 10s guiones de este capitdo. Para desplegar e m s archivos en
~ t i n t a , - ~ w k i g que'e&e
l . el dombn
de la carpeia XSL a partir del mmbbredeJpn$ramq dispuitible en el primer
pariunetio ep b e a de comandos (yaque el orrjeto%pplication definido
en la unidad Forms no es accesible por media & iiiia aplicacib CGI).
-

Procesamiento de grandes documentos XML


Como ya se ha visto, suelen existir muchas tecnicas distintas para realizar la
misma tarea con XML. En la mayoria de 10s casos se puede escoger cualquier
solution, teniendo en mente el objetivo de escribir un codigo menor y mas facil de
mantener. Pero cuando se necesita procesar un gran numero de documentos XML
o documentos XML muy grandes, hay que tener en cuenta la eficiencia.
Comentar la teoria por si misma no es muy util, asi que crearemos un ejemplo
que pueda utilizarse (y modificarse) para probar distintas soluciones. El ejemplo
se llama LargeXml, y trata un campo especifico: llevar datos desde una base de
datos a un archivo XML y en sentido inverso. El ejemplo puede abrir un conjunto
de datos (usando dbExpress) y replicar despues 10s datos varias veces en un
ClientDataSet que resida en memoria. La estructura del ClientDataSet en memo-
ria se duplica a partir del componente de acceso a 10s datos:

Despues de utilizar un grupo de botones de radio para definir la cantidad de


datos que se desea procesar (algunas opciones pueden requerir minutos en un
ordenador lento); 10s datos se duplican mediante este codigo:
while ClientDataSet1.RecordCount < nCount do
begin
SimpleDataSet1.RecNo : = Random (SimpleDataSet1.RecordCount)
+ 1;
ClientDataSetl-Insert;
ClientDataSetl. Fields [0].AsInteger := Random (10000);
for I : = 1 to SimpleDataSetl.Fie1dCount - 1 do
ClientDataSetl.Fields [i].AsString : =
SimpleDataSetl.Fields [i].AsString;
ClientDataSetl-Post;
end:

De un ClientDataSet a un documento XML


Ahora que el programa tiene en memoria un gran conjunto de datos, propor-
ciona tres modos distintos de guardar el conjunto de datos en un archivo. El
primer0 es guardar directamente la propiedad XMLData del ClientDataSet en un
archivo, consiguiendo asi un documento basado en atributos. Probablemente no sea
este el formato que se desee, por lo que la segunda solucion es aplicar una transfor-
macion de XmlMapper mediante un componente XMLTransf ormClient . La
tercera solucion implica procesar directamente el conjunto de datos y escribir cada
registro en un archivo:
procedure TForml.btnSaveCustomClick(Sender: TObject);
var
str: TFileStream;
s: string;
i: Integer;
begin
str : = TFileStream.Create ( 'data3.xmZ ', fmcreate) ;
try
ClientDataSet1.First;
.
s : = '<?xmZ version="l 0" s tandalone="yes " ?><employee> ';
str .Write (s[1] , Length (s)) ;

while not ClientDataSetl.EOF do


begin
:= ' I .

for i : = 0 to ClientDataSetl.Fie1dCount - 1 do
s : = s + MakeXmlstr
(ClientDataSetl.Fields [i] . FieldName,
ClientDataSetl.Fields[i].AsString);
s : = MakeXmlStr ( 'employeeData ', s) ;
str-Write(s[1] , length (s)) ;
ClientDataSet1.Next
end;
s : = '</employee>';
str.Write (s[1] , length (s)) ;
finally
str . Free;
end;
end;

Este codigo utiliza una funcion auxiliar simple per0 eficaz para crear 10s nodos
XML:
function MakeXmlstr (node, value: string) : string;
begin
Result : = ' < I + node + ' > I + value + I < / ' + node + ' > I ;

end :

Si se ejecuta el programa, se podra ver el tiempo que se tarda en cada opera-


cion, como lo muestra la figura 22.13. Guardar 10s datos del ClientDataSet es el
enfoque mas rapido, per0 probablemente no se consiga el efecto deseado. El
streaming personalizado es solo ligeramente mas lento, per0 deberia considerarse
que este codigo no necesita que 10s datos se lleven en primer lugar a un
ClientDataSet, porque se puede aplicar directamente, incluso en un conjunto de
datos unidireccional de dbExpress. Deberiamos olvidarnos de utilizar el codigo
basado en el XmlMapper para un conjunto de datos grande, porque es varios
cientos de veces mas lento, incluso para un conjunto de datos pequeiio (ni siquiera
hemos podido probarlo con un conjunto de datos grande, porque, sencillamente,
tarda demasiado). Por ejemplo, 10s 50 milisegundos que necesita un streaming
personalizado para un conjunto de datos pequeiios se convierten en m b de 10
segundos cuando usamos la proyeccion, y el resultado es muy parecido.

313 Robert Nelson 332 -

--
G Ion 937 .
6716 k !- 5 -
161 7 Janet Bddwn 2
' E _ s-' -,7
-
4747 LesLe Johnson __ 1410 -
h",bM - - '2-L.
1
+I

Figura 22.13. El ejemplo LargeXml en accion.

De un documento XML a un ClientDataSet


Una vez que se tiene un documento XML grande, conseguido por un programa
(como en este caso) o a partir de una fuente externa, es necesario procesarlo.
Como ya se ha visto, el soporte de XmlMapper es demasiado lento, por lo que
solo quedan tres alternativas: una transformacion XSL, un SAX o un DOM. Las
transformaciones XSL probablemente Sean lo suficientemente rapidas, per0 en
este ejemplo hemos abierto el documento con un SAX, ya que se trata del enfoque
mas rapido y no necesita mucho codigo. El programa tambien puede cargar un
documento en un DOM, per0 no hemos escrito el codigo necesario para recorrer
el DOM y guardar 10s datos de nuevo en un ClientDataSet.
En ambos casos, hemos probado el motor OpenXml contra el motor MSXML.
Esto permite ver las dos soluciones SAX como comparacion, porque (lamentable-
mente) el codigo es ligeramente distinto. A mod0 de resumen de 10s resultados:
usar la SAX de MSXML es ligeramente mas rapido que usar el SAX de OpenXML
(la diferencia esta en torno a un 20 por ciento), mientras que la carga en el DOM
significa una amplia ventaja a favor de MSXML. El codigo del SAX de MSXML
utiliza la misma arquitectura ya comentada en el ejemplo SaxDemol, por lo que
aqui solo vamos a mostrar el codigo de 10s controladores que se utilizan. Como
puede verse, a1 comienzo de un ejemplo employeeData se inserta un nuevo regis-
tro, que se envia cuando se cierra el mismo nodo. Los nodos de menor nivel se
aiiaden como campos al registro actual. Este es el codigo:
procedure TMyDataSaxHandler.startElement(var strNamespaceUR1,
strLocalName,
strQName: WideString; const ~Attributes: IVBSAXAttributes);
begin
inherited;
i f strLocalName = 'employeeDatat then
Forml.clientdataset2.Insert;
strcurrent : = ' I ;
end;

procedure TMyDataSaxHandler.characters(var strchars:


WideString) ;
begin
inherited;
strcurrent : = strcurrent + Removewhites (strchars);
end;

procedure TMyDataSaxHandler.endElement(var strNamespaceUR1,


strLocalName,
strQName : WideString) ;
begin
i f strLocalName = employeeData ' then
Forml.clientdataset2.Post;
if stack.Count > 2 then
Forml.ClientDataSet2.FieldByName (strLocalName).Asstring
.= strcurrent;
inherited;
end;

El codigo para 10s controladores de eventos en la version OpenXml es pareci-


do. Todo lo que cambia son las interfaces de 10s metodos y 10s nombres de 10s
parametros:
type
TDataSaxHandler = class (TXmlStandardHandler)
protected
stack: TStringList;
strcurrent: string;
public
constructor Create(aowner: TComponent); override;
function endElement(const sender: TXmlCustomProcessorAgent;
const locator: TdomStandardLocator;
namespaceUR1, tagName: widestring): TXmlParserError;
override;
function PCDATA(const sender: TXmlCustomProcessorAgent;
const locator: TdomStandardLocator; data: widestring):
TXmlParserError; override;
f u n c t i o n s t a r t E l e m e n t ( c o n s t sender: TXmlCustomProcessorAgent;
const locator: TdomStandardLocator; namespaceURI,
t a g N a m e : widestring;
attributes: TdomNameValueList) : TXmlParserError; override;
d e s t r u c t o r Destroy; override;
end;

Tambien es mas dificil invocar el motor de SAX, como se muestra en el codigo


siguiente (del que hemos eliminado el codigo de creacion del conjunto de datos, la
medida del tiempo y el registro):
p r o c e d u r e TForml.btnReadSaxOpenClick(Sender: TObject);
var
agent: TXmlStandardProcessorAgent;
reader: TXmlStandardDocReader;
filename: string;
begin
L o g : = memoLog. L i n e s ;
filename := ExtractFilePath (Application.Exename) + ' d a t a 3 . xml ';
agent : = TXmlStandardProcessorAgent.Create(ni1);
reader:= TXmlStandardDocReader.Create ( n i l ) ;
try
reader.NextHandler : = TDataSaxHandler.Create (nil); / /
our custom c l a s s
agent.reader : = reader;
agent.processFile(filename, filename);
finally
agent.free;
reader.free;
end;
end;
Servicios
Web y SOAP

De entre todas las caracteristicas mas recientes de Delphi, una sobresale por
encima de todas: el soporte para servicios Web incluido en el producto. El hecho
de que lo tratemos hacia el final del libro no tiene nada que ver con su importan-
cia, sino solamente con el logico desarrollo del texto y con el hecho de que no es el
mejor punto de partida para comprender la programacion en Delphi.
El tema de 10s servicios Web es muy amplio e implica varias tecnologias y
estindares de negocio. Como siempre, nos centraremos en la implementacion sub-
yacente de Delphi y en la parte tecnica de 10s servicios Web, en lugar de analizar
el panorama global y las implicaciones empresariales.
Este capitulo tambien es importante porque Delphi 7 aiiade una gran cantidad
de potencia a la implementacion de servicios Web que ofrecia Delphi 6, incluyen-
do soporte para adjuntos, cabeceras personalizadas y muchas cosas mas. Vere-
mos como crear un cliente y un servidor de servicios Web, y tambien como
transportar datos de una base de datos sobre SOAP empleando la tecnologia
DataSnap. Este capitulo trata 10s siguientes temas:
Servicios Web.
SOAPyWSDL.
DataSnap sobre SOAP
Manejo de adjuntos.
UDDI.

Servicios Web
Esta tecnologia en rapida expansion tiene el potencial de cambiar la forma en
la que Internet funciona para las empresas. Explorar una pagina Web para hacer
un pedido esta bien para un unico usuario (las conocidas aplicaciones B2C o
aplicaciones de empresa a consumidor), pero no para una empresa (las aplicacio-
nes B2B de empresa a empresa). Si queremos comprar unos cuantos libros, visi-
tar el sitio Web de un vendedor de libros y escribir nuestros pedidos estara bien,
pero si nuestro negocio es una libreria y queremos hacer cientos de pedidos a1 dia,
este metodo esta lejos de ser el mas adecuado, en particular si tenemos un progra-
ma que nos ayude a hacer el seguimiento de ventas y determinar nuevos pedidos.
Seria ridiculo tomar la salida de este programa y volver a escribirla en otra apli-
cacion.
La idea de 10s s e ~ i c i o Web
s es solventar este problema: el programa utilizado
para el seguimiento de ventas, puede crear automaticamente una consulta y en-
viarla a un servicio Web, el cual, devuelve inmediatamente la informacion sobre
el pedido. El siguiente paso podria ser una consulta sobre el numero de envio. A
continuacion, el programa puede utilizar otro servicio Web para hacer el segui-
miento del envio hasta que llegue a su destino y asi poder decirle a1 cliente cuanto
tardara en llegar. Cuando llegue el envio, el programa puede enviar una notifica-
cion SMS o mediante un busca a las personas que tengan solicitudes pendientes,
emitir una orden de pago con el servicio Web de un banco, ... y podriamos conti-
nuar pero creemos que ya se ha captado la idea. Los servicios Web estan pensa-
dos para la interoperabilidad informatics a1 igual que la Web y el correo electronico,
estan pensados para la comunicacion entre personas.

SOAP y WSDL
Los servicios Web son posibles gracias a1 Simple Object Access Protocol
(SOAP). Creado sobre el protocolo HTTP estandar para que un servidor Web
pueda manejar las consultas SOAP y que 10s paquetes de datos puedan pasar a
traves de cortafuegos. SOAP define una notacion basada en XML para solicitar
la ejecucion de un metodo por parte de un objeto en el servidor, pasandole 10s
parametros. Mediante otra notacion se define el formato de la respuesta.

I ROTA: SOAP h e desarrollado originalmente por DevelopMentor (la corn-


pafiia de entrenamiento de Don Box, el experto en COM)y Microsofi, para
superar la debilidad del uso de DCOM en s e ~ d o r e Web.
s Sometido a1
r W para f su es
C n i m u c h &qxis&~ lo .Mgieron. co.aparti-
cular empujc de IBM. Es muy pronto pQa aaber si se producid una
1
eatandarizacih real para que 10s programas de software de Miqrosoft, IBM,
Sun, Oracle y muchos otros interaden realmeate o si algunas de cstas
marcas tratarh de promover una version privada del esthdar. En cual-
quier caso, SOAP es s61o una de Ias piedras angulares de la arquiWhm
.NETde Microsoft, asi como de las platafonnas aduales de Sun y Omclc.

SOAP reemplazara, a1 menos entre ordenadores diferentes, el uso de COM.


Del mismo modo, la definicion de un servicio SOAP en el formato Web Services
Description Language (WSDL) sustituira a1 IDL y las bibliotecas de tipos utiliza-
das por COM y COM+.
Los documentos WSDL son otro tipo de documentos XML que proporcionan
la definicion de metadatos de una consulta SOAP. Cuando obtenemos un archivo
en este formato (generalmente publicado para definir un servicio), podremos crear
un programa para llamarlo.
De manera especifica. Delphi proporciona una proyeccion bidireccional espe-
cifico entre WSDL y las interfaces. Esto significa que podemos coger un archivo
WSDL y generar una interfaz para el. Podemos incluso crear un programa de
cliente que incluya las consultas de SOAP mediante estas interfaces y utilizar un
componente especial de Delphi que nos permita convertir las consultas de la interfaz
local en llamadas SOAP (a menos que queramos generar manualmente el XML
necesario para una consulta SOAP).
En sentido inverso, podemos definir una interfaz (o utilizar una existente) y
permitir que un componente Delphi genere una descripcion WSDL para ella. Otro
componente nos proporciona una proyeccion de SOAP a Pascal para que a1 inser-
tar este componente y un objeto que implemente la interfaz dentro del programa
de servidor, consigamos en unos minutos poner en marcha un servicio Web.

Traducciones BabelFish
Como primer ejemplo del uso de un servicio Web, vamos a crear un cliente
para el servicio de traduccion BabelFish ofrecido por AltaVista. Se puede encon-
trar este y muchos otros servicios para su experimentacion en el sitio Web de
XMethods (~vw.xmethods.com).
Despues de descargar la descripcion WSDL de este servicio del sitio de
XMethods (disponible tambien entre 10s archivos de codigo fuente para este capi-
tulo), hemos invocado a1 Web Services Importer de Delphi en la pagina Web
Services del cuadro de dialog0 New Items y hemos seleccionado el archivo. El
asistente permite acceder a una vista previa de la estructura del servicio (vCase la
figura 23.1) y generar las interfaces en lenguaje Delphi apropiadas en una unidad
como la siguiente (con muchos de 10s comentarios eliminados):
klsDL Conocrmnlx
E8 . Bab$F~shSewm
I'(DZDB671Z-BBBO-lDA6-8DBC-8A445595ABOC)'l
function BabelF~sh(const trmslatlonrodo: Wldel
end;

f m c c x o n CetBabelFlsMortType(UseWSDL- Boolean-Systs

--An-
- - .- - -
1 I -- lil --

I
Figura 23.1. El WSDL Import Wizard en accion.

unit ~abelFishService;

interface

uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;

type
BabelFishPortType = interface(IInvokab1e)
[ ' (D2DB6712-EBEO-lDA6-8DEC-8A445595AEOC)' 1
function BabelFish(const translationmode: WideString;
const sourcedata: WideString): WideString; stdcall;
end;

function GetBabelFishPortType(UseWSDL: Boolean=System.False;


Addr: string="; HTTPRIO: THTTPRIO = nil): BabelFishPortType;

implementation

// omitido

initialization
InvRegistry.RegisterInterface(TypeInfo(BabelFishPortType),
' urn:xmethodsBableFish ', ' ') ;
1nvRegistry.RegisterDefaultSOAPAction
(TypeInfo (BabelFishPortType),
' urn:xmethodsBableFish#Babe1Fish ') ;
end.

Observe que la interfaz hereda de la interfaz Ilnvokable. Esta interfaz no


aiiade nada con respecto a 10s metodos de la interfaz base llnterface de Delphi,
sino que se compila con el indicador utilizado para establecer la generacion RTTI,
( $M+}, como la clase T P e r s i s t e n t . En la seccion de inicializacion puede
verse tambien que la interfaz se registra en el registro de invocacion global (o
I nvReg i s t r y), pasando la referencia de informacion de tipo del tipo de interfaz.

NOTA: Dis-r de info@n pan$ mtmfaccs q m&mpte d


avance tecd&gicq &I imparfslnte relacionado-conla i ~ c a c i l i t SOAP.
l
No es que proyea$h de ~ C l h .PGcal 6 no'sea M p b m e (es v i
a paf'd
simplificar el prowso) sino que' d$sponex d.e. i n t k m n a d b RTfl para ma
interfaz es lo que realmente hace t p e la arquite&ra sea pdente .j;r~pusta.

El tercer elemento de la unidad generada por el WDSL Import Wizard es una


funcion global que toma su nombre del servicio, introducida en Delphi 7. Esta
funcion ayuda a simplificar el codigo utilizado para llamar a1 servicio Web. La
funcion G e t B a b e l F i s h P o r t T y p e devuelve una interfaz del tipo apropiado,
que puede usarse para lanzar directamente una Ilamada. Por ejemplo, el siguiente
codigo traduce una breve frase de ingles a italiano (corno indica el valor de su.
primer parametro, e n -i t ) y la muestra en pantalla:
ShowMessage (GetBabelFishPortType .BabelFish ( 'en-it' , 'Hello,
w o r l d ! ' )) ;

Si se presta atencion a1 codigo de la funcion G e t B a b e l F i s h P o r t T y p e , se


vera que crea una componente interno de invocacion de la clase THTTPRIO para
procesar la Ilamada. Puede colocarse manualmente este componente en el formu-
lario cliente (corno en el programa de ejemplo) para tener mas control sobre sus
diversas variables (y controlar sus eventos).
Este componente puede configurarse de dos maneras basicas: puede hacerse
referencia a1 archivo o a1 URL WSDL, importarlo, y extraerlo a partir del URL
de la llamada SOAP; o puede proporcionarse un URL direct0 para la Ilamada. El
ejemplo tiene dos componentes que proporcionan 10s enfoques alternatives (que
tienen exactamente el mismo efecto):
o b j e c t HTTPRIOI: T H T T P R I O
WSDLLocation =
'C:\md6code\23\BdbelFish\Bdbe1FishService.~ml'
Service = ' B a b e l F i s h '
Port = ' B a b e l F i s h P o r t '
end
o b j e c t HTTPRI02: T H T T P R I O
U R L = 'http://services. xrnethods. net :80/perl/sodplite. c g i '
end

Llegados a este punto, poco mas hay que hacer ya. Tenemos informacion sobre
el servicio que podemos usar para invocarlo y conocemos 10s tipos de 10s parametros
requeridos por el unico metodo disponible tal y como se indican en la interfaz.
Los dos elementos se unen extrayendo la interfaz a que queremos llamar directa-
mente desde el componente HTTPRIO, con una expresion como HTTPRIOl a s
Babe 1Fi s hPor tTy p e . Puede parecer sorprendente, per0 es increiblemente
simple.
Esta es la llamada a1 servicio Web realizada por el ejemplo:
EditTarget.Text : = (HTTPRIO1 as BabelFishPortType).
BabelFish(ComboBoxType.Text, Editsource-Text);

La salida del programa, como muestra la figura 23.2, permite aprender idio-
mas (aunque, en este caso, el profesor tiene algunas limitaciones, claro). No he-
mos repetido este mismo ejemplo con opciones de compra, divisas, pronosticos
del tiempo y muchos otros servicios disponibles, porque tendrian todos un aspect0
muy parecido.

ADVERTENCIA: Aunpe la interfaz de servicio Web pmporciona 10s


tipos de 10s padmetros, en muclios casos se necesitarb ooakuhar.la docu-
mentacion red del servicio para saber q d significan reabmitk 103 valores
de 10s parbeti.0~y c6mo 10s interpreti el servicio. El setvhio Web de
BabeIFish es un ejempio de esto, ya que ha sido necesario &liar docu-
mentacion textual paraenmntrar la lista de las tipos de trst&ccion, dispo-
nibles en el programa he muestra en un cuadro combinado.

pzq
i .--.-.__I
I~hs1s a sample message lci an aularnabc llanslal~on
len_de lu am automat~scheUbwsel-
Ideses !st e m Basp~ebnze~ge

Dud 1
Figura 23.2. Un ejemplo de la traducclon de ingles a alernan conseguida con
BabelFish de AltaVista rnediante un serviclo W e b .

Creacion de un servicio Web


Si llamar a un servicio Web en Delphi es sencillo, lo mismo podriamos decir
del desarrollo de un servicio. Si vamos a la pagina Web Services del cuadro de
dialog0 New Item, podemos ver la opcion SOAP Server Application. Seleccio-
nandola, Delphi nos presentara una lista que es bastante parecida a la seleccion de
una aplicacion WebBroker. De hecho, el servicio Web tipicamente se alberga en
un servidor Web empleando una de las tecnologias de extension de servidores
(CGI, ISAPI, modulos Apache, etc. ..) disponibles o el Web App Debugger para
las pruebas iniciales. Despues de completar este paso, Delphi aiiadira tres compo-
nentes a1 modulo Web resultante, que no es mas que un modulo Web basico sin
adiciones especiales:
El componente HTTPSoapDispatcher recibe la consulta Web como
lo haria cualquier otro repartidor HTTP.
El componente HTT PSoapPasca 1Invo ker realiza la operacion inver-
sa a la del componente HTTPRIO: es capaz de traducir consultas SOAP en
llamadas para interfaces Pascal (en lugar de convertir las llamadas a meto-
dos de la interfaz en consultas SOAP).
El componente WS DLHTMLPub1ish puede usarse para extraer la defini-
cion WSDL del servicio a partir de las interfaces que soporta, y realiza el
papel contrario a1 del Web Services Importer Wizard. Tecnicamente, se
trata de otro repartidor HTTP.

Un servicio Web de conversion de divisas


Una vez preparado este marco (que tambien podemos crear aiiadiendo 10s tres
componentes anteriores a un modulo Web ya existente) podemos comenzar a
escribir el servicio.
Como ejemplo, vamos a transformar el ejemplo de conversion a1 euro del capi-
tulo 3 en un servicio Web llamado Convertservice. Lo primer0 ha sido aiiadir a1
programa una unidad que defina la interfaz del servicio:
tn?e
IConvert = interface (IInvokable)
[ ' {FFlEAA45-0B94-4630-9A18-E768A91A78E2) ' 1
function Convertcurrency (Source, Dest: string; Amount:
Double) : Double;
s tdcall ;
function ToEuro (Source: string; Amount : Double) : Double;
s tdcall ;
function FromEuro (Dest: string; Amount: Double): Double;
stdcall;
function TypesList : string; stdcall;
end;

Si definimos una interfaz directamente en el codigo, sin necesidad de utilizar


una herramienta como el Type Library Editor, conseguimos una gran ventaja, ya
que podemos crear facilmente una interfaz para una clase ya existente sin tener
que aprender a utilizar una herramienta especifica para ello. Fijese en que le
hemos dado un GUID a la interfaz, como es habitual, y que hemos utilizado la
convencion de llamada stdcall,ya que el convertidor de SOAP no soporta la
convencion de llamada predefinida, reg ister .
En la misma unidad que define la interfaz del servicio, deberiamos ademas
registrarlo. Esta operacion sera necesaria tanto en la parte del cliente como en la
del servidor, ya que podremos incluir la unidad de definicion de esta interfaz en
ambos.
uses InvokeRegistry;

initialization
InvRegistry.RegisterInterface(TypeInfo(1Convert));

Ahora que disponemos de una interfaz que podemos mostrar a1 publico, tene-
mos que proporcionarle una implementacion. Para ello utilizaremos, una vez mas,
el codigo estandar de Delphi (con la ayuda de la clase TInvo kableclass
predefinida):
type
TConvert = c l a s s (TInvokableClass, IConvert)
protected
f u n c t i o n ConvertCurrency (Source, Dest: string; Amount:
Double) : Double;
stdcall;
f u n c t i o n T o E u r o (Source : string; Amount: Double) : Double;
s tdcall;
f u n c t i o n F r o m E u r o (Dest: string; Amount: Double) : Double;
stdcall;
f u n c t i o n TypesList: string; stdcall;
end;

La implementacion de estas funciones, que llaman a1 codigo del sistema de


conversion a1 euro del capitulo 3, no se cornentan aqui porque tiene poco que ver
con el desarrollo del servicio. No obstante, es importante tener en cuenta que esta
unidad de implementacion tambien tiene una llamada de registro en su seccion de
inicializacion:
1nvRegistry.RegisterInvokableClass (TConvert);

Publicacion del WSDL


A1 registrar la interfaz, se permite que el programa genere una descripcion
WSDL. La aplicacion del servicio Web (a partir de la actualization Delphi 6.02)
es capaz de mostrar una pagina inicial que describe sus interfaces y 10s detalles de
cada interfaz, y devuelve el archivo WSDL. A1 conectar a1 servicio Web mediante
un navegador, ser vera algo similar a lo que muestra la figura 23.3.

NOT*: Aunclu6 otr& arcpitccturas de servicios Web proporcionan


auto^^^ uu modo de el seretieio%bWd'e el navegador,
e m th&aqsuele ~ e £MA, r 9orque utilizar seryiciqs web'kene sentido en
una arquitectQraen la qqs is.ieractbeadiSiqt8s aplicadbneg. Si todo lu t p e
se necesita es mostrar datos ed pn aahgkdm. dskria creme un 'BitioWeb.
ConvertService Service Info Page -
ConvertService - PortTypes:

Iconvert [WSDL]
0 Convertcurrency
0 ToEuro
0 FromEuro
0 TypesLtst
IWSDLPublish [.=I
~ yhl eb dl Servlre
LI-t; ,711 !h* P 0 r t P p ~ 5p ~ ~ b l ~ b ~
0 GetPortTypeLirt
0 GetWSDLForPortType
0 GetTypeSystemsList
0 GetXSDForTypeSystem
WSIL: Lird t > : ' I S - l n s ~ e r t l n n dnwriwnt ot 5eralces

Figura 23.3. La descripcion del servicio Web ConvertService proporcionada por


componentes Delphi.

Esta caracteristica autodescriptiva no estaba disponible en 10s servicios Web


creados en Delphi 6 (que solo proporcionaba un listado WSDL a bajo nivel), per0
es bastante sencilla de afiadir (o personalizar). Si se analiza el modulo Web SOAP
de Delphi 7, ser vera una accion predefinida con un controlador para el evento
OnAct i o n que invoca el siguiente comportamiento predefinido:
WSDLHTMLPublishl.ServiceInfo(Sender, Request, Response,
Handled) ;

Esto es todo lo que hay que hacer para proporcionar esta caracteristica a un
servicio Web de Delphi ya esistente que carezca de ella. Para conseguir de forma
manual una prestacion similar, hay que llamar a1 registro de invocacion (el objeto
global I n v R e g i s t r y ) , con llamadas comoGet I n t e r f a c e E x t e r n a l N a m e
y G e t M e t h E x t e r n a l N a m e . Lo que es importante es la capacidad del servicio
Web de autodocumentarse para cualquier otro programador o herramienta de
programacion, presentando el WSDL.

Creacion de un cliente personalizado


Veamos la aplicacion cliente que llama a1 servicio. No necesitamos partir del
archivo WSDL, dado que ya tenemos la interfaz Delphi. Ni siquiera es necesario
que el formulario tenga el componente HTTPPRIO, el cual se crea en el codigo:
private
Invoker: THTTPRio;
procedure TForml.FormCreate(Sender: TObject);
begin
Invoker : = THTTPRio.Create(ni1);
1nvoker.URL : = 'http://localhost/scripts/ConvertService.exe/
soap/iconvert ' ;
ConvIntf : = Invoker a s IConvert;
end;

Como una alternativa al uso del un archivo WSDL, el componente que invoca
a SOAP puede asociarse con un URL. Una vez que se ha realizado esta asocia-
cion y la interfaz necesaria se ha extraido del componente, podemos empezar a
escribir el codigo Pascal para invocar a1 servicio, como hemos visto anteriormen-
te. Un usuario puede rellenar 10s dos cuadros combinados, llamando a1 metodo
TypesList,que devuelve una lista de las monedas disponibles dentro de una
cadena (separada por puntos y coma). Extraeremos esta lista sustituyendo cada
punto y coma por un caracter de nueva linea y asignando despues directamente la
cadena multilinea a 10s elementos combinados:
procedure TForml.Button2Click(Sender: TObject);
var
TypeNames: string;
begin
TypeNames : = ConvIntf.TypesList;
ComboBoxFrom.1tems.Text : = StringReplace (TypeNames, I ; ' ,

sLineBreak, [rfReplaceAll]) ;
ComboBoxTo. Items : = ComboBoxFrom. Items;
end :

Despues de seleccionar dos divisas, podemos realizar la conversion con este


codigo (la figura 23.4 muestra el resultado):
procedure TForml.ButtonlClick(Sender: TObject);
begin
LabelResu1t.Caption : = Format ('tn', [(ConvIntf.ConvertCurrency(
ComboBoxFrom.Text, ComboBoxTo-Text, StrToFloat(EditAmount.Text)))]);
end;

-[ "z
Fill List I
DEM =I

Figura 23.4. El cliente Convertcaller del servicio Web Convertservice rnuestra 10s
POCOS rnarcos alemanes necesarios para conseguir muchisimas liras italianas, antes
de que el euro lo cambiara todo.
Peticion de datos de una base de datos
Para este ejemplo, hemos creado un servicio Web (basado en el Web App
Debugger) capaz de presentar datos sobre 10s empleados de una empresa. Estos
datos de proyecto sobre la tabla EMPLOYEE de la base de datos InterBase de
muestra que hemos usado a menudo en este libro. La interfaz Delphi del servicio
Web se define en la unidad SoapEmployeeIntf de este modo:
type
ISoapEmployee = interface (IInvokable)
['{77DOD940-23EC-49A5-9630-ADE0751E3DB3)']
function GetEmployeeNames: string; stdcall;
function GetEmployeeData (EmpID: string): string; stdcall;
end:

El primer metodo devuelve una lista de 10s nombres de todos 10s empleados de
la empresa, y el segundo devuelve 10s detalles de un empleado determinado. La
implementacion de esta interfaz se proporciona en la unidad SoapEmployeeImpl
con la clase siguiente:
type
TSoapEmployee = class(TInvokableClass, ISoapEmployee )
public
function GetEmployeeNames: string; stdcall;
function GetEmployeeData (EmpID: string) : string; s tdcall;
end;

La implementacion del servicio Web recae en 10s dos metodos anteriores y


algunas funciones auxiliares para gestionar 10s datos XML devueltos. Pero antes
de llegar a la parte XML del ejemplo, vamos a analizar brevemente la seccion de
acceso a la base de datos.
Acceso a los datos
Toda la conectividad y el codigo SQL de este ejemplo se guarda en un modulo
de datos independiente. Por supuesto, podriamos haber creado dinamicamente
algunos componentes de conexion y de conjuntos de datos en 10s metodos, per0
hacerlo seria contrario a1 enfoque de una herramienta de desarrollo visual como
Delphi. El modulo de datos tiene la siguiente estructura:
object DataModule3: TDataModule3
object SQLConnection: TSQLConnection
ConnectionName = 'IBConnection'
DriverName = 'Interbase'
Loginprompt = False
Params .Strings = // omitido
end
object dsEmplList: TSQLDataSet
ComrnandText = 'select EMP-NO, L A S T-NAME, FIRST-NAME from
EMPLOYEE '
SQLConnection = SQLConnection
o b j e c t dsEmplListEMP-NO: TStringField
o b j e c t dsEmplListLAST-NAME: TStringField
o b j e c t dsEmplListFIRST-NAME: TStringField
end
o b j e c t dsEmpData: TSQLDataSet
ComrnandText = 'select * f r o m E M P L O Y E E where E m p N o = : i d f
Params = <
item
DataType = ftFixedChar
Name = 'id'
ParamType = ptInput
end>
SQLConnection = SQLConnection
end
end

Como puede verse, el modulo de datos tiene dos sentencias SQL en sendos
componentes SQLDataSet. La primera se utiliza para obtener el nombre e
identificador de cada empleado, y la segunda devuelve el conjunto de datos com-
pleto para un empleado dado.
Paso de documentos XML
El problema esta en como devolver 10s datos a un programa cliente remoto. En
este ejemplo, hemos usado un buen enfoque: devolver documentos XML, en lugar
de trabajar con las complejas estructuras de datos de SOAP. (No es facil entender
que XML pueda verse como un mecanismo de comunicacion para la invocacion
mediante SOAP, junto con el mecanismo de transporte que proporciona HTTP,
per0 luego no se utilice para 10s datos que se transmiten. Aun asi, muy pocos
servicios Web devuelven documentos XML, por lo que puede que otros progra-
madores no lo tengan asi de claro.)
En este ejemplo, el metodo GetEmployeeNames crea un documento XML
que contiene una lista de empleados, con sus nombres y apellidos como valores y
el identificador en la base de datos como atributo, empleando las dos funciones
auxiliares MakeXml S tr (descrita en el ultimo capitulo) y MakeXmlAt tr ibut e
(que se muestra aqui):
f u n c t i o n TSoapEmployee.GetEmployeeNames: string;
var
dm: TDataModule3;
begin
d m : = TDataModule3 .Create ( n i l ) ;
try
dm.dsEmplList.0pen;
Result : = '<employeeList>' + sLineBreak;
w h i l e n o t dm.dsEmplList.EOF do
begin
Result : = Result + ' ' + MakeXmlStr ('employee',
dm.dsEmp1ListLASTNAME.AsString + ' ' +
dm.dsEmplListFIRSTNME.AsString,
MakeXmlAttribute ( 'id',
dm.dsEmplListEMPN0.AsString)) + sLineBreak;
dm.dsEmplList.Next;
end;
Result : = Result + ' < / e m p l o y e e L i s t > ';
finally
dm. Free;
end;
end;

function MakeXmlAttribute (attrName, attrvalue: string) :


string;
begin
Result : = attrName + ' = " ' + attrvalue + "";
end ;

En lugar de emplear la generacion manual de XML, podriamos haber emplea-


do el XML Mapper o alguna otra tecnologia, per0 hemos preferido crear directa-
mente XML en forma de cadenas. Usaremos el XML Mapper para procesar 10s
datos recibidos en el cliente
-- - -- - - - -- - -.- - -- - . .

NOTA: Puede que se pregunte por qui crea el programa una nueva instan-
cia del mMulo de datos cada vez. La parte negativa de este enfoaue es oue
6:1 programa establece cada vez un;a nueva conexion con I
:una operation bastante lenta); perco la pmte positiva es qu
-'----=-
mln 2 - - - - --I--:
nenwn --.-
d-
relmmnaan con ---
- - - el
-- --- ne
..--
-1
ilnn --
1
--- -r-------
ilna anlicacihn milltihun --
---------aa r --
s e eiecil-
-J---

tan concurrentemente dos peticiones a1 servicio Web, se puede utilizar una


. . .. . . . .- . . . - .,
conexiirn carnpartida a la base de datos, per0 hay que usar componentes de
conjunto ae aatos cusumos para a acceso a aatos. rwrmws aespmar ms
. .
conjuntos be dams en el d&go & la fuudb y mantener &lo la conexi6n en
el modulo de datos, o tener un mbdulo de datos cornpartido global para la
conexion (usado por varias hebras) y una instancia especifica de un segun-
do modulo de datos albergado por 10s Gaqjuntos be datos para cada llamada
a m&odo.

Prestemos ahora atencion a1 segundo metodo, Get EmployeeData.Utiliza


una consulta parametrica y da formato a 10s campos resultantes en nodos XML
independientes (mediante otra funcion auxiliar, FieldsToXml):
function TSoapEmployee .GetEmployeeData (EmpID: string) : string;
var
dm: TDataModule3;
begin
dm : = TDataModule3 .Create (nil);
try
dm.dsEmpData.ParamByName('ID') .Asstring : = EmpId;
dm.dsEmpData.Open;
Result : = FieldsToXml ( 'employee ', dm. dsEmpData) ;
finally
dm. Free;
end;
end;

f u n c t i o n FieldsToXml (rootName: string; data: TDataSet): string;


var
i: Integer;
begin
Result : = ' < ' + rootName + ' > ' + sLineBreak;;
f o r i : = 0 to data.FieldCount - 1 d o
Result : = Result + ' ' + MakeXmlStr (
.
Lowercase (data.Fields [i] FieldName) ,
data. Fields [i] .Asstring) + sLineBreak;
Result : = Result + ' < / I + rootName + ' > ' + sLineBreak;;
end;

El programa cliente (con proyeccion XML)


El paso final para este ejemplo es escribir un programa cliente de prueba.
Podemos hacerlo como siempre, importando el archivo WSDL que define el servi-
cio Web. En este caso, tambien tendremos que convertir 10s datos XML que se
reciben en algo mas manejable, en particular la lista de empleados que devuelve el
metodo Get Emp 1 o ye eName s . Como ya comentamos, hemos usado el XML
Mapper de Delphi para convertir la lista de empleados recibida del servicio Web
enun conjunto de datos que pueda mostrarse mediante una DBGrid.
Para realizar esto, en primer lugar hemos escrito el codigo para recibir el
XML con la lista de empleados y copiarlo en un componente de memo y de ahi, a
un archivo. Despues, hemos abierto el XML Mapper, cargado el archivo y gene-
rado a partir de el la estructura del paquete de datos y el archivo de transforma-
cion. (Puede encontrarse el archivo de transformacion entre el resto de archivos
de codigo fuente del ejemplo SoapEmployee.) Para mostrar 10s datos XML dentro
de una DBGrid, el programa utiliza un componente XMLTransformProvider, que
hace referencia a1 archivo de transformacion.
object XMLTransformProviderl: TXMLTransformProvider
TransforrnRead.TransformationFile = 'EmplListToDataPacket. xtr'
end

El componente Client D a t aSet no esta conectado a1 proveedor, ya que


trataria de abrir el archivo de datos XML especificado por la transformacion. En
este caso, 10s datos XML no se encuentran en un archivo, si no que se pasan a1
componente tras llamar a1 servicio Web. Por este motivo, el programa lleva direc-
tamente en el codigo 10s datos a1 ClientDataSet.
procedure TForml.btnGetListClick(Sender: TObject);
var
strXml: string;
begin
strXml : = Get1SoapEmployee.GetEmployeeNames;
strXML : =
XMLTransformProvider1.T~an~f0rmRead.Transfor~ML(strXml);
ClientDataSetl.XmlData : = strXml;
ClientDataSetl.0pen;
end ;

Con este codigo, el programa puede mostrar la lista de empleados en una


DBGrid, como muestra la figura 23.5. Cuando se consiguen 10s datos de un em-
pleado especifico, el programa extrae el identificador del registro activo desde el
ClientDataSet y muestra el XML resultante en un campo de memo:
procedure TForml.btnGetDetailsClick(Sender: TObject);
begin
Memo2.Lines.Text : = GetISoapEmployee.GetEmployeeData(

end ;

Nelson Robert
Young B w e

Jdnson Leslie
Forest Ph1
Weslon K. J.
Lee Te~ri
Hall Stewart
Young Katherine
Papadopoules Chris
Fisher Pete

De Sarra Ropr

Figura 23.5. El programa cliente del ejemplo de servicio Web SoapEmployee.

Depuracion de las cabeceras SOAP


Una indicacion final sobre este ejemplo esta relacionada con el uso del Web
App Debugger para probar aplicaciones SOAP. Por supuesto, puede ejecutarse el
programa servidor desde el IDE de Delphi y depurarlo facilmente, per0 tambien
pueden vigilarse las cabeceras SOAP que se pasan a traves de la conexion HTTP.
Aunque prestar atencion a SOAP desde esta perspectiva de bajo nivel puede ser
algo muy poco sencillo, es el mod0 definitive de comprobar si algo va ma1 con
una aplicacion SOAP de cliente o servidor. Como ejemplo, en la figura 23.6
puede verse el registro HTTP de una solicitud SOAP del ultimo ejemplo.
El Web App Dcbugger podria no cstar siempre disponiblc, por eso otra tecnica
habitual es controlar 10s eventos del componente HTTPRIO, como hace el ejem-
plo BabelFishDebug. El formulario del programa time dos componentes de memo
en 10s que puede verse la peticion SOAP y la respuesta:
p r o c e d u r e TForml.HTTPRIO1BeforeExecute(const MethodName:
String;
v a r SOAPRequest: W i d e s t r i n g ) ;
begin
MemoRequest.Text : = SoapRequest;
end:

p r o c e d u r e TForml.HTTPRIO1AfterExecute(const MethodName: String;


SOAPResponse: T S t r e a m ) ;
begin
S0APResponse.Position : = 0;
MemoResponse.Lines.LoadFromStream(S0APResponse);
end ;

Content Type fext/xrnl


User Agent Baland SOAP 1 2
Host Iocalhosl 1024
Contenc Length 508
C o r m c t ~ o nKeep Alwe
Cache Control n o a c h e

thd vewon="1.0"?)
t SOAP-ENV:Envelopemlns:S~P~ENV="hll~//schemas.mlsoap.ug/w~en~ebpe~~
m l n s xsd="hltp~//www w3.org12001 M L S c h e m a "
mlns HSI-'lafp //www w3 org/2001 /XMLSchemamstance"
xmlns SO~.ENC="htfp.//schemas.xmlsoap.o~g/soap/enc~n'~cSOAP~ENV:Body
SOAP-ENV encodiylStyle="http://a:hemas.Mnlmap org/soap/encod1ng/"~tNS1:GetErnployeeData
xmhs NS 1="run SoapEmployeelnlf.ISoapEmployee"~ tEmplD
wxtype-"xsd slrmg">ll c/EmplD>c/NSl GelEmployeeDala>t/SOAP.ENV Body)c/SOAP.ENV Envelope,

Figura 23.6. El registro HTTP del Web App Debugger incluye la peticion SOAP
a bajo nivel.

Exponer una clase ya existente


como un servicio Web
Aunque podria desearse desarrollar un servicio Web desde la nada, en algunos
casos puede existir codigo que se quiera hacer accesible. Este proceso no es
demasiado complejo, dada la arquitectura abierta de Delphi en este campo. Para
probarlo, habra que seguir estos pasos:
1. Crear una aplicacion de servicio Web o aiiadir 10s componentes relaciona-
dos a un proyecto WebBroker ya existente.
2. Definir una interfaz que herede de llnvokable y aiiadirle 10s metodos que
se desean hacer accesibles en el servicio Web (mediante la convencion de
llamada s tdcall). Los metodos seran parecidos a 10s de las clase que se
quiera hacer accesible.
3. Definir una nueva clase que herede de la clase que se desea exponer e
implementar la interfaz. Los metodos se implementaran llamando a 10s
metodos correspondientes de la clase base.
4. Escribir una metodo de creacion de un objeto de la clase de implernentacion
para cada vez que lo necesite una peticion SOAP.
Este ultimo paso es el mas complejo. Podria definirse una fabrica y registrarla
de esta manera:
procedure MyObjFactory (out Obj : TObject) ;
begin
Obj : = TMyImplClass.Create;
end ;

initialization
InvRegistry.RegisterInvokableClass(TMyImplClass, MyObjFactory);

Sin embargo, este codigo crea un objeto nuevo para cada llamada. Utilizar un
unico objeto global seria igual de malo: varios usuarios podrian tratar de usarlo,
y si el objeto tiene un estado o sus metodos no son concurrentes, podrian darse
problemas. Queda la necesidad de implementar algun tipo de control de sesion,
que es una variante del prob!,cma que teniamos con el primer servicio Web que se
conectaba a la base de datos.

DataSnap sobre SOAP


Ahora que tenemos una idea razonablemente aceptable de como crear un servi-
dor y un cliente SOAP, vamos a ver como utilizar esta tecnologia para crear una
aplicacion DataSnap multicapa. Usaremos un Soap Server Data Module para
crear el nuevo servicio Web y el componente Soapconnection para conectarle una
aplicacion cliente.

Creacion del servidor SOAP DataSnap


Vamos a ver en primer lugar el caso del servidor. Accederemos a la pagina
Web Services del cuadro de dialogo New Items y usaremos el icono Soap
Server Application para crear un nuevo servicio Web, y despues el icono Soap
Server Data Module para aiiadir un modulo de datos de servidor DataSnap a1
servidor SOAP. Hemos hecho esto en el ejemplo SoapDataServer7 (que usa la
arquitectura Web App Debugger para poder hacer pruebas). A partir de aqui,
todo lo que hay que hacer es escribir un servidor DataSnap normal (o una aplica-
cion DataSnap de capa intermedia), como se comento en el capitulo 16. En este
caso, hemos aiiadido el acceso a InterBase a1 programa mediante dbExpress, con
lo que tenemos la siguiente estructura:
o b j e c t SoapTestDm: TSoapTestDm
object SQLConnectionl: TSQLConnection
ConnectionName = ' I B L o c d l '
end
object SQLDataSetl: TSQLDataSet
SQLConnection = SQLConnectionl
CommandText = 'select * f r o m EMPLOYEE'
end
o b j e c t DataSetProviderl: TDataSetProvider
DataSet = SQLDataSetl
end
end

El modulo de datos creado para un servidor DataSnap basado en SOAP define


una interfaz personalizada (para que se le puedan aiiadir metodos) que hereda de
IAppServerSOAP, que se define en una interfaz publicada (incluso aunque no
herede de Ilnvokable).

para exponer'datos media& SOAP. ~ e l i h 7i ha py$t&do a ese ~ & r


predefmido mediante la interfaz beredada lAppSe;iu&fJ~I?. ,que es
fbncionalmente identica pero permite que el eistam pueda determinar el
tipo de llamada atendiendo a1 nombre de la interfaz. En breve, veremos
c6mo llamar a una aplicaci6n antigua desde un cliente creado con Delphi 7,
ya que este proceso no es autondtticol .

La clase de implementation, TSoapTes t Dm, es el modulo de datos, como en


otros servidores DataSnap. Este es el codigo Delphi generado, con el aiiadido del
metodo personalizado:
type
ISampleDataModule = interface(IAppServerS0AP)
[ ' {D47A293F-4024-4690-9915-8A68CB273D39) '1
f u n c t i o n GetRecordCount: Integer; stdcall;
end;

TSampleDataModule = class(TSoapDataModule,
ISampleDataModule,
IAppServerSOAP, IAppServer)
DataSetProviderl: TDataSetProvider;
SQLConnectionl: TSQLConnection;
SQLDataSetl: TSQLDataSet;
public
f u n c t i o n GetRecordCount: Integer; stdcall;
end;

El TSoapDataModule base no hereda de T I n v o k a b l e C l a s s . No se


trata de un problema siempre que se proporcione un procedimiento de creacion
adicional para crear el objeto (que es lo que hace automaticamente la
T I n v o k a b l e C l a s s ) y se aiiada el codigo de registro (como ya se comento
anteriormente):
p r o c e d u r e TSampleDataModuleCreateInstance(out obj: TObject);
begin
obj : = TSampleDataModule .Create (nil);
end ;

initialization
InvRegistry.RegisterInvokab1eC1ass(
TSampleDataModule, TSampleDataModuleCreateInstance);
InvRegistry.RegisterInterface(TypeInfo(ISampleDataModu1e));

La aplicacion servidor tambien publica las interfaces IAppServerSOAP e


IAppServer, gracias al codigo (breve) de la unidad SOAPMidas. Como compa-
racion, puede encontrarse el servidor DataSnap de SOAP creado con Delphi 6 en
la carpeta SoapDataServer.
El ejemplo sigue pudiendose compilar en Delphi 7, y funciona bien, per0 debe-
rian escribirse 10s nuevos programas, con la estructura del nuevo; en la carpeta
SoapDataServer7 se encuentra un ejemplo.
-. - - - - - - - -- -

TRUCO: Las aplicaciones de servicios Web en Delphi 7 pueden incluir


mas de un m6dulo de datos SOAP. Para identificar un m6dulo de datos
SOAP especifico, utilizaremos la propiedad SOAPServerIID del com-
ponente SoapConnection o afiadiremos el nombre de la hterfaz del m6dulo
de datos a1 final del URL.

El servidor tiene un metodo personalizado (la version para Delphi 6 del pro-
grama tambien tenia uno, per0 jamas funciono) que utiliza una consulta con la
sentencia SQL s e l e c t count ( * ) f r o m EMPLOYEE.
f u n c t i o n TSampleDataModu1e.GetRecordCount: Integer;
begin
/ / l e e e l r e c u e n t o d e r e g i s t r o s m e d i a n t e una consulta
SQLDataSet2.0pen;
Result : = SQLDataSetZ.Fields[O].AsInteger;
SQLDataSet2.Close;
end ;
Creacion del cliente SOAP DataSnap
Para crear la aplicacion cliente, llamada SoapDataClient7, comenzamos a partir
de un programa sencillo y le aiiadimos un componente Soapconnect ion (des-
de la pagina Web Services de la paleta), conectandolo a1 URL del servicio Web
DataSnap y haciendo referencia a la interfaz especifica que queriamos usar:
object SoapConnectionl: TSoapConnection
URL = ' h t t p : / / l o c a l h o s t :1024/SoapDa t a S e r v e r 7 . s o a p d a t a s e r v e r /
' + ' s o a p / I s a m p l e d a tarnodule '
SOAPServerIID = ' I A p p S e r v e r S O A P - [ C 9 9 F 4 7 3 5 - 0 6 0 2 - 4 9 5 C - 8 C A 2 -
E53E5A439E61) '
UseSOAPAdapter = False
end

Fijese en la ultima propiedad, Use SOAPAdap t er, que indica que trabaja-
mos contra un servidor creado con Delphi 7. Como comparacion, el ejemplo
SoapDataClient (de Delphi 6), que utiliza un servidor creado con Delphi 6 y se ha
vuelto a compilar con Delphi 7, debe tener establecida esta propiedad como True.
Este valor obliga a1 programa a usar la interfaz IAppServer simple en lugar de
la nueva interfaz IAppServerSOAP.
Desde aqui, todo es como siempre: aiiadir un componente Cl ient Dat aSe t,
un Datasource y una DBGrid a1 programa, escoger el unico proveedor disponible
para el conjunto de datos cliente y conectar el resto. No es sorprendente que para
este ejemplo tan simple la aplicacion cliente tenga poco codigo personalizado:
una unica llamada para abrir la conexion cuando se hace clic sobre un boton
(para evitar errores de arranque) y una llamada Appl yUpdat e s para enviar 10s
cambios de vuelta a la base de datos.

SOAP frente a otras conexion con DataSnap


A pesar del aparente parecido de este programa con el resto de 10s programas
de cliente y sewidor DataSnap creados en el capitulo 16, existe una diferencia
muy importante que hay que resaltar: 10s programas SoapDataServer y
SoapDataCliente no utilizan COM para exponer o llamar a la interfaz
IApp Server SOAP.Mas bien a1 contrario, las conexiones basadas en sockets y
HTTP de DataSnap siguen basandose en objetos COM locales y en el registro del
sewidor en el Registro de Windows. Sin embargo, el soporte basado en SOAP
nativo permite una solucion completamente personalizada que es independiente
de COM y que ofrece muchas mas posibilidades de adaptarse a otros sistemas
operativos. (Se puede volver a compilar este sewidor en Kylix, per0 no es asi
para 10s programas del capitulo 16.)
El programa cliente tambien puede llamar a1 metodo personalizado que hemos
aiiadido a1 servidor para devolver el recuento de registros. Este metodo podria
usarse en una aplicacion real para mostrar solo un numero limitado de registros
per0 informar a1 usuario del numero de registros que aun no se han descargado
desde el servidor. El codigo del cliente para llamar a1 metodo se basa en un
componente HTTPRIO adicional:
p r o c e d u r e TFormSDC.Button3Click(Sender: TObject);
var
SoapData: ISampleDataModule;
begin
SoapData : = HttpRiol a s ISampleDataModule;
ShowMessage (IntToStr (SoapData.GetRecordCount));
end;

Manejo de adjuntos
Una de las caracteristicas mas importantes que Borland ha afiadido a Delphi 7
es el completo soporte de adjuntos SOAP. Los adjuntos en SOAP permiten enviar
datos que no Sean texto XML, como archivos binarios o imagenes. En Delphi, 10s
adjuntos se gestionan a traves de flujos. Se puede leer o indicar el tip0 de codifi-
cation del adjunto, per0 la transformacion de un flujo bruto de bytes hacia y
desde una codificacion dada depende del codigo. Aun asi, este proceso no es
demasiado complejo, si se tiene en cuenta que Indy incluye unos cuantos compo-
nentes de codificacion.
Como ejemplo del uso de adjuntos, hemos escrito un programa que reenvia el
contenido binario de un ClientDataSet (que tambien alberga imagenes) o una sola
de las imagenes.
El servidor tiene esta interfaz:
tn?e
ISoapFish = i n t e r f a c e ( IInvokable)
[ ' {4E4C57BF-4AC9-41C2-BB2A-64BCE4 7OD4SO} ' 1
f u n c t i o n GetCds: TSoapAttachment; stdcall;
f u n c t i o n GetImage(fishName: s t r i n g ) : TSoapAttachment;
s tdcall;
end;

La implernentacion del metodo G e t C d s usa un ClientDataSet que hace refe-


rencia a la clasica tabla BIOLIFE, crea un flujo en memoria, copia en el 10s datos,
y despues adjunta el flujo a1 resultado T S o a p A t t a c h m e n t :
function TSoapFish.GetCds: TSoapAttachment; stdcall;
var
memStr: TMemoryStream;
begin
Result : = TSoapAttachment-Create;
memStr : = TMemoryStream-Create;
WebModule2.cdsFish.SaveToStream(MemStr); // binary
Result.SetSourceStream (memStr, soReference);
end;
En el cliente, hemos preparado un formulario con un componente
Client Dataset conectado a una DBGrid y una DBImage. Todo lo que hay
que hacer es conseguir el adjunto SOAP, guardarlo en un flujo temporal en me-
moria y despues copiar 10s datos desde el flujo de memoria al ClientDataSet local.
p r o c e d u r e TForml.btnGetCdsClick(Sender: TObject);
var
sAtt: TSoapAttachment;
memStr: TMemoryStream;
begin
nRead : = 0;
sAtt : = (HttpRiol a s ISoapFish) .GetCds;
try
memStr : = TMemoryStream.Create;
tr Y
.sAtt SaveToStream(memStr) ;
memStr.Position : = 0;
ClientDataSetl.LoadFromStream(MemStr);
finally
memStr. Free;
end;
finally
DeleteFile (sAtt .CacheFile) ;
sAtt.Free;
end;
end ;

ADVERTENCIA: De manera predeterminada, 10s adjuntos de SOAP reci-


bidos por un cliente se guardan en un archivo temporal, a1 que hace referen-
cia la propiedad CacheFile del objeto TSOAPAttachment. Si no se
borra este archivo, permanecera en una carpeta que albergue archims tem-
porales.

Este codigo produce el mismo efecto visual que una aplicacion cliente que
cargue un archivo local en un ClientDataSet, como muestra la figura 23.7. En
este cliente SOAP hemos usado un componente HTTPRIO de manera explicita
para poder inspeccionar 10s datos entrantes (que posiblemente seran muy grandes
y lentos). Por este motivo, hemos puesto a cero una variable nRead global antes
de invocar a1 metodo remoto. En el evento OnReceivingData de la propiedad
HTTPWebNode del objeto HTTPRIO, aiiadiremos 10s datos recibidos a la varia-
ble nRead. Los parametros Read y T o t a l que se pasan a1 evento se refieren a1
bloque de datos especifico que se envia a traves de un socket, por lo que resultan
casi inutiles por si solos para inspeccionar el progreso:
p r o c e d u r e TForml.HTTPRIO1HTTPWebNodelReceivingData(
Read, Total: Integer);
begin
Inc (nRead, Read) ;
StatusBarl.SimpleText : = IntToStr ( n R e a d ) ;
App1ication.ProcessMessages;
end ;

90020 Triggerfishy Clown Triggerf'ish


90030 Snapper Red Emperor
90050 Wrasse G~anlM m r ~Wrasse
90070 Angelfish Blue Angellish
9M80 Cod Lunartail Rockead
90090 Scorpionlish Fnefish

Figura 23.7. El ejemplo Fishclient recibe un ClientDataSet binario dentro de un


adjunto de SOAP.

Soporte de UDDI
La gran popularidad de XML y SOAP abre nuevas vias para que las aplicacio-
nes de comunicacion B2B interacthen. XML y SOAP proporcionan una base,
per0 no bastan (la estandarizacion en 10s formatos XML, en el proceso de comu-
nicacion y en la disponibilidad de la informacion sobre un negocio son todos ellos
elementos claves de una solucion real). Entre 10s estandares propuestos para su-
perar esta situacion 10s mas notables son Universal Description, Discovery, and
Integration (UDDI, www.uddi.org) y Electronic Business using extensible Markup
Language (ebXML, www.ebxml.org). Estas dos soluciones se solapan y difieren
en parte y ahora se trabaja con mas empeiio en ellas por parte del consorcio
OASIS (Organizationjor the Advancement of Structured Information Standards,
www.oasis-open.org). No vamos a entrar en 10s problemas de 10s procesos de
negocio; en lugar de eso, solo vamos a comentar 10s elementos tecnicos de UDDI,
ya que, de manera especial, Delphi 7 soporta este estandar.

~ Q u es
e UDDI?
La especificacion Universal Description, Discovery, and Integration (UDDI)
es un esfuerzo para crear un catalog0 de servicios Web ofrecidos por empresas de
todo el mundo. El objetivo de esta iniciativa es crear un marco de trabajo abierto,
global e independiente de plataformas para permitir que las entidades de negocio
se encuentren entre si, definir como interactuan con la red Internet y compartir un
registro de negocio global. Por supuesto, la idea es acelerar la adopcion del co-
mercio electronico, en forma de aplicaciones B2B.
Basicamente, UDDI es un registro de negocios global. Las empresas pueden
registrarse en el sistema, describir su organizacion y 10s servicios Web que ofre-
cen (en UDDI, el termino "servicios Web" se usa en un sentido muy amplio,
incluyendo direcciones de correo electronico y sitios Web). La informacion del
registro de UDDI para cada empresa se divide en tres campos:
Paginas blancas: Incluyen informacion de contacto, direcciones y cosas
similares.
Paginas amarillas: Registran la compaiiia en una o mas taxonomias, in-
cluyendo categorias industriales, productos vendidos por la empresa, in-
formation geografica y otras taxonomias (posiblemente personalizables).
Paginas verdes: Proporcionan la lista de 10s sewicios Web ofrecidos por
la empresa. Cada servicio se lista bajo un tip0 de servicio (llamado un
tModel), que puede estar predefinido o un tip0 descrito especificamente
por la empresa (por ejemplo en terminos de WSDL).
Tecnicamente, el registro UDDI deberia verse como un DNS actual, y deberia
tener una naturaleza distribuida similar: varios servidores, reflejados y con alma-
cenamiento de datos para acelerar 10s procesos. Los clientes pueden guardar en
cache datos siguiendo unas reglas determinadas.
UDDI define modelos de datos especificos para una entidad de negocio, un
sewicio de negocio y una plantilla de enlace. El tipo BusinessEntity incluye infor-
macion esencial sobre el negocio, como su nombre, la categoria a la que pertenece
informacion de contacto. Soporta las taxonomias de las paginas amarillas, con
informacion industrial, tipos de producto y detalles geograficos.
El tipo Businessservice incluye descripciones de 10s servicios Web (usados
por las paginas verdes). El tip0 principal es solo un contenedor para 10s servicios
relacionados. Los s e ~ i c i o pueden
s estar ligados a una taxonomia (zona geografi-
ca, producto, etc.. .). Toda estructura BusinessService incluye una o mas
BindingTemplates (la referencia a1 s e ~ i c i o )La
. BindingTemplate tiene un tModel.
El tModel incluye informacion sobre formatos, protocolos y seguridad, y referen-
cias a especificaciones tecnicas (probablemente mediante el formato WSDL). Si
varias empresas comparten un tModel, un programa puede interactuar con todas
ellas con el mismo codigo. Un programa de negocio determinado, por ejemplo,
puede ofrecer un tModel para que otros programas interactuen con el, sin impor-
tar la empresa que haya adoptado el software.
La API de UDDI se basa en SOAP. Mediante SOAP, se pueden registrar datos
y consultar un registro. Microsoft tambien ofrece un SDK basado en COM, e
IBM tiene un conjunto de herramientas Java Open Source para UDDI. Las API de
UDDi incluyen consultas ( f i n d x x y g e t x x ) y publicaciones ( s a v e x x y
d e l e t e x x ) para cada una d e las cuatro estructuras de datos principales
( b u s i n e s s ~ n t i tb~u ,s i n e s s s e r v i c e , b i n d i n g T e r n p l a t e y t M o d e l ) .

UDDI en Delphi 7
Delphi 7 incluye un navegador UDDI que puede usarse para encontrar un
servicio Web cuando se importa un archivo WSDL. El navegador UDDI, que
muestra la figura 23.8, lo activa el WSDL Import Wizard. Este navegador solo
utiliza la version 1 de servidores UDDI (hay disponible una interfaz mas nueva,
pero no esta soportada) y tiene unos cuantos registros UDDI predefinidos). Se
pueden aiiadir configuraciones predefinidas en el archivo UDDIBrow.ini que se
encuentra en la carpeta bin de Delphi.
Este es un mod0 muy practico de acceder a informacion sobre servicios Web,
pero no es todo lo que permite Delphi. Aunque el UDDI Browser no este disponi-
ble como una aplicacion independiente, las unidades de interfaz UDDI estan dis-
ponibles (y no es trivial importarlas). Por ello, se puede escribir un navegador
UDDI propio.

Figura 23.8. El navegador UDDI incorporado al ID€ de Delphi.

Vamos a bosquejar una solucion sencilla, que es un buen punto de partida para
un navegador UDDI mas complete. El ejemplo UddiInquiry, que se muestra en la
figura 23.9, tiene un gran numero de caracteristicas, pero no todas ellas funciona-
ra correctamente (en particular las caracteristicas de busqueda por categoria). El
motivo es que usar UDDI implica recorrer estructuras de datos muy complejas,
que no siempre se proyectan del mod0 mas obvio mediante el importador WSDL.
Esto complica bastante el codigo del ejemplo; por eso solo vamos a mostrar el
codigo de una busqueda sencilla, y no todo (otro motivo es que algunos lectores
pueden no tener particular interes por UDDI).

Search t o r lmicro Search by Name Search by Category

1 BusinessKay I
Micro C, Inc.
Micro Focus
] Description
We provide systems inte...
Welcome to the future of ...
516ab96a-50f5-48b4-9fO4- ...
7e76378cfa28-47a2-b8a...
4
_1
Micro Focus Welcome to the future of ... 9566~530-7d59-11d6-8c3...
Micro lntormalica LLC This is a UDDI Business ... dce959d-200d-4d9e-bee ...
MICRO MACHINES Plant and Machinery for ... ca2551 cc088f-46b7-9cl ...
Micro Motion Inc. Micro Motion manufactur ... d4e4b830-fl9e-4edI-9144...
MicroApplications. Inc. informalion syslems dev... a23~901e-834~-4b8~bf3...
rnrcrobizl desc 87f5ta08-508e-4065-b379 ... A

r ? dv e ~ s ~ o n = " l .encoding=*utf-8"
~l' 1,
- s o a p : Envelope .<rnlns.io~p="htt~~://sct~emas.xmlsoap.org/soap/envelope/'
umlnr-.: ~ ~ 1 = " h t t p : / / w ~ ~ . w 3 . ~ rl/XMLSchema-instance'
g/200
.dns: v s d = " h t t p : / / c c ~ v ~ ~ ~ . ~ ~ ~ 3 . o r ~ / 2 0 O 1 / X k 1 L S c h ~ n ~ a ' ~
- <soap:Bodyr
- <businessDeta~lgeneric="l.O uperator="Microsoft Corporation" truncated="false"
:ri~ln~="urn:urldi-org:clpin:
- <bus~nessEnt~ty bus1nesst~ey="59593094-dfld-4f53-9a2c-BbffcBc93513'
operator="Microroft Corporation" author~zedName="ScottWitkin">
- .rd~scove~-vURLC>

Figura 23.9. El ejemplo Uddilnquiry presenta un limitado navegador UDDI.

Cuando arranca el programa, enlaza el componente HTTPRIO que alberga


con la interfaz Inquiresoap de UDDI, definida en la unidad inquiry-vl que
proporciona Delphi 7:
procedure TForml.FormCreate(Sender: TObject);
begin
httpriol-Url : = comboRegistry.Text;
inftInquire := httpriol as Inquiresoap;
end;

A1 hacer clic sobre el boton Search, el programa llama a la funcion


find business de la API de UDDI. Ya que la mayoria de las funciones
UDDI-necesitan muchos parhmetros, se ha importado empleando un 6nico
parametro basado en un registro de tipo FindBusiness. Devuelve un objeto
businessList2:
procedure TForml.btnSearchClick(Sender: TObject);
var
findBusinessData: Findbusiness;
businessListData: businessList2;
begin
httpriol.Ur1 : = cornboRegistry.Text;

businessListData : =
inftInquire.find-business(findBusindBusinessData);
BusinessListToListView (businessListData);
findBusinessData.Free;
end ;

El objeto bus ines sList 2 es una lista que se procesa en el metodo


bus ines sListToListView del formulario principal del programa, mos-
trando 10s detalles mas importantes en un componente de vista de lista:
procedure TForml.businessListToListView(businessList:
businessList2);
var
i: Integer;
begin
ListViewl.Clear;
for i : = 0 to businessList.businessInfos.Len do
begin
with ListViewl.Items.Add do
begin
Caption : = businesslist. businessInfos [i] .name;
SubItems.Add (businessList.business1nfos [i].description);
SubItems.Add (businessList.business1nfos [i].businessKey);
end ;
end ;
end ;

A1 hacer doble clic sobre el elemento de vista de lista, se pueden explorar aun
mas sus detalles, aunque el programa muestra la informacion XML resultante en
un formato de texto plano (o en una vista XML basada en TWebBrowser) y no
lo procesa mas. Como se ha mencionado, no queremos entrar en detalles tecnicos;
si se siente interes, se puede analizar con mas detalle el codigo hente.
Parte V
Apendices
Herramientas
Delphi del autor

Durante 10s ultimos aiios el autor de este libro ha desarrollado algunos peque-
iios componentes y herramientas complementarias de Delphi. Algunas de estas
herramientas fueron creadas para libros o como resultado de la ampliacion de
ejemplos de libros. Otras fueron escritas como ayuda para tareas repetitivas.
Todas estas herramientas estan disponibles gratuitamente y algunas incluyen el
codigo fuente. Este aphdice proporciona una lista, incluyendo especialmente las
mencionadas en este libro. En el for0 de discusion del autor se ofrece soporte para
todas estas herramientas (vease www.marcocantu.com para obtener las direccio-
nes).

CanTools Wizards
Este es un conjunto de asistentes que podemos instalar en Delphi, en un menu
desplegable extra o como un submenu del menu Tools. Los asistentes (disponi-
bles gratuitamente en www.marcocantu.com/cantoolsw)no estan relacionados
entre si y tienen caracteristicas diferentes:
List Template Wizard: Racionaliza el desarrollo de clases similares ba-
sadas en listas, cada una con su propio tip0 de objetos. Este asistente se
menciona en el capitulo 4. Como realiza una operacion de busqueda y
reemplazo en un archivo fuente base puede usarse siempre que necesitemos
codigo repetido y el nombre de la clase (u otra entidad) cambie.
O O P Form Wizard: Mencionado en el capitulo 4. Permite ocultar 10s
componentes publicados de un formulario, haciendo el formulario m b orien-
tad0 a objetos y ofreciendo un mejor mecanismo de encapsulacion. Debe-
mos ejecutarlo cuando un formulario este activo y rellenara el controlador
de eventos O n c r e a t e . Despues, tendremos que mover manualmente par-
te del codigo a la seccion de inicializacion de la unidad.
Object Inspector Font Wizard: Permite cambiar el tip0 de letra del Object
lnspector (algo especialmente util para presentaciones, ya que el tipo de
letra del Object lnspector es demasiado pequeiio para mostrarse con fa-
cilidad en una pantalla de proyeccion). Otra opcion permite modificar una
caracteristica interna del Object lnspector y mostrar 10s nombres de 10s
tipos de letra (en la lista combinada desplegable de esa propiedad) usando
un tipo de letra especifico.
Rebuild Wizard: Permite reconstruir todos 10s proyectos de una subcarpeta
determinada desputs de cargar cada uno de ellos secuencialmente en el
IDE. Podemos usar este asistente para coger una serie de proyectos (como
10s de este libro) y abrir el que nos interesa haciendo clic en la lista:

Isbookshd7codeU4RunPropR~~1Prop
dpr
e bmkshd7codeU4\ICompess~Compress
I
Tambien podemos compilar automaticamente un proyecto concreto o co-
menzar una (lenta) creacion de multiples proyectos: en el cuadro de resul-
tados del compilador, haremos clic sobre un boton para proceder solo si la
opcion del entorno correspondiente esta fijada. Si esta opcion del entorno
no esta fijada, no veremos 10s errores del compilador, porque 10s mensajes
del compilador son reemplazados en cada cornpilacion.
Clip History Viewer: Mantiene una lista de elementos de texto que hemos
copiado a1 Portapapeles. Un campo de la ventana del visor muestra las
ultimas 100 lineas copiadas. Editar ese campo (y hacer clic sobre S a v e )
modifica este historic0 del Portapapeles. Si mantenemos abierto Delphi, el
Portapapeles recogera texto de otros programas (pero solo texto, por su-
puesto). En ocasiones ocurren errores relacionados con el Portapapeles
provocados por este asistente.
VCL Hierarchy Wizard: Muestra la jerarquia (casi) completa de VCL,
incluyendo componentes de terceros que hayamos instalado, y permite buscar
una clase y ver multiples detalles (clases basicas y subclases, propiedades
publicadas, etc.). Hacer clic en el boton regenera tanto la lista como el
arb01 (secuencialmente, por lo que la barra de progreso se muestra dos
veces):

La lista de clases se genera usando un nucleo de clases predefinido (faltan


algunas por lo que se aceptan sugerencias) y aiiadiendo cada componente
de 10s paquetes instalados (10s de Delphi, 10s nuestros y 10s de terceros)
junto con las clases de todas las propiedades publicadas que son de tip0
clase. A pesar de todo, las clases usadas solo como propiedades publicadas
no se incluyen.
Extended Database Forms Wizard: Hace muchas mas cosas que el
D a t a b a s e Forms Wizard disponible en el IDE de Delphi, permitiendo-
nos elegir 10s campos que queremos colocar en un formulario y usar con-
juntos de datos diferentes de 10s basados en BDE.
Multiline Palette Manager: Permite convertir l a c o m p o n e n t Palette de
Delphi en un control de fichas con multiples lineas de fichas:
Programa de conversion VclToClx
Podemos usar esta herramienta independiente para convertir proyectos de Delphi
de VCL a CLX (y viceversa, si lo configuramos para ello). Puede convertir simul-
taneamente todos 10s archivos de una carpeta y sus subcarpetas. Este es un ejem-
plo de su resultado:

E !bwks\rnd7code\08\VI1\VI1dpr
E-\books\md7code\OB\Pol1Fo1m\PoliForrn dp
E:\books\rnd7code\OBF1arnes2\F1arnes2 dp

Do RepbDsI R-

Old -- . -1% - - -_-_I


QForms Fums

El codigo fuente del programa esta disponible en la c a r p e t a T o o l s del codigo


del libro. El programa VclToClx convierte nombres de unidades (basandose en un
fichero de configuracion) y manipula 10s DFM renombrando 10s archivos DFM a
XFM y corrigiendo las referencias en el codigo fuente. El programa no es sofisti-
cado, no analiza el codigo pero busca apariciones del nombre de la unidad segui-
dos de una coma o un punto y coma, como ocurre en una sentencia u s e s . Tambien
requiere que el nombre de la unidad vaya precedido por un espacio, pero podemos
modificar el programa para que busque una coma. No debemos saltarnos esta
comprobacion, jo la unidad Forms se convertira en QForms per0 la unidad QForms
se reconvertira en QQForms!

Object Debugger
En tiempo de diseiio, podemos usar el Object Inspector para fijar las propie-
dades de 10s componentes de nuestros formularios y otros modulos. En Delphi 4,
Borland present6 un Debug Inspector de tiempo de ejecucion, que tiene una
interfaz similar y muestra informacion parecida. Antes de que Borland aiiadiera
esta caracteristica, el autor implement6 una copia del Object Inspector de tiem-
po de ejecucion pensado para depurar programas:

DlapMode M d
Enabbd TIW
EutsmkdSckcl Trw
+Font [ O W 01343DF8) -
Charsc( 1
Cdn
He*
,;,-
&ridowTed
. . , l

Nams T m t Nau Roman


Pilch IpDeld

-.
S k 0
SM n

Este programa ofrece acceso de lectura y escritura a todas las propiedades


publicadas de un componente, y tiene dos cuadros combinados que permiten se-
leccionar un formulario y un componente dentro del formulario. Algunos de 10s
tipos de propiedad tienen editores de propiedades a medida (listas y similares).
Podemos situar el componente Ob je c t D e b b u g e r en el formulario princi-
pal de un programa (o podemos crearlo dinamicamente en el codigo): aparecera
en su propia ventana. Puede mejorarse, per0 incluso en su forma actual esta
herramienta es practica y tienen muchos usuarios.
El codigo fuente de este componente esta disponible en la carpeta ~ o o l del
s
codigo del libro.

Memory Snap
Existen multiples herramientas para analizar el estado de la memoria de una
aplicacion Delphi.
Este es un gestor de memoria personalizado que se conecta con el gestor de
memoria por defecto de Delphi, analizando todas las asignaciones y liberaciones
de memoria. Ademas de informar del numero total (algo que ahora Delphi hace
por defecto), puede guardar una descripcion detallada del estado de la memoria en
un archivo. Memory Snap mantiene en memoria una lista de bloques asignados
(hasta una cantidad maxima, facilmente modificable), de mod0 que puede volcar
el contenido de la pila a un fichero con una perspectiva de bajo nivel. Esta lista se
genera examinando cada bloque de memoria y determinando su naturaleza con
tecnicas empiricas que podemos ver en el codigo fuente (aunque no son faciles de
comprender). La salida se guarda en un archivo, porque esta es la unica actividad
que no requiere una asignacion de memoria que pueda afectar a 10s resultados.
Este es un fragment0 de un fichero de ejemplo:
00C035CC: object: [TList - 161
00C035EO: buffer with heap pointer [00C032BO]
00C03730: string: [5-11 : Edit1
00C03744: object: [TEdit - 5441
00C03968: object: [TFont - 361
OOCO3990: object: [TSizeConstraints - 321
00C039B4: object: [TBrush - 241
00C039F4: buffer with heap pointer [00C01FE4]
OOCO3B34: buffer with heap pointer [00COlF18]
OOCO3B48: string: [O-01 : dD
00C03B58: string: [ll-21: c:\mman.log

El programa puede ampliarse para que analice el uso de la memoria por tipos
(cadenas, objetos, otros bloques), vigile 10s bloques no liberados y mantenga las
asignaciones de memoria bajo control.
El codigo fuente de este componente tambien esta disponible gratuitamente en
la carpeta Tools del codigo del libro.

Licencias y contribuciones
Como hemos dicho, algunas de estas herramientas estan disponibles con su
codigo fuente completo. Estan protegidas bajo licencia LGPL (Lesser General
Public License, www.gnu.org/copyleft/lesser.htm),lo que significa que pueden
ser usadas gratuitamente y redistribuidas de cualquier modo, incluyendo modifi-
caciones, mientras el autor retiene el copyright. La LGPL no permite cerrar el
codigo fuente de nuestras extensiones, per0 podemos usar este codigo de bibliote-
ca en programas comerciales, independientemente de la disponibilidad del codigo
fuente. En caso de ampliar estas herramientas corrigiendo errores o aiiadiendo
nuevas caracteristicas, el autor solicita que se le envien las actualizaciones de
mod0 que pueda distribuirlas y evitar multiplicar el codigo en diferentes versio-
nes, aunque la licencia no nos obliga a ello.
Contenido
del CD-ROM

Este libro se basa en ejemplos. Tras la presentacion de cada concept0 o com-


ponente Delphi, encontrara un programa de ejemplo (a veces mas de uno) que
demuestra como se puede usar dicha caracteristica. En total, en el libro se presen-
tan mas de 300 ejemplos. La mayoria de 10s ejemplos son bastante sencillos y se
centran en una unica caracteristica. Los ejemplos mas complejos se elaboran
normalmente paso a paso, con pasos intermedios como soluciones parciales y
mejoras.

le la base de datos de ejemplo; fonnamt parte


da de Delphi. &Enotros casosi es &esa@'la
DWG EMPLOYEE.[ y tambih el sewidor &
WSLOIJ ~lt:t;jernpw I I I L G ~ ~ S B

merflase, pba sqguaeao).


- <

Tambien hay una version HTML del codigo fuente, en la que la sintaxis apare-
ce resaltada, junto con un indice completo de las palabras claves y 10s
identificadores (clase, funcion, metodo y nombres de propiedades, entre otros). El
archivo del indice es un archivo HTML, por lo que podra utilizar su explorador
facilmente para encontrar todos 10s programas que usen la palabra clave o el
identificador Delphi que este buscando (no es un completo motor de busqueda
per0 se le acerca mucho).
La estructura del directorio del codigo de ejemplo es bastante simple. Basica-
mente, cada capitulo del libro posee su propia carpeta y una subcarpeta para cada
ejemplo (ej: 0 3 \ F i l e L i s t).En el texto, se hace referencia a 10s ejemplos solo
por su nombre (ej: FileList).

r u , ~ que
1
chivo Readrne.de 10s archlvos de c M g q
, , conuene Importanre informacion sobre el uso legal y efectivg
sdware.

En la carpeta Delphi7 del CD-ROM encontrara la version de prueba de Delphi


7, la edicion superior limitada en el tiempo. Para poder instalar esta version de
Delphi 7, que le permitira seguir sin problemas todos 10s ejemplos descritos en el
libro, es necesario que efectue una operacion de registro para obtener el numero
de serie y la clave de activacion.
No tiene mas que seguir las instrucciones de la utilidad de instalacion para
completar el proceso de registro y activar su Delphi 7. Para ello necesitara dispo-
ner de una conexion a Internet y una cuenta de correo electronico. En el proceso
tendra que crear una cuenta de registro en la pagina Web de Borland, responder a
algunas preguntas y, finalmente, obtendra por correo electronico el numero de
serie y la clave de activacion.
Tambien encontrara en la carpeta PDF, anexos en 10s que le explican entre
otras cosas, algunas de las tecnologias que conforman la iniciativa .NET, 10s
cambios especificos que se realizaron en el lenguaje Delphi para hacerlo compa-
tible con el Common Language Runtime, etc.
alfabetico

#O, 356#10, 141


#13#10, 141
$, 818
aaManua1, 633
$00000000, 234
abstract, 1 18
SOOFFFFFF, 234
Abstractos, metodos, 1 18
$IFDEF LINUX, 228
Access, 81 5
$LIBPREFIX, 54 1
Acciones, 251, 631
$LIBSUFFIX, 541
Accion, destino de la, 3 16
$LIBVERSION, 541
Aceleradoras, teclas, 261
&, 255, 261, 1083
Acerca de, 396
{$IF), 90
ActionManager, 25 1
{$IFDEF LINUX), 140
ActiveForm, 638
{SIFDEF MSWINDOWS), 140
ActiveForms, 644
{$M+), 174, 176, 1133
ActiveRecord, 9 14
{$Warn UNSAFE-CAST OFF), 73
ActiveX, 598, 633-634, 648, 819, 1037
{$Warn UNSAFE-CODE OFF), 73
Control Wizard, 638
{$Warn UNSAFE-TYPE OFF), 73
controles, 977
-DF, extension, 78
Data Objects (ADO), 803
-DP, extension, 79
uso de controles, 636
-PA, extension, 81
y componentes Delphi, 635
.NET, 657, 1051
AdapterGrid, 1037
.NET Framework Assembly Registration
AdapterMode, 1039
Utility, 657
Adapterpageproducer, 1042
arquitectura, 221, 493
adCriteriaAIICols, 838
COM y, 656
adCriteriaKey, 838
:=, 51
adCriteriaTimeStamp, 838
@, 1117
adCriteriaUpdCols, 838
p d d R e f , 124, 600
Add, 194, 276
declspec(dllexport), 533
To Repository, 85
-Release, 600, 6 10
addAffectGroup, 840
Addchild, 276, 1092 Apache, 1057, 1086
AddNode, 280 API de Windows, 234,265, 531
Addobject, 269 Aplicaciones Delphi, arquitectura, 403
Adjuntos, 1149 AppID, 1060
AdjustLineBreaks, 144 Application, 108, 181, 262
ADO, 803-805 ApplicationEvents, 128, 404
Cursor Engine, 822 ApplyUpdates, 864,1041, 1075, 1148, 1106
ADO.NET, 845 AppServer, 869
ADOCommand, 808 Arb01 de datos, 275
ADOConnection, 808, 81 1, 813, 821, 829-830 Architect Studio, 36
ADODataSet, 808, 837 ArrangeIcons, 424
ADODB, 812 Arrastre, 156
ADOQuery, 808, 818 as, 120, 164, 195
ADOStoredProc, 808 ASI400, 807
ADOTable, 808, 81 1, 816 ASCII, 244
ADOX, 814 archivo, 975
adResyncUnderlyingValues, 840 Asignacion de objetos, 105-107
ADTG, 844 Asociativas, listas, 198
AffectRecords, 840 Assign, 175
Afterconnect, 869 Assigned, 109
AfterDelete, 865 AssignTo, 175
AfterEdit, 687 Associate, 249, 377
AfterInsert, 787
Afteropen, 688
at (a), 11 17
At, 22 1
AfterPost, 687, 863, 865 Atozed Software, 1050-1051
Afterscroll, 866 Attributes, 830
AfterUpdateRecord, 873 AutoActivate, 633
Agente de conexion, 87 1 Autocheck, 250
akBottom, 257 Autocomplete, 244
akRight, 257, 355 AutoCompleteOptions, 245
akTop, 355 Autoconnect, 626
alBottom, 1060 AutoHint, 302
alclient, 1060 AutoHotKey, 261
Alignment, 69 1, 862 Automation, 612, 614
AllocMemCount, 139 Automatization OLE, 6 12
AllocMemSize, 139 interfaz de, 629
AllowAl1Up, 3 17 servidor de, 6 17
AllowGrayed, 24 1 AutoPaletteScroll, 64
AlphaBlendValue, 366 AutoPaletteSelect, 64
Alto-bajo, tecnica de, 786 Autosnap, 259
alTop, 260, 1060 AWpHORpNEGATIVE, 367
ampersand (t), 1083 AW-HOR-POSITIVE, 367
Anchor, 285 AW-VER-NEGATIVE, 367
Anchors, 257, 355 AW-VER-POSITIVE, 367
Anclajes, 257 AxBorderStyle, 646
Animate, 366
Animatewindow, 367
ANSI, 81 4
AnsiContainsText, 149
AnsiDequotedStr, 143 BabelFish, 1 131
AnsiIndexText, 149 Bands, 3 19
AnsiMatchText, 149 Barra de herramientas, 316
AnsiQuoteStr, 143
Barras de desplazamiento, 248
AnsiReplaceText, 149 BaseCLX, 170, 172, 21 5
AnsiResembleText, 149 BDE, 173, 804
AnsiString, 143 BeforeEdit, 687
BeforePost, 687 caFree, 4 10
BeforeUpdateRecord, 873 Caja negra, 95
BeginThread, 140 Calculado, campo, 695, 790
BeginTrans, 829 Callback. 563
Beveled, 259 Callbacks, 22 1
Beyond Compare, 46 Campos, 687
Biblioteca del formulario. elimination, 185
de clases estandar, 169 Canal Alpha, 366
de tipos, 6 18 CancelBatch, 835
dinamica, 527 Cancelupdate, 83 5
en tiempo de ejecucion (RTL), 135 Caption, 133, 164, 261
Bibliotecas, 527 CaretPos, 304
del sistema de Windows, 530 Cascade, 424
BindingTemplates, 11 52 Cascading Style Sheets (CSS), 993
Bit menos significative, 304 CASE, 567
bkcancel, 392 Casilla de ver~ficacion,24 1
bkOK, 392 Casos de uso, 572
BLOB, 202, 872-873, 892 Catalogo, 8 14
campos, 206 CD-ROM, 1 165
Bloqueo CDATA, 1088
optimists, 836 cdecl, 53 1
pesimista, 832 cdPreventFullOpen, 394
recursive, 142 CDS, 669
tipos de, 83 1 CellData, 993
BMP, 681 CFG
extension, 77 archivo, 70
body, 1056 extension, 77
Bookmarksize, 902 Changed, 5 10
BoolToStr, 142 Characters, 1099
Bordes, 232 CheckBox, 241
Borland Checked, 245
Memory Manager. 538 CheckListBox, 244,272
Registry Cleanup Utility, 75 ChildValues, I089
BorlndMM.DLL, 154, 538-539 ciMultiInstance, 874
BPG, 69 ckAttachToInterface, 627
extension, 77 ckNewlnstance, 627
BPL, extension, 77 ckRemote, 627
BRC32.exe, 75 ckRunningInstance, 627
BRCC32.exe, 75 ckRunningOrNew, 627
BringToFront, 412 clActiveCaption, 234
Businessservice, 1 152 clActiveForeground, 234
Buttonstyle, 676 Clases de escucha, 189
Blisqueda, dialog0 de, 798 Class Completion, 49-50, 92
ClassPDColorPropPage, 642
Class-DFontPropPage, 642
Cla~s~DPicturePropPage, 642
Class-DStringPropPage, 642
C#, 45 Classes, 169, 180, 21 5
CAB CLassInfo, 165
archivo comprimido, 646 Classparent, 164
extension, 77 ClassType, 164
CacheFile, 1 150 Clave de registro, 842
Cachesize, 825 Clave externa, 790
Cadenas clBase, 233-234
de conexion, editor de, 809, 8 16 clBtnFace, 234
exportar, 537 clCream. 233
clDisabledBase, 234 ColorDialog, 394
clGreen, 233 Colores, 233
ClientDataSet, 173, 670, 696, 727, 822, 854, ColorRef, 537
871, 1085, 1105 ColorToString, 269
Clientelservidor, 848-849 ColumnLayout, 243
Clientelservidor, arquitectura, 728 Columns, 243, 676, 992
ClientToScreen, 252,263 COM+, 154, 599, 648-650, 845, 851, 874, 1148
Clip History Viewer, 1160 eventos, 653
clMedGray, 233 COM
clMoneyGreen, 233 aplicacion contenedor, 630
clNone, 1071 objetos locales, 1148
Clone, 828 Comandos, 250
cloneNode, 1091 ComboBox, 243
clRed, 233 ComboBoxEx, 245
clsilver, 233 ComConst, 154
clSkyBlue, 233 ComCtrls, 276
clUseClient, 822, 834, 840 CommandText, 817, 844
clUseCursor, 825 CommandType, 844
clUseServer, 822 Commit, 78 1
clWhite, 233 CommitRetaining, 782
clwindow, 234 CommitTrans, 829
CLX, 38, 170, 172, 220, 222 ComObj, 154
cm-Activate, 500 Comparevalue, 146
cm-BiDiModeChanged, 500 Compartido, controlador de eventos, 30 1
cm-BorderChanged, 500 Compatibilidad, 1 13
cm-Changed, 643 Compatible en tipo, 124
cm-ColorChanged, 500 Compilador
cm-Ctl3DChanged, 500 advertencias de, 73
cm-CursorChanged, 500 mensajes de, 73
cm-Deactivate, 500 Compilar, 7 1-72
cm-EnabledChanged, 500 Complejos, ntimeros, 152
cm-Enter, 500 Component Palette, 38, 63, 66, 171
cm-Exit, 500 Componentcount, 18 1
cm-FocusChanged, 500 ComponentIndex, 180
cm-FontChanged, 500 Components, 181-182
cm_GotFocus, 500 Components, matriz, 181
cm-LostFocus, 500 Componentstate, 41 1
cm-MouseEnter, 498 Compuestos, documentos, 629
cm-MouseExit, 498 ComServ, 154
cmdFile, 844 Concurrencia, 65 1
CmdGotoPage, 1037 Conexion
CmdLine, 140 agente de, 871
CmdNextPage, 1037 cadenas de, 809-81 1
CmdPrevPage, 1037 Conjuntos de registros
coBookMark, 829 desconectados, 840-841
Code, 177, 189 permancntes, 843-844
Completion, 50-5 1, 102 Connected, 858
Explorer, 46-48 Connection, 8 1 1, 872
Insight. 49 ConnectionBroker, 855, 871
Parameters, 52 Connectionstring, 809, 81 1-8 12, 8 17, 822
Templates, 52 ConnectKind, 627
Codificaciones, 1082 Conscientes de 10s datos, controles, 878
Colecciones, 196 ConstraintErrorMessage, 861
Color, 233 Constraints, 258, 260, 861
Key, 366 Constructor virtual, 133
ColorBox, 245 Constructores, 103-104
Consulta tipo de, 823
en vivo, 779 ubicacion de, 822
libre, 800 CursorLocation, 834
Container, 630 CursorRect, 264
Contenedor, 242 CurValue, 839
Contenedores, 193, 196 CustomConstraint, 86 1
ContentType, 995
Contnrs, 196
Contribuciones, 1 164
Controlador, 61 3
ControlBar, 31 8, 320 Data Link, 81 1
Controls, 182, 393 Data, 177
Convert, 76, 155 Data-aware, 237, 675, 877-879, 882, 897, 91 1
CONVERT.EXE, 208 controles orientados a campos, 880
ConvUtils, 148, 154 Database Explorer, 75
Cookies, 1056 Datachange, 885
CoolBar, 3 18-3 19CORBA, 852 DataCLX, 170, 173
Correo electronico, 973 DataEvent, 879
protocolos de, 974 DataField, 675, 878, 881
enviado, 975 DataGrid, 1 1 13
recibido, 975 DataLinkDir, 812
Cracker, 112 DataNavigador, 1 1 13
Create, 103 DataRelation, 845
CreateComObject, 623 DataSet, 845, 861, 991
createElement, 1090 DataSetAdapter, 1037
CreateFileMapping, 548 DataSetField, 871
CreateForm, 380 DataSetPageProducer, 987
CreateGUID, 143 Datasetprovider, 862
CreateHandle, 235 DataSetReader, 845
Createoleobject, 623 DataSetTableProducer, 987, 991
Createparams, 235 Datasnap, 850
CreateTable, 91 8 Datasource, 675, 818, 878, 881, 1077
CreateWindowEx, 354 DateUtils, 99, 136, 148, 217
CreateWindowHandle, 235 DAX, 613
Creational Wizard, 595 DBCheckBox, 675
Cross-platform Form Modules (XFM). 224 DBCtrlGrid, 882, 888
csDestroying, 4 1 1 DBEdit, 675
csDropDown, 243 dbExpress, 727-728, 804
csDropDownList, 243 dbGo, 173, 807, 828
csExecute, 978 DBGrid, 112-1 13, 675-676, 788, 793. 818. 859,
CSS, 993 871, 888, 917, 1142
archivo, 995 personalization, 893-897
cssimple, 243 DBI, extension, 82
ctAnchor, 1039 DBNavigator, 675
ctDynamic, 825 DCI, extension, 82
ctstatic, 825 DCOM, 652, 85 1, 869
Cuadricula, 676 DCOMConnection, 854, 858
Cuadricula, HTML, 1061 DCP, extension, 77
Cubos, 198 DCT, extension, 82
CUR, extension, 77 DCU
Currency, 862 archivo, 552
Cursores, 822 extension, 7 8
conjunto de claves, 824 DDE, 598
dinamico, 824 DDL, 789
estatico, 824 DDP, extension, 78
solo avance. 824 Debugger Optiones, 129
Decision Cube. 173 Difference, pestaiia, 583
Defittributes, 239 Dinamica, biblioteca, 72
default, 100 Dinamicas, propiedades, 8 12-8 13
DefaultColWidth, 890 Dinamico, enlace, 528
DefaultDrawing, 892 Dinamicos, mttodos, 1 17
DefaultExpression, 861 Directorio en un conjunto de datos, 9 18-9 19
DefaultRowHeight, 896 DisableCommit, 652
DefaultTextLIneBreakStyle, 140 DisableIfNoHandler, 3 14
DefaultTimeout, 1043 Disparador de servidor, 788
DefineProperties, 175 Disparadores, 79 1
Definepropertypage, 644 dispinterface, 614, 61 6-617, 640
Definidores de estado, 250 DisplayFormat, 862, 864, 992
Delete, 194, 1037 DisplayLabel, 689, 862
delete-xx, 1 153 DisplayName, 891
Delphi DisplayText, 891
ediciones de, 36 DisplayType, 1039
paquetes, 550 DisplayValues, 862
Delphi for .NET Preview, 656 Displaywidth, 818, 862
Delphi Form Module (DFM), 224 Distributed COM (DCOM), 8 5 1
DELPHI32.DCT, 6 6 Divisas, 158
DelphiMM, 154 DivMod, 146
Delta, 674 DLL, 528-530, 531-532, 535, 544, 549-551, 648
paquete, 865 en tiempo de ejecucion, 542
DEM, extension, 82 creacion de, 534-535
Dephi ActiveX (DAX), 173 de C++, 532-534
deprecate, 90 extension, 79
Depurador, 87 normas de creacion, 530
Derechos de acceso, 1047 dmAutomatic, 157, 277
DESC, 826-827 DMT, extension, 82
Descendiente, conversion, 1 19 DNS, 1 152
Design Critic, 584 doAutoIndent, 1090
DesignIntf, 522 Document Import Signature, 586
DesignOnly, 562 Document Type Definition, 1098
DesingEditors, 522 Documentacion, 585
Destroy. 104, 108 DOF
DestroyComponents, 180 archivo, 83
Destruccion de objetos, 108 extension, 79
Destructores, 104 DOM, 1085
DevelopMentor, 1 130 para creacion de documentos, 1090-1094
DFM, 174, 207, 224 programacion con, 1085-1 086
archivos, 57, 83 DOMVendor, 1083
extension, 7 8 DoVerb, 631
DFN, extension, 79 DPK, extension, 79
Diagram Editor, 574 DPKL, extension, 79
Diagram View, 54 DPKW, extension, 79
Diagram, 54-55Diagramas DPR, 83
de actividad, 574 extension, 79, 581
de clase, 569 DragMode, 157, 277
de colaboracion, 574 DragTree, 278
de componentes, 574 Draw, 431
de dependencia de unidades, 574 Drawcell, 890
de despliegue, 574 DrawText, 892
de estado, 574 DRO, extension, 82
de mapa mental, 574 DropDownRows, 676
de robustez, 574 dsBrowse, 686
de secuencia. 57 1 dsCalcFields, 686
dsCurValue, 686 EqualsValue, 146
dsEdit, 686 Esquematica, informacion. 8 13
dsFilter, 686 Estilo, 304
dshactive, 686 Estilos de ventana, 354
dsInsert, 686 Estatica, sobrescritura, 2000
DSM, extension, 80 Euro, 158
dsNewValue, 686 Event Types View, 584
dsOIdValue, 686 Evento, 191
DST Eventos, 188, 190
archivos, 39 COM+, 653
extension, 82 programacion guiada por, 4 12
dt-Singleline, 892 Events, 236
dt-WordBreak, 892 Excel, 815, 817, 821
DTD, 1098 Excepciones, 124-125
Dual, soporte, 222 clases de, 127
DXSock, 986 depuracion y, 128
dynamic, 117 soporte, 124
except, 125-126
Exception, 127
EXE, 553
extension, 8 0
ebXML, 1 15 1 EXEC, 1056
EchoMode, 238 Execute, 507
Edit, 522, 1037, 1039 ExecuteTarget, 5 12
componente, 237-238 Executeverb, 522-523
EditFormat, 862 ExpandFileName, 144
EditMask, 239, 862 Experts, 87
Editor Properties, 5 1 exports, 53 1 , 537
EDivByZero, 127 Extended Database Forms Wizard, 1161
EFileStreamError, 206 Extendedselect, 243
ElnvalidCast, 120 extern "C", 533
Emptyparam, 81 3 External Translation Manager, 75
Enablecommit, 652 external, 539
Enabled, 232, 3 14 ExtractStr~ngs,2 16
Encapsulacion. reglas de la, 186
Encapsulado, 95, 10 1
con propiedades, 97
campos protegidos y, 1 11
EndDocument, 1099, 1101 FalseBoolStrs, 142
EndElement, 1099-1 100 fdApplyButton, 394
EndThread, 140 FetchDetails, 873
EndUserSessionAdapter, 1045 FetchDetailsOnDemand. 873
Enlace FetchOnDemand, 873
de datos, 878 Fetchparams, 868
archivos de, 81 1-812 fgConflictingRecords, 839
dinamico, 528 fgPendingRecords, 835
posterior, 114 FieldAddress, I76
Enlazador inteligente, 186 FieldByName, 687-890
Ensamblaje, 656 FieldDataLink. 884
EnsureRange, 145 Fields, 1037
Enterprise Studio, 36 FieldsDef, 902
Entrada de teclado, 356 FieldValues, 688
EnumModules, 139, 562-563 FIFO, 197
Environment Options, 40-4 1, 53 Filecreate, 144
Environment Variables, pagina, 40 Filter, 673, 814
Envoltorio, 200-20 1 Filtered, 8 14
FilterGroup, 835-836, 839 Generadores de 64 bits, 786
Filtrado, 672 GeneratorField, 788
finally, 125-126 Gestion de archivos, 162
Find, 395 get-xx, 1 153
find-xx, 1153 GetBufStart, 276
Findclose, 162 GetCanModify, 921
Findcomponent, 184, 392 Getclass, 556
FindFist, 162 GetDataAsXml. 1 107
FindGlobalComponent, 185 GetFileVersion, 144
FindNext, 162 GetInterfaceExternalName, 1 137
FindNextPage, 288 GetKeyState, 304
FList, 201 GetLocaleFormatSettings, 144
FloatToCurr. 143 GetLongHint, 303
FloatToDateTime, 143 GetMem, 139
Flotantes, sugerencias. 262 GetMemoryManager, 139
fmOpenRead, 205 GetMethExternalName, 1 137
fmShareDenyWrite, 205 GetNamePath, 175
fmShareExclusive, 206 Getobjectcontext, 652
Foco, 188, 256 GetOleFont, 627
de entrada, 254 Getolestrings, 628
FocusControl, 54, 255 Getowner, 175
FollowChange, 674 GetPackageDescription, 562
FOnChange. 19 1 GetPackageInfo, 562, 564
FOnCLick, I90 GetPackageInfoTable, 139, 562
Font, 233, 896 GetProcAddress, 542, 546
FontDialog, 394 GetPropInfo, 178
FontNamePropertyDisplayFontNames,60 GetPropValue, 178
Footer, 992 GetShareData, 549
ForEach, 199 GetShortHint, 303
Form Designer, 56, 62, 83 GetStrProp, 178
Formstyle, 423 GetSystemMetrics, 355
Formularios dentro de paquetes, 553 GetTickCount, 624
formview, 1039 GetVerb, 522
FRecordCount, 91 0 GetVerbCount, 522
Free, 104, 108-109, 198 GExperts, 4 1
FreeAndNil, 108 glibc, 154
FreeLibrary, 542, 556 Global Desktop, 39
FreeMem, 139 Google, 978
FreeNotification, 41 1 Goteo de memoria, 105
FriendlyName, 1062 GreaterThanValue, 146
FROM, clausula, 821 grEOF, 9 12
FromCommon, 161 GroupBox, 181 , 2 4 1
FromStart, 1071 Grouped, 3 17
fsMDIChild, 423 GroupIndex, 3 17, 429, 6 3 1
fsMDIForm, 423 GUID de Windows, 786
FTP, 982 GUlD, 122, 657, 813, 1135
Fuentes, 233 GuidAttribute, 657
desplegables, 60
FullExpand, 277
function, 92

Handle, 235
HandlesPackages, 560
HandlesTarget, 5 12
Generacion de codigos, 175 Hash, 198
Generador, 787 Header, 992
Hebras, 1141 IConnectionPoint, 653
Herencia, 109, 1 13, 200, 432 lconview, 246
de un formulario base, 433 IconView, 27 1
Hide, 232 IDAPI, 803
HideOptions, 1047 Identificador de sesion, 1056
Hilos, 4 12 Identificador global, 790
HInstance, 563 IdHTTPServer, 985, 994
Hint, 303 IDispatch, 139, 614-6 15, 623
Hintcolor, 262 IDL, lenguaje, 6 18
HintHidePause, 262 IdMessage, 975
Hintpause, 262 IDOMAttr, 1086
Hintshortpause, 262 IDOMElement, 1086
Hojas de estilo, 993 IDOMNode, 1086
HorzScrollBar, 249 IDOMNodeList, 1086
HTML, 987 IDOMParseError, 1083
extension, 80 IDOMText, 1086
HTML 4 , 9 8 8 IdPop3,975
archivo, 82 1 IdSMTP, 975
estructuras, 1068 IdURL, 984
generation de paginas, 988 IFDEF, 227
HTTP, 852,977, 982 Iflhen, 145, 149
servidor, 9 8 5 ignorablewarning, 1100
socket, 869 IInterface, 122-124, 139
HttpDecode, 969 IInvokable, 139, 1132, 1145
HttpEncode, 969 IISAM, 815-816, 818
HTTPRIO, 1 134, 1 137, 1 144 ImageIndex, 252
HTTPSoapDispatcher, 1135 ImageList, 59
HTTPSoapPascalInvoker, 1135 Images, 252
httpsrvr.dll, 852, 855 Import Type Library, 622
HTTP We bNode. 1 1SO ImportedConstraint, 86 1
IN, clausula, 821
IncludeInDelta, 874
IncludeTrailingBackslash, 144
IncludingTrailingPathDelimiter,144
IAppServer, 850-851, 1 146-1 148 Increase, 9 7
IAppServerSOAP, 1 146-1 148 Indexado, 671
IBBackupService, 777 IndexFieldNames, 826
IBConfigService, 777 IndexOf, 149
IBDatabase, 777 IndexOfName, 194
IBDatabaseInfo, 777 InetXCustom, 1 1 10-1 11 1
IBDataSet, 780, 792 InetXPageProducer, 1108-1 109, 11 13
IBEvents, 777 Infinity, 145
IBInstall, 777 InflateRect, 892
IBLogService, 777 Information de clase, 167
IBQuery, 780 inherited, 48, 116, 434, 893
IBRestoreService, 777 InheritsFrom, 1 19, 164
IBSecurityService, 777 INI, archivos, 39
IBServerProperties, 777 Inicial, pantalla, 397
IBSQL, 777 InputBox, 396
IBSQLMonitor, 777, 783 InputQuery, 396
IBStatisticalService, 777 Inquiresoap, 1 154InRange, 145
IBUninstall, 777 Insert, 194, 1039
IBUpdateSQL, 779-780 Insertcomponent, 180, 182
IBValidationService, 777 InsertObjectDialog, 6 3 1
IBX, 777 Installable Indexed Sequential Access Method
ICO, extension, 77 (IISAM), 81 5
InstanceS~ze,164 IWURL, 1061
Instancias de formulario, 793 IXMLDocument, 1087
Integrated Translation Environment (ITE), 530 IXMLDocumentAccess, 1083
InterBase Admin, 777 IXMLNode, 1087
InterBase Express, 173, 783-785 IXMLNodeCollection, 1087
InterceptGUID, 854 IXMLNodeList, 1087
Interfaces, 12 1
Interfaz
de envio. 616
de usuario, 283
InterLockedIncrement, 1066 LabeledEdit, 59, 238
Intermediacion, 6 13 LabelPosition, 238
InternalInitFieldDefs, 904 Labelspacing, 238
Internet Express, 1 108 Languaje Exceptions, 129
Internet, pagina, 40 LargeImages, 270
InternetCloseHandle, 983 LayoutChanged, 895
Internetopen, 982-983 IbOwnerDrawFixed, 267
InternetOpenURL, 982-983 IbOwnerDrawVariable, 267
InternetReadFile, 982-983 Left, 232
INTO, clausula, 82 1 LessThanValue, 146
IntraWeb, 173, 1049-1050 library, 90, 535
arquitecturas, 1057 LIC, extension, 80, 640
Introduced, 4 8 Licencias, 1164
IntToStr, 141 LIFO, 197
Invalidate, 365-366 Ligeros, clientes, 850
InvalidateRegion, 366 like, 788
IObjectContext, 652 Linestart, 2 16
is, 119, 164 Linuy 162, 170, 200, 227, 932
ISAPI, bibliotecas, 1057 List Template Wizard, 1159
IsConsole, 139 Lista de referencias grafica, 270-275
IsEqualGUID, 143 Listas, 242
IsInfinite, 145 ListBox, 242, 267
IslnTransaction, 652 listener, 189
IWClientSideDataSet, 1076 Listview, 270
IWClientSideDataSetDBLink, 1076 LIVE-SERVER-AT-DESIGNTIME, 626
IWCSLabel, 1076 LoadBalanced, 874
IWCSNavigator, 1076 LoadFromFile, 193, 276, 844
IWDataModulePool, 105 1 LoadFromStream, 204
IWDBGrid, 1071, 1075-1076 LoadLibrary, 542, 556
IWDialogs, 105 1 Loadpackage, 556
IWDynamicChart, 1076 LocalConnection, 855
IWDynGrid, 1076 Locate, 673
IWEdit, 1052 Locked, 6 3 1
IWGranPrimo, 105 1 LockType, 831, 834, 840
IWGrid, 1061 LogChanges, 675
IWImagen, 1063 LoginFormAdapter, 1046
IWLayoutMgrForm, 1069 LongMonthNames, 110
IWLayoutMgrHTML, 1069 IoPartialKey, 673
IWListBox, 1052 Lote, 839
IWModuleController, 1067 Lotes, 834
IWOpenSource, 1051 Lotus 1-2-3, 815
IWPageProducer, 1066 ItBatchOptimistic, 831, 834, 837, 840
IWRun, 1052 Itoptimistic, 83 1
IWServerControllerBaseNewSession,1065 ItPessimistic, 83 1-832
IWTemplateProcessorHTML, 1069 ItReadOnly, 83 1
IWTranslator. 105 1 ItUnspecified, 831
Modified, 643
ModifyAccess, 1047
maAutomatic, 26 1 ModuleIsPackage, 56 1
Macros, 587 Move, 921
Madre, clase, 163 Mozilla, 1055
Maestroldetalle, 792, 870, 104 1 MPB, extension, 570
MainForm, 380 mrOk, 505
MainMenu, 59 mscorlib.dll, 658
MakeObjectInstance, 431 MSXML SDK, l o 8 6
Maletin, 844 MTS, 648, 851-852
malloc, 154 Multicapa, aplicaciones Datasnap. 847
maManual, 261 Multiline Palette Manager, 1161
Manejadores de mensajes, 1 17 Multiplataforma, 932
MapViewOfFile, 549 MultiSelect, 242, 280
Marshalling, 599, 61 3 MultiSelectStyle, 280
MaskEdit, componente, 238 MyBase, 173, 1085
MasterAdapter. 1041 Metodo, punteros a, 189
Math, 145, 217 Modelo de codigo, 578
Matrices, propiedades basadas en, 100 Modulo de datos transaccionales, 65 1
MaxRecords, 1 109
MaxSessions, 1043
MaxValue, 862
Mayusculas, 789
mbMenuBarBreak, 252 Name mangling, 537, 552
mbRight, 253 Name, propiedad, 184
MDAC, 805, 807, 842, 850 Namevalueseparator, 194
MDI, 423 NegInfinity, 145
aplicaciones, 422 nerError, 152
cliente, 423 nerLoose, 152
Memo, componente, 239 nerstrict, 152
Memory Snap, 1 163-1 164 NetCLX, 170, 173
Mensajes de correo, generacion automatica de, 974 New Items, cuadro de dialogo, 85
Menu Des~gner,2 5 1 Newvalue, 839
Menu, 261 nil, 104, 108-109, 187
MergeChangesLog, 675 No modal, cuadro de dialogo, 390
message, 1 17 NodeIndentStr, 1090
MessageBox, 396, 536 NodeType, 1088
MessageDlg, 395 Nodevalue, 1088Nod0, 1088
MessageDlgPos, 395 de arbol, 280
Messages, 230 Nombre-valor, pares, 194
Method Implementation Code Editor, 582 Nombres de proyecto, cambio de, 540
MethodAddress. 176 Normalizacion, 795
MethodName, 176 Norrnalizacion, reglas de, 790-791
MidasLib, 669Middleware, 812 Notebook, 285
MilliSecondOfMonth, 148 Notification, 4 1 1
MIME, tipo, 995 null, 152
MinSize, 259 NullAsStringValue, 152
MinValue, 862 NullEqual~tyRule,152
MmSystem, 497 NullMagnitudeRule, 152
ModalResult, 390, 505 NullStrictConvert. 152
ModelMaker, 41, 47, 567-569, 592
ModelMaker, integracion de Delphi con, 576-575
Modelo
de hilos, 649
de referencia a objetos, 104 OASIS, 1 15 1
transaccional, 649 OBJ, extension, 80
ObjAuto, 2 17 OnDragOver, 157, 277
Object OnDrawItem, 265-266
Browser, 74 OnDropDown, 244
Debugger, 1162-1 163 OnEnter, 255-256
Inspector Font Wizard, 1160 OnException. 128
Inspector, 58. 59, 191 OnExit, 255
Pascal, 89 OnFilterRecord, 686. 8 14
Repository, 84-85, 535 OnFormatCeII, 993
Treeview, 54, 59, 61-62 OnGetData, 875
ObjectBinaryToText, 208 OnGetDataSetProperties. 874
ObjectPropertiesDialog, 632 OnGetEditMask, 246
Objects, 268 OnGetPickList, 247
Ob.jeto OnGetValue, 1044
COM, 599 OnHelp, 353, 404
interno, 633 OnHint, 303, 404
Objetos OnIdle, 404
de automatization, alcance de, 624 OnKeyDown, 236
y memoria, 107 OnKeyPress, 237, 356
OCX, 636, 646 OnMeasureItem, 265-266, 268
extension, 80 OnMessage, 404
ODBC, 803, 805 OnMouseDown, 133,236,274
of object, 189 OnMouseMove, 236
ofAllowMultiSelect, 394 OnPageAccessdenied, 1047
ofExtensionDifferent, 394 OnPaint, 236, 365
Office, aplicaciones, 629 OnReceivingdata, 1 150
OID global, 786 OnReconcileError, 853, 863, 865
OID, 786 OnRecordChangeComplete, 839
OiFontPk, 60 OnRecordsetCreate, 8 13
OLAP. 173 OnShorCut, 404
Oldcreateorder, 225 Options, 862, 872
OldValue, 839 Oracle, 807, 842
OLE Automation, 97, 612 ORDER BY, clausula, 827
OLE DB, 803, 805, 809 Orientado a objetos, enfoque, 135
proveedores, 805 out, 101
OLE, 598, 629 overload, 93
Olecontainer, 630 override, 1 14, 123
Olevariant, 637-638 Owner, 182, 231
OnActionUpdate, 404 Owner-draw, 265
OnActivate, 404
OnCalcFields, 696
OnCanViewPage, 1047
Onchange, 191, 236, 924
OnChar, 493 Page, 1057
Onclick, 166, 184, 190-192, 236 Pagecontrol, 181, 284, 1083
Onclose, 783 PageControls, 285
OnCloseUp, 244 PagedAdapter, 1037
OnColumnClick, 273 Pageproducer, 987
OnCommandGet, 994 PageScroller, 249
Oncompare, 273 Pagesize, 1037
OnContextMenu, 253-254 PakageInfo, 139
Oncreate, 379 Panel, 181
OnCreateNodesClass, 280 Paquetes, 527, 553
OnDataChange, 879 interfaces en, 558
OnDestroy, 557 versiones de, 55 1
OnDoubleClick, 39 1 de datos, 853
OnDragDrop, 157, 277 delta, 853, 865
Paradox, 8 16, 82 1 ProviderName, 859
param, 647 Proyecto
Paramcount. 140 en blanco, plantilla de. 85
Params, 868, 1 109 opciones de, 69
ParamStr, 137, 140 Proyectos, gestionar, 67-68
Parcheo, 546 public, 96, 100, 176
Parent, 133, 23 1 published, 100, 174, 176
Parentcolor, 233, 635 Paginas
ParentFont, 233, 635 amarillas, 1 152
Parser, 1083 blancas, 1 152
Parametros verdes, 1 152
consulta preparada con, 790 P~iblico,96
consultas por, 868
PAS. extension, 80
Pascal orientado a objetos, 89
PasteSpecialDialog, 632
Patrones de diseiio, 590-592 QDialogs, 224
PChar, 537-538 QForms, 224
Permanencia, 207 QGraphics, 224
Permisos, 1043 QPainterH, 222
Personalizada QStdCtrls, 223
clase stream, 210 Qt, 2 2 0 , 2 2 2
variante, 153 Qt/C++, 236
Pes~mista,bloqueo, 832 QtFreeEdition, 222
Peticion de entrada, 1045 Query, 1039
PickList, 676 QueryInterface, 600, 610
PixelsPerInch, 378-379 QueryTableProducer, 988
Plantilla, 66
Plantillas
de componentes. 65
de codigo, 45, 593
modificar, 52 RadioButton, 24 1
platform, 90
RadioGroup. 241
Playsound, 497 raise, 125
poAllowCommandText, 873 Random, 145
poCascadeDeletes, 873 RandomFrom, 145
poDefault, 368 RandomRange, 145
poDefaultPosOnly, 368 Rangos, 248
Preferences, pagina, 4 0 Rave Reports, 932
Principal, formulario, 429 RAVE, 173
Privada, parte de la declaration, 186 Rave, 93 1
Privado, 96 RC, extension, 81
private. 96, 176 RDBMS, 849
procedure, 92 RDS, 805
ProcessMessages, 365, 4 12 RDSConnection, 808
Professional Studio, 36
Read, 202, 1 150
Propiedades read, 98
ficha de, 642 ReadBuffer, 203
por su nombre, 177 Readcomponent, 203
Propietario ReadComponentRes, 208
cambio de, 182 Rebuild Wizard, 1 1GO
componente, 180 ReconcileProducer, 1 1 12
protected, 96, 176 Recordcount, 826
Protegido, 96 Recordsize, 9 12
Proveedores, 806 Redondeo, 147
ProviderFlags, 862 Referencias de clase, 130-132
Refresco, 865 rsPattern, 260
Refresh, 865 rsupdate, 560
RefreshRecords, 865-866 RTL, 123, 127, 152, 170, 220
RefreshSQL, 796 unidades de la, 136
REG, extension, 81, 609 VCL y, 138
regasm, 657 RTTI, 119, 121, 133, 164, 556, 558, 1093
Register, 524, 53 1 operadores, 12 1
Registerclass, 186, 558 RunOnly, 562
RegisterClasses, 186 RWebApplication, 1065
RegisterConversionType, 16 1
RegisterPooled, 874
Registro de Windows, 78 1, 1 148
Registro, 64
Registros, conjunto de, 81 3 safecall, 869
Reglas de negocio, 926 SafeLoadLibrary, 542, 556
Reingenieria, 587-588 Samevalue, 146
Release, 124, 1063 save-xx, 1 15 3
Remoteserver, 859 SaveDialog, 394
Remove, 194
SavePictureDialog, 394
RemoveComponent, 182 Savepoint, 674
Repaint, 365 SaveToFile, 193, 843
Replace, 395 SaveToStream, 204
Replicacion, 827-829 SAX, 1085, 1099
Required, 1062 ScaleBy, 377
Reserva, de conexiones, 84 1 SCHEMA.IN1, 8 19-820
Resizestyle, 260 ScktSrvr.exe, 852
Resolutor, 864 Screen, 378-379, 409
Resolver, 864 Screensnap, 368
ResolveToDataSet, 863-864 ScrollBar, 248
Resource Explorer, 76 ScrollBox, 250
Resource Workshop, 76 SecondOtWeek, 148
resourcestring, 14 1 Seguimiento activo, 274
Restricciones, 860 Seguridad de tipos, 199
Resync, 840 SelAttributes, 239
ResyncValues, 840 SelCount, 243
Rethink Orthogonal, 595 Selected, 243
RethinkHotKeys, 261 Selections, 280
RethinkLines, 26 1 SelectNextPage, 287-288
Retrollamada, 563 Self, 93-94, 104, 123, 189
Retrollamadas, 221 Sender, 157
Reutilizacion, 1 15 ServerGUID, 854
RGB, 234 ServerName, 854
RichEdit, 239 Servicios de componentes de Microsoft, consola
Rollback, 781 de, 654
RollbackRetaining, 782 Sesiones, 1043
RollbackTrans, 829 gestion de, 1064-1066
Root, 924 Sessionservice, 1045
ROT, 627 SetAbort, 652
RoundTo, 146 SetBounds, 232
Row, 243 SetComplete, 652
RowAttributes, 992 SetDataField, 882
RowCurrentColor. 107 1 SetFieldData, 92 1
RowLayout, 243 SetForegroundWindow, 421
RowLimit, 107 1 SetMemoryManager, 139
RPS, extension, 8 1 SetOleFont, 627
rsline, 260 Setolestrings, 628
SetShareData, 549 servidor, 672
SGML, 1080 servidores, 648, 727
SharedConnection, 855 update, 796
ShareMem, 154 SQLDataSet, 857, 1140
ShellApi, 977 SQLQueryTableProducer, 988
ShellExecute, 977 Standalone, 1057
ShortMonthNames, 110 StartDocument, 1099, 1101
Show, 559 StartElement, 1099-1 100, 1102-1 103
ShowColumnHeaders, 273 starting with, 788
Showing, 233 State, 245, 914
ShowMessage, 178, 396, 536 State-setters, 250
ShowMessagePos, 396 StateImages, 270
ShowModal, 545, 559, 1063 Stateless COM (MTS), 85 1
Showsender, 166 Statics, 4 8
Signals, 236 StatusBar, 102
Signatures, 207 stBoth, 273
SimpleObjectBroker, 873 stData, 273
SimplePanel, 255, 301 stdcall, 531, 534, 1145
SimpleRoundTo, 147 StdConvs, 148, 155, 159, 161
SimpleText, 30 1 StdCtrls, 223
Sincronizacion, problemas, 3 16 Stones, 148
Sincronizador multilectura, 142 stored, 100
Singleton, patron, 590 StoreDefs, 670
siProviderSpecific, 813 StrCopy, 92 1
Size, 202, 8 18 Stream, 2 14
SizeGrip, 302 Streaming, 174-175, 202, 206
SizeOf, 164 sistema de, 186
Skin, 304 Streams, 205, 209
SmallImages, 270 compresion de, 2 13
SMTP, 975 conjunto de datos basado en, 9 17-9 19
SNA Server, 807 StringReplace, 67 1
Snap To Grid, 56 StringToColor, 269
SOAP Server Application, 1 134 StringToFloat, 495
SOAP, 652, 852, 1129, 1131, 1140, 1145 StripParamQuotes, 988
DataSnap sobre, 1 145 StrUtils, 149, 2 17
depuracion de cabeceras, 1143 style, 1055
proyeccion sobre Pascal, 1 133 SubMenuImages, 252
SOAPConnection, 1148 Sugerencias, personalization, 263
SOAPMidas, 1 147 SupportCallbacks, 854
SOAPServerIID, 1147 SupportedBrowsers, 1055
Sobrecargadas, funciones, 537 Supports, 561, 829
Sobrescribir, 1 15 Sustitucion de etiquetas, 1045
Socket, conexion de, 970 Synchronize, 97 1
Socketconnection, 854 SyncObjs, 217
SortType, 273 SysConst, 141
Soundex, 149 SysInit, 139-140, 561
Source Options, 45, 52 System, 139-140
SpeedButton, 2 3 1 SysUtils, 136, 141-142, 162,217
Splitter, 258, 260
SQL Monitor, 75
SQL Server Profiler, 834
SQL Server, 834
SQL TabbedNotebook, 285
edicion, 801 Tabcontrol, 284
insert, 796 Tabla de metodos virtuales (VMT), 123, 552
sentencia, 728, 779, 800, 792 TableAttributes, 992
TableName, 816, 819, 9 18 Terminate, 1063
Taborder, 254 Text IISAM, 819-820
Tabs, 64 Text, 133, 244
TabSet, 285 TextBrowser, 983-984
Tabsheet, 284, 286 TextHeight, 268
TabSheets, 285 Texto, archivos de, 8 19
TabStop, 254 Textviewer, 240
TabVisible, 2 8 8 TField, 992
TActiveForm, 644 TFieldDataLink, 879, 886, 888
TADTField, 692 TFields, 902
Tag, 188, 990 TFileData, 92 1, 924
TAggregateField, 692 TFileRec, 14 1
TAlignment, 3 17 TFileStream, 204
TApplication. 130, 404 TFloatField, 690
TArrayField, 692 TFMTBCDField, 693
TAutoIncField, 692 TForm, 645
TBCDField, 693 TFormatSettings, 144
tbHorizonta1, 424 TFormClass, 132
TBitBtn, 499 tgcustom, 990
TBitmap, 204 tglmage, 990
TBits, 216 tgLink. 990
TBlobFieId. 204 TGraphicControl, 230-231, 639
TBlobStream, 205 TGraphicField, 693
TBooleanField, 693 tgTable, 990
TBucketList, 198 THandleStream, 204
TButton, 164, 166, 221 THashedStringList, 199
tbvertical, 424 THeapStatus, 139
TCompressStream, 2 13 THintInfo, 264
TControl, 171. 219, 230, 283 Threads, 142, 412
TControlClass, 133 threadvar, 1065
TConvTypeFactor, 16 1 THTTPRIO, 1 133
TCPIIP, sockets, 852 TIcon, 204
TCurrencyField, 693 TidThreadSafeInteger, 1066
TCustomControl, 639 Tiempo de ejecucion
TCustomDBGrid, 895 verificaciones en, 200
TCustomEdit, 494 Tile, 424
TCustomGrid, 889, 892 TileMode, 424
TCustomListBox, 244 TMenuItem, 261
TCustomMemoryStream, 204 TMethod, 177, 189
TCustomVariantType, 15 1, 153 tModel, 1 152
TDataEvent, 879 TMREWSync, 142
TDataLink, 878-879 TMultiReadExclusiveWriteSynchronizer,142
TDataModule, 216, 855 TNotifyEvent, 2 15
TDataSet, 675, 897, 898, 902, 914, 919 TO-DOList, 41
TDatasetField, 870 TObject, 121, 139, 163-164, 165-167
TDataSetProvider, 863 TObjectBucketList, 198
TDateField, 690 TObjectList, 197, 919
TDateTime, 97, 139, 148, 916, 923 TObjectQuery, 197
TDateTimeField, 693 Tocommon, 16 1
TDBMS. 823 TODO
TDecompressionStream, 2 15 comentarios, 42
TDecompressStream, 2 13 extension, 81
TDump, 534 TOleContainer, 63 1
Teamsource, 75 TOleControl, 640
Tema, 304 TOleServer, 626
Template, 66 TOleStream. 205
ToolBar, 30 1, 3 13 TVarData, 139
Tooltip Expression Evaluation, 53 TVariantField, 695
Tooltip Symbol Insight, 4 8 TVariantManager, 139
Top, 232 TWebAppPageModuleFactory, 1046
Total, 1 150 Type Library Editor, 1135
TOwnedCollection, 2 15 Type Library Importer, 657
Transaccion. 65 1. 783 TypeInfo. 179
componente de, 78 1 Types, 15 1.230
context0 de, 782 TypInfo, 178, 217, 1093
Transaction DDL, propiedad, 829
TransformNode, 11 19
Transparentcolor, 366
TransparentColorValue, 366
TReader, 206-207 UDDI Browser, 1 153
TRecall, 2 16 UDDI, I151
TReconcileErrorForm, 840 en Delphi 7. 1153
TRect, 15 1 UDL, extension, 8 1
Treeview, 270, 275-276, 1087 UML, 567, 568-569, 572
Tres capas, arquitectura logica de, 849 UndoLastChange, 674
TResourceStream, 2 0 5 , 2 1 0 Union SQL, 833
Trolltech, 220 Unique Table, propiedad, 834
TrueBoolStrs, 142 Unit Code Editor, 580
try, 125-126 Union interna, 796, 798
trylexcept, 129 UnloadPackage, 556-557
TryEncodeData, 143 Update, 365
TryEncodeTime, 143 UpdateBatch, 835
TryStrToCurr, 143 UpdateCriteria, 838
TryStrToDate, 143 UpdateList, 202
TryStrToFloat, 143 UpdateOIeObject, 643
TSchemaInfo, 8 13 UpdatePropertyPage, 643
TSearchRec, 92 1 UpdateRegistry, 857, 874
TSmallPoint, 15 1 UpdatesPending, 836
TSOAPAttachment, 1050 UpdateSql, 860
TSoapAttachment, 1149 Updatestatus, 835
TSoapDataModule. 1 147 UpdateTarget, 5 12
TSQLTimeStampField, 694 UpDown, 249
TStack. 197 upper, 788
TStream, 202-203 UserFrame, 1071
TStringList. 193, 204, 215, 217, 508 uses, sentencias, 226, 229
TStrings, 149, 193, 204, 2 15 UseSOAPAdapter, 1 148
TStringStream, 204 Usuarios, 1043
TTextRec, 141
TThreadList, 2 15
TTimeField, 694
TTimeStamp, 923
TToolButton, 250
Validacion, 1098
TTreeItems, 280
ValueFromIndex, 194
TTreeNode, 278
ValueListEditor, 246
Tuberia (I), 303 Values, 194
Turbo var, 101, 107, 118
Grep, 76
VarArrayCreate, 673
Pascal, 4 6 VarArrayOf, 673
TextFile, 130 VarCmply 153
Register Server, 76 VarComplexCreate, 153
TUserSession, 1065, 1066, 1070 Variant, 136
TVarBytesField, 694
Variantes personalizadas, 152
Variants: 151, 153, 217 WideSameText, 143
Varias paginas, aplicaciones de, 1060 Widestring, 650
VarUtils, 15 1, 21 7 WideUpperCase, 143
VBX, 634 Widgets, 220
VCL Hierarchy Wizard, 1161 Width, 259
VCL, 38, 138, 170,202. 215 WINAPI, 53 3
VclToClx, 1 I62 WindowMenu, 423
Ventanas WindowPRoc, 235
hijo. 423 Windows 2000, 353
marco, 423 Windows, 230, 536, 932
Verbos, 6 3 1 Windowstate, 368
Verificacion, valor de, 198 WinInet, 982
VertScrolIBar, 249 Winsight, 75
ViewAccess, 1047 WndProc, 235
virtuaI, 1 14, 1 17 Word, 629
Virtuales Wordstar. 46
metodos, 1 17 Worl Wide Web Consortium (W3C), 987
teclas, 3 13 wpLoginRequired, 1046
Virtuals, 4 8 Write, 202
Visible, 233. 862
VisibleButtons, 676
VisibleRowCount, 896
Vista
de diferencias, 582 xaAbortRetaining, 830-83 1
virtual, 894 xaCommitRetaining, 830-83 1
Vistas, 54 XDB, extension, 1098
Visual Basic, 633 Xerces, 1086
VisualCLX, 170, 172, 221 XFM, 174,224
VCL frente a. 220 XHTML, 988
Visuales, controles, 2 19 XLSReadWrite, 8 17
Visualizador de registros, 889 XMethods, 1 13 1
XML Mapper, 75, 1103-1 104, 1141-1 145
XML Schema Validator (XSV), 1099
XML Schema, 1099
XML, 669, 671, 844, 1079-1080
W3C, 987, 1082, 113 1 analizador sintactico, 843
Watch List, 87 bien formado, 1082
WAV, archivos, 496 con transformaciones, 1 103
Web App Debugger, 75, 1068, 1143-1 144, 1146 datos, 1139
Web Surface Designer, 1040, 1042 esquemas, 1098
Web interfaces de enlace de datos, 1094-1099
aplicaciones, 105 1 paso de documentos, 1140
exponer como un servicio, 1144 sintaxis, 1080-1082
servicio, 1 130, 1 134 XMLBroker, 1108-1 109, 11 15
WebApplication, 1060 XMLData, 669
WebBroker, 978, 1045, 1066, 1145 XMLDocument, 1083
Webconnection, 855 xmldom, 1086
WebDispatch, 1109 XMLTransform, 855, 1 105
WebSnap, 48, 978, 1041-1042, 1066 XMLTransformClient, 855, 1 105
WebUserList. 1045 XMLTransformProvider, 855, 1105, 1 142
while, 167
WideCompareStr, 143
WideCompareText, 143
WideFormat, 143
WideLowerCase, 143
WideSameStr. 143

También podría gustarte