Está en la página 1de 480

lndice

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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

Siete versiones y contando


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

La estructura del libro


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

Normas usadas en este libro


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

ADVERTENCIA:Indica un procedimiento que, en teoriq podria causar


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

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


bles y consejos 6tiles.
Bases
Delphi 7

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


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

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

Una vision global del IDE


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

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

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

Un IDE para dos bibliotecas


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

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


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

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


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

Configuracion del escritorio


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

[Alignmentpalette]
Create=l
Visible=O

Las configuraciones de escritorio tienen mas fierza que las configuraciones de


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

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

Sobre 10s menus


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

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


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

El cuadro de dialogo Environment Options


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

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

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


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

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


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

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

IWWY
-A
7 Check comp~lerfelhngs 1 Marco

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

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


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

Mensajes ampliados del compilador


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

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

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

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

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

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


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

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


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

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


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

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

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


Environment Options.

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


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

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


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

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

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


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

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

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


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

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

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


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

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


proporcionara una implementacion predefinida para ese metodo.

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

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


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

Hay algunas caracteristicas avanzadas de la funcion Code Completion que no


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

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

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

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

Mas teclas de metodo abreviado del editor


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

ti-
h e

Q+fcrolim

J
Ths IS a simple version

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


perrnite establecer esas relaciones).

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


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

r-
M base

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

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


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

complejo con un menu desplegable y sus elementos.

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

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


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

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

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


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

Fuentes desplegables en el Object Inspector


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

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

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


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

su texto resulte mhs visible. Esta caracteristica resulta especialmente util


en el caso de presentaciones publicas.

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

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

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


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

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

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


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

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

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


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

Secretos de la Component Palette


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

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


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

Una importante caracteristica no documentada de la Component Palette es la


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

Copiar y pegar componentes


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

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


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

De las plantillas de componentes a 10s marcos


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

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


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

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


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

T
Framel

