Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Biblia Delphi
Biblia Delphi
. .
Agradecimientos ....................................................................................................................... 6
Contactar con el autor .............................................................................................................. 7
.
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
. ..
18 Generation de informes con Rave ............................................................................. 931
.
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.
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++.)
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.
[Alignmentpalette]
Create=l
Visible=O
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.
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.
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.
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.
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.
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.
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) ;
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;
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.
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.
ti-
h e
Q+fcrolim
J
Ths IS a simple version
r-
M base
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,
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 .
I
B F d
j
1
Charsel
Cob
ITFant)
'DEFAULT-CHARSET
' W cWindowText
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:
hn New (Newl)
em, Open (Open11
bM,Save (Save1 )
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
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
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 )
I
yarn- - - - - - - --.
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.
.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.
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.
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.
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.
.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.
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.
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=
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.
Console
Cwone* Applcat~on
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.
--
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
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;
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.
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;
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.
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 ;
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;
NOTA:~ i ~ ceg
s equo el a & d ~ p & i ~ d d a dpun f o n d ario, no 3. Mia& -~4'
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;
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 ) ;
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.
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.
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 :
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)
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.
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.
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!
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.
-
Figura 2.7. El resultado del ejemplo PolyAnimals.
type
TMyClass = class
procedure One; virtual;
procedure Two; (metodo estdtico)
end;
TMyDerivedClass = class (TMyClass)
procedure One; override;
procedure Two;
end;
type
TMyClass = class
procedure One;
end;
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.
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;
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.
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.
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.
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
- .- - - - - - - - - ---- - - -- - -
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 ') ;
'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:
-- -
- --- - - . - -.
. . .- .-
.- . - .- . -
. -- .- ..- -- - -
-- ~~ - - . -
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
........ .
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.
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.
I
El tamafio emutable bajo el microscopio
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 .
/ / 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.
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;
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 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.
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)
duAngstroms : TConvType;
dulrlicrons: TConvType;
dulrlillimeters: TConvType;
duMeters : TConvType;
duKilometers: TConvType;
duInches: TConvType;
duMiles: TConvType;
duLightYears: TConvType;
duFurlongs: TConvType;
duHands : TConvType ;
duPicas: TConvType;
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
// 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;
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.
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) ;
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.
Figura 3.4. La salida del ejemplo EuroConv, que muestra el uso del motor de
conversion de Delphi con una unidad de medida personalizada.
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.
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
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).
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;
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);
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 . . .
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...
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
ventana
(subclases de
Controles TWinControl)
(oomponentes visuales)
Controles no
de ventana
(TComponent) (subclases de
TGraphicControl)
Componentes no visuales I
I
(otras subclases de
TComponent)
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.
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.
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
- -
.
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.
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;
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;
1
Figura 4.3. En el ejemplo Changeowner, al hacer clic sobre el boton Change se
mueve el componente Buttonl a1 segundo formulario.
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 ~
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.
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.
- -
MyButton = class
OnClick: TNotifyEvent;
end;
var
Form1 : TForml ;
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.
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.
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.
. - - - - -- - - -
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
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-
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;
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.
// 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 ;
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.
- , ---- - -
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:
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;
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
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.
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 ;
Decompress
1
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 ;
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:
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.
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;
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.
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:
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.
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:
1 NOTA:
mit&
pKogrr;maddreesmpertos pug& no* q4e en CLX hqy un
utibadi3 can *a hecdrencia, EventHandler, que se cmres-
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.
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.
Test Html
Test text with bold
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.
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:
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];
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;
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 :
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.
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.
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 ;
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.
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.
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.
-
wo
k bled ntab order
I
- --
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.
- .. --
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.
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.
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 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.
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.
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:
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.
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. -
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.
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
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;
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.
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)
{ $ IFDEF MSWINDOWS]
{SR *.dfm]
{SENDIF]
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;
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.
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;
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;
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;
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 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;
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.
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;
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 -
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.
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:
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:
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.
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
< . >
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.
- -
- --- -- - .- - - - - - --
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.
[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.
Figura 6.9. El editor del cornponente ActionList, con una lista de acciones predefinidas
que se pueden w a r .
-
, - -
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 >.
-
1 Calmofies: 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 ;
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.
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.
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 :
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:
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
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;
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.
// 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;
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
eat
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.
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.
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 ;
procedure TForml.VirtualListActionlGetItem(Sender:
TCustornListAction;
const Index: Integer; var Value: String;
var ImageIndex: Integer; var Data: Pointer) ;
begin
Value : = 'Item' + IntToStr (Index);
end;
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.
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).
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.
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.
lag &nu -fbs Iform border style, estilo del h d e del formuhrio). Asi
tdremos 'Pbssingle, fbsDialog, etc.
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.
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.
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;
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;
. . . . . . . Aid,
..
. .
..
..
..
.... . . . . . . . . . . . . . . . . .
: , I '- ,"On= . . . .l~dill
......................
. .
. .
..I
.. ::::I
. , . . Edit2
.
. . . . . . . .
. . . . . . . . . . .
. .
.. .... . . . . . . . . . . .
, .... . . . . . . . . . . . . . . . .
, . . , Edit3 . . . . . .
.... . . . . . . . . . . . .
I . . . . . . . . .: : : : : . : : : : . . .
I . . . . .
.. . . . .
.. I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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;
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.
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.
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 ;
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 .
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.
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).
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;
r1 I -
b
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.
// 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
// 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.
r --Man
- -
-
fam l~olrn~
Figura 7.10. La ficha Forms del cuadro de dialogo Project Options de Delphi.
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;
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:
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.
-- -.. -- - . -
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.
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 ;
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
// 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.
- . .- - - ... . - . - .. - - .
.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).
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;
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.
A
Elector - - I r Eismph - I
--- --
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.
- ---- ~ - ~- -~ -
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;
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 ;
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
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.
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.
p r o c e d u r e TForml.FormDeactivate(Sender: TObject);
begin
LabelForm.Caption : = 'Form2 N o Activo';
LabelForm.Color : = clBtnFace;
end;
Figura 8.1. El ejemplo ActivApp muestra si la aplicacion esta activa y cual de sus
formularios esta activo.
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;
OnActiveFormChange.
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;
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 ;
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.
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);
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 ;
procedure TPrimeAdder.ShowTota1;
begin
ShowMessage ( ' Thread: ' + IntToStr ( FTotal) ) ;
end :
procedure TPrimeAdder-Updateprogress;
begin
Forml.ProgressBar1.Position : = £Position;
end :
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.
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) ;
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.
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.
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.
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) ;
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) ;
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.
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
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;
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.
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 ;
//ReloadButtonZClick
for I := 1 to 2 do
FormList [I] .LoadFile;
type
TImageViewerForm = class (TViewerForm)
procedure ButtonLoadClick (Sender: TOb ject) ; override;
public
procedure LoadFile; override;
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.
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.
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;
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 ;
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
- - - - -- - - -- - -
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)
;
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;
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;
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.
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.
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.
&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.
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;
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:
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
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
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.
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
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;
I
Pfoyr~ks Events 1- - -
I
.. -
1 ~elc 120 A
Name Mdnockl
ParelllCobr True
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 ;
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 ;
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 }
'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
-
Figura 9.7. El resultado del cornponente Arrow con un lapiz grueso y una trarna
especial.
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;
TRUCO: El uso de Self como ~a&nit& dkla inmcacibn dcl mttodo del :
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 :
.
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;
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:
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;
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 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).
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.
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
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;
end;
FCollString : = str;
end;
destructor TCanTest.Destroy;
begin
FColl. Free;
inherited;
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;
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).
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 ;
requires
vcl,
Mdpack ,
designide;
contains
PeSound i n ' PeSound. pas ',
PeFSound i n ' PePSound. pas ' (SoundPorm];
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.
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
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.
.
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.
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.
.
www .d e l p h i - j edi org.
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.
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:
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.
exports
Triple, Double;
exports
Triple (N: Integer),
Triple (C: Char) name ' T r i p l e c h a r ' ;
- - - -- - - - - - -
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.
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 ;
Figura 10.2. El resultado del ejemplo CallFrst, que llama a la DLL que hernos creado
en Delphi.
{SLIBSUFFIX 60 ' 1
Figura 10.3. La pggina Application del cuadro de dialogo Project Options tiene ahora
una seccion llamada Library Name.
cons t
DllName = ' F i r s t d l l . d l l ' ;
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.
N m e . _ .-- . ~ - B a s s ! W - - P h -
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.
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.
p r o c e d u r e SetShareData ( I: Integer) ; s t d c a l l ;
begin
ShareData" : = I ;
end;
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 ' ) ;
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.
Figura 10.4. Si ejecutarnos dos copias del prograrna UseMern, verernos que 10s
datos globales de su DLL no son cornpartidos.
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.
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.
proyectos son todos referenciados por el archivo & grupo de proyecfo (BPG)
de la carpeta.
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.
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;
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.
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.
/ / 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 ' ) ;
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( )
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
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.
-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.
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.^ ,
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.
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Dates, StdCtrls;
type
MMW1N:CLASSINTERFACE TDateForm; ID=37;
var
DateForm: TDateForm;
implementation
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Dates, StdCtrls;
implementation
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.
--
I - I p7t<~_--- I I - - I 1,-
Figura 11.8. La vista Difference de ModelMaker.
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.
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. )
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 ;
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
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;
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
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.
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;
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.
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.
I
Figura 12.2. El asistente COM Object Wizard.
. 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-
.
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
/ / 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 ;
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 - - -- -
-
- *
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
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.
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:
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:
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 . .
names: TF~rstServer
I
Ud 6 name: I~.~rchwo
desprograma\8orIand\D~h17\Impo .. I I
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;
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 ;
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:
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.
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.
I
Figura 12.6. La segunda barra de herramientas del ejemplo OleCont (arriba) es
sustituida por la barra de herramientas del servidor (debajo).
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
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.
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;
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.
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 ;
. . a . .
TRUCO:Del~hioro~orcionacuatro fichas de DroDkdades inteeradas Dara w r
en la unidad A c C t r l s .
. . . . . . y
. . . . #. . . .
. . . . . . . . .'. . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Direction. adR~ghf(3)
11 II
.....................................
......................................
.....................................
......................................
.......................................
......................................
.......................................
Pencolor: New... I
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
h o w point color: 0 NW. I
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.
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;
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:
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+.
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.
-*
-
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.
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;
+* 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.
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.
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;
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
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.
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.
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.
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>
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:
tienen en memoria. Por este motivo, 10s indices 10s consideran como sim-
ples campos.
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 :
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.
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.
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.
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.
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:
Name
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.
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;
-
-
+
-
-
-
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
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-
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;
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.
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
- -- --- - - - - - - -
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);
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.
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:
/ / dibujo predefinido
DBGridl.DefaultDrawDataCel1 (OutRect, Column.Field, S t a t e ) ;
Figura 13.12. El programa DrawData muestra una cuadricula que incluye el texto de
un campo de memo y el omnipresente pez de Borland.
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.
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;
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;
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;
Jamaica
&= 1756943
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 ~. -. - - - - - - -- - - - - .--
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.
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
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.
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
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.
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.
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).
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 .
nombre.
-
mole V w Sewer JP
839 ' * % I - - -- .- -- - -- -
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).
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.
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);
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.
/ / 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;
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).
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
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.
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.
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;
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
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.
Figura 14.7. El ejemplo SchemaTest permite ver las tablas de una base de datos y las
columnas de una tabla dada.
'All shown
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
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.
// 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);
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;
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).
- . -
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
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;
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.
- -
- 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.
- . - - - - .- -- - ~ .
-- - - -- - --
-R d *
c Skip
Cbnd
C Coned
C Rdmh
C Mape
. -- -
Figura 14.12. El dialogo Reconcile Error que ofrece Delphi en el Object Repository y
que usa el ejemplo CdsDelta.
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;
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;
212850 0
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
/ / 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] ) ) ;
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.
- -- - --
.-
BpplyEvenl - -- I
On New Recud I
r OnPost I
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))
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)
) ;
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;
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;
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
-
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;
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
\
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
dd
L ID
-
19 Bc~lmdCorp
23 Wlr$echItaha Srl
21 NAME
Davd l
24 Chuck J
AMOUNT300
3W
-
--
// 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;
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
--
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.
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.
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
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.
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.
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
,
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) ;
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.
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.
N n b e _ - - - --
Data Souce
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.
-
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.
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' ) ;
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.
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;
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.
- +
Figura 15.6. El forrnulario del ejernplo DataClone, con dos copias de un conjunto de
datos (el original y el clon).
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:
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.
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.
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' ;
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"
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;
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.
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:
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.
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.
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.
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;
- - -- -
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.
!nrlanci-g I ~ u l i i l eInstance
AI
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&
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.
...............................,
,
L"*
,~!s.Jus.s-w ,- -. -. .--.
kodified ---
621 99912 Bruce 28/12/1988
- - -
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;
RJresh Lop.
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.
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.
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.
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.
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
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.
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.
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;
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;
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 ;
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 ;
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.
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 ;
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.
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).
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
11 81 1 a236220472
Habrtal 1s around bouldels. caves.
I
coral ledges and crevices m shallow
waters. Swims alone or in groups.
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.
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!
-
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;
-
end;
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.
// 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;
// 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 :
// 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 ;
[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 ) ) ) ;
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 ;
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 :
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;
procedure TMDCustomDataSet.Interna1Last;
begin
EofCrack : = InternalRecordCount;
FCurrentRecord : = EofCrack;
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;
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);
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.
ad 1 A-
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 ;
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 .
end;
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.
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 :
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 ;
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 :
21 3242.43 12
33 6716,54
28 -
3722,38
24 4747.94 23
John 62 597,24 14
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.
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.
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.
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).
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.
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.
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.
- - - -
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.
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.
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
--
I W a Ted --
- - - --
1 FIRST-NAME
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.
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.
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
1v oasv*lrRegm -W1-
Simple T a b d l
Figura 18.6. El informe maestro-detalle. El Band Style Editor aparece por delante.
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;
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.
Corporate Headquarters
BudgetCjalary
1 000.000.00
LOCATION
Monterey
Lee, Tern 53 793.00
Bender, Ollver H. 212.850.00
I Engineering
Nelson, Robelt
1.100.000.(
105.900.0
I
age 1 o f 3
Brown, Kelly
-
-
270M,C
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
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--
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:
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 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 :
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).
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).
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.
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.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
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.
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);
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:
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.
' w
-
['d
I http ,,localhost 8080/1e.
I HttpSen-Demo
Recargar
-
Inca
Request /test
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.
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.
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;
Figura 19.8. El resultado del ejemplo HtmlProd para el boton Print Line.
DataSetTableProducer Demo
II American Clountries
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.
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.
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
0-
--, -t4)
Rhm Ad
-
R y p a C r 7
@ IU
lmo
Mtp {lladhost/cg~-bnlcgsddeexe
--
-
---
yi
IS
Figura 20.1. La salida de la aplicacion CgiDate, vista en un navegador.
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
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.
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 + ...
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 >
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;
State
1
Canada
England
FijI
France
Hang Kong
Italy
Japan
Netherlands
Switzerland
USA
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 ( ) en cada celda vacia. Es necesario hacer esto
en cada tabla HTML generada por 10s productores de tablas de Delphi.
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.
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.
- - -- - -- - -- - - - --- - - - -- -
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.
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:
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:
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.
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;
implementation
{SR . d f m ) {.htzd)
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.
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:
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.
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;
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.
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:
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 ) .
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.
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
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
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 .
end
end
end
-iJp1
- -- - ---- - -- .-- - --
+
- - - - -- - -. --
AdaplmPagcRoduar AdaptetForrnl
AdaplerFolrnl
E L
AdaplerCornmar
AdapIetFreldGro
I 1
@fowsw HTML Scrlpl I
A
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.
1&3'sf0 -d'.
q
.
Figura 20.12. El ejemplo WSnapMD muestra una estructura maestroldetalle y tiene
una representacion personalizada.
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:
P I a h TRy
Scrr:an td CY[ZuGcM3zRx85F
Scmon h r 6
Script
Figura 20.13. Dos instancias del navegador funcionan con dos sesiones distintas de
la misrna aplicacion de WebSnap.
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
-
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.
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
siones para C++ Builder y Java. Se esta trabajando en una version .NET y
probablemente estara disponible junto con una futura version de Delphi
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
3wmi- 1 -Ia
lnlraweb Version. 5 0 43
HTTP Pmt.8080
A
Packaged Enterprise
L~censeNumber 0
.-. ...............
.-.
-- -
-
- II
,,,
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;
--
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);
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;
Inc (UserSession.UserCount);
Global 24
Form 14
User. 14
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.)
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.
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:
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.8. El formulario principal del ejemplo IWGridDemo utiliza una cuadricula
con marco con hipervinculos hacia el formulario secundario.
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
ma- - u
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
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.
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.
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).
- . - -- .- - -
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.
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
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:
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.
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);
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
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;
// 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) ;
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.
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.
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;
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;
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
- - -.- - - -- -
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.
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 + ') ') ;
pam~w ( I- s ~ a l ~ ~ o c l m---
bwkslbooksl
ent
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.
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 ;
hltg//ww.ma~cocantu.~m Cantu
II
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.
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.
- - -- -,
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
nentName> forma parte del codigo HTML generado utilizado para de-
Name=WebCom~o-
-- - - - ~ -- - - ~ -
.-
-
te de datos.
at?,*
L
-
-
- -
InelXPagePraducerl ErnpFlo
DataForrnl
A
LaslNarne
DalaNavqalor F1r~tNarne
E:
Salary
SlalusColurnnl
- - - -
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
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.
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:
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.
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>
. - --. - - - .
GXLGLISIULI a 1 c;uu~gu
A ~ uLn g u GLI ~aLUUL Durlanu LUIUGIGIL~;~: y S U ~ ~ S
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.
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).
-
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 :
--
G Ion 937 .
6716 k !- 5 -
161 7 Janet Bddwn 2
' E _ s-' -,7
-
4747 LesLe Johnson __ 1410 -
h",bM - - '2-L.
1
+I
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.
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
type
BabelFishPortType = interface(IInvokab1e)
[ ' (D2DB6712-EBEO-lDA6-8DEC-8A445595AEOC)' 1
function BabelFish(const translationmode: WideString;
const sourcedata: WideString): WideString; stdcall;
end;
implementation
// omitido
initialization
InvRegistry.RegisterInterface(TypeInfo(BabelFishPortType),
' urn:xmethodsBableFish ', ' ') ;
1nvRegistry.RegisterDefaultSOAPAction
(TypeInfo (BabelFishPortType),
' urn:xmethodsBableFish#Babe1Fish ') ;
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.
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 .
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;
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
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.
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 :
-[ "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;
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;
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---
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
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.
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.
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;
initialization
InvRegistry.RegisterInvokab1eC1ass(
TSampleDataModule, TSampleDataModuleCreateInstance);
InvRegistry.RegisterInterface(TypeInfo(ISampleDataModu1e));
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.
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;
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 ;
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.
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).
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>
businessListData : =
inftInquire.find-business(findBusindBusinessData);
BusinessListToListView (businessListData);
findBusinessData.Free;
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):
E !bwks\rnd7code\08\VI1\VI1dpr
E-\books\md7code\OB\Pol1Fo1m\PoliForrn dp
E:\books\rnd7code\OBF1arnes2\F1arnes2 dp
Do RepbDsI R-
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
-.
S k 0
SM n
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
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.
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