I1
IS Smt Smi
(@dad

Framel
All

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

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

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

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

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

Figura 1.9. El Project Manager de Delphi.

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


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

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

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

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

Compilar y crear proyectos


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

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


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

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

?. Unit idenlifier does m t match hle name


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

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

1 M

LI'.Unsafe typecast

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

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


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

Exploracion de las clases de un proyecto


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

Figura 1.11. Project Browser

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


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

Los archivos creados por el sistema


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

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


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

BPG Borland Project Group Desarrollo Necesario para compi-


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

BPL Borland Package Library Compilacion: Se distribuiran a otros


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

CAB Formato de archivo com- Compilacion Distribuido a usuarios


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

.CFG Archivo de configuracion Desarrollo Necesario


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

.DCP Delphi Component Packa- Compilacion Necesario cuando


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

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


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

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


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

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

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


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

DLL Dinamic Link Library (Bi- Cornpilacion Vease .EXE


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

DOF Delphi Option File: archivo Desarrollo Necesario solo si se


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

DPK y Delphi Package: el archivo Desarrollo Si.


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

DPR Archivo Delphi Project. Desarrollo


(Este archivo contiene en
realidad codigo fuente
Pascal.)

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


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

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


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

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

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


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

LIC Los archivos de licencia Asistente No. Es necesario usar


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

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

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


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

.PAS Pascal file (Archivo de Desarrollo


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

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

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


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

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


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

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


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

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

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


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

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

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


seleccionadas.

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


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

Un vistazo a 10s archivos de codigo fuente


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

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


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

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

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

[Parameters]
Runparams=
HostApplication=

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


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

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

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


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

Console
Cwone* Applcat~on

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


Appbcalmn Module

TI
Flame Package Ploiect GI- Resource DLL
Wnald
Service

1 - C

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

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


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

I La plmWla de pmyecb en blanco I


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

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


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

lnstalar nuevos asistentes DLL


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

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


introducir la ruta.

Actualizaciones del depurador en Delphi 7


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

--

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

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


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

Caracteristicas centrales del lenguaje


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

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


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

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

La llamada a create invoca un constructor predefinido disponible para cada


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

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


resulte intis facil de entender.

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


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

function TDate.LeapYear: Boolean;


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

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


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

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


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

Mas sobre metodos


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

Creacion de componentes de forma dinamica


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

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

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

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

ADVERTENCIA: Los especificadores de acceso solo restringen el acceso


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

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


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

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


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

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


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

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

Encapsulado con propiedades


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

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

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


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

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

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


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

Propiedades de la clase TDate


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

Cada uno de estos metodos se implementa facilmente utilizando funciones


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

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

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


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

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


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

Caracteristicas avanzadas de las propiedades


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

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

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

Afiadir propiedades a formularios


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

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

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

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


e B p e cs t o r .

Conviene usar las propiedades tambien en las clases de formulario para


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

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

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


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

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

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


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

Las normas de escritura de constructores para componentes personalizados


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

Destructores y el metodo Free


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

El modelo de referencia a objetos de Delphi


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

TheDay objeto TDay

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


variable que se refiere a el.

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


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

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

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

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


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

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


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

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


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

end;

/ / llamar.. .
CaptionPlus (Buttonl)

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

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

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


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

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


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

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

Herencia de 10s tipos existentes


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

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

TRUCO: Cuando usamos la information regional, el programa NewDate


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

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

jueves, 25 de dlclembre de 2003

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

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

Acceder a datos protegidos de ottas clases


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

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


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

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


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

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


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

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

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


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

Herencia y compatibilidad de tipos


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

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

Enlace posterior y polimorfismo


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

TDog = class (TAnimal)


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

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


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

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

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


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

-
Figura 2.7. El resultado del ejemplo PolyAnimals.

Sobrescribir y redefinir metodos


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

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

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


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

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


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

type
TMyClass = class
procedure One;
end;

TMyDerivedClass = class (TMyClass)


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

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


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

Metodos virtuales frente a metodos dinamicos


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

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

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


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

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


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

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


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

Esta tecnica puede resultar extremadamente util para un programador vetera-


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

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

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


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

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

Conversion descendiente con seguridad


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

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


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

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


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

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


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

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


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

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


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

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

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


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

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


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

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


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

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


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

GUIH no resultan muy utiles.


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

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


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

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

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

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


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

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


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

Trabajar con excepciones


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

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


control de flujo en un progami. Es rcmmendabfe mmtener el uso de sen-
rencias .
*._ _ - _ _ n _I
para co~nproopr
C d 3 . I L- ,I 1- >.:_
mcqmaa aei usuano y ouas posmres concucio-
nes de error. S61o d&erf& asarse ex.cepcianes para eituaciones a n o d e s
o inesperadas.

Flujo de programa y el bloque finally


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

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


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

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

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

TRUCO:Controlar la excepcion es generalmente mucho menos importan-


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

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

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

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


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

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


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

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


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

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

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

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

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


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

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


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

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

1
- - .-

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

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

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

........ .

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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

Crear componentes usando referencias de clase


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

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

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


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

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


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

Informacion de clase en tiempo de ejecucion.

Las unidades de la RTL


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

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

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


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

I
El tamafio emutable bajo el microscopio

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


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

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


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

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


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

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


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

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


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

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


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

La unidad System se compone entre otras cosas de:


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

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


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

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

Las unidades SysUtils y SysConst


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

ShowMessage ( 'Ejecutando en Windows: ' + IntToStr


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

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


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

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


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

Nuevas funciones de SysUtils


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

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


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

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


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

SysUtils incluye tambien una funcion IsEqualGUID y dos funciones de


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

Rutinas extendidas de formato de cadenas en Delphi 7


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

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


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

Nuevas funciones matematicas


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

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


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

RandomRange y RandomFrom se pueden usar en lugar dc la traditional


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

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

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

TambiCn ha habido algunos cambios en las operaciones de redondeo estandar


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

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

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


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

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


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

su codigo, apreciara las caracteristicas disponibles en este motor.

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

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


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

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


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

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


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

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


genealogylcoding .html .

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


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

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


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

procedure TForml.ButtonIndexClick(Sender: TObject);


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

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


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

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

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


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

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


S t r Demo comentado anteriormente.

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

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


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

La unidad Variants y VarUtils


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

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


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

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


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

Variantes personalizadas y numeros complejos


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

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

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


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

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


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

ADVERTENCIA: Construir una variante personalizada no es una tarea


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

Las unidades DelphiMM y ShareMem


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

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


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

Unidades relacionadas con COM


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

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

Una version sobrecargada de la funcion convert permite convertir valores


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

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


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

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

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


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

procedure TForml.ChangeFamily(Sender: TObject);


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

Las variables aFamilies y CurrFamily se declaran en la parte privada


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

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


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

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


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

procedure TForml.EditTypeDragDrop(Sender, Source: TObject;


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

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

Fathom
Furbngs Qerlinalica Type Cmvwted Am&
Hands
Paces

Cham

I 1

Figura 3.3. El ejemplo ConvDemo en tiempo de ejecucion.

Las unidades habran de corresponderse con aquellas disponibles en la familia


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

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

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


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

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

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

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


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

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


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

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

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

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

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

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


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

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

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

Ilabn L#e[ITLJ '


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

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

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


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

function EuroConvert (const AValue: Double;


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

function EuroRound (const AValue: Double): Double;


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

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

Gestion de archivos con SysUtils


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

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

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

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

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


1bFiles:

procedure TForml.AddFilesToList(Filter, Folder: string;


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

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


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

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


, por
la aplicacion FilesList.

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

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

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

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

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

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

if Sender.ClassParent <> nil then


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

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


(Sender-Instancesize));
end;

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


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

Class Name TButton


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

Class Name. TBitBtn


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

Class Nam: TEB


Paent Class: TCustomEdit
lnstace Size: 544

Figura 3.7. El resultado del ejemplo Ifsender

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

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

Mostrar informacion de clase


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

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

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


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

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

i
7

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


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

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


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

El paquete RTL, VCL y CLX


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

ventana
(subclases de
Controles TWinControl)
(oomponentes visuales)

Controles no
de ventana

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

(otras subclases de
TComponent)

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


de la VCL.

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


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

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


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

Las clases componentes pueden dividirse ademas en dos grupos principales:


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

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

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


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

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

Partes especificas de VCL de la biblioteca


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

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

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

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

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

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


tiene importancia en Kylix.

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


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

Streaming de objetos frente a generaci6n de c6digos


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

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


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

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

Acceso a campos y mbtodos publicados


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

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


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

Acceso a las propiedades por su nombre


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

ADVERTENCIA: Estas subrutinas siempre han estado no documentadas


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

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


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

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


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

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

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

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

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

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


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

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


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

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


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

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


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

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

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

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


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

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


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

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

Para demostrar que el propietario del componente Buttonl cambia realmen-


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

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


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

Eliminacion de campos del formulario


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

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

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


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

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


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

Ocultar campos del formulario


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

procedure TForml.EditlChange(Sender: TObject);


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

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


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

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


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

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


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

La propiedad personalizada Tag


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

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

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

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


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

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

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


un registro con un campo Code y un campo Data.

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


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

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


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

MyButton = class
OnClick: TNotifyEvent;
end;

TForml = class (TForm)


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

var
Form1 : TForml ;

Ahora dentro de un procedimiento, se puede escribir:

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


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

Los eventos son propiedades


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

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


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

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


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

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


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

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


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

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


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

El mismo codigo simplificado esta presente en muchos otros controladores de


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

Listas y clases contenedores


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

Listas y listas de cadena


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

Pares nombre-valor (y extensiones de Delphi 7)


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

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


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

Un boton del formulario aiiade una fecha aleatoria a lista:


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

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


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

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

n ) hcraposible la exidencia de nada sin6 o m 1


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

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

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

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

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


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

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


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

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


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

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


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

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

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


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

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


Itual ~ u c k e For

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

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


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

function TCustomBucketList.ForEach(AProc: TBucketProc;


AInfo: Pointer) : Boolean;

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


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

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


cation de una cadena.

Contenedores y listas con seguridad de tipos


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

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

evitar una conversion de tipos adicional.

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


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

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

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

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


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

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


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

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

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

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

TStream = class (TObject)


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

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

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

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

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

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


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

Una tecnica totalmente alternativa consiste en permitir que componentes espe-


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

Clases especificas de streams


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

Uso de streams de archivo


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

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


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

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


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

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

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

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


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

Las clases TReader y TWriter


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

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

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

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


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

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

procedure TformText.ConvertAndShow (aStream: TStream);


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

PadO W Fmm in Ex

TOP- 113
W d h - 545

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

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

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

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

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

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

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


mismo por el ejernplo ForrnToText.

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

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


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

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


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

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

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


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

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


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

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


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

Compresion de streams con ZLib


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

Orginal file ID\md7code\04VCnmpressVCompressForm pas

Compressed lik D.\rnd7code\04VComp1essVComp1essForm.zlib

Decompress
1

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


biblioteca ZLib.

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

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

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


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

Resumen sobre las unidades principales


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

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

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


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

nes anteriores de Delphi, la clase TDa taModule se


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

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


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

Novedades en la unidad Classes


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

Otras unidades principales


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

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


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

VCL frente a VisualCLX


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

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


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

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


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

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


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

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


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

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


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

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


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

Soporte dual de bibliotecas en Delphi


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

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

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

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

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


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

Clases iguales, unidades diferentes


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

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


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

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


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

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


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

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

object Forml : TForml object Forml: TForml


Left = 192 Left = 192
Top = 107 Top = 107
Width = 350 Width = 350
Height = 210 Height = 210
Caption = ' Q L i b C o r n p l Caption = ' L i b C o n p '
Color = clBackground Color = clBtnFace
VertScrollBar .Range = 161 Font.Charset = DEFAULT-CHARSET
HorzScrollBar.Range = 297 Font-Color = clWindowText
Font.Height = -11
Font.Name = 'MS S a n s S e r i f '
Font.Style = [ I
TextHeight = 13 TextHeight = 13
Textwidth = 6 Oldcreateorder = False
PixelsPerInch = 96 PixelsPerInch = 96
object Buttonl: TButton object Buttonl: TButton
Left = 56 Left = 56
Top = 64 Top = 64
Width = 75 Width = 75
Height = 25 Height = 25
Caption = ' A d d ' Caption = ' A d d '
TabOrder = 0 TabOrder = 0
OnClick = ButtonlClick OnClick = ButtonlClick
end end
object Editl: TEdit object Editl: TEdit
Left = 40 Left = 40
Top = 32 Top = 32
Width = 105 Width = 105
Height = 21 Height = 21
TabOrder = 1 TabOrder = 1
Text = ' m y name' Text = ' m y name'
end end
object ListBoxl: TListBox object ListBoxl: TListBox
Left = 176 Left = 176
Top = 32 Top = 32
Width = 121 Width = 121
Height = 129 Height = 129
Rows = 3 ItemHeight = 13
1tems.Strings = ( 1tems.Strings = (
'marc0 ' ' m r c o'
'john' 'john'
'helen' ) 'helen' )
TabOrder = 2 TabOrder = 2
end end
end end

Sentencias uses
Las unicas diferencias entre ambos ejemplos estan relacionadas con las senten-
cias u s e s . El formulario de la aplicacion CLX tiene el siguiente codigo inicial:
u n i t QLibCompForm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms,
QDialogs, QStdCtrls;

El formulario del programa VCL posee la tradicional sentencia u s e s :


u n i t LibCompForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;

El codigo de la clase y del unico controlador de eventos es absolutamente


identico. Por supuesto, la clasica directiva del compilador { S R * . dfm) se
sustituye por { $ R * .xfm) en la version CLX del programa.
lnhabilitar el soporte de ayuda a la biblioteca dual
Cuando se pulsa la tecla F1 en el editor para solicitar ayuda sobre una rutina,
clase o metodo de la biblioteca de Delphi, normalmente se podra escoger entre las
declaraciones VCL y CLX de cada tema. Sera necesario escoger antes de acceder
a la pagina indicada, lo que puede resultar bastante pesado despues de un tiempo
(ademas, muchas veces las paginas resulta identicas). Si no importa la CLX y
solo se tiene pensado usar la VCL (o a1 contrario), se puede inhabilitar esta alter-
nativa mediante la orden Help>Customize, con lo que se elimina todo lo que
tenga CLX en su nombre de entre 10s contenidos, el indice y 10s enlaces, y despues
guardando el proyecto. Despues se reinicia el IDE de Delphi y el motor de ayuda
no volvera a preguntar sobre CLX nunca mas. Por supuesto, no hay que olvidarse
de aiiadir esos archivos de ayuda de nuevo si se decide comenzar a utilizar CLX.
Del mismo modo, se puede reducir la ocupacion de memoria y el tiempo de carga
del IDE de Delphi a1 desinstalar 10s paquetes relacionados con CLX.

Eleccion de una biblioteca visual


A1 tener dos bibliotecas de interfaz de usuario diferentes que podemos usar en
Delphi, tendremos que seleccionar una para cada aplicacion visual.
Para efectuar la seleccion, debemos evaluar ciertos criterios. El primer criterio
es la capacidad de transporte. Si nos preocupa el hecho de ejecutar un programa
en Windows y en Linux con la misma interfaz de usuario, usando CLX probable-
mente podremos conseguir que las cosas resulten mas sencillas y permitira mante-
ner un archivo de codigo fuente unica con muy pocas I FDEF.Se puede aplicar lo
mismo, en el caso de considerar que Linux es (o posiblemente se transformara) en
nuestra plataforma clave. En cambio, si la mayoria de 10s usuarios que utilizan
nuestro programa son usuarios de Windows y simplemente queremos ampliar la
oferta con una version para Linux, se puede mantener el sistema dual VCLJCLX.
Esto implica probablemente dos conjuntos diferentes de archivos de codigo fuente
o demasiadas IFDEF. Otro criterio es el de la apariencia originaria. A1 utilizar
CLX en Windows, algunos de 10s controles se comportaran de un mod0 ligera-
mente diferente a1 que 10s usuarios esperarian (a1 menos 10s usuarios expertos).
En el caso de una interfaz de usuario simple (ediciones, botones, cuadriculas), eso
no importara demasiado, per0 si hay muchos controles de vista en arb01 y vista en
lista, las diferencias se haran patentes. Por otra parte, con CLX se puede dejar
que 10s usuarios seleccionen una apariencia a su gusto, diferente de la apariencia
basica de Windows y que la utilicen de manera consistente en las plataformas.
Esto significa que un aficionado a Motif sera capaz de escoger este estilo cuando
se le obligue a usar la platafonna Windows. Mientras que esta flexibilidad resulta
habitual en Linux, es extrafio usar una apariencia no nativa en Windows.
Usar controles originarios implica ademas que desde el momento en que se
consigue una nueva version del sistema operativo Windows, la aplicacion se adap-
tara (probablemente) a ella.Esto resulta muy ventajoso para el usuario, per0 po-
dria originar un monton de quebraderos de cabeza en caso de incompatibilidades.
Las diferencias en la biblioteca de controles comunes de Microsoft durante 10s
ultimos aiios ha sido una gran fuente de frustracion para 10s programadores de
Windows en general, incluidos 10s de Delphi.
Otro criterio es el despliegue: si se utiliza CLX, habra que incluir en el progra-
ma de Windows y Linux las bibliotecas Qt. Segun diversas pruebas, la velocidad
de las aplicaciones VCL y CLX es similar. Para comprobarlo, se pueden usar las
aplicaciones de ejemplo L i b s p e e d y Q L i b S p e e d , en las que se crean 1000
componentes y se muestran en pantalla. Otro criterio importante para decidir usar
CLX en lugar de VCL es la necesidad del soporte de Unicode. CLX tiene soporte
Unicode en sus controles de manera predefinida (incluso en plataformas Win9x
en que no lo soporta Microsoft). Sin embargo, la VCL tiene muy poco soporte de
Unicode incluso en las versiones de Windows que lo ofrecen, lo que hace dificil
construir aplicaciones VCL para paises en que el conjunto local de caracteres se
gestiona mejor cuando se basa en Unicode.

Ejecucion en Linux
La cuestion real sobre la eleccion de bibliotecas se reduce a la importancia que
tenga Linux o Unicode para nosotros y para 10s usuarios. Es muy importante
destacar que si se crea una aplicacion CLX, se podra compilar de nuevo inalterada
(con el codigo fuente esacto) en Kylis que crea una aplicacion originaria de
Linux, a no ser que se haya hecho algo de programacion con la API de Windows,
en cuyo caso la compilacion condicional resulta esencial.
Como ejemplo, se ha vuelto a compilar el ejemplo QLibComp y se puede ver
en ejecucion en la figura 5 . 2 , donde tambien aparece el IDE de Kylix en accion en
un sistema KDE.

Figura 5.2. Una aplicacion escrita con CLX puede volver a compilarse directamente
bajo Linux con Kylix.

Compilacion condicional de las bibliotecas


Si se quiere mantener un unico archivo de codigo fuente, pero compilar con
VCL en Windows y con CXL en Linux, se pueden utilizar 10s simbolos especifi-
cos de plataforma (como $ 1 FDE F LINUX) para distinguir las dos situaciones en
caso de compilacion condicional. Pero si queremos poder compilar una parte del
codigo en ambas bibliotecas en Windows, se puede definir un simbolo propio y
bien utilizar la compilacion condicional o (a veces) verificar la presencia de
identificadores que existan solo en VCL o solo en CLX, como a continuacion:
( $ I F Declared (QForms))
. . .CLX-specific c o d e
($IFEND)
Conversion de aplicaciones existentes
Ademas de comenzar nuevas aplicaciones CLX, tal vez queramos convertir
algunas de las aplicaciones VCL ya esistentes a la nueva biblioteca de clases.
Existen una serie de operaciones que habra que realizar, para las que el IDE de
Delphi no proporciona ayuda alguna:
Habra que cambiar el nombre del archivo DFM por XFM y actualizar
todas ]as sentencias { $ R * . D FM } como sentencias { $ R * .X FM } .
Habri que actualizar todas las sentencias u s e s del programa (en las uni-
dades y en 10s archivos dc proyecto) para referirse a las unidades CLX, en
lugar de a las unidades VCL. Fijese en que si faltan algunas, habra proble-
mas a1 e-iecutar la aplicacion.
- -
- - -- -

TRUCO:Para evitar que una aplicacibn CLX compile si contiene referen-


cias a unidades VCL, se pueden desplazar las unidades VCL a un directorio
rl;4Lran+a hain
u u w l w u r u
.r n&+sr ;nel..;r m n t n f i a m a t r
1 l4 u
vw,v r hwvl- -wIUIL
)I M L ~ en la rub de busqueda. De
U ~

este modo. ias referkcias a unidades VCIL W e s que queden causariin an


error "Unitnot found"' (Unidad no encontrada) .

La tabla 5.1 es una comparacion de 10s nombres de las unidades visuales VCL
y CLX, esceptuando la parte de la base de datos y algunas unidades a las que
raramente se hace referencia:

Tabla 5.1. Nombres de unidades equivalentes VCL y CLX.

ActnList QActn List


Buttons QButtons
Clipbrd QClipbrd
ComCtrls QComCtrls
Consts QConsts
Controls QControls
Dialogs QDialogs
ExtCtrls QExtCtrls
Forms QForms
Graphics QGraphics
Grids QGrids
ImgList QlmgList
Menus QMenus
Printers QPrinters
Search QSearch
StdCtrls QStdCtrls

Tambien podriamos convertir referencias a Windows y Messages en referen-


cias a la unidad Qt. Algunas estructuras de datos de Windows estan ahora dispo-
nibles en la unidad Types, por lo que tal vez queramos aiiadirla a nuestros
programas CLX. Sin embargo, hay quc tener en cuenta que la unidad QTypes no
esta en la version CLX de la unidad Types de VCL; estas dos unidades no estan
relacionadas en absoluto.

ADVERTENCIA: iPreste atenci6n a las sentencias uses! Si por casuali-


dad compilamos un proyecto que incluya un formulario CLX, per0 no ac-
tualizamos el codigo fuente del proyecto y dejarnos en el la referencia a la
unidad Forms de la VCL, el programa se ejecutara pero se detendra inme-
diatamente. La raz6n es que no se creo ningrin formulario VCL, por lo que
el programa finaliza directamente. En otros casos, intentar crear un formu-
lario CLX dentro de una aplicacion VCL originaria errores en tiempo de
ejecucion. Por ultimo, el IDE de Delphi podria aiiadir incorrectamente refe-
rencias a las sentencias uses de la biblioteca erronea Y asi acabariarnos
teniendo una unica sentencia uses, que se referiria a la misma unidad,
para ambas bibliotecas, pero solo la segunda de ellas seria efectiva. Esto en

Las clases TControl y derivadas


Una de las subclases mas importantes de TComponent es TControl, que
corresponde a 10s componentes visuales. Esta clase basica esta disponible tanto
en la CLX como en la VCL y define conceptos generales, como la posicion y
tamaiio del control, el control padre que lo contiene y muchos mas.
Sin embargo, para la implementacion real, tenemos que referirnos a sus dos
subclases. En VCL, estas son Twincontrol y TGraphicControl,en CLX
son TWidgetControl y TGraphicControl.Veamos sus caracteristicas
clave:
Los controles basados en ventanas: Son componentes visuales basados
en una ventana del sistema operativo. Un TWinCont r o 1 en una VCL
tiene un manejador de ventana, un numero que se refiere a una estructura
interna de Windows. Un T W i d g e t C o n t r o 1 en CLX tiene un manejador
Qt, una referencia a1 objeto interno Qt. Desde el punto de vista del usuario,
10s controles basados en una ventana pueden recibir el foco de entrada y
algunos pueden contener otros controles. Este es el mayor grupo de com-
ponentes de la biblioteca de Delphi. Podemos dividir 10s controles basados
en una ventana en dos grupos mas: envoltorios de controles originarios de
Windows o Qt y controles personalizados, que normalmente heredan de
TCustomControl.
Los controles graficos: Son componentes visuales que no se basan en una
ventana del sistema operativo. Por lo tanto, no tienen manejador, no pue-
den recibir el foco y no pueden contener otros controles. Estos controles
heredan de T G r a p h i c C o n t r o 1y 10s pinta su formulario padre, que les
envia eventos relacionados con el raton y de otros tipos. Como ejemplos de
controles no basados en ventanas estan L a b e l y S p e e d B u t t o n . Existe
una serie de controles en este grupo, que resultaban decisivos para minimi-
zar el uso de 10s recursos del sistema en las primeras versiones de Delphi
(en Windows de 16 bits). Usar controles graficos para ahorrar recursos de
Windows sigue siendo util en Win9x/Me, que ha elevado aun mas 10s limi-
tes del sistema per0 no ha conseguido librarse aun de ellos (no como
Windows NTl2000).

Parent y Controls
La propiedad P a r e n t de un control indica que otro control es responsable de
mostrarlo. Cuando dejamos un componente en un formulario en el Form Designer,
el formulario se transformara tanto en padre como en propietario del nuevo con-
trol.
Pero si dejamos el componente en un Panel, ScrollBox u otro componente
contenedor, este se convertira en su padre, mientras que el formulario seguira
siendo el propietario del control.
Cuando creamos el control en tiempo de ejecucion, sera necesario establecer el
propietario (usando el parametro del constructor C r e a t e ) , per0 habra que esta-
blecer tambien la propiedad P a r e n t o el control no sera visible.
A1 igual que la propiedad Owner, la propiedad P a r e n t posee su inverso. La
matriz C o n t r o l s , de hecho, lista todos 10s controles hijos del actual, enumera-
dos de 0 a C o n t r o l s c o u n t - 1.
Se puede analizar esta propiedad para trabajar con todos 10s controles que
aloje otro control, utilizando en ultimo termino un metodo recursivo que opere
sobre 10s controles hijos de cada subcontrol.
Propiedades relacionadas con el tamaho
y la posicion del control
Algunas de las propiedades introducidas por T C o n t r o l y comunes a todos
10s controles son aquellas relacionadas con el tamaiio y la posicion. La posicion
de un control la fijan sus propiedades L e f t y Top y su tamaiio las propiedades
H e i g h t y W i d t h . Tecnicamente, todos 10s componentes tienen una posicion,
porque cuando abrimos de nuevo un formulario existente en tiempo de diseiio,
queremos que se puedan ver 10s iconos de 10s componentes no visuales en la
posicion exacta en la que 10s situamos. Esta posicion es visible en el archivo de
formulario.

I Una caracteristica importante de la posicion de un componente es que, como


cualquier otra coordenada, siempre se relaciona con la zona de cliente de su
componente padre (indicada por su propiedad P a r e n t ) . En el caso de un formu-
lario, la zona del cliente es la superficie incluida dentro de sus bordes y la etiqueta
(exceptuando 10s propios bordes). Hubiera sido un poco confuso trabajar con las
coordenadas de la pantalla, aunque existen algunos metodos preparados para su
uso que convierten las coordenadas entre el formulario y la pantalla, y viceversa.
Sin embargo, fijese en que las coordenadas de un control siempre son relativas
a1 control padre, como un formulario u otro componente contenedor. Si se coloca
un panel en un formulario y un boton en un panel, las coordenadas del boton son
relativas a1 panel y no a1 formulario que contiene el panel. En este caso, el compo-
nente padre del boton es el panel.

Propiedades de activacion y visibilidad


Se pueden usar dos propiedades basicas para dejar que el usuario active u
oculte el componente. La mas sencilla es la propiedad E n a b l e d . Cuando se
desactiva un componente (cuando E n a b l e d se define como F a l s e ) , normal-
mente hay alguna pista visual que se lo indica a1 usuario. En tiempo de diseiio, la
propiedad desactivada no siempre provoca un efecto, per0 en tiempo de ejecu-
cion, 10s componentes estan, por lo general, en gris.
Para ver una tecnica mas drastica, se puede ocultar completamente un compo-
nente, ya sea utilizando el correspondiente metodo H i d e o definiendo su propie-
dad V i s i b l e como F a l s e . Sin embargo, hay que tener en cuenta que leer el
estado de la propiedad V i s i b l e no indica si el control es realmente visible. En
realidad, si el contenedor de un control esta oculto, incluso aunque el control este
configurado como V i s ib l e , no se puede ver. Por esta razon, existe otra propie-
dad, Showing, que es una propiedad solo de lectura en tiempo para determinar
si el control es realmente visible para el usuario; es decir, si es visible, su control
padre tambien lo es, el control padre del control padre tambien lo es, y asi sucesi-
vamente.

Fuentes
Normalmente se usan dos propiedades para personalizar la interfaz de usuario
de un componente, Co l o r y F o n t . Hay diversas propiedades relacionadas con
el color. La propiedad c o l o r se refiere normalmente a1 color de fondo del com-
ponente. Ademas, existe una propiedad C o l o r para las fuentes y muchos otros
elementos graficos. Muchos componentes tienen tambien las propiedades
P a r e n t c o l o r y P a r e n t F o n t , que indican si el control deberia utilizar la
misma fuente y color que su componente padre, que suele ser el formulario. Se
pueden usar estas propiedades para cambiar la fuente de cada control en un for-
mulario configurando sencillamente la propiedad F o n t de este ultimo.
Cuando se configura una fuente, introduciendo valores para 10s atributos de la
propiedad en el o b j e c t I n s p e c t o r o utilizando el cuadro de dialog0 estandar
de seleccion de fuente, se puede escoger una de las fuentes instaladas en el siste-
ma. El hecho de que Delphi permita usar todas las fuentes instaladas en el sistema
tiene tanto ventajas como inconvenientes. La principal ventaja es que si se tiene
instalado un cierto numero de agradables fuentes, el programa podra utilizarlas.
El inconveniente es que si se distribuye la aplicacion, estas fuentes puede que no
se encuentren disponibles en 10s ordenadores de 10s usuarios.
Si el programa utiliza una fuente que el usuario no tiene, Windows elegira
alguna otra fuente para reemplazarla. Un resultado cuidadosamente formateado
del programa puede verse arruinado por la sustitucion de fuentes. Por esta razon,
probablemente deberiamos confiar solo en las fuentes estandar de Windows (corno
MS Sans Serif, System, Arial, Times New Roman, etc.).

Colores
Existen diversas formas de fijar el valor de un color. El tip0 de esta propiedad
es T C o l o r , que no es un tipo de clase sino simplemente un tipo entero. Para
propiedades de este tipo, se puede escoger un valor de una serie de constantes de
nombre predefinidas o introducir directamente un valor. Las constantes para 10s
colores son entre otras c l B l u e , c l s i l v e r , c l W h i t e , c l G r e e n , c l R e d y
muchas mas (incluidas las aiiadidas con Delphi 6: clMone yGreen, c l SkyBlue,
c l C r e a m y clMedGray). Como una alternativa mejor, se puede utilizar uno de
10s colores usados por el sistema para indicar el estado de algunos elementos.
Este conjunto de colores es diferente en VCL y CLX.
VCL incluye colores predefinidos de Windows como el fondo de una ventana
( c l w i n d o w ) , el color del texto de un menu resaltado ( c l H i g h t l i g h t T e x t ) ,
el titulo activo ( c l A c t i v e c a p t i o n ) y el color de la cara ubicua del boton
( c l B t n F a c e ) . CLX contiene un conjunto diferente e incompatible de colores
del sistema, como c l B a c k g r o u n d , que es el color estandar de un formulario,
c l B a s e , utilizado por 10s cuadros de edicion y otros controles visuales,
c l A c t i v e F o r e g r o u n d , el color de primer plano para 10s controles activos y
c l D i s a b l e d B a s e , el control de fondo para 10s controles de texto desactivados.
Todas las constantes mencionadas aqui estan listadas en 10s archivos de ayuda de
la VCL y CLX bajo el titulo "TColor type" (Tipo TColor).
Otra opcion consiste en especificar un T C o l o r como un numero (un valor
hexadecimal de 4 bytes) en lugar de utilizar un valor predefinido. Si se utiliza esta
tecnica, se deberia saber que 10s tres bytes menores de dicho numero representan
las intensidades RGB de color del azul, verde y rojo respectivamente. Por ejem-
plo, el valor SO 0 FFO 0 0 0 se corresponde a un color azul puro, el valor
$ 0 0 0 0 ~ ~ 0a10verde, el valor SOOOOOOFF a1 rojo, el valor $ 0 0 0 0 0 0 0 0 a1
negro y el valor $0 0 FFFFFF a1 blanco. A1 especificar valores intermedios, se
puede obtener cualquiera de 10s 16 millones de colores posibles.
En lugar de especificar directamente estos valores hexadecimales, deberiamos
utilizar la funcion RGB de Windows, que tiene tres parametros, todos entre el 0 y
el 255. El primer0 indica la cantidad de rojo, el segundo la cantidad de verde y el
ultimo la cantidad de azul. Utilizar la funcion RGB hace que 10s programas Sean
por lo general m b faciles de leer que si usamos una constante hexadecimal sola.
En realidad, RGB es casi una funcion de la API de Windows. Esta definida por las
unidades relacionadas con Windows y no por las unidades de Delphi, per0 no
existe una funcion similar en la API de Windows. En C, existe una macro que
tiene el mismo nombre y efecto. RGB no esta disponible en CLX, por lo que hemos
escrito una version propia del siguiente modo:
function RGB ( r e d , g r e e n , b l u e : B y t e ) : C a r d i n a l ;
begin
R e s u l t : = b l u e + g r e e n * 256 + r e d * 256 * 256;
end:

El byte m b significative del tipo T C o l o r se utiliza para indicar en que paleta


deberia buscarse el color correspondiente mas proximo, per0 no hablaremos aqui
sobre las paletas. (Los sofisticados programas de tratamiento de imagenes usan
tambien este byte para llevar la informacion sobre transparencia de cada elemento
que aparece en pantalla.)
En cuanto a las paletas y la correspondencia de color, fijese en que Windows
sustituye a veces un color arbitrario por el color solido mas proximo, a1 menos en
10s modos de video que usan una paleta. Esto siempre ocurre en el caso de fuen-
tes, lineas, etc., En otras ocasiones, Windows usa una tecnica de punteado para
imitar el color solicitado a1 dibujar un ajustado modelo de pixeles con 10s colores
disponibles. En adaptadores de 16 colores (VGA) y a gran resolution, con fre-
cuencia acaban por verse extraiios modelos de pixeles de diferentes colores y no
del color que se tenia en mente.

La clase TWinControl (VCL)


En Windows, la mayoria de 10s elementos de la interfaz de usuario son venta-
nas. Desde el punto de vista de un usuario, una ventana es una parte de la pantalla
rodeada por un borde, que tiene un titulo y normalmente un menu de sistema. Pero
tecnicamente hablando, una ventana es una entrada en una tabla interna del siste-
ma, que se corresponde normalmente con un elemento visible en pantalla con un
codigo asociado. La mayoria de estas ventanas tienen la funcion de controles,
otras las crea temporalmente el sistema (por ejemplo, para mostrar un menu des-
plegable). Tambien hay otras ventanas creadas por la aplicacion per0 que perma-
necen ocultas a1 usuario y se usan solo como un medio para recibir un mensaje
(por ejemplo, 10s sockets sin bloqueo utilizan ventanas para comunicarse con el
sistema).
El comun denominador de todas las ventanas es que el sistema Windows las
conoce y que para mostrar su comportamiento se refieren a una funcion. Cada vez
que pasa algo en el sistema, se envia un mensaje de notificacion a la ventana
adecuada, que responde ejecutando algo de codigo. Cada ventana del sistema
tiene una funcion asociada (denominada por lo general su procedimiento de venta-
na), que gestiona 10s diversos mensajes de interes para la misma.
En Delphi, cualquier clase TW inCo nt ro 1 puede sobrescribir el metodo
WndProc o definir un nuevo valor para la propiedad WindowProc.Sin embar-
go, 10s mensajes interesantes de Windows pueden seguirse mejor mediante
controladores de mensajes especificos. Aun mejor, la VCL convierte estos mensa-
jes de bajo nivel en eventos.
En resumen, Delphi nos permite trabajar a un alto nivel, simplificando el desa-
rrollo de la aplicacion, per0 permitihdonos aun asi acudir a un nivel mas bajo
cuando sea necesario.
Fijese tambien en que a1 crear una instancia de una clase basada en
TWinControl no se crea automaticamente su correspondiente manejador de
ventana. En realidad, Delphi usa una tecnica de inicializacion perezosa, por lo
que el control de bajo nivel se crea solo cuando es necesario, normalmente desde
el momento en que un metodo accede a la propiedad Handle.El metodo get de
esta propiedad llama la primera vez a HandleNeeded, que a su vez llama a
C r e a t e H a n d l e ... y asi sucesivamente, hasta llegar a CreateWnd,
Createparams y CreateWindowHandle (la secuencia es bastante com-
pleja). Por el contrario, se puede conservar un control existente en memoria (tal
vez invisible) per0 destruir su manejador de ventana y ahorrar recursos del siste-
ma.
La clase TWidgetControl (CLX)
En CLX, todo control TWidgetControl tiene un objeto interno Qt, a1 que
se hacc rcferencia utilizando la propiedad Handle.Esta propiedad posee el mis-
mo nombre que la propiedad correspondiente de Windows, per0 es totalmente
difercnte en su ambito interno.
El objeto Qt/C++ lo posee normalmente el correspondiente objeto TWidget-
Control.La clase utiliza la construccion retardada (el objeto interno no se crea
hasta que se necesite uno de sus metodos), como se puede ver implementada en el
metodo Initwidget y en otros metodos. La clase CLX tambien libera el objeto
interno cuando se destruye. Sin cmbargo, tambien es posible crear un widget
alrededor de un objeto existente Qt: en este caso, el widget no poseera a1 objeto Qt
ni lo dcstruira. Este comportamiento se indica en la propiedad OwnHandle.
En realidad, cada componente VisualCLX tiene dos objetos C++ asociados, el
Qt Handle y el Qt Hook,que es el objeto que recibe 10s eventos del sistema.
Con el diseiio Q t actual, ~t Hook tiene que ser un objeto C++, que actua como
intermediario de 10s controladores de eventos del control de Delphi. El metodo
Hoo kEvent s asocia el objeto gancho a1 control CLX.
A diferencia de Windows, Qt define dos tipos diferentes de eventos:
Events: Son la traduccion de una entrada de usuario o eventos dc sistcma
(como la pulsacion de una tecla, un movimiento de raton o un dibujo).
Signals: Son eventos de componentes internos (que se corresponden con
las operaciones internas o abstractas VCL, como OnClic k y OnChange)
Sin embargo, 10s evcntos de un componente C L X fundcn eventos y seiiales.
Los eventos de controlcs CLX genericos dc Delphi incluyen OnMouseDown,
OnMouseMove,OnKeyDown, OnChange,OnPaint y muchos mas, exacta-
mentc como la VCL (que lanza la mayoria de 10s eventos cn respuesta a mensajes
dc Windows).

1 NOTA:
mit&
pKogrr;maddreesmpertos pug& no* q4e en CLX hqy un
utibadi3 can *a hecdrencia, EventHandler, que se cmres-

Abrir la caja de herramientas


de componentes
Si s e quiere escribir una aplicacion Delphi, hay que abrir un nuevo proyecto
Delphi y nos encontraremos ante un gran numero de componentes. El problema es
que para cada operacion, existen diversas alternativas. Por ejemplo, se puede
mostrar una lista de valores utilizando un cuadro de lista, un cuadro combinado,
un grupo de botones de radio, una malla de cadena (srring grid), una vista en lista
o incluso una lista en arb01 si existe un orden jerarquico. Para seleccionar una de
ellas; debemos considerar cual sera la tarea de la aplicacion. Hemos elaborado un
resumen bastante conciso de las opciones alternativas para realizar algunas ta-
reas muy comunes.

NOTA: Para algunos de 10s cantroles descritos en las siguientessecciones,


Delphi incluye tambikn una versibn &fa-aware, in&& nomlalmente por
el prefijo DB. C ~ m ose verh, la versibn DB de un control sueh prestar una
funcion similar a la de su equivalente "esthdar",per0 las propiedades y las
formas en que se usa son bastante diferentes. Por ejemplo, en un control
E d i t se usa la propiedad'~ex t, mientras que en un componente DBEdi t
se accede a1 campo Value del objeto relacionado.

Los componentes de entrada de texto


Aunque un formulario o componente puede controlar directamente la entrada
del teclado, utilizando un evento OnKeyPress, no se trata de una operacion
muy comun. Windows ofrece controles preparados para su uso para obtener en-
tradas de cadena e incluso construir un sencillo editor de textos. Delphi dispone
de varios componentes ligeramente diferentes en este campo.
El componente Edit
El componente Edit permite a1 usuario introducir una unica linea de texto.
TambiCn se puede mostrar una linea de texto con un control Label o un control
StaticText, pero estos componentes se utilizan, por lo general, solo para texto fijo
o para salidas generadas por el programa, no para entradas. En CLX, tambien
hay un control originario de digito LCD que se puede usar para mostrar numeros.
El componente Edit usa la propiedad Text, mientras que muchos otros con-
troles usan la propiedad Caption para referirse al texto que muestran. La unica
condicion que se puede imponer al usuario es el numero de caracteres aceptados.
Si queremos que se acepten solo unos caracteres especificos, se puede controlar el
evento OnKeyPress del cuadro de edicion. Por ejemplo, se puede escribir un
metodo que compruebe si el caracter es un numero o la tech Retroceso (quc tiene
un valor numeric0 de 8). Si no es asi. cambiamos el valor de la t e c h a1 caracter
cero (#0)>de mod0 que el control de edicion no lo procese y se produzca un sonido
de advertencia:
procedure TForml.EditlKeyPress(
Sender: TObject; var Key: Char) ;
begin
/ / v e r i f i c a s i l a t e c h es u n numero o r e t r o c e s o
i f not (Key i n [ ' 0 ' . . ' 9 ' , # 8 ] ) then
begin
Key : = #O;
Beep;
end ;
end ;

NOTA: Una pequefia diferencia de CLX es que el control Edit no tiene un


mecanismo Undo incorporado. Otra qs que la propiedad Pass~wordChar
se sustituye por la propiedad EchoMode. Nosotros no podemt1s establecer
el carhcter que aparece, sin0 si visualizar el texto enteroo mvJuar
lugar un asterisco.

El control LabeledEdit
Delphi 6 aiiadio un control llamado LabeledEdit, que es un control Edit
con una etiqueta adjunta. La etiqueta aparece como propiedad del control com-
puesto, que hereda de TCustomEdit.
Este componente es muy comodo, porque nos permite reducir el numero de
componentes de nuestros formularios, moverlos mas facilmente y tener una orga-
nizacion mas consistente para las etiquetas de todo un formulario o una aplica-
cion. La propiedad EditLabel esta conectada con el subcomponente, que tiene
las propiedades y eventos normales. Dos propiedades mas, LabelPosit ion y
Labelspacing, nos permiten configurar las posiciones relativas de 10s dos
controles.

NOTA: Este componente se ha airadid0 a la unidad EWCtrls para mostrar


el'u$o de subcomponentes en el Obj ect ~ n s ~ e c t o r .

El componente MaskEdit
Para personalizar aun mas la entrada de un cuadro de edicion, se puede utilizar
el componente Mas kEdit.Tiene una propiedad EditMask que es una cadena
que indica para cada caracter si deberia ser una mayhcula, minuscula o un nu-
mero y otras condiciones similares.
El editor Input Mask permite introducir una mascara, per0 tambien nos pide
que indiquemos un caracter que reserve el sitio para la entrada y decidir si se
guarda el material presente en la mascara junto con la cadena final. Por ejemplo,
se puede escoger mostrar el prefijo de zona del numero de telefono entre parente-
sis solo como una entrada de sugerencia o guardar 10s parentesis con la cadena
que almacena el numero resultante. Estas dos entradas en el editor'lnput Mask
corresponden a 10s dos ultimos campos de la mascara (separados por puntos y
coma). Se puede ver el editor de la propiedad EditMask a continuacion:

-
Date 06/27/94
Long Tme 09 05 15PM
Shod T~me
ILL.-
kid*... I I T ( I Hdp I

I TRUCO:Si hacemob did. sobre el&th MEW cfBt Inpd Mask ~ditnr. 1
se pueden eswger & c m de entrada predefbidas p&3 difkrentes paises.

Los componentes Memo y RichEdit


Los controles comentados hasta ahora solo permiten usar una linea de entrada.
El componente Memo puede contener varias lineas de testo, per0 (en las platafor-
mas Win9.5198) todavia mantiene el limite de texto (32 KB) de Windows de 16
bits y solo permite una unica fuente para todo el texto. Se puede trabajar con el
texto del campo memo linea por linea (utilizando la lista de cadena Lines) o
acceder a todo el texto de una sola vez (usando la propiedad Text).
Si queremos alojar una gran cantidad de testo o cambiar las fientes y las
alineaciones de parrafo, en la VCL deberiamos utilizar el control RichEdit, un
control comun de Win32 basado en el formato de documento RTF. Se puede encon-
trar un ejemplo de un completo editor basado en un componente RichEdit entre
10s programas de ejemplo que incluye Delphi. (El nombre del ejemplo tarnbien es
RichEdit). El componente RichEdit tiene una propiedad DefAttributes
que indica 10s estilos predefinidos y una propiedad SelAtt ributes que indica
el estilo de la selection actual. Estas dos propiedades no son del tipo TFont,pero
son compatibles con las fuentes, por lo que podemos usar el metodo AS s ign para
copiar el valor, como en el siguiente fragment0 de codigo:
procedure TForml.ButtonlClick(Sender: TObject);
begin
.
if RichEdit1 SelLength > 0 then
begin
FontDialogl.Font.Assign (RichEdit1.DefAttributes);
if FontDialog1.Execute then
RichEditl.SelAttributes.Assign (FontDialogl.Font);
end;
end;
El control CLX TextViewer
Entre todos 10s controles comunes, CLX y Qt no tienen un control RichEdit.
Sin embargo, ofrecen un completo visor HTML, que es muy potente para mostrar
texto formateado per0 no para escribirlo. Este visor HTML esta insertado en dos
controles diferentes, el control TextViewer de una unica pagina o el control
TextBrowser con enlaces activos. Como simple demostracion, hemos aiiadido un
campo de memo y un visor de texto a un formulario CLX y 10s hemos conectado
de forma que todo lo que se teclee en el campo de memo aparezca inmediatamente
en el visor. Hemos llamado a1 ejemplo HtmIEdit, no porque sea un autentico
editor HTML, sin0 porque este es el mod0 mas sencillo de crear una vista previa
de HTML dentro de un programa. El formulario del programa se puede ver en
tiempo de ejecucion en la figura 5.3.

Test Html
Test text with bold

and linally a "deaf' hyped&, marcocantu.com

4
Figura 5.3. El ejemplo HtmlEdit en tiempo de ejecucion: cuando se aiiade nuevo
texto HTML al campo de memo, se puede previsualizar inmediatamente.

Linux. Para daptar este ejcrnplo g Windows y Delphi, sblo es necesarid .


copiar Iw archivos y v o h r a compilar.

Selection de opciones
Existen dos controles estandar Windows que permiten a1 usuario escoger dife-
rentes opciones, asi como otros dos controles para agrupar conjuntos de opciones.
Los componentes CheckBox y RadioButton
El primer control estandar de seleccion de opciones es la casilla de verificacion
(o check box), que corresponde a una opcion que se puede seleccionar sea cual sea
el estado de otras casillas de verificacion. Configurar la propiedad AllowGrayed
de la casilla de verificacion nos permite mostrar tres estados diferentes (seleccio-
nado, no seleccionado y en gris), que se alternan a medida que el usuario hace clic
sobre la casilla de verificacion.
El segundo tip0 de control es el boton de radio, que corresponde a una selec-
cion exclusiva. Dos botones de radio del mismo formulario o dentro del mismo
contenedor de grupo de radio no se pueden seleccionar a1 mismo tiempo y uno de
ellos deberia estar siempre seleccionado (como programador, se tiene la respon-
sabilidad de escoger uno de 10s botones de radio en tiempo de diseiio).
Los componentes GroupBox
Para alojar varios grupos de botones de radio, se puede usar un control
GroupBox para mantenerlos juntos, tanto funcional como visualmente. Para cons-
truir un cuadro de grupo con botones de radio, sencillamente hay que colocar el
componente GroupBox sobre un formulario y, a continuation, aiiadir 10s boto-
nes de radio a1 cuadro de grupo, como en el siguiente ejemplo:

Se pueden controlar 10s botones de radio de forma individual, pero es mas


sencillo desplazarse a traves de la matriz de controles que posee el cuadro de
grupo. Aqui tenemos un pequeiio extract0 de codigo para obtener el texto del
boton de radio seleccionado de un grupo:
var
I: Integer;
Text: string;
begin
for I : = 0 to GroupBoxl.ControlCount - 1 do
if (GroupBoxl.Controls [I] as TRadioButton) .Checked then
Text : = (GroupBoxl.Contro1s[I] as TRadioButton) .Caption;

El componente RadioGroup
Delphi posee un componente similar que se puede utilizar de forma especifica
para botones de radio: el componente RadioGroup. Un RadioGroup es un cua-
dro de grupo con algunos clones de botones de radio en su interior. La diferencia
es que estos botones de radio internos se gestionan automaticamente desde el
control contenedor. Utilizar un grupo de radio es, por lo general, mas sencillo que
utilizar el cuadro de grupo, puesto que 10s diversos elementos forman parte de una
lista, como en un cuadro de lista. Asi es como se puede obtener el texto del
elemento seleccionado:
Text : = RadioGroupl.Items [RadioGroupl.ItemIndex];

Otra ventaja es que un componente R a d i o G r o u p puede alinear


automaticamente 10s botones de radio en una o mas columnas (corno indica la
propiedad columns) y se pueden aiiadir facilmente nuevas opciones en tiempo
de ejecucion, aiiadiendo cadenas a la lista de cadenas 1tems. Sin embargo, aiia-
dir nuevos botones de radio a un cuadro de grupo resulta bastante complejo.

Cuando hay muchas selecciones, 10s botones de radio no resultan apropiados.


El numero de botones de radio mas habitual es inferior a cinco o seis, para no
abarrotar la interfaz de usuario. Cuando tenemos mas opciones, podemos usar un
cuadro de lista o uno de 10s otros controles que muestran listas de elementos y
permiten la seleccion de uno de ellos.

El componente ListBox
La seleccion de un elemento en un cuadro de lista usa las propiedades ~t ems
e ItemIndex como en el codigo anterior para el control RadioGroup. Si hay
que acceder con frecuencia a1 texto de 10s elementos del cuadro de lista seleccio-
nado, se puede escribir una funcion envoltorio como esta:
f u n c t i o n SelText (List: TListBox) : string;
var
nItem: Integer;
begin
n I t e m : = List.ItemIndex;
i f n I t e m >= 0 then
Result : = List. Items [nItem]
else
Result := ' ' ;
end;

Otra caracteristica importante es que a1 utilizar el componente ListBox, se


puede escoger entre permitir solo una seleccion, como en un grupo de botones de
radio, y permitir selecciones multiples, como en un grupo de casillas de verifica-
cion. Esta eleccion la hacemos especificando el valor de la propiedad
~ u l iselect.
t Existen dos tipos de selecciones multiples en cuadros de lista en
Windows y en Delphi: seleccion multiple y seleccion ampliada. En el primer caso,
un usuario selecciona diversos elementos haciendo clic sobre ellos, mientras que
en el segundo caso, el usuario puede usar las teclas Mayus y Control para selec-
cionar diversos elementos consecutivos o no consecutivos, respectivamente. Esta
segunda opcion la determina el estado de la propiedad ExtendedSele ct .
En el caso de un cuadro de lista de seleccion multiple, un programa puede
recuperar informacion sobre un numero de elementos seleccionados utilizando la
propiedad selcount y se puede establecer que elementos estan seleccionados
examinando la matriz Sele cted. Dicha matriz de valores booleanos tiene el
mismo numero de entradas que un cuadro de lista. Por ejemplo, para concatenar
todos 10s elementos seleccionados en una cadena, se puede buscar en la matriz
Sele cted del siguiente modo:
var
SelItems: string;
nItem: Integer;
begin
SelItems : = ' I ;

f o r nItem : = 0 t o ListBoxl.Items.Count - 1 do
i f ListBoxl. Selected [nItem] then
SelItems : = SelItems + ListBoxl.Items [nItem] + ' ' ;
En CLX (no como en la VCL), se puede configurar una ListBox para que
utilice un numero fijo de columnas y filas, utilizando las propiedades columns,
Row, ColumnLayout y RowLayout.De ellas, la ListBox de la VCL tiene
solo la propiedad columns.

El componente ComboBox
Los cuadros de lista acaparan mucho espacio en pantalla y ofrecen unas opcio-
nes fijas (es decir, un usuario puede escoger solo entre 10s elementos de la lista y
no puede introducir ninguna opcion que el programador no haya tenido explicita-
mente en cuenta). Se pueden solucionar ambos problemas utilizando un control
ComboBox,que combina un cuadro de edicion y una lista desplegable. El com-
portamiento de un componente ComboBox cambia mucho dependiendo del valor
de su propiedad sty1e :
El estilo csDropDown: Define un cuadro combinado tipico, que permite
editar directamente y mostrar un cuadro de lista mediante solicitud.
El estilo csDropDownList: Define un cuadro combinado que no permite
editar (pero en el que se pueden pulsar ciertas teclas para seleccionar un
elemento).
El estilo cssimple: Define un cuadro combinado que siempre muestra el
cuadro de lista bajo el.
Fijese en que acceder a1 texto del valor seleccionado de un cuadro combinado
es mas sencillo que hacer la misma operacion en el caso de un cuadro de lista,
pucsto que podemos sencillamente usar la propiedad Text.Un truco util y habi-
tual para 10s cuadros combinados consiste en aiiadir un nuevo elemento a la lista
cuando un usuario introduce texto y pulsa la tech Intro. El siguiente metodo
comprueba primer0 si el usuario ha pulsado esa tecla, analizando el caracter con
el valor numeric0 (ASCII) de 13. A continuacion, verifica que el texto del cuadro
combinado no este vacio y que no esta ya en la lista (si su posicion en la lista es
menor que cero). Veamos el codigo:
procedure TForml.ComboBoxlKeyPress(
Sender: TObject; var Key: C h a r ) ;
begin
/ / s i el usuario pulsa l a tecla Intro
i f Key = Chr ( 1 3 ) then
with C o m b o B o x 3 do
i f (Text <> " I and (1tems.IndexOf (Text) < 0 ) then
1tems.Add (Text);
end :

TRUCO:En CLX, el cuadro combinado puede aiiadir automaticamente el


texto escrito en el cuadro de edicion a la lista desplegable, cuando el usua-
ria pulsa la tecla Intro. Ademas, algunos eventos ocurren en diferentes
ocasiones en la VCL.

Dcsdc Delphi 6, se incluyen dos nuevos eventos para el cuadro combinado. El


cvcnto onC lo s eUp corresponde a1 cicrre de la lista desplegable y complemcnta
a1 cvento OnDropDown que existia previamente. El evento onseiect solo se
lanza cuando el usuario hace una seleccion en la lista desplegable, en lugar de
escribir cn la parte de edicion.
Otro mcjora es la propiedad AutoComplete. Cuando se fija, el componente
ComboBox (y tambicn el componente List Box) busca automaticamente la ca-
dena mas parecida a aquella que el usuario esta escribiendo, sugiriendo la parte
final del testo.
La parte principal de esta caracteristica, tambidn disponible en CLX, se
implements en el mctodo TCustomList Box. KeyPres s.

El componente CheckListBox
Otra ampliacion del control de cuadro de lista la representa el componente
CheckListBox, un cuadro de lista con cada uno de 10s elementos precedidos
por una casilla de verificacion.
Un usuario puede seleccionar un unico elemento de la lista, pero tambien hacer
clic sobre las casillas de verificacion para alternar su estado. Esto hace que
CheckListBox sea un componente realmente adecuado para las selecciones
multiples o para resaltar el estado de una serie de elementos independientes (en
forma de una serie de casillas de verificacion).
10s grupos de colores que queremos ver en la lista (colores estandar, colores
ampliados, colores de sistema. etc.).
Los componentes ListView y TreeView
Si queremos una lista mas sofisticada, se puede utilizar el habitual control
ListView, que hara que la interfaz de usuario de la aplicacion parezca muy mo-
derna. Este componente es ligeramente mas complejo de usar, como ya se vera.
Otras alternativas para listar valores son el control comun TreeView, que mues-
tra elemcntos en una disposicion jerarquica y el control StringGrid, que muestra
divcrsos elementos para cada linea.
Si utilizamos 10s controles comunes en nuestras aplicaciones, 10s usuarios ya
sabran como interactuar con ellos y consideraran la interfaz de usuario del pro-
grama como actualizada. TrecVicw y ListView son dos componentes clave del
Esplorador de Windows y se puede suponer que muchos usuarios estaran familia-
rizados con ellos: incluso mas que con 10s controles tradicionales de Windows.
CLX tambien aiiade un control IconView, que es similar en parte de las caracte-
risticas a la ListView VCL.

ADVERTENCIA: El control ListView en la CLX no dispone de 10s estilos


de icono pequeiiolgrande de su contraparticla en Windows, pero un control
similar, IconView, proporciona esta capacidad.

El componente ValueListEditor
Las aplicaciones de Delphi utilizan normalmente la estructura nombrelvalor
que ofrecen en principio las listas de cadena. Delphi 6 introdujo una version del
componente StringGrid (tecnicamente una clase descendiente de TCustomDraw-
s t r i n g ) que se ha hecho concordar especificamente con este tip0 de listas de
cadena. El ValueListEditor tiene dos columnas en las que puede mostrar y dejar
que el usuario edite 10s contenidos de una lista de cadena con parejas nombrel
valor: como muestra la figura 5.4. Esta lista de cadena la indica la propiedad
S t r i n g s del control.
La potencia de este control se basa en que se pueden personalizar las opciones
de edicion para cada posicion de la cuadricula (grid)o para cada valor clave,
usando la propiedad solo en tiempo de ejecucion de matriz I t e m P r o p s . Para
cada elemento, se puede indicar:

Si es solo de lectura.
El numero masimo de caracteres de la cadena.
Una mascara de edicion (solicitada en ultimo termino en el evento
OnGetEditMask).
Plain Mema
rone=~
rwo=2
three-3

Figura 5.4. El ejemplo NameValues usa el componente ValueListEditor, que muestra 10s
pares nombrelvalor o clave/valor de una lista de cadena, tambien visible en un simple
campo de memo.

Los elementos de una lista de selection desplegable (solicitada en ultimo


termino en el cvento OnGet Pic kList).
La aparicion de un boton que muestre un dialogo de edicion (en el evento
OnEditButtonClick).
No es necesario decir que este comportamiento se parece al que esta general-
mente disponible para las cuadriculas de cadena (string grids) y el control DBGrid,
y tambien para el comportamiento del Ob j ect Inspector.
La propiedad Itemprops habra de establecerse en tiempo de ejecucion, a1
crear un objeto de la clase T I ternprop y asignarlo a un indice o a una clave de
la lista de cadena. Para tener un editor predefinido para cada linea, se puede
asignar el mismo objeto de propiedad de elemento varias veces. En el ejemplo,
este editor compartido configura una mascara de edicion de hasta tres numeros:
procedure TForml.FormCreate(Sender: TObject);
var
I: Integer;
begin
SharedItemProp : = TItemProp.Create (ValueListEditorl);
Shared1temProp.EditMask : = ' 999;O; ' ;
SharedItemProp.EditStyle : = esEllipsis;

FirstItemProp : = TItemProp-Create (ValueListEditorl);


for I : = 0 to 10 do
FirstItemProp.PickListAdd(1ntToStr (I));

Memol.Lines : = ValueListEditor1.Strings;
ValueListEditorl.ItemProps [0] : = FirstItemProp;
f o r I : = 0 t o ValueListEditor1.Strings.Count - 1 do
ValueListEditorl.1temProps [I] : = SharedItemProp;
end ;

Se debe repetir un codigo similar en caso de que cambie el numero de lineas,


por ejemplo a1 aiiadir nuevos elementos en el campo de memo y copiarlos a la lista
de valores.
procedure TForml.ValueListEditorlStringsChange(Sender: TObject);
var
I: Integer;
begin
Memol.Lines : = ValueListEditorl.Strings;
ValueListEditorl.1temProps [0] : = FirstItemProp;
f o r I : = 0 t o ValueListEditorl.Strings .Count - 1 do
i f n o t Assigned (ValueListEditorl. Itemprops [I]) then
ValueListEditorl.1temProps [I] : = SharedItemProp;
end ;

I asi que s61o asignmos el editor a las llneas qua w tienen &a h . I
Otra propiedad, K e y O p t i o n s , permite a1 usuario editar tambiin las claves
(10s nombres), aiiadir nuevas entradas, eliminar las existentes y contar con nom-
bres duplicados en la primera parte de la cadena. Es raro que no se puedan aiiadir
nuevas claves a no ser que se activen tambien las opciones de edicion, lo que
dificulta permitir que el usuario aiiada entradas adicionales mientras que se man-
tienen 10s nombres de las entradas basicas.

Rangos
Por ultimo, existen unos cuantos componentes que podemos usar para selec-
cionar valores dentro de un rango. Los rangos se pueden usar para entradas nu-
mericas y para seleccionar un elemento de una lista.
El componente ScrollBar
El control independiente ScrollBar es el componente original de este grupo,
per0 rara vez se utiliza por si solo. Las barras de desplazamiento (scroll bars) se
asocian normalmente con otros componentes, como cuadros de lista y campos de
memo o se asocian directamente con formularios. En todos estos casos, la barra
de desplazamiento se puede considerar parte de la superficie de otros componen-
tes. Por ejemplo, un formulario con una barra de desplazamiento es en realidad un
formulario que tiene una zona que parece una barra de desplazamiento pintada en
su borde, una caracteristica regida por un estilo especifico de Windows de la
ventana formulario. Con parecer, nos referimos a que no es tecnicamente una
ventana separada del tipo de componente ScrollBar. Estas barras de desplaza-
miento "falsas" se controlan normalmente en Delphi usando propiedades especifi-
cas del formulario y 10s otros componentes que las alojan: V e r t S c r o l l B a r y
HorzScrollBar.

Los componentes TrackBar y ProgressBar


El uso direct0 del componente S c r o l l B a r es bastante extraiio, sobre todo
con el componente T r a c k B a r introducido con Windows 95, que se usa para
dejar que el usuario seleccione un valor en un rango. Entre 10s controles comunes
de Win32, se encuentra el control acompaiiante de ProgressBar, que permite que
el programa muestre un valor en un rango, mostrando el progreso de una opera-
cion larga. Estos dos componentes se pueden ver aqui:

El componente UpDown
Otro control relacionado es el componente U p D o w n , que suele estar conectado
a un cuadro de edicion de forma que el usuario pueda teclear un numero en el o
aumentar y disminuir el numero utilizando dos pequeiios botones de flecha. Para
conectar 10s dos controles, se define la propiedad As s o c i a t e del componente
UpDown. Podemos utilizar el componente U p D o w n como un control indepen-
diente, que muestre el valor actual en una etiqueta, o de cualquier otro modo.
I

ua;Edit con el ~ p ~ o en
w un
n "nico control.
.
NOTA: En CLX no existe el control UpDown, sino un SpinEdit
-
que as&ia.
. . . , a. .. .

El componente PageScroller
El control PageScroller de Win32 es un contenedor que permite desplazar el
control interno. Por ejemplo, si se coloca una barra de herramientas en la barra de
desplazamiento de la pagina y la barra de herramientas es mas grande que el
espacio disponible, el PageScroller mostrara dos pequeiias flechas en el lateral. Si
hacemos clic sobre dichas flechas se desplazara la zona interna. Este componente
se puede usar como una barra de desplazamiento, per0 tambien sustituye en parte
a1 control ScrollBox.

El componente ScrollBox
El control ScrollBox representa una zona de un formulario que se puede des-
plazar independientemente del resto de la superficie. Por esta razon, el ScrollBox
tiene dos barras de desplazamiento utilizadas para mover 10s componentes inser-
tados. Podemos colocar facilmente otros componentes dentro de un ScrollBox,
como en el caso de un panel. De hecho, un ScrollBox es basicamente un panel con
barras de desplazamiento para mover su superficie interna, un elemento de la
interfaz usado en muchas aplicaciones de Windows. Cuando tenemos un formula-
rio con muchos controles y una barra de herramientas o barra de estado, podria-
mos usar un ScrollBox para cubrir la zona central del formulario, dejando sus
barras de desplazamiento y barras de estado fuera de la zona de desplazarniento.
A1 confiar en las barras de desplazamiento del formulario, se podria permitir que
el usuario moviera la barra de herramientas y la barra de estado fuera de vista
(una situation muy extrafia).

Comandos
La categoria final de componentes no es tan clara como en 10s casos anterio-
res, y tiene que ver con 10s comandos. El componente basico de este grupo es el
T B u t t o n (o boton pulsador, en la jerga de Windows). Mas que botones indepen-
dientes, 10s programadores de Delphi utilizan botones (u objetos T T o o l B u t t o n )
dentro de barras de herramientas (en las primeras fases de Delphi, se usaban
botones de atajo dentro de paneles). Ademas de botones y controles similares, la
otra tecnica clave para invocar comandos es el uso de 10s elementos de menu,
parte de 10s menus desplegables enlazados con 10s menus principales de 10s for-
mularios o 10s menus desplegables locales que se activan mediante el boton dere-
cho del raton.
Los comandos relacionados con el menu o la barra de herramientas entran en
distintas categorias dependiendo de su proposito y de la retroalimentacion que
ofrece su interfaz a 10s usuarios:
Comandos: Elementos del menu utilizados para ejecutar una accion.
Definidores d e estado (state-setters): Elementos del menu utilizados para
activar o desactivar una opcion o para cambiar el estado de un elemento
concreto. Los elementos de estado de estas ordenes normalmente tienen
una marca de verificacion a su izquierda para indicar que estan activos (se
puede conseguir automaticamente este comportamiento usando la propie-
dad A u t o c h e c k ) . Los botones generalmente se pintan en un estado pre-
sionado para indicar el mismo estado (el control ToolButton tiene una
propiedad Down).
Elementos de radio: Elementos del menu que posecn una marca circular y
estan agrupados para representar las selecciones alternativas, como 10s
botones de radio. Para obtener 10s elementos de radio del menu, hay que
configurar sencillamente la propiedad RadioItem como True y esta-
blecer la propiedad GroupIndex para 10s elementos alternatives del menu
con el mismo valor. De un mod0 similar, se pueden agrupar botones de la
barra de herramicntas que Sean mutuamente exclusives.
Enlaces d e didogo: Elementos que hacen que aparezca un cuadro de dia-
logo y normalmente estan indicados por tres puntos (...)despues del texto.

Comandos y acciones
Como se vera, las aplicaciones modernas de Delphi tienden a usar el compo-
nente Act ionlist o su extension ActionManager para gestionar comandos del
mcnu o dc la barra de herramientas. En pocas palabras, se define una serie dc
objetos dc accion y se asocia cada uno de ellos con un boton de la barra de
hcrramicntas ylo un elemento del menu. Se puede definir la ejccucion del coman-
do en un unico lugar pero actualizar tanibien la interfaz de usuario conectandola
simplemente con la accion: el control visual relacionado reflejara automaticamente
cl estado del objeto de accion.
Menu Designer
Si simplemente se necesita inostrar un menu sencillo en la aplicacion, se puede
colocar un componentc MainMenu o PopupMenu en un formulario y hacer
doble clic sobre dl para lanzar el Menu Designer, que muestra la figura 5.5. Se
pueden aiiadir nuevos clementos de menu y proporcionarles una propiedad
Caption,usando un guion (-) para separar las etiquetas de 10s elementos del
menu.

Figura 5.5. El Menu Designer de Delphi en funcionarniento.

Delphi crea un nuevo componente para cada elemento de menu que se aiiada.
Para dar nombre a cada componente, Delphi usa el titulo que introducimos y
adjunta un numero (de tal mod0 que Open se convierta en Openl). Debido a que
Delphi elimina espacios y otros caracteres especiales del titulo cuando crea el
nombre, si no queda nada, Delphi aiiade la IetraNal nombrc. Finalmente adjunta
el numcro, asi que 10s elementos de separation del menu se denominaran N1,N2
y asi sucesivamente. A1 saber lo que suele hacer Delphi de manera predefinida, se
deberia pensar cn editar el nombre en primer lugar. lo que es necesario si se quiere
acabar con un esquema dc nombrado de componentes sensato.
-

ADVERTENCIA: No se debe usar la propiedad Break, que se emplea


para incorporar un menu desplegable en diversas columnas. El valor
mbMenuBarBreak indica que este elernento apareceri en una segunda
linea o en las siguientes. El valor mbMenuBrea k indica que este elemento
se aiiadira a una segunda columna o a la siguiente del menu desplegable.

Para conseguir un menu de aspect0 mas modcrno, se puede a5adir un control


dc lista dc imagenes al programa, que contenga una scrie de mapas de bits, y
conectar la lista dc imagenes con el menu mediante su propiedad Images. Se
puedc dcfinir una imagen para cada elemento de menu fijando el valor correct0
para su propicdad I m a g e Index . La definicion de imagenes para mcnus es bas-
tante flexiblc (pucdc asociarse una lista de imagenes con cualquier menu desple-
gable especifico, e incluso con un elemento de menu dado, mediante la propiedad
SubMenuImages). A1 disponer de una lista de imagenes mas pequeiia especifi-
ca para cada menu dcsplcgablc en lugar de una gran lista de imagenes para todo el
menu se permitc una mayor particularization de una aplicacion en tiempo de
ejecucion.

TRUCO: Crear elementos de menu en tiempo de ejecucion es algo tan


habitual que Delphi ofrece algunas funciones listas para w a r en la unidad
Menus. Los nombres de estas funciones globales son autoexpiicativos:
NewMenu, NewPopupMenu, NewSubMenu, Newltem y NewLine.

Menus contextuales y el evento OncontextPopup


El componente PopupMenu aparece normalmente cuando el usuario hace
clic con el boton derecho del raton sobre un componente que usa el menu contextual
dado como el valor de su propiedad PopupMenu. Sin embargo, ademas de co-
nectar el menu contextual a un componente con la propiedad correspondiente,
podemos llamar a su metodo Popup,que necesita la posicion del menu contextual
en las coordenadas de la pantalla. Pueden obtenerse 10s valores adecuados a1
convertir un punto local en un punto de pantalla con el metodo ClientToScreen
del componente local, que en este fragment0 de codigo es una etiqueta:
procedure TForml.Label3MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
ScreenPoint: TPoint;
begin
// si se c u m p l e la c o n d i c i d n . . .
if Button = mbRight then
begin
ScreenPoint : = Label3. ClientToScreen (Point (X, Y) ) ;
PopupMenul.Popup (ScreenPoint.X, ScreenP0int.Y)
end;
end ;

Una tecnica alternativa es el uso del evento OnContextMenu.Este, introdu-


cido en Delphi 5, ocurre cuando un usuario hace clic con el boton derecho del
raton sobre un componente (exactamente lo que hemos rastreado anteriormente
con la comprobacion if But ton = mbRight). La ventaja esta en que el mismo
evento ocurre tambien en respuesta a la combinacion de teclas Mayus-F10, asi
como mediante las teclas del menu de metodo abreviado de algunos teclados.
Podemos utilizar este evento para mostrar un menu contextual con el siguiente
codigo:
procedure TFormPopup.LabellContextPopup(Sender: TObject;
MousePos: TPoint; var Handled: Boolean);
var
ScreenPoint: TPoint;
begin
// a d a d e e l e m e n t o s d i n d m i c o s
PopupMenu2. Items .Add (NewLine);
PopupMenu2. Items .Add (NewItem (TimeToStr (Now), 0, False,
True, nil, 0 , ' I ) ) ;
/ / muestra el menu c o n t e x t u a l
ScreenPoint : = ClientToScreen (MousePos);
PopupMenu2.Popup (ScreenPoint.X, ScreenP0int.Y);
Handled : = True;
// e l i m i n a 10s e l e m e n t o s dindmicos
PopupMenu2. Items [4]. Free;
PopupMenu2. Items [ 3 ] .Free;
end;

Este ejemplo aiiade algo de comportamiento dinamico a1 menu de atajo, aiia-


diendo un elemento temporal que indica cuando se muestra el menu contextual.
Este resultado no es particularmente util, per0 demuestra que si se necesita mos-
trar un simple menu contextual, se puede usar sin problemas la propiedad
PopupMenu del control en cuestion o de uno de sus controles padre. Gestionar el
evento OnContextMenu tiene sentido solo cuando se desea aiiadir algo de pro-
cesamiento adicional.
El parametro Handled se preinicia como False, de mod0 que si no hace-
mos nada en el controlador de eventos, el menu contextual se procesara con nor-
malidad. Si hacemos algo en el controlador de eventos para sustituir el
procesamiento normal del menu contextual (corno contextualizar un dialogo o un
menu personalizado, como en este caso), se deberia definir Handled como T r u e
y el sistema dejara de procesar el mensaje. Deberiamos establecer H a n d l e d
como T r u e en contadas ocasiones, dado que, por lo general, controlamos el
evento O n c o n t e x t Popup para crear de forma dinamica o personalizar el menu
contextual, per0 a continuacion podemos dejar que el controlador predefinido
muestre realmente el menu.
El controlador de un evento O n c o n t e x t P o p u p no se limita a mostrar un
menu contextual, sino que puede realizar cualquier otra operacion, como mostrar
directamente un cuadro de dialogo. Este es un ejemplo de una operacion de hacer
clic con el boton derecho utilizada para modificar el color del control.
procedure TFormPopup.Label2ContextPopup(Sender: TObject;
MousePos: TPoint; var Handled: Boolean) ;
begin
ColorDialogl.Color : = Label2.Color;
if ColorDialogl.Execute then
Label2.Color : = ColorDialogl.Color;
Handled : = True;
end :

Todos 10s fragmentos de codigo de esta seccion estan disponibles en el ejemplo


CustPop para la VCL y QCustPop para la CLX.

Tecnicas relacionadas con 10s controles


Despues de esta vision global de 10s controles de Delphi de uso mas habitual,
vamos a dedicarnos a comentar tecnicas genericas importantes no relacionadas
con un componente especifico. Hablaremos sobre el foco de entrada, 10s anclajes
de control, el uso del componente separador y de la visualizacion de sugerencias
flotantes. Por supuesto, en estos temas no se tratara todo lo que se puede hacer
con controles visuales, per0 proporcionaran un buen punto de arranque para co-
menzar a explorar algunas de estas tecnicas habituales.

Gestion del foco de entrada


Usando las propiedades T a b s t o p y T a b O r d e r disponibles en la mayoria de
10s controles, se puede especificar el orden en que 10s controles reciben el foco de
entrada cuando el usuario pulsa la tecla Tab. En lugar de configurar la propiedad
de orden de tabulacion de cada componente de un formulario manualmente, se
puede usar el menu de metodo abreviado del Form Designer para activar el cua-
dro de dialogo Edit Tab Order, como muestra la figura 5.6.
Ademas de la configuration basica, es importante saber que cada vez que un
componente recibe o pierde el foco de entrada, recibe el correspondiente evento
OnEnter u O n E x i t . Esto permite definir y personalizar el orden de las opera-
ciones de usuario. Algunas de estas tecnicas se demuestran en el ejemplo InFocus,
que crea una ventana bastante comun de contrasefia y nombre de usuario. El
formulario tiene tres cuadros de edicion con etiquetas que indican su significado,
como muestra la figura 5.7. En la p a r k inferior de la ventana esta la zona de
estado con mensajes de peticion que guian a1 usuario. Cada elemento habra de
introducirse de forma consecutiva.

wo
k bled ntab order
I

Figura 5.6. El cuadro de dialogo Edit Tab Order.

Figura 5.7. El ejernplo InFocus en tiernpo de ejecucion.

Para la salida de informacion sobre el estado, hemos utilizado el componente


S t a t u s B a r , con una unica zona de salida (obtenida a1 configurar su propiedad
S i m p l e P a n e l como T r u e ) . Veamos un resumen de las propiedades para este
ejemplo. Fijese en el caracter 8 de las etiquetas, que indica una tecla de metodo
abreviado y en la conesion de dichas etiquetas con 10s cuadros de edicion corres-
pondientes (usando la propiedad F o c u s C o n t r o 1):
o b j e c t FocusForm: TFocusForm
Activecontrol = EditFirstName
Caption = ' I n P o c u s '
o b j e c t Labell: TLabel
Caption = ' & F i r s t name'
FocusControl = EditFirstName
end
o b j e c t EditFirstName: TEdit
OnEnter = GlobalEnter
OnExit = EditFirstNameExit
end
o b j e c t Label2 : TLabel
Caption = ' & L a s t n a m e '
FocusControl = EditLastName
end
o b j e c t EditLastName: TEdit
OnEnter = GlobalEnter
end
o b j e c t Label3 : TLabel
Caption = ' & P a s s w o r d 1
FocusControl = Editpassword
end
o b j e c t Edit Password: TEdit
Passwordchar = ' * '
OnEnter = GlobalEnter
end
o b j e c t StatusBarl: TStatusBar
Simplepanel = True
end
end

El programa es muy sencillo y solo realiza dos operaciones. La primera con-


siste en identificar, en la barra de estado, el control de edicion que tiene el foco.
Esto lo consigue manejando el evento O n E n t e r de 10s controles, utilizando un
controlador de eventos generic0 para no repetir codigo.
En el ejemplo, en lugar de almacenar informacion adicional para cada cuadro
de edicion, hemos verificado cada control del formulario para determinar que
etiqueta esta conectada a1 cuadro de edicion actual (que indica el parametro
Sender):
procedure TFocusForm. GlobalEnter (Sender: TObject) ;
var
I: Integer;
begin
f o r I : = 0 t o Controlcount - 1 do
// s i e l c o n t r o l e s u n a e t i q u e t a
if (Controls [ I ] i s TLabel) and
// y l a e t i q u e t a e s t d c o n e c t a d a a 1 c u a d r o d e e d i c i o n a c t u a l
(TLabel (Controls [I]) .FocusControl = Sender) then
// c o p i a e l t e x t o , s i n e l c a r d c t e r i n i c i a l &
StatusBarl.Simp1eText : = ' E n t e r ' + Copy
(TLabel (Controls [I]) .Caption, 2, 1000) ;
end ;

El segundo controlador de eventos del formulario se refiere a1 evento OnExi t


del primer cuadro de dialogo. Si el control se deja en blanco, se niega a liberar el
foco y lo vuelve a recuperar despues de mostrar un mensaje a1 usuario. Los
metodos buscan tambien un valor de entrada dado, rellenan automaticamente el
segundo cuadro de edicion y desplazan el foco directamente a1 tercero:
procedure TFocusForm. EditFirstNameExit (Sender: TObj ect) ;
begin
i f EditFirstNarne.Text = " t h e n
begin
// n o d e j a s a l i r a 1 u s u a r i o
EditFirstName. SetFocus;
MessageDlg ( 'Es n e c e s a r i o e l n o m b r e ' , mtError, [mbOK] , 0) ;
end
else i f EditFirstName.Text = 'Adrnin' then
begin
// c u b r e e l s e g u n d o c u a d r o d e e d i c i 6 n y s a l t a a 1 t e r c e r o
EditLastNarne.Text : = ' A d r n i n ' ;
EditPassword.SetFocus;
end ;
end ;

- --
TRUCO: La versi6n CLX de este ejemplo tiene el m i s m &hgo y estA
disponible como programa QInFocus.

Anclajes de control
Para permitir la creacion de una interfaz de usuario agradable y flexible, con
controlcs que se adapten a1 tamaiio real del formulario, Delphi permite determinar
la posicion relativa de un control con la propiedad Anchors. Antes de que se
introdujera esta caracteristica en Delphi 4, todo control situado en un formulario
tenia unas coordenadas relativas a 10s bordes superior e izquierda, a no ser que se
encontrara alineado con el borde inferior o de la derecha. La alineacion es aconse-
jable para algunos controles, per0 no para todos ellos, en particular para 10s
botones.
A1 usar anclajes. podemos hacer que la posicion de un control sea relativa a
cualquiera de 10s lados del formulario. Por ejemplo, para tener un boton anclado
en la esquina inferior derecha del formulario, colocamos el boton en la posicion
deseada y configuramos su propiedad Anchors como [akRight , a kBottom] .
A1 cambiar el tamaiio del formulario, la distancia a1 boton desde 10s laterales a 10s
que se ancla se mantiene fija, el boton permanecera en su esquina.
Por otra parte, si colocamos un componente grande como un Memo o un ListBox
en medio de un formulario, podemos configurar su propiedad Anchors para
incluir 10s cuatro lados. De este modo, el control se comportara como un control
alineado y aumentara o disminuira segun el tamaiio del formulario, per0 habra
cierto margen entre el y 10s lados del formulario.
- .. --

TRUCO: Los anclajes, a1 igual que las restricciones, funcionan tanto en


tiempo de diseiio como en tiempo dt:ejecucion, por lo que deberiamos defi-
m a n t e s posi6fe-mos 'deesta ca-&
i mi&trT
I disefiamos el formulario y tambib en tiempo de ejecuei6n.
I
Como ejemplo de ambas tecnicas, podemos probar la aplicacion Anchors. que
tiene dos botones en la esquina inferior derecha y un cuadro de lista en el centro.
Como muestra la figura 5.8,los controles se mueven automaticamente y disminu-
yen de tamafio a1 mismo tiempo que cambia el tamaiio del formulario. Para que el
formulario funcione de forma correcta, debemos definir tambien su propiedad
constraints. si no, a medida que el formulario reduzca su tamaiio 10s contro-
les pueden solaparse o dcsaparecer.

Figura 5.8. Los controles del ejemplo Anchors se mueven y cambian de tamaiio
automaticamente con el formulario. No se necesita ninghn codigo adicional, solo usar
correctamente la propiedad Anchors.

Si climinamos todos 10s anclajes o dos opuestos (por ejemplo, izquierdo y


derecho). las operaciones de rnodificacion del tamaiio haran que el control flote en
el formulario. El control mantienc su tamaiio actual y el sistema afiade o elimina
el mismo numero de pixeles a cada uno de sus lados. Esto puede definirse como
anclaje centrado, porque si el componente csta en un principio en el medio del
formulario, mantcndra esa posicion. En cualquier caso, si deseamos disponer de
un control centrado. deberiamos usar por lo general ambos anclajes, de tal mod0
que si el usuario aumenta el tamaiio del formulario, el tamaiio del control aumenta
a su vez. en el caso comentado, aumentar el tamafio del formulario hace que el
pequeiio control permanezca en el medio.

Uso del componente Splitter


Esisten muchas maneras de implementar tecnicas de division de formularios
en Delphi, per0 la mas sencilla consiste en emplear el componente Splitter, que se
encuentra en la ficha Additional de la Component Palette. Para que resulte
mas eficaz, el divisor se puede usar combinado con la propiedad constraints
de 10s controles a 10s que haga referencia. Como veremos en el ejemplo Splitl,
csto permite definir las posiciones maxima y minima del divisor y del formulario.
Para crear este ejemplo, hay que colocar sencillamente un componente ListBos en
un formulario y, a continuacion, aiiadir un componente Splitter, una segunda
ListBox, otro Splitter y por ultimo un tercer componente ListBox. El formulario
tiene tambien una barra de herramientas simple basada en un panel.
Simplemente a1 colocar estos dos componentes divisores, se proporciona a1
formulario la funcionalidad complcta de mover y modificar el tamaiio dc 10s con-
troles que contiene en tiempo de ejecucion. Las propiedadcs W i d t h , Beveled y
Color de 10s componentes divisores definen su apariencia, y en el ejemplo Splitl
podemos usar 10s controles de la barra de herramientas para cambiarlos. Otra
propiedad relcvante es MinSi ze,que determina el tamaiio minimo de 10s com-
ponentes del formulario. Durante la operacion de division (vease la figura 5.9).
una linea marca la posicion final del divisor, per0 no podemos arrastrar esta linea
mas alla de un cierto limite. El comportamiento del programa Splitl consiste en
no permitir que 10s controles sc hagan demasiado pequeiios. Una tecnica alterna-
tiva es definir la nueva propiedad Autosnap del divisor como T r u e . Esta pro-
piedad hara que el divisor oculte el control cuando su tamaiio vaya mas alla del

zard

Wh -..-
Dog
cat
11
hr~~np
ug
ee

Rhi Lzf
She,Sheep
Hare

para cada control del formulario, incluso para aquellos no contiguos al divisor.

Es aconsejable probar el ejemplo Split 1, para que se comprenda completamen-


te como afecta el divisor a sus controles configuos y al resto de componentes del
formulario. Incluso aunque se f?ie su propiedad MinSize, un usuario puede
rcducir el tamaiio de todo el formulario a su minima espresion, ocultando algunos
de 10s cuadros dc lista. Si se prueba la version Split2 dcl ejemplo, se comprendera
me.jor. En Split2 se nianipula la propiedad Constraints de 10s controles
ListBox.
object ListBoxl: TListBox
Constraints.MaxHeight = 400
Constraints,MinHeight = 200
Constraints.MinWidth = 1 5 0

Las restricciones dc tamaiio sc aplican solo cuando se modifica el tamaiio dc


10s controles, por eso, para que este programa funcionc de manera satisfactoria,
se debe dar a la propiedad Resizestyle de 10s dos divisores el valor rsupdate.
Estc valor indica quc la posicion del control se actualizara con cada movimiento
del divisor. no solo a1 final de la operacion. Si en su lugar se escoge el valor
rsLine o el nucvo valor rspattern, el divisor simplemente dibujara una
linca en la posicion solicitada. comprobando la propiedad MinSize per0 no las
rcstricciones dc 10s controles.
-

TRUCO:Cuando configuramos la propiedad Autosnap del componente


Splitter como T r u e , el divisor ocultara por cornpleto el control contiguo
cuando el tamaiio de dicho control sea inferior a1 rninimo establecido para
el componente Splitter.

Division en sentido horizontal


Tambien se puede usar el componente splitter para realizar una division
cn sentido horizontal, en lugar de la division predefinida en sentido vertical. Basi-
camente, podemos colocar un componente sobre un formulario, alinearlo con la
parte superior del formulario y, a continuacion, colocar el divisor en el formula-
rio. Por defecto, el divisor se alineara a la izquierda. Hay que escoger el valor
alTop para la propiedad Align y ya esta. Se puede ver un formulario con un
divisor horizontal en el ejemplo SplitH. Este programa posee dos componentes de
memo en 10s que podemos abrir un archivo, y tiene un divisor definido como:
object Splitterl: TSplitter
Cursor = crVSplit
Align = alTop
OnMoved = SplitterlMoved
end

El programa dispone de una barra de estado, que registra la altura actual de


10s dos componentes de memo. Controlamos el evento OnMoved del divisor (el
unico evento de este componente), para actualizar el texto de la barra de estado.
Este mismo codigo se ejecuta siempre que se adapte el tamaiio del formulario:
procedure TForml.SplitterlMoved(Sender: TObject);
begin
StatusBarl.Panels [0].Text : = Format ( ' U p p e r memo: %d - Lower
memo: % d ' ,
[Memoup-Height, MemoDown.Height1);
end;

Teclas aceleradoras
Desde Delphi 5, no se necesita aiiadir el caracter & a la propiedad Caption
de un elemento de menu, que proporciona una tecla aceleradora automatica si se
omite una. El sistema automatico de teclas aceleradoras de Delphi tambien puede
averiguar si hemos insertado teclas aceleradoras que resulten conflictivas y ajus-
tarlas sobre la marcha. Esto no significa que debamos dejar de aiiadir teclas
aceleradoras personalizadas con el caracter &, porque el sistema automatico uti-
liza sencillamente la primera letra disponible y no sigue 10s esthdares predefinidos.
Podriamos encontrar claves mnemotecnicas mejores que las elegidas por el sis-
tema.
Esta caracteristica es controlada por la propiedad Auto Hotkeys,disponi-
ble en el componente menu principal y en cada uno de 10s menus desplegables y
elementos del menu. En el menu principal, esta propiedad tiene de manera
predefinida el valor maAutomat i c , mientras que en 10s menus desplegables y
en 10s elementos del menu es maparent,de manera que el valor fijado para el
componente menu principal lo utilizaran de forma automatica todos 10s
subelementos, a no ser que tengan un valor especifico maAutomat i c o
maManua1.
El motor que se esconde tras este sistema es el metodo Re thin kHotkeys de
la clase TMenuItem y su compaiiero InternalRethinkHotkeys.Existe
tambien un metodo llamado RethinkLine s,que verifica si un menu desplega-
ble posee dos separadores consecutivos o comienza o termina con un separador.
En todos esos casos, se elimina automaticamente el separador.
Una de las razones por las que Delphi incluye esta caracteristica es el soporte
para traducciones. Cuando se necesita traducir el menu de una aplicacion, resulta
comodo no tener que trabajar con teclas aceleradoras o a1 menos no tener que
preocuparse de 10s posibles problemas entre dos elementos de un mismo menu. A1
tener un sistema que pueda resolver automaticamente problemas similares, conta-
mos en definitiva con una gran ventaja. Otro motivo era el propio IDE de Delphi.
Con todos 10s paquetes que se pueden cargar de forma dinamica, que instalan
elementos del menu en el menu principal del IDE o en 10s menus contextuales, y
con diferentes paquetes cargados en distintas versiones del producto, resulta casi
imposible conseguir teclas aceleradores no conflictivas en cada menu. Por esa
razon, este mecanismo no es un asistente que realiza un analisis estatico de 10s
menus en tiempo de diseiio, sino que se creo para resolver el problema real de la
administracion de 10s menus creados de forma dinamica en tiempo de ejecucion.
ADVERTENCIA: Esta caracteristica es realmente 6til, pero al estar acti-
vada por defecto, puede estropear el c6digo existente. Un problema puede
ser que si se usa el titulo en el cbdigo, 10s caracteres & adicionales pueden
romperlo. Aun asi, el cambio es bastante simple: todo lo que es necesario
hacer es establecer la propiedad AutoHot keys del componente menu
principal como maManual.

Sugerencias flotantes
Otro elemento habitual en las barras de herramienta es la sugerencia de la
barra, tambien llamada sugerencia flotante, un texto que describe brevemente el
boton que se encuentra en ese momento bajo el cursor. Este testo suele mostrarse
en un cuadro amarillo junto al cursor del raton que haya permanecido parado
durante un boton durante una cantidad de tiempo dada.
Para aiiadir sugerencias a la barra de herramientas de una aplicacion, sencilla-
mente hay que definir su propiedad ShowHints como T r u e y escribir texto
para la propiedad Hint de cada boton. Podria desearse habilitar las sugerencias
para todos 10s componentes de un formulario o para todos 10s botones de una
barra de herramientas o panel.
Si queremos tener mayor control sobre como aparecen las sugerencias, pode-
mos usar algunas de las propiedades y eventos del objeto Application. Este
objeto global tiene, entre otras; las siguientes propiedades:

El color de fondo de la ventana de sugerencia.


El tiempo que habra de mantenerse el cursor sobre
un componente antes de que aparezcan las suge-
rencias.
I HintHidePause EI tiempo durante el que se muestra la sugerencia.
Hintshortpause El tiempo que habra de esperar el sistema para
mostrar una sugerencia, si acaba de mostrar otra
distinta.

Un programa, por ejemplo, podria permitir que un usuario personalizase el


color de fondo de la sugerencia, seleccionando uno especifico mediante el siguien-
te codigo:
ColorDialog.Color : = Application.HintColor;
if ColorDialog.Execute then
Application.HintColor : = ColorDialog.Color;
Como alternativa, podemos cambiar el color de la sugerencia, controlando la
propiedad OnShowH i n t del objeto Appl i c a t i o n . Este controlador puede
cambiar el color de la sugerencia solo para controles especificos. El evento
OnShowHint se usa en el ejemplo CustHint.

Personalization de las sugerencias


A1 igual que se pueden aiiadir sugerencias a la barra de herramientas de una
aplicacion, se pueden aiiadir sugerencias a formularios o a 10s componentes de un
formulario. Para un control grande, la sugerencia aparecera cerca del cursor del
raton. En algunos casos, es importante saber que un programa puede personalizar el
mod0 en que se muestran las sugerencias. Una cosa que se puede hacer es modificar
el valor de las propiedades del objeto A p p l i c a t i o n . Para conseguir mayor con-
trol sobre las sugerencias, podemos personalizarlas mas asignando un metodo a1
evento OnShow H i n t de la aplicacion. Podemos engancharlas de forma manual o
(mejor) aiiadir el componente App 1i c a t i o nEve n t s a1 formulario y controlar
su evento OnShowHint. El metodo del gestor de eventos tiene algunos parametros
interesantes, como una cadena con el texto de la sugerencia, un indicador booleano
para su activation y una estructura T H i n t I n f o con mas informacion, como el
control, la posicion de la sugerencia y su color. Cada parametro se pasa mediante
referencia, de mod0 que tengamos la oportunidad de cambiarlos y tambien de modi-
ficar 10s valores de la estructura T H i n t I n f o . Por ejemplo, podemos cambiar la
posicion de la ventana de sugerencia antes de que aparezca.
Asi se ha hecho en el ejemplo CustHint, que muestra la sugerencia de la
etiqueta en el centro de su zona.
procedure TForml.ShowHint (var HintStr: string; var Canshow:
Boolean;
var Hint Inf o : THint Inf o) ;
begin
with HintInfo do
// s i e l c o n t r o l e s l a e t i q u e t a , r n u e s t r a l a s u g e r e n c i a e n
e l rnedio
if Hintcontrol = Label1 then
HintPos : = HintContro1.ClientToScreen (Point (
HintControl.Width div 2, HintControl.Height div 2 ) ) ;
end;

El codigo obtiene el centro del control generic0 (de H i n t I n f o .


H i n t c o n t r o 1 ) y despues convierte sus coordenadas a coordenadas de panta-
lla, aplicando el metodo C l i e n t To S c r e e n a1 propio control.
Ademas podemos actualizar el ejemplo CustHint de un mod0 diferente. El
control ListBos del formulario tiene algunos de 10s elementos de testo algo lar-
gos, asi que se podria desear mostrar el texto completo en una sugerencia mien-
tras que el raton se mueva sobre el elemento. Fijar una unica sugerencia para el
cuadro de lista no serviria, por supuesto. Una buena solucion es personalizar el
sistema de sugerencias proporcionando una sugerencia de manera dinamica que
se corresponda con el texto del elemento de la lista que se encuentre bajo el
cursor. Tambien se necesita indicar al sistema a que area pertenece la sugerencia,
para que al mover el cursor sobre la siguiente linea se muestra una nueva sugeren-
cia. Se puede realizar esto fijando el campo C u r s o r R e c t del registro
THi n t I nfo,que indica el area del componente sobre la que puede moverse el
cursor sin deshabilitar la sugerencia. Cuando el cursor sale de dicha zona, Delphi
oculta la ventana de sugerencia. Este es el fragment0 de codigo relacionado que se
ha aiiadido al metodo ShowHint:
else i f Hintcontrol = ListBoxl then
begin
nItem : = ListBoxl.ItemAtPos(
Point (CursorPos.X, CursorPos. Y) , True) ;
i f nItem >= 0 then
begin
// e s t a b l e c e l a cadena d e s u g e r e n c i a
HintStr : = ListBoxl. Items [nItem];
// determina el drea d e v a l i d e z d e l a sugerencia
CursorRect : = ListBoxl. ItemRect (nItem);
/ / se r n u e s t r a s o b r e e l e l e r n e n t o
HintPox : = HintControl.ClienteToScreen (Point ( 0 ,
ListBoxl.ItemHeight * (nItem - ListBoxl.TopIndex)));
end
else
Canshow : = False;
end :

El resultado final es que cada linea del cuadro de lista parece tener una suge-
rcncia especifica, como muestra la figura 5.10. La posicion de la sugerencia se
calcula de tal manera que cubra el testo del elemento actual, extendiendose mas
alla del borde del cuadro de lista.

Selection sequence: - a as in apple and


apples - b as In borland barbarians ~ l e z

Figura 5.10. El control ListBox del ejemplo CustHint rnuestra una sugerencia
diferente, dependiendo del elemento de la lista sobre el que se encuentre el raton.

Estilos y controles dibujados por el propietario


En Windows, el sistema es normalmente el responsable de dibujar botones,
cuadros de lista, cuadros de edicion, elementos del menu y elementos similares.
Basicamente, dichos controles saben como dibujarse. Sin embargo, como alterna-
tiva, cl sistema permite que el propietario de estos controles, un formulario por lo
general, 10s dibuje. Esta tecnica, disponible para botones, cuadros de lista, cua-
dros combinados y elementos de menu, se denominaowner-draw (dibujo por par-
te del propietario).
En la VCL, la situacion es ligeramente mas compleja. Los componentes pue-
den encargarse de dibujarse a si mismos en este caso (como en la clase T B i t Btn
para 10s botones de mapas de bits) y posiblemente de activar 10s eventos corres-
pondientes. El sistema envia la solicitud para dibujar al poseedor (normalmente el
formulario) y el formulario reenvia el evento de nuevo al control adecuado, acti-
vando sus controladores de eventos. En CLX, algunos de estos controles, como
ListBoxes y ComboBoxes, presentan eventos aparentemente muy similares a la
tecnica de dibujo por parte del propietario de Windows, pero 10s menus no 10s
tienen. El enfoque nativo de Qt consiste en usar estilos para establecer el compor-
tamiento grafico de todos 10s controles del sistema, de una aplicacion concreta o
de un control dado.
1
NOTA: La mayoria de . b s contmles cardm e s de Win32'poseen soporte
peta La t b i c a ownerdraw,d e n o m i d pc lo general dibujo personaliza-
". . -
do, Se puede particul&zar la aprwicda' de una ListView, TreeYiew,
TabCmtrol, Pagecontrol, HeaderConW,-swtus~aro I oolaar. LOS con-
". I
.. .
treks TdBar, ListView y T m V i e w tambih Soportan un mod0 avanzado
de dibqjb personalizado, una capacidad de dibujo mas ajustada introducida
por Microsoft en las riltimas versibdebde.la.b~b1iotecadc controles comu-
nes de Win32. El inconveniente de e s t a t h i c a es que a1 cambiar el cstilo de
la inter& de usuario de Windows. en ql fbfuro (y sicmpre succde), 10s
c o ~ ~ t r ~dibujados
les por el propidaria, que cncajan a la perfection en 10s
estibs de interfaz de usuario actual&. p a r e c e r h dcsfasados y 'hcra de
kg?. Como- - estamos creando una interfae de usuario ~ersonalizada.
r sera
nece*d o que la actualicemos nosbtros misrnos. Por I contraste. si sc usa la C

aparicmcia e s t h d a r de 10s contmlesr, lw appcaciones


appcac se adaptwan a ona
nueva1 v e r s i h de estos controlo9.
.. . i\ .-

Elementos del menu dibujados por el usuario


Gracias a la VCL, el desarrollo de elementos del menu graficos resultan bas-
tante sencillo en comparacion con el enfoque tradicional de la API de Windows:
dcfinimos la propiedad OwnerDraw de un componente elcmento del menu como
True y controlamos sus eventos OnMeasureItem y OnDrawItem. En el
evento OnMeasure I t em, se puede establecer el tamaiio de 10s elementos del
menu. Este controlador de eventos se activa una vez para cada elemento del menu,
cuando aparece el menu desplegable, y tiene dos parametros de referencia que
podemos configurar: W i d t h y H e i g h t . En el evento OnDraw I t em, dibujamos
la imagen real. Este controlador de eventos se activa cada vez que hay que volver
a dibujar el elemento. Esto ocurre cuando Windows muestra por primera vez 10s
elementos y cada vez que cambia su estado, por ejemplo, cuando el raton se
mueve sobre un elemento, deberia de aparecer resaltado.
Para dibujar 10s elementos del menu, tenemos que considerar todas las posibi-
lidades, como dibujar 10s elementos resaltados con colores especificos, dibujar
una marca de verificacion si fuese necesario, etc. Por suerte, el evento Delphi
pasa a1 controlador el objeto c a n v a s en que deberia pintarse, el rectangulo de
salida y el estado del elemento (si esta seleccionado o no). En el ejemplo ODMenu,
se controla el color de resaltado, per0 se omiten otros aspectos avanzados (corno
las marcas de verificacion). Se ha fijado la propiedad OwnerDraw del menu y se
han escrito controladores para algunos de 10s elementos del menu. Para escribir
un controlador unico para cada evento de 10s tres elementos del menu relaciona-
dos con el color, hemos configurado su propiedad Tag con el valor del color en el
controlador de eventos o n c r e a t e del formulario. Esto hace que el controlador
del actual evento o n c l i c k de 10s elementos resulte bastante sencillo:
procedure TForml.ColorClick(Sender: TObject);
begin
ShapeDemo.Brush.Co1or : = (Sender as TComponent).Tag
end ;

El controlador del evento O n M e a s u r e I t e m no depende de 10s elementos


reales, sin0 que emplea valores fijos (diferentes del controlador del otro menu
desplegable). La parte mas importante del codigo esta en 10s controladores de 10s
eventos OnDrawItem.
Para el color, empleamos el valor de la etiqueta (tag)para dibujar un rectangu-
lo del color dado, como muestra figura 5.11. Sin embargo, antes de hacer esto,
hemos de rellenar el fondo de 10s elementos del menu (la zona rectangular que se
pasa como un parametro) con el color esthdar para el menu (clMenu) o 10s
elementos del menu seleccionados ( c l H i g h l i g h t ) :
procedure TForml.ColorDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean) ;
begin
// f i j a e l c o l o r d e l fondo y l o p i n t a
i f Selected then
ACanvas.Brush.Color : = clHighlight
else
ACanvas.Brush.Co1or : = clMenu;
ACanvas. FillRect (ARect);
// m u e s t r a e l c o l o r
ACanvas.Brush.Color : = (Sender as TComponent).Tag;
Inf lateRect (ARect, -5, -5) ;
ACanvas.Rectangle (ARect.Left, ARect.Top, ARect.Right,
ARect .Bottom) ;
end;
Figura 5.11. El menu dibujado por el propietario del ejemplo ODMenu.

Los tres controladores para este evento de 10s elementos del menu desplegable
Shape son todos ellos distintos, aunque usan un codigo similar:
p r o c e d u r e TForml.EllipselDrawItem(Sender: TObject; ACanvas:
TCanvas ;
ARect : TRect; Selected: Boolean) ;
begin
// f i j a e l c o l o r d e l f o n d o y l o p i n t a
i f Selected then
ACanvas.Brush.Color : = clHighlight
else
ACanvas.Brush.Color : = clMenu;
ACanvas .FillRect (ARect);
// d i b u j a l a e l i p s e
ACanvas.Brush.Co1or : = clwhite;
InflateRect (ARect, -5, -5) ;
ACanvas.Ellipse (ARect.Left, ARect.Top, ARect.Right,
ARect .Bottom) ;
end;

-- .. .
NOTA: Para acomodar el cada vez mayor nhero de egtados en el estiko de
interfaz de usuario de Windows 2000 , belphi inahye el evento
OnAdvancedDraw I tern para 10s menu:

Una ListBox de colores


Los cuadros de lista tienen tambien una capacidad de dibujo personalizado,
que significa que un programa puede pintar 10s elementos de un cuadro de lista.
Este mismo soporte se ofrece en el caso de 10s cuadros combinados y tambien esta
disponible en la CLX. Para crear un cuadro de lista dibujado por el propietario,
configuramos su propiedad Style como 1bOwnerDrawFixed o IbOwner-
Drawvariable. El primer valor indica que vamos a configurar la altura de 10s
elementos del cuadro de lista estableciendo la propiedad ItemHeight y quc
esta sera la a h a de todos 10s elementos. El segundo estilo de dibujo personaliza-
do indica un cuadro dc lista con elementos de diferentes alturas. En este caso, cl
componentc desencadenara el evento OnMeasure It em de cada elemento, para
pedir a1 programa por sus alturas.
En el ejemplo ODList (y en su version QODList), nos quedaremos con cl
primcr enfoque, el mas sencillo. El cjcmplo contiene informacion sobre el color
junto con 10s elementos dcl cuadro dc lista y, a continuacion, dibuja 10s elementos
usando cstos colores (en lugar de usar un unico color para toda la lista).
El archivo DFM o XFM de todo formulario, como este entrc otros, tiene un
atributo TextHeight, que indica cl numero de necesarios para mostrar texto.
Estc cs el valor que deberiamos usar para la propiedad ItemHeight del cuadro
de lista. Una solucion alternativa consiste en calcular cstc valor en tiempo de
cjccucion, de forma que si mas tarde cambiamos la fuentc en tiempo de diseiio, no
tcngamos que recordar configurar la altura de 10s elcmentos en funcion de la
misma.

NOTA: Acabaq~psd e s c r i b i r Text Height como un atributo dcl for-


mulario, no toma"una propiedad. No se trata de una propiedad, sino de un
valor 1 4 dtsl fmulario, Si no as propiedad, cabria preguntarse cirmo es
queBelphi k Haida en-&arch*! DFM.La respuesta es que el mecanismo
de streamhg.deDelphi se basa en propiedades m i s unos clones especiales
Ile propjabdes krcadospor cl mctodo Def ineproperties.

Dado quc TextHeight no es una propiedad, aunque aparece en la lista de la


descripcion del formulario, no podemos acceder a el directamente. A1 estudiar el
codigo fuente dc la VCL, se ve que este valor se calcula mediante una llamada a
un metodo privado del formulario: GetTextHeight.A1 ser privado, no pode-
mos llamar a esta funcion, pero podemos duplicar su codigo dentro del metodo
Formcreate dcl formulario, tras haber seleccionado la fuentc dcl cuadro dc
lista:
Canvas.Font : = ListBoxl.Font;
ListBoxl. IternHeight := Canvas .TextHeight ( ' 0 ' ) ;

Lo siguiente es aiiadir algunos elementos a1 cuadro de lista. Como este es un


cuadro de lista de colores, queremos aiiadir nombres de colores a1 Items del
cuadro de lista y 10s valores de color correspondientes a1 almacenamiento de
datos Objects relacionado con cada elemcnto dc la lista. En lugar de aiiadir 10s
dos valores por separado, hemos escrito un procedimiento para aiiadir nuevos
elementos a la lista:
procedure T0DListForm.AddColors (Colors: array of TColor);
var
I: Integer;
begin
f o r I : = L o w (Colors) t o High (Colors) d o
ListBoxl.Items.Add0bject (ColorToString ( C o l o r s [ I ] ) ,
TObject ( C o l o r s [ I ]) ) ;
end ;

Este mctodo usa un parametro dc matriz abierta, una matriz de un numero no


dcterminado de clementos del mismo tipo. Para cada elemento pasado como
paramctro, aiiadimos el nombre dcl color a la lista y aiiadimos su valor a 10s datos
rclacionados, llamando a1 metodo AddOb je c t . Para obtener la cadcna corres-
pondicnte al color, llamamos a la funcion Delphi C o l o r T o S t r i n g . ~ s t de-
a
vuelve una cadcna que contiene la constante de color correspondiente, si existe, o
cl valor hexadecimal del color. Los datos de color se aiiaden a1 cuadro de lista
despues de comprobar su valor de acuerdo con el tipo de datos TOb j e c t (una
referencia de cuatro bytcs), segun lo requiere el metodo AddOb je c t .

TRUCO: Ademas de ColorToStr ing, que convierte un valor de color


en la correspondiente cadena con el identificador o el valor hexadecimal, la
funcion StringToColor de Delphi convierte una cadena de formato
apropiado en un color.

En el cjemplo ODList; este metodo se llama en el controlador de eventos


o n c r e a t e del formulario (despues de fijar la a h a de 10s elementos):
Addcolors ([clRed, clBlue, clYellow, clGreen, clFuchsia,
cllime, clpurple,
clGray, RGB (213, 23, 123), RGB (0, 0, O), clAqua, clNavy,
clOlive, clTeal] ) ;

Para compilar la version CLX de este codigo, hemos aiiadido la funcion RGB
descrita anteriormente. El codigo usado para dibujar 10s elementos no es especial-
mente complejo. Sencillamente obtenemos el color asociado con el elemento, lo
establecemos como el color de la fuente y despues dibujamos el testo:
p r o c e d u r e TODListForm.ListBoxlDrawItem(Control: Twincontrol;
Index: Integer;
Rect : TRect; State: TOwnerDrawState) ;
begin
w i t h Control as TListbox d o
begin
// elimina
Canvas. FillRect (Rect);
// dibuja el elemento
Canvas.Font.Color : = TColor ( I t e m s - O b j e c t s [Index]);
Canvas.TextOut(Rect.Left, R e c t - T o p , Listboxl.Items[Index]);
end ;
end :
El sistema ya establece el color de fondo adecuado, de mod0 que el elemento
seleccionado aparezca de forma adecuada, aunque no aiiadamos codigo adicional.
Aun mas, el programa permite aiiadir nuevos elementos a1 hacer doble clic sobre
el cuadro de lista:
procedure TODListForm.ListBoxlDblClick(Sender: TObject);
begin
i f ColorDialogl.Execute t h e n
Addcolors ([ColorDialogl.Color]);
end;

Si se intenta usar esta capacidad, se vera que algunos de 10s colores aiiadidos
se transforman en nombres de color (una de las constantes de color de Delphi),
mientras que otros se convierten en numeros hexadecimales.

Controles ListView y TreeView


Ya hemos presentado 10s diversos controles visuales que se pueden usar para
mostrar listas de valores. Los componentes estandar de cuadro de lista y de cua-
dro combinado son todavia muy comunes, per0 normalmente se sustituyen por 10s
controles mas potentes de ListView y TreeView. De nuevo, estos dos controles
forman parte de 10s controles comunes de Win32, guardados en la biblioteca
ComCt l 3 2 . DLL. En Qt y VisualCLX existen controles similares, tanto en
Windows como en Linux.

Una lista de referencias grafica


Cuando usamos un componente L i s t v i e w , podemos proporcionar mapas de
bits que indiquen el estado del elemento (por ejemplo, el elemento seleccionado) y
que describan 10s contenidos del elemento de un mod0 grafico.
Para conectar las imagenes a una lista o a un arbol, hay que recurrir a1 compo-
nente I m a g e L i s t que ya hemos empleado para las imagenes del menu. Un
ListView puede tener en realidad tres listas de imagenes: una para 10s iconos
grandes (la propiedad L a r g e I m a g e s ) , una para 10s iconos pequeiios (la propie-
dad S m a l l I m a g e s ) y otra para el estado de 10s elementos (la propiedad
s t a t e I m a g e s ) . En el ejemplo RefList, se han fijado las dos primeras propie-
dades empleando dos componentes I m a g e L i s t diferentes.
Cada uno de 10s elementos de ListView tiene un I m a g e I n d e x que se refiere
a su imagen en la lista. Para que esta tecnica funcione como es debido, 10s ele-
mentos de las dos listas de imagenes deberian seguir el mismo orden. Cuando
tengamos una lista de imagenes fija, podemos aiiadirle elementos usando el
ListView Item Editor de Delphi, que se encuentra conectado a la propiedad I t e m s .
En este editor, podemos definir elementos y tambien subelementos. Los
subelementos aparecen solo en la vista detallada (cuando configuramos el valor
vs R e p o r t de la propiedad Views t y l e ) y estan conectados con 10s titulos

Ft
definidos en la propiedad C o l u m n s :

W bbb

-
ADVERTENCIA: El conti01 Listview no tieno ep CLX fas vjstqs de icom
pequdoa y g r a d e s . En Qt,este tip0 de apariencia esddisponi.ble graciaa a
otro a m p a e f i t e . el IcanView. -

En el ejemplo RefList (una simple lista de referencias a libros, revistas,


CD-ROM y sitios Web), 10s elementos se almacenan en un archivo, puesto que
10s usuarios del programa pueden editar 10s contenidos de la lista, que se guardan
automaticamente cuando se abandona el programa. De este modo, las ediciones
que realiza el usuario se convierten en permanentes. Guardar y cargar 10s conte-
nidos de un ListView no son tareas insignificantes en absoluto, puesto que el tip0
TList I t e m s no dispone de un mecanismo automatico para guardar datos. Como
tCcnica alternativa sencilla, hemos copiado 10s datos en una lista de cadena, usan-
do un formato personalizado. A continuacion, la lista de cadena se puede guardar
en un archivo y cargar de nuevo con una unica orden
El formato de archivo es sencillo, como se puede ver en el siguiente codigo.
Para cada elemento de la lista, el programa guarda el titulo en una linea, el indice
de la imagen en otra linea (que lleva como prefijo el caracter @) y 10s subelementos
en las lineas siguientes, sangradas por un caracter de tabulacion:
procedure TForml.FormDestroy(Sender: TObject);
var
I, J : Integer;
List: TStringList;
begin
// almacena 10s elementos
List : = TStringList-Create;
try
for I : = 0 to ListViewl.1tems.Count - 1 do
begin
// g u a r d a e l t i t u l o
List .Add (ListViewl.Items [I] .Caption) ;
// g u a r d a e l i n d i c e
List .Add ( ' @ ' + IntToStr (ListViewl.Items [I].ImageIndex) ) ;
// g u a r d a 10s s u b e l e m e n t o s ( s a n g r a d o s )
for J : = 0 to ListViewl.Items[I].SubItems.Count - 1 do
List-Add (#9 + ListViewl. Items [I].SubItems [J]);
end;
List.SaveToFile (ExtractFilePath (App1ication.ExeName) +
I t e m s . txt ') ;
finally
List.Free;
end;
end;

A continuacion, 10s elementos se cargan de nuevo en el metodo Formcreate:


procedure TForml.FormCreate(Sender: TObject);
var
List: TStringList;
NewItem: TListItem;
I: Integer;
begin
// detiene el mensaje d e advertencia
NewItem : = nil;
// c a r g a 10s e l e m e n t o s
ListViewl.1tems.Clear;
List : = TStringList.Create;
try
List.LoadFromFi1e (
ExtractFilePath (App1ication.ExeName) + 'Items
for I : = 0 to List .Count - 1 do
i f List [I] [I] = # 9 then
NewItem.SubItems .Add (Trim (List [I]) )
else i f List [I] [I] = ' @ I then
NewItem.ImageIndex : = StrToIntDef (List [I][
else
begin
// u n n u e v o e l e m e n t o
NewItem : = ListViewl.Items.Add;
NewItem.Caption : = List [I] ;
end;
finally
List.Free;
end;
end;

El programa posee un menu que podemos emplear para escoger una de las
distintas vistas que soporta el control ListView y para aiiadir casillas de verifica-
cion a 10s elementos, como en un control CheckListBox. Se pueden ver las distin-
tas combinaciones de estos estilos en la figura 5.12.
Otra caracteristica importante, que es habitual en la vista detallada o de infor-
me del control, consiste en dejar que un usuario clasifique 10s elementos de una de
las columnas. En la VCL, esta tecnica requiere tres operaciones. La primera es
cstablecer la propiedad SortType de ListView como st Both o st Data. De
este modo, la ListView realizara la clasificacion no basandose en 10s titulos, sin0
llamando a1 evento Oncompare de cada uno de 10s dos elementos que ha de
clasificar.

Fle View Heb I

I-
Borland
Develo..
Delohi Delohi Delahi
ClienIfS ... Develo ... Informant
Masterinq
Delohi
The
Delphi ... I
in Java fib ~ i mr ~ l p
Borland Develooers Conference ...
.:-Delohi ClienVServer
00 Pelohi Develooer's Handbook
n ~ $ + D e l o hInformant
i
nQ Mastering D e b h i
n & T h e D e b h i Maaanne
q -Thinkina in Java
OW marco@marcocantu.com
OQwww.borland.com
O.dwww.marcocanlu.com

'igura 5.12. Diferentes ejemplos de las combinaciones de estilos de un componente


ListView en el programa RefList, obtenidos al cambiar la propiedad Viewstyle y
anadir las casillas de verificacion.

Como queremos realizar la clasificacion de cada una de las columnas de la


vista dctallada, tambien controlamos el evento OnColumnClick (quc ocurre
cuando el usuario hace clic sobre 10s titulos de la columna cn la vista detallada,
pero solo si la propiedad ShowColumnHeaders esta definida como True).
Cada vez que hacemos clic sobre una columna, el programa guarda el numero de
la misma en el campo privado nsortcol de la clase de formulario:
p r o c e d u r e TForml.ListViewlColumnClick(Sender: TObject;
Column: TListColumn);
begin
nSortCol : = Column.Index;
ListViewl.AlphaSort;
end;

A continuation; en el tercer paso, el codigo de clasificacion usa el titulo o uno


de 10s subelementos segun la columna de clasificacion en uso:
procedure TForml.ListViewlCompare(Sender: TObject;
Iteml, I tem2 : TListItem; Data: Integer; var Compare: Integer) ;
begin
i f nSortCol = 0 t h e n
Compare : = CompareStr (Iteml.Caption, Item2.Caption)
else
Compare : = CompareStr (1teml.SubItems [nSortCol - 11,
Item2 .SubItems [nSortCol - 11 ) ;
end;

En la version CLX del programa (llamada QRefList) no es necesario seguir


ninguno de estos pasos. El control ya es capaz de realizar la clasificacion por si
mismo cuando se hace clic sobre su titulo. Automaticamente se consiguen varias
columnas que se auto-ordenan (tanto ascendente como descendentemente).
Las caracteristicas finales que hemos afiadido a1 programa estan relacionadas
con las operaciones de raton. Cuando el usuario hace clic con el boton izquierdo
del raton sobre un elemento, el programa RefList muestra una descripcion del
elemento seleccionado. A1 hacer clic con el boton derecho del raton sobre el ele-
mento seleccionado, este pasa a su mod0 edicion y el usuario puede cambiarlo
(tengamos en cuenta que 10s cambios se guardan automaticamente cuando finali-
za el programa).
Veamos el codigo utilizado para ambas operaciones, en el controlador del
evento OnMouseDown del control ListView:
p r o c e d u r e TForml.ListViewlMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
strDescr: string;
I: Integer;
begin
// s i h a y u n e l e m e n t o s e l e c c i o n a d o
i f ListViewl.Se1ected <> n i l t h e n
i f Button = &Left then
begin
/ / crea y muestra una d e s c r i p c i o n
strDescr : = ListViewl.Columns [0] .Caption + # 9 +
ListViewl.Se1ected.Caption + #13;
f o r I : = 1 to ListViewl.Selected.SubItems.Count d o
strDescr : = strDescr + ListViewl.Columns
[I] .Caption + # 9 +
ListViewl.Selected.SubItems [I-l] + #13;
ShowMessage (strDescr);
end
e l s e i f Button = &Right then
/ / edita e l t i t u l o
ListViewl.Se1ected.EditCaption;
end;

Aunque este ejemplo no incluye todas las caracteristicas, muestra parte del
potencial del control ListView. Tambien hemos activado la caracteristica de "se-
guimiento activo", que permite que la vista en lista resalte y subraye el elemento
que se encuentra bajo el raton. Las propiedades mas relevantes de ListView pode-
mos verlas en su descripcion textual:
object ListViewl: TListView
Align = alClient
Columns = <
item
Caption = 'Referencia '
Width = 2 3 0
end
item
Caption = 'Autor'
Width = 1 8 0
end
item
Caption = 'Pais '
Width = 8 0
end>
Font.Height = - 1 3
Font .Name = 'MS Sans Serif'
Font.Style = [fsBold]
FullDrag = True
Hideselection = False
HotTrack = True
HotTrackStyles = [htHandPoint, htUnderlineHot]
SortType = stBoth
Viewstyle = vsList
OnColumnClick = ListViewlColumnClick
Oncompare = ListViewlCompare
OnMouseDown = ListViewlMouseDown
end

Para crear la version CLX de este ejemplo, QRefList, hub0 que emplear solo
una de las listas de imagenes y desactivar 10s menus de imagenes pequeiias e
imagenes grandes, dado que ListView esta limitado a 10s estilos de vista detallada
y en lista.
Los iconos grandes y pequeiios estan disponibles en un control diferente, deno-
minado Iconview. Como ya se comento, el soporte de clasificacion ya se encuen-
tra ahi, lo que podria haber ahorrado la mayoria del codigo de este ejemplo.

Un arbol de datos
Ahora que hemos visto un ejemplo basado en ListView, podemos examinar el
control TreeView (arbol de datos). El TreeView posee una interfaz de usuario que
es flexible y potente (con soporte para editar y arrastrar elementos). Tambien es
estandar, porque es la interfaz de usuario del explorador de Windows. Existen
propiedades y diversos modos de personalizar el mapa de bits de cada linea o de
cada tipo de linea.
Para definir la estructura de nodos del TreeView en tiempo de diseiio, podemos
emplear el editor de propiedades TreeView Items:
de
(3 eded
a rkd
. .

Sin cmbargo, cn este caso, hcmos decidido cargarlo en 10s datos del TreeView
a1 arrancar, de un mod0 similar a1 del ultimo ejemplo.
La propicdad Items del componente TrccView time muchas funciones mieni-
bro que podcmos emplear para modificar la jerarquia de cadenas. Por ejcmplo,
podemos crcar un arb01 de dos niveles con las siguientes lineas:
var
Node: TTreeNode;
begin
Node : = TreeViewl. Items .Add (nil, 'First level');
TreeViewl. Items .Addchild (Node, I Second level1) ;

Utilizando cstos dos metodos (Add y Addchild), podemos crear una com-
pleja estructura en tiempo de ejecucion. Para cargar la informacion, podemos
emplear dc nuevo una StringList en tiempo de ejecucion, cargar un archivo de
testo con la informacion y analizar la estructura gramaticalmente.
Sin embargo. dado que el control Treeview posee un mdtodo LoadFromFile,
10s ejemplos DragTree y QDragTree utilizan el siguiente codigo, mucho mas
sencillo:
procedure TForml.FormCreate(Sender: TObject);
begin
TreeViewl.LoadFromFile (ExtractFilePath
(Application.ExeName) +
'TreeText. txt ' ) ;
end;

El mCtodo LoadFromFile carga 10s datos en una lista de cadena y verifica


el nivel de cada elemento fijandose en el numero de caracteres de tabulacion. (Si
siente curiosidad, fijese en el metodo TTreeStrings .Get Buf Start que
puede encontrarse en la unidad ComCtrls en el codigo fuente de la VCL incluida
en Delphi.) Los datos que hemos preparado para TrccView corrcsponden a1 orga-
nigrama dc una cmpresa multinacional. Los datos preparados para el TrceView
forman cl diagrama de la organizacion de una emprcsa multinacional, como muestra
la figura 5 . 1 3 .

3 US Hsadquarle~s
Q Board d Dnectors

-
Mon~caRoss
Sales
Fat E j s t
Steve Fubens
Palls
Tck1o
Sirmoilre
Terty Merks

Q Sidney
John Calgary
Matk R m n
!3 J o h Rcitsr
Ian Green
El AdmLi~sl~ation

Figura 5.13. El ejernplo DragTree despues de cargar 10s datos y expandir las ramas.

En lugar dc cspandir 10s elementos de 10s nodos uno a uno. tambien se puedc
usar cl mcnu File>Expand All de este programa. quc llama a1 metodo
FullExpand del control TreeView o e.jecuta el codigo cquivalente (en este caso
especifico dc un arb01 con un elemento raiz):
TreeViewl. Items [0] .Expand ( T r u e );

Ademas de cargar 10s datos, el programa 10s guarda a1 finalizar y asi hacc que
10s cambios Sean permanentes. Tambien hay unos cuantos elementos de menu
para personalizar la fucnte del control TreeView y modificar otros sencillos
paramctros. La caracteristica especifica implementada en cste ejemplo es el so-
porte para arrastrar elementos y subarboles enteros. Hemos definido la propiedad
DragMode del componente como dmAutomatic y escrito 10s controladores de
eventos para 10s eventos OnDragOver y OnDragDrop.
En el primer0 de 10s dos controladores, el programa se asegura que el usuario
no esta intentando arrastrar un elemento sobre un elemento hijo (que seria despla-
zado junto con el elemento y originaria una repeticion infinita):
p r o c e d u r e TForml.TreeViewlDragOver(Sender, Source: TObject;
X, Y: Integer; State: TDragState; var Accept: Boolean);
var
TargetNode, SourceNode: TTreeNode;
begin
TargetNode : = TreeViewl GetNodeAt (X, Y ) ;.
/ / a c e p t a a r r a s t r e d e s d e e l mismo
i f (Source = Sender) a n d (TargetNode <> nil) t h e n
begin
Accept : = True;
/ / determina origen y destino
SourceNode : = TreeViewl-Selected;
// busca la cadena padre destino
while (TargetNode.Parent <> nil) and (TargetNode <>
SourceNode) d o
TargetNode : = TargetNode.Parent;
/ / si se encuentra el origen
if TargetNode = SourceNode then
/ / no permite el arrastre a un hijo
Accept : = False;
end
else
Accept : = False;
end;

El efecto conseguido por este codigo es que (a escepcion del caso concreto que
se necesita evitar) un usuario puede arrastrar un elemento del TreeView a otro. Es
sencillo escribir el codigo necesario para desplazar 10s elementos, porque el con-
trol TreeView ofrece soporte para dicha operation, mediante el metodo MoveTo
de la clase TTreeNode.
procedure TForml.TreeViewlDragDrop(Sender, Source: TObject; X,
Y: Integer) ;
var
TargetNode, SourceNode: TTreeNode;
begin
TargetNode : = TreeViewl .GetNodeAt ( X , Y) ;
if TargetNode <> nil then
begin
SourceNode : = TreeViewl.Selected;
SourceNode.MoveTo (TargetNode, naAddChildFirst);
TargetNode .Expand (False);
TreeViewl.Selected : = TargetNode;
end;
end;
- - - - - - . - - - -- - - --- -- - --

NOTA: Entre las demos incluidas con Delphi, hay una muy interesante que
muestra un control TreeView de dibujo personalizado. El ejemplo se en-
cuentra en el subdirectorio CustomDraw.

La version adaptada de DragTree


Y a que este programa puede usarse en demostraciones de adaptacion y
portabilidad, hemos creado una version que se puede compilar como una aplica-
cion VCL nativa con Delphi y como una aplicacion CLX con Kylix. Esto es algo
distinto de la mayor parte de 10s programas de este libro, que pueden adaptarse a
Delphi usando VisualCLX y tambien Qt sobre Windows. Seguir un camino dis-
tinto de vez en cuando puede resultar interesante. Lo primer0 que resulta necesa-
rio hacer es usar dos conjuntos distintos de sentencias uses,mediante la compi-
lacion condicional. La unidad del ejemplo PortableDragTree comienza de esta
manera:
unit TreeForm;

interface

uses
SysUtils, Classes,

{SIFDEF LINUX]
Qt, Libc, QGraphics, QControls, QForms, QDialogs,
QStdCtrls, QComCtrls, QMenus, QTypes, QGrids;
{SENDIF]

{ S I F D E F MSWINDOWS]
Windows, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, Menus, Grids;
{SENDIF)

Una directiva condicional similar se usa en la seccion inicial de la implemen-


tacion, para incluir el archivo de recursos adecuado para el formulario (10s dos
archivos de recursos son distintos):
{ $ IFDEF LINUX I
{ $ R * .xfm)
{SENDIF]

{ $ IFDEF MSWINDOWS]
{SR *.dfm]
{SENDIF]

He omitido algunas de las caracteristicas especificas de Windows, de manera


que la unica diferencia en el codigo se encuentra en el metodo FormCreate.El
programa carga el archivo de datos de la carpeta predefinida del usuario, no de la
misma carpeta que el ejecutable. Segun el sistema operativo del que se trate, la
carpeta del usuario sera el directorio home (y el archivo oculto comienza por un
punto) o el area especifica Mis Documentos (accesible con una llamada espe-
cial de la API):
procedure TForml.FormCreate (Sender: TObject) ;
var
path: string:
begin
{ $ IFDEF LINUX]
filename : = GetEnvironmentVariable ( ' H O M E ' ) + '/
.TreeText.txtt;
{$ELSE1
SetLength (path, 100) ;
ShGetSpecialFolderPath (Handle, P C h a r ( p a t h ) ,
CSIDL-PERSONAL, False) ;
path : = PChar (path); // cadena d e longitud fija
filename : = path + ' \TreeText.txt'
{SENDIP}
TreeViewl.LoadFromFile (filename);
end ;

Nodos de arbol personalizados


Delphi 6 aiiadio unas cuantas caracteristicas nuevas a 10s controles Treeview,
como l a seleccion multiple (veanse las propiedades M u 1t i S e 1e c t y
M u l t i s e l e c t S t y l e , y la matriz s e l e c t i o n s ) , una clasificacion mejorada
y diversos eventos nuevos. Sin embargo, la mejora clave es permitir que el pro-
gramador determine la clase de 10s elementos de nodo de la vista en arbol. Tener
elementos de nodo personalizados implica la capacidad de adjuntar datos
personalizados a 10s nodos de un mod0 simple, orientado a objetos. Para soportar
esta tecnica, existe un nuevo metodo A d d N o d e para la clase T T ree I t e m s y un
nuevo evento especifico, O n C r e a t e N o d e s C l a s s . En el controlador de este
evento, devolvemos la clase del objeto que se va a crear, que habra de heredar de
TTreeNode.
Como esta tecnica es muy comun, hemos creado un ejemplo para explicarla de
forma pormenorizada. El ejemplo CustomNodes no se centra en un caso del
mundo real, sin0 que ilustra una situacion bastante compleja, en la que existen
dos clases de nodos de arbol personalizados diferentes, derivados el uno del otro.
La clase basica aiiade una propiedad E x t r a C o d e , proyectada a metodos virtuales,
y la subclase sobrescribe uno de esos metodos. En el caso de la clase basica, la
funcion G e t E x t r a C o d e devuelve sencillamente el valor, mientras que en el de
la clase derivada, el valor se multiplica por el valor del nodo padre. Veamos las
clases y este segundo metodo:
type
TMyNode = c l a s s (TTreeNode)
private
FExtraCode: Integer;
protected
p r o c e d u r e SetExtraCode (const Value: Integer) ; virtual;
function GetExtraCode: Integer; virtual;
public
property ExtraCode: Integer r e a d GetExtraCode w r i t e
SetExtraCode;
end ;

TMySubNode = class (TMyNode)


protected
f u n c t i o n GetExtraCode: Integer; override;
end ;
f u n c t i o n TMySubNode.GetExtraCode: Integer;
begin
Result : = fExtraCode * (Parent a s TMyNode) .Extracode;
end:

A1 tener estas clases de nodo de arbol personalizadas, el programa crea un


arbol de elementos, usando el primer tipo para 10s nodos del primer nivel y la
segunda clase para 10s otros nodos. Como solo tenemos un controlador de eventos
O n C r e a t e N o d e C l a s s , este utiliza la referencia de clase almacenada en un
campo privado del formulario ( C u r r e n t N o d e C l a s s del tipo T T r e e N o -
declass):
p r o c e d u r e TForml.TreeViewlCreateNodeClass(Sender:
TCustomTreeView;
v a r NodeClass: TTreeNodeClass);
begin
NodeClass : = CurrentNodeClass;
end;

El programa establece esta referencia de clase antes de crear nodos para cada
tipo, por ejemplo, con un codigo como el siguiente:
var
MyNode : TMyNode ;
begin
CurrentNodeClass : = TMyNode;
MyNode : = TreeViewl. Items .Addchild (nil, 'item' + IntToStr
(nValue)) as TMyNode;
MyNode.ExtraCode : = nValue;

Cuando se ha creado el arbol completo, en el momento en que el usuario


selecciona un elemento, podemos convertir su tipo a TMyNode y acceder a las
propiedades adicionales (pero tambien a metodos y datos):
p r o c e d u r e TForml.TreeViewlClick(Sender: TObject);
var
MyNode: TMyNode;
begin
MyNode : = TreeViewl.Selected a s TMyNode;
Labell. Caption : = MyNode .Text + ' [ ' + MyNode .ClassName + 'I
- ! + IntToStr (MyNode.Extracode) ;
-
end;

Este es el codigo empleado en el ejemplo CustomNodes para mostrar la


descripcion del nodo seleccionado en una etiqueta. Fijese en que cuando seleccio-
namos un elemento dentro del arbol, su valor se multiplica por el de cada uno de
10s nodos padre. Aunque existen formas realmente mas sencillas de obtener este
mismo efecto, una vista en arbol con objetos de elementos creados a partir de
diferentes clases de una jerarquia ofrece una estructura orientada a objetos que
podemos emplear como base de un codigo mas complejo.
Creacion
de la interfaz
de usuario

Acabamos dk comentar 10s conceptos bisicos de la clase T C o n t rol y sus


clases derivadas en las bibliotecas VCL y VisualCLX. Despues hemos dado un
rapido repaso a 10s principales controles que se pueden usar para construir una
interfaz de usuario, tales como, 10s componentes de edicion, listas, selectores de
rango y muchos mas. En este capitulo varnos a centrarnos en otros controles
utilizados para definir el diseiio global de un formulario, como Pagecontrol y
Tabcontrol. Despues de estos componentes, vamos a comentar las barras de he-
rramientas y de estado, con algunas caracteristicas bastante avanzadas. Con esto
conseguiremos la base para el resto del capitulo, en el que se habla de acciones y
de la arquitectura Action Manager.
Las modernas aplicaciones de Windows suelen tener varios modos de ofrecer
ordenes, como elementos de menu, botones de la barra de herramientas, atajos de
menu y demas. Para separar las ordenes reales que puede dar un usuario de sus
multiples representaciones en la interfaz de usuario, Delphi usa el concept0 de
acciones.
En las ultimas versiones de Delphi, esta arquitectura se ha extendido para
hacer que la construccion de la interfaz de usuario sobre las acciones sea comple-
tamente visual. Ahora tambien se puede dejar que 10s usuarios del programa
personalicen esta interfaz facilmente, como sucede con muchos programas profe-
sionales. Finalmente, Delphi 7 afiade a 10s controles visuales que soportan la
arquitectura Action Manager una interfaz mejor y mas moderna, que soporta la
apariencia y comportamiento de XP. En Windows XP se pueden crear aplicacio-
nes que se adapten a1 tema activo, gracias sobre todo a1 nuevo codigo interno de la
VCL. Este capitulo trata 10s siguientes temas:
Formularios de varias paginas.
Paginas y pestafias.
Componentes ToolBar y StatusBar.
Temas y estilos.
Acciones y listas de acciones.
Acciones predefinidas en Delphi.
Los componentes ControlBar y CoolBar.
Anclaje de barras de herramientas y otros controles
La arquitectura Action Manager.

Formularios de varias paginas


Cuando tenemos que mostrar mucha informacion y muchos controles en un
cuadro de dialog0 o en un formulario, podemos emplear varias paginas o fichas.
La metafora es la de un cuaderno de notas: usando solapas o pestafias, un usuario
puede seleccionar una de las fichas posibles. Existen dos controles que podemos
emplear para crear una aplicacion de varias fichas en Delphi:
El componente PageControl: Tiene solapas en uno de 10s laterales y va-
rias fichas (parecidas a 10s paneles) que cubren el resto de su superficie.
Como hay una ficha por solapa, podemos simplemente colocar componen-
tes en cada ficha para obtener el efecto adecuado tanto en tiempo de dise-
iio, como en tiempo de ejecucion.
Tabcontrol: Solo tiene la parte de la solapa per0 no ofrece fichas en las
que almacenar la informacion. En este caso, sera conveniente emplear uno
o mas componentes para imitar la operacion cambio de ficha, o podremos
colocar distintos fonnularios dentro de las pestaiias para simular las pagi-
nas .
Una tercera clase relacionada, TabSheet, representa una unica ficha de
PageControl. Este componente no es independiente ni esta en la Component
Palette. Una TabSheet (o pagina de pestafia) se crea en tiempo de diseiio usan-
do el menu local de PageControl o, en tiempo de ejecucion, usando metodos del
mismo control.
NOTA: Delphi incluye todavia (en la pestaiia Win 3.1 de la Component
Palette) 10scomponentes Notebook, TabSet y TabbedNotebook introduci-
dos en las versiones de 32 bits (es decir, desde Delphi 2). Para cualquier
otro fin, 10s componentes PageControl y Tabcontrol, que encapsulan
controles comullei de Win32, ofrecen una ikterfaz de usuario mbs modema
- - - . - - - -. -
.
En realidad. en las verslones de 32 b ~ t sde U e l ~ h,i .el c o m ~ o n e n t e
rabbedNotebook se implement0 de nuevo usando el control PageControI
(je Win32 de forma interna, para reducir el tamaiio del codigo y actualizarr
. .

Pagecontrols y Tabsheets
Como es habitual, cn lugar de repetir la lista de propiedades y metodos dcl
sistcma dc ayuda del componente PageControl. hemos creado un ejenlplo quc
bosqueja sus capacidadcs y permite modificar su comportamiento en ticmpo de
ejccucion. El cjcmplo, denominado Pagcs, tienc un PageControl con tres paginas.
La estructura del PageContro