Sie sind auf Seite 1von 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 PageControl y de otros componentes clave se muestra en el
listado 6.1.

Listado 6.1. Secciones clave del DFM del ejemplo Pages.

object Farml: TForml


BorderIcons = [biSystemMenu, biMinimize]
Borderstyle = bssingle
Caption = ' P a g e s T e s t '
OnCreate = Formcreate
object PageControll: TPageControl
Activepage = TabSheetl
Align = alClient
HotTrack = True
Images = ImageListl
MultiLine = True
object TabSheetl: TTabSheet
Caption = ' P a g e s '
object Label3 : TLabel
object ListBoxl: TListBox
end
object TabSheet2: TTabSheet
Caption = 'Tab S i z e '
ImageIndex = 1
object Labell: TLabel
// o t r o s c o n t r o l e s
end
object TabSheet3: TTabSheet
Caption = ' T a b t e x t '
ImageIndex = 2
object Memol: TMemo
Anchors = [akLeft, akTop, akRiqht , akBottom]
OnChanqe = MemolChanqe
end
o b j e c t BitBtnChanqe: TBitBtn
Anchors = [akTop, akRiqht]
Caption = '&Change '
end
end
end
o b j e c t BitBtnPrevious: TBitBtn
Anchors = [akRiqht, akBottom]
Caption = '&Previous '
OnClick = BitBtnPreviousClick
end
o b j e c t BitBtnNext : TBitBtn
Anchors = [akRiqht, akBottom]
Caption = '&Nextf
OnClick = BitBtnNextClick
end
o b j e c t ImaqeListl: TImaqeList
Bitmap = { . . . I
end
end

Fijese en que las solapas estan conectadas a mapas de bits mediante un control
ImageList y en que algunos controles usan la propiedad A n c h o r s para mantener
una distancia fija con 10s bordes derecho o inferior del formulario. Aunque el
formulario soporte un reajuste del tamaiio (esto hubiera resultado mucho mas
complicado de realizar con tantos controles), las posiciones pueden variar cuando
las solapas aparecen en varias lineas (simplemente aumenta la longitud de 10s
titulos) o en el lateral izquierdo del formulario.
Cada objeto T a b S h e e t tiene su propio C a p t i o n , que aparecera en la sola-
pa de la hoja. En tiempo de diseiio, podemos usar el menu local para crear fichas
nuevas y para movernos por ellas. Podemos ver el menu local del componente
P a g e C o n t r o 1 en la figura 6.1, junto con la primera ficha. Esta ficha contiene
un cuadro de lista y un pequeiio titulo y comparte dos botones con las otras fichas.
Si colocamos un componente en una ficha, esta disponible solo en dicha ficha.
Para tener el mismo componente (en este caso, dos botones de mapas de bits) en
cada ficha, sin duplicarlo, sencillamente hay que colocar el componente en el
formulario, fuera del Pagecontrol (o antes de alinearlo con la zona de cliente) y,
a continuacion, moverlo hacia delante de las fichas, mediante la orden B r i n g
To F r o n t del menu local del formulario. Los dos botones que hemos colocado
en cada ficha se pueden usar para mover las fichas adelante y atras, y ofrecen una
alternativa a1 uso de las solapas. Veamos el codigo asociado a uno de ellos:
procedure TForml.BitBtnNextClick(Sender: TObject);
begin
PaqeControll.SelectNextPaqe (True);
end;
I Paged ) @ Tebr Sue I
.
A Tabs T& I
Ckk on lha Mbcx

a, to change papc

New_P q e
Mxt Pagl

Control

Add tu R_epo.;itory.. .

1
frcw as Text
TextDFM
- .-

Figura 6.1. La prlmera h o p de Pagecontrol del ejemplo Pages con su menu local.
- I
El otro boton llama a1 mismo procedimiento y pasa False como su parametro
para selcccionar la ficha anterior. Fi-jese cn que no es neccsario verificar si esta-
mos cn la primcra o en la ultima ficha, porque el metodo SelectNext Page
considera quc la ultima ficha es la quc csta antes de la primcra y nos llevara
directamcnte cntre estas dos fichas.
Ahora podemos centrarnos de nucvo en la primera ficha. Posce un cuadro de
lista, que cn tiempo de ejecucion contiene 10s nombres de las solapas. Si un usua-
rio hace clic sobre un elemento del cuadro dc lista, la pagina actual cambia. Este
cs el terccr mctodo del que disponemos para cambiar de ficha (despues de las
solapas y de 10s botones Next y Previous). El cuadro de lista se rcllena con cl
metodo Formcreate, asociado con el evento Oncreate dcl formulario, y
copia cl titulo de cada ficha (la propicdad Page contiene una lista de objetos
Tabsheet):
for I : = 0 to Pagecontroll. Pagecount - 1 do
ListBoxl.Items.Add (PageContro1l.Pages.Caption);
Cuando hacemos clic sobre un elemento de la lista, podemos seleccionar la
pagina correspondiente:
procedure TForml.ListBoxlClick(Sender: TObject);
begin
Pagecontroll-Activepage : = Pagecontroll-Pages
[ListBoxl.ItemIndex];
end;

La segunda pagina contiene dos cuadros de edicion (conectados a dos compo-


nentes UpDown), dos casillas de verificacion y dos botones de radio, como mues-
tra la figura 6.2. El usuario puede escribir un numero (o escogerlo, pulsando
sobre 10s botones de Flecha arriba o Flecha abajo con el raton o pulsando las
teclas de cursor arriba o abajo mientras el foco esta en el cuadro de edicion que
corresponda), marcar las casillas de verificacion y 10s botones de radio y, a con-
tinuacion, hacer clic sobre el boton Apply para realizar las modificaciones:
p r o c e d u r e TForml.BitBtnApplyClick(Sender: TObject);
begin
// e s t a b l e c e a n c h o , a l t o y l i n e a s de l a s o l a p a
PageControll.TabWidth : = StrToInt (EditWidth.Text);
PageControll.TabHeight : = StrToInt (EditHeight.Text);
PageControll.MultiLine : = CheckBoxMu1tiLine.Checked;
// muestra u o c u l t a l a dltima solapa
TabSheet3.TabVisible : = C h e c k B o x V i s i b l e . C h e c k e d ;
/ / f i j a l a p o s i c i o n de l a s o l a p a
i f RadioButtonl-Checked t h e n
PageControll.TabPosition : = tpTop
else
PageControll.TabPosition : = tpleft;
end;

Figura 6.2. La segunda pagina del ejemplo puede ser usada para ajustar el tamaiio y
posicion de las pestaiias, que aqui se muestran a la izquierda de la pagina.

Con este codigo, podemos cambiar cl ancho y la altura de cada solapa (recuer-
de que 0 significa que el tamaiio se calcula automaticamente a partir del espacio
que ocupa cada cadena). Podemos escoger tener diversas lineas de solapas o dos
pequeiias flechas para recorrer la zona de la solapa. y podemos moverlas al late-
ral izquierdo de la ventana. El control tambien permite situar las solapas en la
parte inferior o a la derecha, pero no nuestro programa, porque de ese mod0 la
colocacion de 10s otros controles resultaria bastante compleja. Tambien podemos
ocultar la ultima solapa del Pagecontrol, que corresponde a1 componente
T a b s h e e t 3 . Si ocultamos una de las solapas definiendo su propiedad
T a b V i s i b l e como F a l s e , no podemos alcanzar dicha solapa haciendo clic
sobre 10s botones Next ni Previous, que se basan en el metodo S e l e c t N e x t Page.
En lugar de eso, habria que usar la funcion F i n d N e x t P a g e , que seleccionara
esa pagina incluso aunque la pestaiia no sea visible. La Nueva version del contro-
lador de eventos OnCl i c k del boton Next muestra una llamada a1 metodo
FindNext Page:
procedure TForml.BitBtnNextClick(Sender: TObject);
begin
PageControl1.ActivePage : = Pagecontroll-FindNextPage (
PageControll.ActivePage, True, False);
end;

La ultima ficha posee un componente de memo, de nuevo con 10s nombres de


las fichas (aiiadidas en el metodo FormCrea t e ) . Podemos editar 10s nombres de
las fichas y hacer clic sobre el boton Change para modificar el texto de las
solapas, per0 solo si el numero de cadenas se corresponde con el numero de
pestaiias :
procedure TForml.BitBtnChangeClick(Sender: TObject);
var
I: Integer;
begin
if Memol.Lines.Count <> PageControll.PageCount then
MessageDlg ( ' U n a l i n e d p o r p e s t a d a , p o r f a v o r I , mtError,
[mbOKl, 0 )
else
for I : = 0 to PageControl1.PageCount -1 do
PageControll.Pages [I] .Caption : = Memo1 .Lines [I];
BitBtnChange.Enabled : = False;
end ;

Finalmente, el ultimo boton, Add Page, nos permite aiiadir una nueva hoja de
solapa a1 control de ficha, aunque el programa no aiiada ningun componente a la
misma. El objeto hoja de solapa (en blanco) se crea usando el control ficha como
su propietario, per0 no funcionara a no ser que tambien se configure la propiedad
P a g e c o n t r o l . Sin embargo, antes de hacer esto, deberiamos hacer que la nue-
va solapa fuera visible. Veamos el codigo:
procedure TForml.BitBtnAddClick(Sender: TObject);
var
strcaption: string;
NewTabSheet: TTabSheet;
begin
strcaption : = ' N e w t a b ' ;
if InputQuery ( ' N e w t a b ' , ' T a b C a p t i o n ' , strcaption) then
begin
/ / s e a d a d e una n u e v a f i c h a e n b l a n c o a 1 c o n t r o l
NewTabSheet : = TTabSheet-Create (PageControll);
NewTabSheet.Visible : = True;
NewTabSheet.Caption : = strcaption;
NewTabSheet-Pagecontrol : = PageControll;
PageControl1.ActivePage : = NewTabSheet;
// s e a d a d e a ambas l i s t a s
Memol. Lines .Add (strcaption);
ListBoxl .Items .Add (strcaption);
end;
end;

TRUCO:Siempre que escribimos un formulario basado en un PageCon-


trol, debemos recordar que la primera ficha que aparece en tiempo de ejecu-
cion es la ficha en la que nos encontrabamos antes de compilar el codigo.
Esto significa que si estamos trabajando en la tercera ficha y a continua-
cion compilamos y ejecutamos el programa, este arrancara mostrando di-
cha ficha. Una forma comun de resolver este problema consiste en aiiadir
una linea de codigo al metodo Formcreate para fijar el Pagecontrol o
el cuaderno de notas en la primera ficha. De este modo, la ficha actual en
' tiempo de disefio no determinara la ficha inicial en tiempo de ejecucion.

Un visor de imagenes con solapas dibujadas


por el propietario
El uso del TabControl y de una tecnica dinamica. como se describio en cl
ultimo ejemplo, se pucde aplicar tambikn cn casos mas generales (y mas senci-
110s). Cada vez quc necesitamos divcrsas fichas que tengan el mismo tip0 dc
contenido, en lugar de duplicar 10s controles en cada ficha, podemos usar un
TabControl y modificar sus contenidos cuando se selecciona una nueva solapa.
Esto es lo que haremos en el ejemplo de mapa de bits de varias fichas, llamado
BmpViewer, La imagen que aparece en el TabControl de este formulario, alinca-
do con toda la zona de cliente, depende de la selection de la solapa que esta sobre
ella (como muestra la figura 6.3).
A1 principio, cl TabControl est6 vacio. Despucs de seleccionar File>Open. el
usuario puede escoger varios archivos en el cuadro de dialog0 Abrir, y la matriz
de cadenas con 10s nombrcs de 10s archivos (la propiedad Files del componente
OpenDialog 1)se aiiade a las solapas (la propiedad Tabs de TabControl 1):
p r o c e d u r e TFormBmpViewer.OpenlClick(Sender: TObject);
begin
i f 0penDialogl.Execute then
begin
TabControll.Tabs.AddStrings (0penDialogl.Files);
TabControll.TabIndex : = 0;
TabControl lchange (Tabcontroll) ;
end ;
end;

I ADVERTENCIA:La propiedad Tabs de uo control TabControl en CLX


es una coleccion, mientras que en la VCL es una simple lista de cadena.
I
Figura 6.3. La interfaz del visor de mapas de bits del ejemplo BmpViewer, con
pestahas dibujadas por el propietario.

Dcspues de mostrar las nuevas solapas, tenemos que actualizar la imagen para
que se corresponda con la primera solapa. Para esto, el programa llama a1 metodo
concctado con el evento OnChange de Tabcontrol, que carga el archivo
correspondiente a la solapa actual en el componente imagen:
procedure TFormBmpViewer.TabControllChange(Sender: TObject);
begin
Imagel.Picture.LoadFromFi1e (TabControll.Tabs
[TabControll.TabIndex]);
end;

Este ejemplo fbnciona, a no ser que seleccionemos un archivo que no contenga


un mapa de bits. El programa advertira a1 usuario con una escepcion estandar,
ignorara el archivo y continuara ejecutandose.
El programa permite tambien pegar el mapa de bits en el portapapeles (aunque
sin copiarlo en realidad, sino solamente aiiadiendo una pestaiia que realizara la
operacion de pegado real cuando se seleccione) y copiar el mapa de bits actual en
el. El soporte para el portapapeles esta disponible en Delphi mediante un objeto
Clipboard definido en la unidad ClipBrd. Para copiar y pegar mapas de bits,
podemos usar el metodo A s s i g n de las clases TClipboard y TBitmap.
Cuando seleccionamos la orden Edit>Paste del ejemplo, se aiiade una nueva
solapa, cuyo nombre es Clipboard, a1 conjunto de solapas (a no ser que ya este
prescnte). A continuacion, el numero de la nueva solapa se usa para modificar la
solapa activa:
procedure TFormBmpViewer.PastelClick(Sender: TObject);
var
TabNum: Integer;
begin
// i n t e n t a c o l o c a r l a f i c h a
T a b N u m : = T a b C o n t r o l l .Tabs. IndexOf ( ' C l i p b o a r d ' ) ;
i f TabNum < 0 then
// c r e a u n a n u e v a f i c h a p a r a C l i p b o a r d
T a b N u m : = TabControll. Tabs .Add ( ' C l i p b o a r d ' ) ;
// v a a l a f i c h a C l i p b o a r d y h a c e q u e s e p i n t e d e n u e v o
TabControll.TabIndex : = TabNum;
TabControllChange (Self);
end;

En cambio, la operacion EditXopy resulta tan sencilla como copiar el mapa


de bits que esta actualmente en el control de imagen:

Para tener en cuenta la posible presencia de la solapa Clipboard, el codigo del


metodo TabControllChange se transforma en:
p r o c e d u r e TFormBmpViewer.TabControllChange(Sender: TObject);
var
TabText : string;
begin
1magel.Visible : = True;
TabText : = TabControll.Tabs [TabControll.TabIndex];
i f TabText <> ' C l i p b o a r d ' t h e n
// c a r g a e l a r c h i v o i n d i c a d o en l a s o l a p a
1magel.Picture.LoadFromFile (TabText)
else
{ s i l a s o l a p a es ' C l i p b o a r d ' y u n mapa d e b i t s
e s t d d i s p o n i b l e en e l p o r t a p a p e l e s )
i f Clipboard.HasFormat (cf-Bitmap) t h e n
1magel.Picture.Assign (Clipboard)
else
begin
/ / s i no e l i r n i n a l a s o l a p a c l i p b o a r d
TabControll.Tabs.Delete (TabControll.Tab1ndex);
i f TabControll. Tabs. Count = 0 t h e n
1magel.Visible : = False;
end ;

Este programa pega el mapa de bits del portapapeles cada vez que cambiamos
de solapa. El programa almacena solo una imagen cada vez y no tiene forma de
almacenar el mapa de bits Clipboard. Sin embargo, si el contenido del portapapeles
cambia y el formato de mapa de bits ya no esta disponible, la solapa Clipboard se
borra automaticamente (como se puede ver en el listado anterior). Si no quedan
mas solapas, el componente Image esta oculto.
Una imagen tambien puede eliminarse utilizando una de las dos ordenes del
menu: Cut o Delete . Cut elimina la solapa despues de hacer una copia del
mapa de bits en el portapapeles. En la practica, el metodo Cut lClick no hace
nada a parte de llamar a 10s metodos CopylClick y DeletelClick. El
metodo CopylClick se encarga de copiar la imagen actual a1 portapapeles,
Delete1C 1ic k sencillamente elimina la solapa actual. Veamos su codigo:
procedure TFormBmpViewer.CopylClick(Sender: TObject);
begin
Clipboard.Assign (1magel.Picture.Graphic);
end;

procedure TFormBmpViewer.DeletelClick(Sender: TObject);


begin
with Tabcontroll do
begin
if TabIndex >= 0 then
Tabs.Delete (TabIndex);
if Tabs.Count = 0 then
Imagel-Visible : = False;
end;
end;

Una de las caracteristicas especiales del ejemplo es que el Tabcontrol tiene la


propiedad OwnerDraw definida como True.Esto significa que el control no
pintara las solapas (que estarin vacias en tiempo de diseiio), sin0 que lo hara la
aplicacion, llamando a1 evento OnDrawTab.En su codigo, el programa muestra
el texto centrado verticalmente, usando la funcion DrawText de la API. El texto
que aparece no es la ruta del archivo completa sino solo el nombre del archivo. A
continuacion, si el texto es distinto de None, el programa lee el mapa de bits a1
que se refiere la solapa y pinta una version reducida del mismo en la propia
solapa. Para ello, el programa usa el objeto TabBmp,que es del tipo TBitmap
y se crea y destruye junto con el formulario. El programa usa tambien la constan-
te BmpSide para colocar de forma adecuada el mapa de bits y el texto:
procedure TFormBmpViewer.TabControllDrawTab(Control:
TCustomTabControl;
TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
TabText: string;
OutRect : TRect;
begin
TabText : = TabControll.Tabs [TabIndex];
OutRect : = Rect;
InflateRect (OutRect, -3, -3) ;
0utRect.Left : = 0utRect.Left + BmpSide + 3;
DrawText (Control.Canvas.Handle, PChar (ExtractFileName
(TabText) ) ,
Length (ExtractFileName (TabText)), OutRect,
dt-Left or dt-SingleLine or dt-VCenter);
if TabText = 'Clipboard' then
if Clipboard .HasFormat (cf-Bitmap) then
TabBmp-Assign (Clipboard) '
else
TabBmp.FreeImage
else
TabBmp.LoadFromFile (TabText);
OutRect .Left : = 0utRect.Left - BmpSide - 3;
OutRect .Right : = OutRect .Left + BmpSide;
Contro1.Canvas.StretchDraw (OutRect, TabBmp);
end;

El programa tiene tambien soporte para imprimir el mapa de bits actual, tras
haber mostrado un formulario de vista previa de la ficha, en el que el usuario
puede seleccionar la escala apropiada. Esta parte adicional del programa no se
comenta en detalle, per0 el codigo esta ahi para quien desee examinarlo.

La interfaz de usuario de un asistente


Exactamente del mismo mod0 que usamos un Tabcontrol sin fichas, tambien
podemos utilizar la tecnica opuesta y emplear un PageControl sin solapas. Ahora
nos centraremos en el desarrollo de la interfaz de usuario de un asistente. En un
asistente, dirigimos a1 usuario mediante una serie de pasos, en una pantalla por
paso y, en cada paso, normalmente queremos ofrecer la oportunidad de pasar al
siguiente o volver atras para corregir la entrada realizada en un paso anterior.
Asi, en lugar de solapas que se puedan seleccionar en un orden cualquiera, 10s
asistentes suelen ofrecer 10s botones Siguiente (Next) y Atras (Back)para despla-
zarse. Este ejemplo no sera complejo, su funcion consiste unicamente en propor-
cionar unas cuantas directrices. El ejemplo se llama WizardUI.
El punto de arranque es crear una serie de paginas en un PageControl y confi-
gurar la propiedad T a b v i s i b l e de cada TabSheet como F a l s e (mientras
mantenemos la propiedad V i s i b l e como T r u e ) . Desde Delphi 5 tambien pode-
mos ocultar las solapas en tiempo de disefio. En este caso, sera necesario utilizar
el menu de metodo abreviado del control de pagina, el cuadro combinado del
Object Inspector, o la Object Tree View para desplazarse a otra ficha, en lugar de
las solapas. Pero, podriamos preguntarnos el porque de no querer ver las solapas
en tiempo de disefio.
Podemos colocar 10s controles en las fichas y, a continuacion, colocar contro-
les adicionales delante de las fichas (como en el ejemplo), sin que sus posiciones
relativas cambien en tiempo de ejecucion. Tambien podriamos querer eliminar 10s
titulos inutiles de las pestafias, que ocupan espacio en memoria y entre 10s recur-
sos de la aplicacion.
En la primera ficha, hemos colocado a un lado una imagen y un control de
biselado y a1 otro lado algo de texto, una casilla de verificacion y dos botones. En
realidad, el boton Next esta dentro de la ficha, mientras que el boton Back esta
sobre ella (y lo comparten todas las fichas). Podemos ver la primera ficha en
tiempo de disefio en la figura 6.4. Las fichas siguientes tienen una apariencia
similar, con una etiqueta, casillas de verificacion y botones en el lateral derecho y
nada en el izquierdo.
Figura 6.4. La primera ficha del ejemplo WizardUl en tiempo de disefio.

Cuando haccmos clic sobrc el boton Next de la primera pagina, el programa


mira el cstado de la casilla dc verification y decidc que ficha cs la siguiente.
Podriamos habcr escrito el codigo de este modo:
p r o c e d u r e TForml.btnNextlClick(Sender: T O b j e c t ) ;
begin
B t n B a c k - E n a b l e d : = True;
i f CheckInprise.Checked then
PageControl1.ActivePage : = TabSheet2
else
PageControl1.ActivePage : = TabSheet3;
// m u e v e l a i m a g e n y e l b i s e l a d o
B e v e l l - P a r e n t : = PageControl1.ActivePage;
1 m a g e l . P a r e n t : = PageControl1.ActivePage;
end;

Tras activar el boton Back comiin. el programa cambia la ficha activa y, por
ultimo, dcsplaza la park grafica a la nucva ficha. Como este codigo ha de repetir-
se para cada boton, lo hemos colocado en un metodo despues de aiiadir unas
cuantas caractcristicas adicionales. Este es cl codigo actual:
p r o c e d u r e TForml.btnNextlClick(Sender: TObject);
begin
i f Check1nprise.Checked then
MoveTo (TabSheet2)
else
M o v e T o ( T a b S h e e t 3 );
end;

procedure TForml.MoveTo(TabSheet: TTabSheet);


begin
// adade l a illtima f i c h a a l a l i s t a
B a c k P a g e s . A d d (PageControl1.ActivePage);
BtnBack.Enabled := True;
// c a m b i a d e f i c h a
PageControl1.ActivePage : = Tabsheet;
// m u e v e l a i m a g e n y el B e v e l
Bevell-Parent : = PageControll.ActivePage;
Imagel-Parent : = PageContro1l.ActivePage;
end;

A d e m b del codigo que ya hemos explicado, el metodo MoveTo aiiade la


ultima ficha (la que esta antes del cambio de ficha) a una lista de fichas visitadas,
que se comporta como una pila. De hecho, el objeto BackPages de la clase
TList se crea a1 arrancar el programa y la ultima ficha siempre se aiiade a1
final. Cuando el usuario hace clic sobre el boton Back, que no depende de la
ficha, el programa extrae la ultima ficha de la lista, borra su entrada y se mueve a
dicha ficha:
procedure TForml.btnBackClick(Sender: TObject);
var
LastPage: TTabSheet;
begin
// o b t i e n e l a u l t i m a f i c h a y s a l t a a e l l a
LastPage := TTabSheet (BackPages [BackPages .Count - 11) ;
PageControl1.ActivePage : = LastPage;
// b o r r a l a u l t i m a f i c h a d e l a l i s t a
BackPages .Delete (BackPages.Count - 1) ;
// f i n a l m e n t e d e s a c t i v a e l b o t o n b a c k
BtnBack. Enabled := not (BackPages.Count = 0) ;
// mueve l a imagen y e l B e v e l
Bevell-Parent : = PageControll.ActivePage;
Imagel-Parent : = PageControl1.ActivePage;
end:

Con este codigo, el usuario puede volver varias fichas a t r b hasta que la lista
quede vacia, punto en el que el boton Back se desactiva. La complicacion a la que
hemos de enfrentarnos es que mientras nos movemos desde una ficha concreta,
sabemos que ficha es su ficha "posterior" y "anterior", per0 no sabemos de que
ficha se procede, porque hay diversas rutas para llegar a una ficha. Solo podemos
retroceder de forma segura, si guardamos la pista de 10s movimientos con una
lista.
El resto del codigo del programa, que simplemente muestra algunas direccio-
nes de sitios Web, es muy sencillo. Lo bueno es que podemos reutilizar la estruc-
tura de desplazamiento de este ejemplo en nuestros programas y modificar solo la
parte grafica y el contenido de las paginas. En realidad, como la mayoria de las
etiquetas de 10s programas muestran direcciones HTTP, un usuario puede hacer
clic sobre dichas etiquetas para abrir el explorador predefinido para que muestre
esa pagina. Para ello, se extrae la direccion HTTP de la etiqueta y se llama a la
funcion ShellExecute.
procedure TForml.LabelLinkClick(Sender: TObject);
var
Caption, StrUrl: string;
begin
Caption : = (Sender as TLabel) .Caption;
StrUrl : = C o p y (Caption, Pos ('http://', Caption), 1000) ;
ShellExecute (Handle, 'open ', PChar (StrUrl) , ' ', ' ', sw-Show) ;
end;

Este metodo esta enganchado a1 evento oncl i c k de varias etiquetas del for-
mulario, que se han transformado en enlaces a1 configurar su cursor como una
mano. Esta es una de las etiquetas:
o b j e c t Label2 : TLabel
Cursor = crHandPoint
Caption = 'Main site: http://www. borland. corn'
OnClick = LabelLinkClick
end

El control ToolBar
Para crear una barra de herramientas, Delphi incluye un componente ToolBar
especifico, que encapsula el control comun de Win32 correspondiente o el widget
Qt correspondiente en VisualCLX. Dicho componente proporciona una barra de
herramientas, con sus propios botones y tiene muchas capacidades avanzadas.
Para usarlo, lo colocamos en un formulario y, a continuacion, usamos el editor de
componentes (el menu de metodo abreviado activado con un clic del boton dere-
cho del raton) para crear unos cuantos botones y separadores.
L a b a r r a de herramientas esta compuesta por objetos de l a clase
TToo lBut ton.Dichos objetos tienen una propiedad bbica, Style,que deter-
mina su comportamiento:
El estilo tbsButton: Indica un boton pulsador estandar.
El estilo tbscheck: Indica un boton con el comportamiento de una casilla
de verificacion, o de un boton de radio si el boton esta agrupado con otros
en su bloque (determinado por la presencia de separadores).
El estilo tbsDropDown: Indica un boton desplegable, una especie de cua-
dro combinado. La parte desplegable se puede implementar facilmente en
Delphi conectando un control PopupMenu a la propiedad Dropdown-
Menu del control.
Los estilos tbsseparator y tbsDivider: Indican separadores con lineas
verticales diferentes o sin ellas (dependiendo de la propiedad Flat de la
barra de herramientas).
Para crear una barra de herramientas grafica, podemos aiiadir un componente
ImageLis t a1 formulario, cargar algunos mapas de bits en el y a continuacion,
conectar la ImageList con la propiedad Images de la barra de herramienta. Por
defecto, las imagenes se asignaran a 10s botones en el orden en el que aparecen,
per0 podemos cambiar facilmente este comportamiento fijando la propiedad
ImageIndex de cada boton de la barra de herramientas. Podemos preparar
listas de imagenes adicionales para condiciones especiales de 10s botones y asig-
narlas a las propiedades DisabledImages y HotImages de la barra de
herramientas. El primer grupo se usa para 10s botones desactivados, el segundo
para el boton actual que esta bajo el raton.
tr -

NOTA: En una aplicacitin, por lo general deberiamos crear barras de he-


rramientas utilizando una ActionList o la reciente arquitectura Action Ma-
w .
naizer. En ese caso.,aDenas asinnaremos com~ortamientoalrmno a 10s botones
w w

de la barra de herramientas, puesto que sus propiedades y eventos serhn


administrados por 10s componentes de accion. Aun mas, se acabad usando
m a herramienta de la c l a w esnecifica T A c t ionToo 1 R a r

El ejemplo RichBar
Como ejemplo del uso de una barra de herramientas, hemos creado la aplica-
cion RichBar, que tiene un componente RichEdi t con el que se puede trabajar
utilizando la barra de herramientas. El programa tiene botones para cargar y
guardar archivos, para las operaciones de copiar y pegar y para cambiar algunos
de 10s atributos de la fuente en uso.
Pretendemos centrarnos aqui en caracteristicas especificas de la ToolBar utili-
zadas por el ejemplo y visibles en la figura 6.5. Esta barra de herramientas tiene
botones, separadores e incluso un menu desplegable y dos cuadros combinados.

An inllotluclion lo the Ijasic fealules o f the RichBa~example. ilisc~rssetlin Chapter 6 of the


book "lvlaste~ingDelphi 7". W ~ i n e nanti copyrighted Ily Mmco 13ant11.

This document explains how do you create a simple editor based on the RichEdit control using Delphi
I
6 The program has a toolbar andmplements a number of features, mcluding a complete scheme for
opening and saving the text Ues, drscussed m this document. In fact, we want to be able to ask the
user to save any modified Ue before opening a new one, to avoid losmg any changes. Sounds k e a
professional apphcaboh doesn't it?

1 l ~ i l eOperations
The most complex part of this program is implemenhng the commands of the File pull-down menu-
New. Ope& Save, and Save As. In tach case. we need to track Mether the current Ue has changed,
C I A ,,l..:C:rLr. nr- AA..ld .,. *.I., ...,- .,..,
a- &- CI, -,-L i,- ,
,-,
- , 6,.
,i'
Figura 6.5. La barra de herramientas del ejemplo RichBar.
Los distintos botones implementan caracteristicas, una de las cuales consiste
en un esquema completo para abrir y guardar archivos de texto (se pide a1 usuario
que guarde cualquier archivo modificado antes de abrir uno nuevo, para no perder
ningun cambio). La parte del programa encargada de la administracion de archi-
vos es bastante compleja, per0 vale la pena explorarla, dado que muchas aplica-
ciones basadas en archivos utilizan un codigo similar.
Ademas de las operaciones de archivo, el programa soporta las operaciones de
copiar y pegar, y la administracion de fuentes. Para las operaciones de copiar y
pegar no es necesario una interaccion real con el portapapeles, dado que el com-
ponente puede controlarlas con ordenes sencillas como:

Es un poco mas avanzado conocer cuando deberian habilitarse estas operacio-


nes (y 10s botones correspondientes). Podemos activar 10s botones Copy y Cut
cuando se selecciona algo de texto, en el evento onselect ionchange del
control RichEdit:
p r o c e d u r e TFormRichNote.RichEditSelectionChange(Sender:
TObject) ;
begin
tbtnCut.Enabled : = R i c h E d i t - S e l L e n g t h > 0;
t b t n C o p y . E n a b l e d : = tbtnCut.Enabled;
end;

La operacion de copia, en cambio, no se puede decidir mediante una accion del


usuario, puesto que depende del contenido del portapapeles, que esta influido
tambien por otras aplicaciones. Un enfoque es utilizar un temporizador y verifi-
car el contenido del portapapeles de vez en cuando. Otra mejor consiste en utilizar
el evento OnIdle del objeto Application (o el componente ApplicationEvents).
Dado que el control RichEdit soporta diversos formatos de portapapeles, el codi-
go no puede fijarse simplemente en ellos, sino que deberia preguntar a1 propio
componente, usando una caracteristica de bajo nivel no exteriorizada por el con-
trol Delphi:
p r o c e d u r e TForrnRichNote.ApplicationEventslIdle(Sender: TObject;
v a r Done: B o o l e a n ) ;
begin
// a c t u a l i z a b o t o n e s d e la b a r r a d e h e r r a m i e n t a s
tbtnPaste.Enabled : = S e n d M e s s a g e (RichEdit.Handle,
em-CanPaste, 0, 0) <> 0;
end;

La administracion basica de la fuente se realiza mediante 10s botones Bold e


Italic, que poseen un codigo similar. El boton Bold alterna el atributo relativo del
texto seleccionado (o cambia el estilo de la posicion de edicion activa):
procedure TFormRichNote.BoldExecute(Sender: TObject);
begin
with RichEdit.SelAttributes do
i f fsBold i n Style then
Style : = Style - [fsBold]
else
Style : = Style + [fsBold];
end;

De nuevo, el estado actual del boton se establece mediante la seleccion activa,


por lo que habra que aiiadir la siguiente linea a1 metodo R i c h E d i t S e l e c -
tionchange:

Un menu y un cuadro combinado en una barra


de herramientas
Ademb de una serie de botones, el ejemplo RichBar posee un menu desplega-
ble y un par de cuadros combinados, una caracteristica compartida por muchas
aplicaciones habituales. El boton desplegable permite seleccionar el tamaiio de la
fuente, mientras que 10s cuadros combinados permiten seleccionar rapidamente la
familia y color de la fuente. Este segundo cuadro combinado se crea, en realidad,
utilizando un control ColorBox.
El boton Size esta conectado a un componente PopupMenu (llamado
S i zeMenu), empleando la propiedad DropdownMenu. Un usuario puede pul-
sar el boton, activar su evento o n c l i c k normalmente o seleccionar la flecha
desplegable, abrir el menu contextual (vease de nuevo la figura 6.5) y escoger una
de sus opciones. Este caso tiene tres tamaiios de fuente posibles, por la definition
del menu:
object SizeMenu: TPopupMenu
object Smalll: TMenuItem
Tag = 10
Caption = 'Small '
OnClick = SetFontSize
end
object Mediuml: TMenuItem
Tag = 16
Caption = 'Medi urn'
OnClick = SetFontSize
end
object Largel: TMenuItem
Tag = 32
Caption = 'Large '
OnClick = SetFontSize
end
end
Cada elemento del menu tiene un indicador del tamaiio real de la fuente, que se
activa mediante un controlador de eventos compartido:
p r o c e d u r e TFormRichNote.SetFontSize(Sender: TObject);
begin
RichEdit.SelAttributes.Size : = (Sender as TMenuItem) .Tag;
end;

Como el control ToolBar es un contenedor de control muy complete, podemos


coger directamente un cuadro de edicion, un cuadro combinado y otros controles
y colocarlos en la barra de herramientas. El cuadro combinado de la barra de
herramientas se inicia con el metodo Formcreate, que extrae las fuentes de
pantalla disponibles en el sistema:
ComboFont . Items : = Screen. Fonts;
ComboFont.ItemIndex : = ComboFont.Items.IndexOf (RichEdit.Font.Name)

El cuadro combinado muestra inicialmente el nombre de la fuente predetermi-


nada utilizada en el control RichEdit, configurada en tiempo de diseiio. Ese valor
se calcula de nuevo cada vez que la seleccion actual cambia, utilizando la fuente
del texto seleccionado, junto con el color actual para el ColorBox:
p r o c e d u r e TFormRichNote.RichEditSelectionChange(Sender:
TObject) ;
begin
ComboFont.ItemIndex : = ComboFont.Items.IndexOf
(RichEdit.Se1Attributes.Name);
ColorBoxl.Selected : = RichEdit.SelAttributes.Co1or;
end;

Cuando seleccionamos una nueva fuente del cuadro combinado, ocurre lo con-
trario. El texto del elemento en uso del cuadro combinado se asigna como nombre
de la fuente para cualquier texto seleccionado en el control RichEdit:

La seleccion de un color en el ColorBox activa un codigo similar

Una barra de estado simple


Crear una barra de estado es aun mas sencillo que crear una barra de herra-
mientas. Delphi incluye un componente StatusBar especifico, basado en el control
comun de Windows correspondiente (en VisualCLX hay tambien un control simi-
lar). Este componente se puede usar casi como un panel, cuando su propiedad
S i m p l e P a n e 1 vale T r u e . En este caso, podemos usar la propiedad
SimpleText para producir texto. Sin embargo, la verdadera ventaja de este
componente, esta en que nos permite definir un numero de subpaneles, activando
simplemente el editor de su propiedad Panels.(Tambien podemos mostrar este
editor de propiedad haciendo doble clic sobre el control de la barra de estado o
realizar las mismas operaciones mediante el Object Tree View.) Cada subpanel
posee sus propios atributos graficos. que podemos personalizar usando el Object
Inspector. Otra caracteristica del componente barra de estado es la zona de
"control del tamaiio", aiiadida en la esquina inferior derecha de la barra, que
rcsulta muy util para ajustar el tamaiio del propio formulario. Sc trata de un
elemento comun de la interfaz de usuario de Windows y que podemos controlar en
parte con la propiedad SizeGrip (se auto-inhabilita cuando el formulario no
resulta redimensionable).
Una barra de estado tiene varias funciones. La mas comun es mostrar informa-
cion sobre el elemento dcl menu que el usuario haya seleccionado. Ademas de
esto. una barra de estado normalmente muestra otra informacion sobre el estado
de un programa: la posicion del cursor en una aplicacion grafica, la linea de testo
actual en un procesador de textos, el estado de las teclas de bloqueo de mayuscu-
las y del teclado numkrico, la hora y la fecha, etc. Para mostrar informacion en un
panel, simplemente usamos su propiedad T e x t , por lo general utilizando una
expresion como:
StatusBarl. Panels [l] .Text := 'rnensaje';

En el ejemplo RichBar, hay una barra de estado con tres paneles: para suge-
rencias sobre ordenes: el estado de la tecla BloqMayiis y la posicion de edicion
activa. El componente StatusBar del ejemplo tiene en realidad cuatro paneles (es
necesario definir el cuarto para delimitar la zona del tercer panel). El ultimo panel
siempre es lo suficientemente grande como para cubrir la superficie que queda en
la barra de estado.

( NOTA: En el c6digo se puede ver que lsts sugermaias se Guestran en el I


primer panel de Ia barra de estado. Esto se podria haber sinapWoado en el
codigo mediante el aso de la propiedad AutoHint, pas mstrar un ckb-
go d s detallado permite personalizar este wmportami&ct,

Los paneles no son componentes independientes, por lo que no podemos acce-


der a ellos por su nombre, solo por posicion, como en el anterior fragment0 de
codigo. Una buena solucion para mejorar la facilidad de lectura de un programa
consiste en definir una constante para cada panel que queramos usar y, a conti-
nuacion, usar dichas constantes al hacer referencia a 10s paneles. Este es el codigo
de ejemplo:

En el primer panel de la barra de estado se va a mostrar el mensaje de sugeren-


cia del boton de la barra de herramientas. El programa consigue este efecto con-
trolando el evento 0 n H i n t de la aplicacion, utilizando el componente
A p p l i c a t ionEvent s y copiando el valor actual de la propiedad Hint de la
aplicacion a la barra de estado:
procedure TFormRichNote.ApplicationEventslHint (Sender: TObject);
begin
StatusBarl.Panels[sbpMessage].Text : = Application.Hint;
end;

Este codigo muestra de manera predefinida en la barra de estado el mismo


testo de las sugerencias contextuales, que no son generadas por 10s elementos de
mcnu. En realidad, podemos usar la propiedad H i n t para especificar cadenas
diferentes para 10s dos casos; escribiendo una cadena dividida cn dos partes me-
diante un separador, el caracter "tuberia" (().Por ejemplo, podriamos introducir
el siguiente como valor de la propiedad H i n t :
' N u e v o 1 C r e a r un n u e v o d o c u m e n t o '

La primera parte de la cadena, Nuevo, la usan las sugerencias contextuales y


la segunda parte, Crenr un nztevo documento, la barra de estado. La figura 6.6
muestra un ejemplo.

II AII i n t ~ o d ~ ~ ctot i the


o ~ ~11asic fe.itt~~es
book "Maste~itrgD e l p l ~7".
~ al p t e ~G nf the
of the RicllBar example. ~ l i s c ~ ~ sinr et h
i W l i n e n an11 copyriglrted I)y I r l a ~ c oL.11rti1.

l h s document explams how do you create a sunple ecttor based on the F x h a t control usmg Delph
6 The program has a toolbar and unplements a number of feahues, mcludmg a complete scheme for
opening and savmg the text files, hscussed m h s document In fact, we want to be able to ask the
user to save any modified file before opening a new one, to avold losmg any changes Sounds Wte a
profess~onalapphcahon doesn't ~ t ?

1 I~ile
Operations
The most complex p a t o f h s program 1s rnplemenhng the commands of the Fie pull-down menu-
*----. .-
New, Open Save, and Save As In each case, we need to track whether the current file has changed, ,
.LA CIA -1..

rCIA- Ihs
.c.* L A -

- s c k t m to the dpboard
.Z," -L-..IA .L*

7
*-..* .LA CIA

7-
h-- *L- A-a-*-.

s
Figura 6.6. La barra de estado del ejernplo RichBar muestra una descripcion mas
detallada que la sugerencia contextual

,. . .- . -

I
< . >

I TRUCO:Cuando la sugerencia de un mntrol estP Eompuesta dc dos cadL-


nas, podemos usar 10s rnktodos GetSho~tHinty GetLongHint par#
e x h e r la primera (corta) y la segunda Wga) subcad- a partir da
cadePa qge pasamos como pmbnetxo, qu+ sronnaltneate es.gl valor de la
brbpiedad Hint,
El segundo panel muestra el estado de la tecla BloqMayus, que se obtiene a1
llamar a la funcion G e t K e y s t ate de la API, que devuelve un numero de
estado.
Si se activa el bit menos significativo de dicho numero (si el numero es impar),
quiere decir que la tecla esta activada. Este estado se verifica cuando la aplica-
cion esta en espera, de forma que la comprobacion se realice cada vez que se
pulsa una tecla, per0 tambien desde el momento en que un mensaje alcanza la
ventana (en caso de que el usuario cambie esta configuracion mientras trabaja
con otro programa). Hemos aiiadido a1 controlador A p p 1i cat io n E v e n t s 1-
I d 1e una llamada a1 metodo personalizado C h e c k c aps 1o c k , implementado
del siguiente modo:
procedure TFormRichNote.CheckCapslock;
begin
if Odd (GetKeyState (VK-CAPITAL)) then
StatusBarl.Panels [sbpcaps].Text : = ' C A P S '
else
StatusBarl. Panels [sbpcaps].Text : = ' ';
end ;

Por ultimo, el programa usa el tercer panel para mostrar la posicion actual de
cursor (medida en lineas y caracteres por linea) cada vez que cambia la seleccion.
Debido a que 10s valores C a r e t P O S se basan en cero (es decir, la esquina supe-
rior derecha es la linea 0, caracter O), hemos decidido aiiadir uno a cada valor
para que resulten mas razonables para un usuario que desconozca este detalle:
procedure TFormRichNote.RichEditSelectionChange(Sender: TObject);
begin
...
// a c t u a l i z a l a p o s i c i o n e n l a b a r r a d e e s t a d o
Status~ar. Panels [sbpposition] .Text : = Format ( ' % d / % d l ,
[RichEdit .CaretPos .Y + 1, RichEdit .CaretPos.X + 11 ) ;
end;

Temas y estilos
En el pasado, un sistema operativo basado en una interfaz grafica determinaba
todos 10s elementos de la interfaz de usuario para 10s programas que se ejecuta-
ban sobre el.
~ltimamente,Linux ha comenzado a permitir que 10s usuarios personalicen la
apariencia tanto de la ventana principal de las aplicaciones como de 10s controles
de la interfaz de usuario como 10s botones. La misma idea (que suele referirse
como slnn, pie1 o tema) ha aparecido en numerosos programas con un impact0 tan
positivo que incluso Microsoft ha comenzado a integrar este concept0 (a1 princi-
pio en programas y despues en todo el sistema operativo).
Estilos CLX
Como ya se ha comentado. en Linux (para ser mas precisos en X Window) el
usuario generalmente puede escoger el estilo de la interfaz de usuario de 10s
controles. Este enfoque esta completamente soportado por Qt y por el sistema
KDE que se basa en el. Qt ofrece unos cuantos estilos basicos, como la apariencia
de Windows, el estilo Motif y otros. Un usuario tambien puede instalar nuevos
estilos en el sistema y ponerlos a disposicion de las aplicaciones.
- -
- --- -- - .- - - - - - --

I NOTA: Los estilos de 10s que hablaremos se refieren a la interfaz de usua- I


rio de 10s controles, no de 10s formularios y sus bordes. Nonnalmente esto
es configurable en 10s sistemas Linux, per0 tbcnicamente se trata de un
elemento separado de la interfaz de usuario.

Ya que esta tecnica se encuentra incrustada en Qt, tambien esta disponible en


la version para Windows de la biblioteca; CLX la pone a disposicion de 10s
desarrolladores en Delphi, de manera que una aplicacion puede tener una aparien-
cia de Motif en un sistema operativo de Microsoft. El objeto global A p p l i c a t ion
de la CLX tiene una propiedad s t y l e que se puede usar para establecer un estilo
pcrsonalizado o uno predefinido, indicado por la subpropiedad D e f a u l t S t y l e .
Por ejemplo, se puede seleccionar una apariencia de Motif mediante este codigo:

En el programa StylesDemo, entre varios controles de muestra, se ha incluido


un cuadro de lista con 10s nombres de 10s estilos predefinidos, tal y como se
indican en la enumeracion T D e f a u l t S t y l e y este codigo para su evento
OnDblClick:
procedure TForml.ListBoxlDblClick(Sender: T O b j e c t ) ;
begin
Application.Style.Defau1tStyle : = TDefaultStyle
(ListBoxl.Item1ndex);
end

El efecto es que a1 hacer doble clic sobre el cuadro de lista, se puede cambiar
el estilo actual de la aplicacion y comprobar inmediatamente su efecto en panta-
lla, como muestra la figura 6.7.

Temas de Windows XP
Con la aparicion de Windows XP, Microsoft ha creado una nueva version,
independiente, de la biblioteca de controles habituales. La antigua biblioteca si-
gue estando disponible por cuestiones de compatibilidad, de manera que un pro-
grama que se ejecute sobre XP puede escoger cual de las dos bibliotecas usar. La
principal difercncia de la nueva biblioteca es que no tiene un motor de representa-
cion fijo, sino que confia en cl motor de temas de XP y delcga la interfaz de
usuario de 10s controles sobre cl tema actual.

Figura 6.7. El prograrna StylesDernos, una aplicacion para Windows que tiene en
este momento un poco habitual aspect0 Motif.

En Delphi 7, la VCL soporta completamentc temas, debido a una gran canti-


dad de codigo intcrno y a la biblioteca de administracion de temas desarrollada
originalmente por Mike Lischke. Algunas de estas nuevas caracteristicas de re-
prescntacion son utilizadas por 10s controles visuales de la arquitectura Action
Manager. independientemente del sistema operativo sobre el que funcione. Sin
embargo, el soporte total de temas solo esta disponible para un sistema operativo
que disponga de esta caracteristicas (por el momento, Windows XP).
Incluso en XP, las aplicaciones de Delphi usan de manera predefinida el enfo-
que tradicional. Para soportar temas XP, se debc incluir un archivo de manifiesto
cn el programa. Se puede hacer de muchas maneras:
Colocar un archivo de manifiesto en la misma carpeta que la aplicacion. Se
trata de un archivo XML que indica la identidad y las dependencias del
programa. El archivo tiene el mismo nombre que el programa ejecutable
con una estension adicional .manifest a1 final (como MiPrograma.
exe .manifest). El listado 6.2 muestra un ejemplo de este tip0 de
archivo.
Afiadir la misma informacion en un archivo de recurso compilado dentro
de la apIicacion. Se debe escribir un archivo de recurso que incluya un
archivo de manifiesto. En Delphi 7, la VCL tiene un archivo de recurso
compilado WindowsXP .res, que se consigue a1 recompilar el archivo
WindowsXP . rc disponible entre 10s archivos fuente de la VCL. El
archivo de recurso incluye el archivo s a m p l e . manifest,que esta dis-
ponible en el mismo sitio.
Usar el componente XpManifest, que Borland ha aiiadido en Delphi 7
para simplificar aun mas estas tareas. Al dejar este componente aparente-
mente inutil sobre el formulario de un programa, Delphi incluira
automaticamente su unidad XPMan, que importa el archivo de recurso
VCL comentado anteriormente.

ADVERTENCIA: Cuando se elimina el componente XpMani fe s t de


una aplicacion, tambien se debe borrar la unidad XPMan de la sentencia
uses manualmente (Delohi no lo hace). Si no se hace esto. incluso sin el

hace pregunrarse por que aorlana creo el componenre en lugar ae propor-


cionar la unidad o el archivo de recurso relacionado). Ademb, este compo-
nente no esth en absoluto documentado.

Listado 6.2. Un archivo de rnanifiesto de rnuestra (Pages.exe.rnanifest).

<?xml version="l.O" encoding="UTP-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft~com:asm.vl"
manifestVersion="l.O">
<assemblyIdentity
version="l.O.O.O"
processorArchitecture="X86"
name="Pages.exe"
type="win32"
/>
<description>Demo de la biblia de Delphi</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X8 6 "
publicKeyToken="6595b64144ccfldf"
language=""
/>
</dependentAssernbly>
</dependency>
</assembly>

Como muestra, en la carpeta del ejemplo Pages comentado anteriormente se


incluye el archivo de manifiesto del listado 6.2. A1 ejecutarlo sobre Windows XP
con el tema estandar de XP, se conseguira un resultado similar a1 mostrado en la
figura 6.8. Se puede comparar con las figuras 6.1 y 6.2 que muestran el mismo
programa con el tema clasico de Windows XP

Pages Click on the listtaw


Tabs S~ze 10 change page
Tabs Text

[F]

Figura 6.8. El ejemplo Pages usa el tema de Windows XP actual, ya que incluye un
archivo de manifiesto.

El Componente ActionList
La arquitcctura de eventos de Delphi es mug abierta: se puede escribir un
scncillo controlador de eventos y conectarlo a 10s eventos O n C l i c k de un boton
de la barra dc hcrramientas y a un menil. Se puede incluso conectar el mismo
controlador de eventos a diferentes botones o elementos de menu, dado que el
controlador puede utilizar el parametro S e n d e r para referirse a1 objeto que
lanzo el evento. Es algo mas dificil sincronizar el estado de 10s botones de la barra
de herramientas y 10s elementos de menu. Si tenemos un elemento de menu y un
boton de la barra de herramientas y ambos accionan la misma operacion, cada vez
que se activa dicha operacion, hay que aiiadir la marca de comprobacion a1 ele-
mento de menu y cambiar el estado del boton para que aparezca como pulsado.
Para superar este problema, Delphi incluye una estructura de gestion de even-
tos basada en acciones. Una accion (u orden) indica tanto la operacion que se
realiza cuando se pulsa un elemento de menu o boton que determina el estado de
todos 10s elementos conectados a dicha accion. La conexion de la accion con la
interfaz de usuario de 10s controles enlazados resulta muy importante y es el
ambito en el que podemos entender las autenticas ventajas de esta estructura.
En esta estructura de manipulacion de eventos participan diversos agentes. La
accion principal la realizan 10s objetos de la accion. Un objeto de accion tiem un
nombre, como cualquier otro componente y otras propiedades que se aplicaran a
10s controles enlazados (llamados tambien clientes de la accion). Entre dichas
propiedades estan C a p t i o n , la representacion grafica ( I m a g e I n d e x ) , el esta-
do ( C h e c k e d , E n a b l e y V i s i b l e ) y la information para el usuario ( H i n t y
H e l p c o n t e x t ) . Tambien estan S h o r t c u t y una lista de S e c o n d a r y S h o r t -
C u t s , la propiedad A u t o c h e c k para acciones de dos estados, el soporte de
ayuda y una propiedad C a t e g o r y utilizada para organizar las acciones en gru-
pos logicos.
La clase basica para todos 10s objetos de accion es TBas i c A c t i o n , que
introduce el comportamiento abstract0 fundamental de una accion, sin ningun
enlace especifico ni correccion (ni siquiera a elementos de menu ni controles). La
clase derivada TC o n t a i n e dAc t i o n introduce propiedades y metodos que per-
miten que las acciones aparezcan en una lista de acciones o administrador de
acciones. La clase derivada TCus t omAc t i o n introduce soporte para las pro-
piedades y metodos de 10s elementos de menu y controles que estan enlazados a
10s objetos de accion. Por ultimo, esta la clase derivada lista para ser usada,
TAction.
Cada objeto de accion esta conectado a uno o mas objetos clientes a traves de
un objeto A c t i o n L i n k. Como indica su propiedad A c t i o n , posiblemente va-
rios controles de diferentes tipos pueden compartir el mismo objeto de accion.
Tecnicamente, 10s objetos A c t i o nL i n k mantienen una conexion bidireccional
entre el objeto cliente y la accion. El objeto ~ ci otn ~ i n k es necesario porque la
conexion funciona en ambas direcciones. Una operacion realizada sobre el objeto
(como un clic) se reenvia a1 objeto de accion y origina una llamada a su evento
O n E x e c u t e ; y una actualizacion del estado del objeto de accion se refleja en 10s
controles clientes conectados. En otras palabras, uno o mas controles cliente pue-
den crear un ActionLink, que se registra con el objeto de accion.
No se deberian definir las propiedades de 10s controles de cliente que se conec-
ten a una accion, ya que esta accion sobrescribe 10s valores de propiedad de 10s
control& de cliente. Por esa razon, normalmente se deberian escribir primer0 las
acciones y despues crear 10s elementos de menu y 10s botones que se quieran
conectar con ellas. Fijese en que cuando una accion no tiene un controlador
O n E x e c u t e , el control de cliente se desactiva automaticamente (o aparece en
gris), a menos que se haya definido la propiedad D i s a b l e I f N o H a n d l e r como
False.
Normalmente, 10s controles de cliente que se conectan a acciones son elemen-
tos de menu y diversos tipos de botones (botones pulsador, casillas de verifica-
cion, botones de radio, botones de velocidad, botones de la barra de herramientas
y similares), per0 tambien se pueden crear nuevos componentes que encajen en
esta estructura. Incluso se pueden definir nuevas acciones y nuevos objetos de
accion de enlace. Ademas de un control de cliente, algunas acciones pueden tener
tambien un componente destino. Algunas acciones predefinidas se conectan con
un componente destino especifico. Otras acciones buscan automaticamente un
componente destino en el formulario que soporte la accion especificada, empe-
zando por el control activo.
Por ultimo, 10s objetos de accion se encuentran dentro de un componente
A c t i o n L i s t o A c t i o n M a n a g e r , la unica clase de la estructura basica que
aparece en la Component Palette. La lista de acciones recibe las acciones
ejecutadas que no controlan 10s objetos de accion especificos y activa
O n E x e c u t e A c t i o n . Si la lista de acciones no controla la accion, Delphi hace
una llamada a1 evento O n E x e c u t e A c t i o n del o b j e t o A p p 1 i c a t i o n . El com-
ponente ActionList tiene un editor especial que se puede utilizar para crear diver-
sas acciones, como se muestra la figura 6.9.

gvaibbk Achm Classes AI I

Figura 6.9. El editor del cornponente ActionList, con una lista de acciones predefinidas
que se pueden w a r .

En el editor, las acciones aparccen en grupos, como indica su propiedad


C a t e g o r y . A1 definir esta propiedad con un valor nuevo, se le indica a1 editor
que introduzca una nueva categoria.
Estas categorias son basicamente grupos logicos, aunque en algunos casos un
grupo de acciones puede funcionar so10 con un tip0 especifico de componente de
destino. Se podria querer definir una categoria para cada menu desplegable o
agruparlos logicamente de otro modo.

Acciones predefinidas en Delphi


Con la lista de acciones y el editor ActionManager, se puede crear una accion
nueva o escoger una de las acciones ya existentes registradas en el sistema, lista-
das en un cuadro de dialog0 secundario, como se ha visto en la figura 6.9. Hay
muchas acciones predefinidas que pueden dividirse en grupos logicos:
Acciones de archivo: Como abrir, guardar corno, abrir con, e.jecutar, pre-
parar para impresion y salir.
Acciones de edicion: Reflejadas en el ejemplo siguiente. Son entre otras:
cortar, copiar, pegar, seleccionar todo, deshacer y borrar.
Acciones RichEdit: Complementan las acciones de edicion para 10s con-
troles RichEdit y son entre otras: negrita, cursiva, subrayado, resaltar,
viiietas y varias acciones de alineacion.
Acciones de ventana MDI: Son todas las operaciones MDI mas comunes:
organizar, cascada, cerrar, dividir (horizontal o verticalmente) y minimi-
zar todo.
Acciones de conjuntos de datos: Relacionadas con tablas de bases de
datos y con consultas. Todas las operaciones que se pueden realizar en un
conjunto de datos. Delphi 7 aiiade a las acciones de conjuntos de datos
basicas un grupo de acciones especificamente adaptadas a1 componente
Client DataSet,incluyendo: aplicar, invertir, deshacer.
Acciones de ayuda: Permiten activar la pagina de contenidos o el indice
del archivo de ayuda de la aplicacion.
Acciones de busqueda: Buscar, buscar primero, buscar siguiente y reem-
plazar.
Acciones de 10s controles solapa y pagina: El desplazamiento pagina
anterior y pagina siguiente.
Acciones de dialogo: Activan color, fuente, abrir, guardar e imprimir dia-
logos.
Acciones de lista: Borrar, copiar, mover, eliminar y seleccionar todo. Es-
tas acciones permiten interactuar con un control de lista. Otro grupo de
acciones, como la lista estatica, la lista virtual y algunas clases de soporte,
permiten definir listas que se pueden conectar a la interfaz de usuario.
Acciones Web: Explorar el URL, descargar el URL y enviar correo elec-
tronic~.
Acciones de herramientas: Solo incluyen el dialogo para personalizar las
barras de accion.
Ademas de manejar el evento OnExecute de la accion y cambiar el estado de
la accion para causar un efecto en la interfaz de usuario de 10s controles clientes,
una accion puede controlar tambien el evento Onupdate,que se activa cuando
la aplicacion no esta en uso.
Esto proporciona la oportunidad de verificar el estado de la aplicacion o del
sistema y cambiar la interfaz de usuario de 10s controles en funcion de ello. Por
ejemplo, la accion estandar PasteEdit activa 10s controles de cliente solo cuando
hay algun texto seleccionado en el portapapeles.
Las acciones en la practica
Ahora que se comprenden las ideas principales de esta caracteristica de Delphi
tan importante, para ver las acciones en la practica estudiaremos el programa
Actions, en el que hemos colocado un nuevo componente ActionList en el
formulario y aiiadido tres acciones de edicion estandar y algunas personalizadas.
El formulario tiene tambien un panel con algunos botones de velocidad, un menu
principal y un control de memo (el objetivo automatic0 de las acciones de edi-
cion). En el listado 6.3 aparecen las acciones, extraidas del archivo DFM.

Listado 6.3. Las acciones del ejemplo Actions.

object ActionListl: TActionList


Images = ImageListl
object Actioncopy: TEditCopy
Category = ' E d i t '
Caption = ' & C o p y 1
Shortcut = <Ctrl+C>
end
object Actioncut: TEditCut
Category = ' E d i t '
Caption = ' C u & t t
Shortcut = <Ctrl+X>
end
object Actionpaste: TEditPaste
Category = ' E d i t '
Caption = ' & P a s t e 1
Shortcut = <Ctrl+V>
end
object ActionNew: TAction
Category = ' P i l e '
Caption = ' & N e w 1
Shortcut = <Ctrl+N>
OnExecute = ActionNewExecute
end
object ActionExit: TAction
Category = ' P i l e '
Caption = ' E & x i t l
Shortcut = <Alt+F4>
OnExecute = ActionExitExecute
end
object NoAction: TAction
Category = ' T e s t '
Caption = ' & N o A c t i o n '
end
object Actioncount: TAction
Category = ' T e s t '
Caption = ' & C o u n t C h a r s '
OnExecute = ActionCountExecute
OnUpdate = Actioncountupdate
end
o b j e c t ActionBold: TAction
Category = ' E d i t '
Caption = ' & B o l d 1
Shortcut = <Ctrl+B>
OnExecute = ActionBoldExecute
end
o b j e c t ActionEnable: TAction
Category = ' Test'
Caption = ' & E n a b l e N o A c t i o n '
OnExecute = ActionEnableExecute
end
o b j e c t ActionSender: TAction
Category = ' T e s t '
Caption = ' T e s t & S e n d e r 1
OnExecute = ActionSenderExecute
end
end

-
, - -
NOTA: Las teclas de mktodo abreviado e s t h almacenadas en 10s archivos
DFM usando numeros de teclas virtuales, entre 10s que hay valores para las
teclas Control y Alt. En este y otros listados a lo largo del libro, se han
reemplazado 10s numeros por 10s valores literales, que se insertan entre 10s
simbolos < y >.
-

Todas estas acciones estan conectadas a 10s elementos de un componente


MainMenu y algunas de ellas tambitn a 10s botones de un control T o o l B a r .
Como muestra la figura 6.10, las imagenes seleccionadas en el control ActionList
afectan solamente a las acciones del editor. Para que las imagenes del lmageList
aparezcan en 10s elementos del menu y en 10s botones de la barra de herramientas,
hay que seleccionar tambien la lista de imagenes en 10s componentes MainMenu
y ToolBar

1 Calmofies: Actions:

Figura 6.10. El editor ActionList del ejemplo Actions.

BQ
Las tres acciones predeterminadas del menu Edit no tienen controladores aso-
ciados, per0 estos objetos especiales tienen un codigo interno para realizar la
accion relacionada con el control de edicion o de memo activo. Estas acciones se
activan y desactivan tambien a si mismas, dependiendo del contenido del
portapapeles y de la existencia de texto seleccionado en el control de edicion
activo. La mayoria de las otras acciones tienen un codigo personalizado, menos
en el caso del objeto NoAction. A1 no tener codigo, el elemento de menu y el
boton asociado a esta orden estan desactivados, aunque la propiedad Enabled
de esta accion esta definida como True.
Hemos afiadido a1 ejemplo y a1 menu Test otra accion que activa el elemento
de menu conectado a1 objeto NoAct ion:
procedure TForml.ActionEnableExecute(Sender: TObject);
begin
NoAction.Disable1fNoHandler : = False;
NoAction.Enabled : = True;
ActionEnable.Enabled : = False;
end ;

Definir Enabled como True,producira el resultado durante un corto perio-


do de tiempo, a menos que se defina la propiedad DisableIfNoHandler,
como se ha visto en el apartado anterior. Tras haber realizado esta operacion, hay
que desactivar la accion en uso, porque no es necesario dar de nuevo la misma
orden. Esta situacion es distinta a la que se produce cuando activamos una ac-
cion, como el elemento del menu Edit>Bold y su correspondiente boton de veloci-
dad. A continuacion, vemos el codigo para la accion Bold (que tiene su propiedad
Aut oChec k fijada como True,para que no resulte necesario modificar el esta-
do de la propiedad Checked en el codigo):
procedure TForml.ActionBoldExecute(Sender: TObject);
begin
with Memo1 . Font do
i f fsBold i n Style then
Style := Style - [fsBold]
else
Style := Style + [fsBold] ;
end ;

El objeto Actioncount tiene un codigo muy sencillo, per0 muestra el fun-


cionamiento de un controlador Onupdate. Cuando el control de memo esta
vacio, se desactiva automaticamente. Se podria haber conseguido el mismo resul-
tad0 controlando el evento OnChange del control de memo, per0 normalmente
no es posible ni facil determinar el estado de un control controlando simplemente
uno de sus eventos. A continuacion, aparece el codigo de 10s dos controladores de
esta accion:
procedure TForml.ActionCountExecute(Sender: TObject);
begin
ShowMessage ( ' C h a r a c t e r s : ' + IntToStr (Length (Memol.Text)) ) ;
end;

procedure TForml.ActionCountUpdate(Sender: TObject) ;


begin
ActionCount.Enabled : = Memol.Text <> " ;
end;

Por i~ltimo,hemos aiiadido una accion especial que comprueba el objeto remi-
tente del controlador de eventos de la accion y obtiene otra informacion sobre el
sistema. Ademis de mostrar la clase y nombre del objeto, hemos aiiadido un
codigo que accede a1 objeto de la lista de acciones, bisicamente para mostrar
como acceder a esta informacion:
procedure TForml.ActionSenderExecute(Sender: TObject);
begin
Memol .Lines .Add ( ' C l a s e r e m i t e n t e : ' + Sender .ClassName);
Memol.Lines.Add ( ' N o m b r e d e l r e m i t e n t e : ' + (Sender as
TComponent) .Name) ;
Memol. Lines .Add ( ' C a t e g o r i a : ' + (Sender as TAction) .Category) ;
Memol.Lines.Add ('Action l i s t n a m e : ' + (Sender as
TAction) .ActionList.Name);
end;

Se puede ver el resultado de este codigo en la figura 6.1 1, junto con la interfaz
dc usuario del ejemplo. Observe que el S e n d e r no es el elemento de menu selec-
cionado, aunqbe el controlador esta conectado a el. El objeto S e n d e r que activa
el evcnto es la accion que intercepta la operacion de usuario.

I Fle Edt Test

Figura 6.11. El ejemplo Actions, con una descripcion detallada del Sender del evento
OnExecute de un objeto de accion.
Por ultimo. hay que tcner presente que tambien se pueden escribir controladorcs
para evcntos del propio objeto ActionList. que jueguen el papel de controladores
globales para todas las acciones de la lista y para el objeto global Appl i c a t i o n ;
que se dispara para todas las acciones de la aplicacion. Antes de invocar a1 evento
O n E x e c u t e de la accion, Delphi activa el evento O n E x e c u t e de la A c t i o n -
L i s t y el evento OnAct i o n E v e n t del objeto global A p p l i c a t ion.Estos
eventos se fiaran en la accion, ejecutando eventualmente algo de codigo compar-
tido, y despues detendran la ejecucion (mediante el parametro H a n d l e d ) o deja-
ran que se propague hasta cl siguiente nivel.
Si no se asigna ningun controlador de eventos para responder a la accion, ni en
la lista de acciones, ni la aplicacion, ni en el ambito accion, la aplicacion trata de
identificar un objetivo a1 quc se pueda aplicar dicha accion.
- - - - - - - - -- .-- . - -

NOTA: Cuando se ejecuta una accion, esta busca un control como destino
de la accion, fijhdose en el control activo, el formulario activo y en otros
controles del formulario. Por ejemplo, las acciones de edicion se refieren a1
control activo en cada momento (si hereda de T C u s tomEdi t ) y 10s con-
troles de conjuntos de datos buscan el conjunto de datos conectado con la
fuente de datos del control data-aware que tiene el foco de entrada. Otras
acciones seguirin distintos enfoques para encontrar un componente desti-
no, pero la idea general es compartida por la mayoria de las acciones
esthdar.

La barra de herramientas y la lista de acciones


de un editor
En un ejemplo anterior (RichBar) se demostro el desarrollo de un editor con
una barra de herramientas y una barra de estado. Tambien podria haberse aiiadi-
do una barra de menu a1 formulario, pero a1 hacerlo hubieramos creado unos
cuantos problemas de sincronizacion del estado de 10s botones de la barra de
herramientas con 10s elementos del menu. Una solucion adecuada a este problema
es usar acciones, como en el ejemplo MdEditl que vamos a comentar.
La aplicacion se basa en un componente ActionList, que incluye acciones para
el manejo de archivos y soporte de portapapeles, con un codigo similar a1 de la
version RichBar. La seleccion del tip0 dc fuente y de color se basa en cuadros
combinados, por lo que no incumbe a acciones (lo mismo que en el caso del menu
desplegable del boton Size). Sin embargo, el menu tiene unas cuantas ordenes
adicionales, como una para el recuento de caracteres y otra para cambiar el color
de fondo. sta as se basan en acciones y lo mismo sucede con 10s tres botones (y
ordenes de menu) nuevos de justificacion de parrafo. Una de las diferencias clave
en esta nueva version es que el codigo nunca se refiere a1 estado de 10s botones de
la barra de herramientas, sin0 que modifica el estado de las acciones. El metodo
RichEdi t Se lec t ionchange no actualiza el estado del boton de negrita
(Bold), que esta conectado a una accion con el siguiente controlador OnUpdate:
p r o c e d u r e TFormRichNote.acBoldUpdate(Sender: T O b j e c t ) ;
begin
acBold.Checked : = fsBold i n RichEdit.SelAttributes.Sty1e;
end;

Para la mayoria de las acciones existen otros controladores de eventos


OnUpdate similares, como por ejemplo para operaciones de recuento (disponi-
ble solo si hay algun texto en el control RichEdit), la operacion save (disponible
si el texto ha sido modificado) y las operaciones Cut y Paste (solo disponibles
si hay texto seleccionado):
p r o c e d u r e TFormRichNote.acCountcharsUpdate(Sender: TObject);
begin
acCountChars.Enab1ed : = RichEdit.GetTextLen > 0;
end;

p r o c e d u r e TFormRichNote.acSaveUpdate(Sender: TObject);
begin
acSave.Enabled : = Modified;
end;

p r o c e d u r e ~ ~ o r m ~ i c h ~ o t e . a c C u t U p d a t e ( S e n d eTObject);
r:
begin
acCut.Enabled : = RichEdit.SelLength > 0;
acCopy.Enabled : = acCut.Enabled;
end :

En el ejemplo antiguo, el estado del boton Paste se actualizaba en el evento


OnIdle del objeto Application. Ahora que se utilizan acciones, se puede
convertir en otro controlador OnUpdate mas:
p r o c e d u r e TFormRichNote.acPasteUpdate(Sender: TObject);
begin
a c P a s t e - E n a b l e d : = S e n m e s s a g e (RichEdit.Handle,
em-CanPaste, 0, 0 ) <> 0;
end ;

Los tres botones de la barra de herramientas para la justificacion de parrafos y


10s elementos de menu asociados deberian funcionar como botones de radio, sien-
do mutuamente exclusivos en cada una de las tres opciones seleccionadas. Por
ello, las acciones tienen un GroupIndex definido como 1,los correspondientes
elementos de menu tienen la propiedad Radio1 tern definida como True y 10s
tres botones de la barra de herramientas tienen su propiedad Grouped definida
como T r u e y la propiedad A l l o w A l l U p como False. (Ademas estan
visualmente encerrados entre dos separadores). Esto es necesario para que el
programa defina la propiedad Checked de la accion correspondiente con el
estilo actual, lo cual evita que no se elimine la marca de las otras dos acciones
directamente.
Este codigo es parte del evento OnUpdate de la lista de accion, ya que se
aplica a multiples acciones:
procedure TFormRichNote.ActionListUpdate(Action: TBasicAction;
v a r Handled: Boolean);
begin
// v e r i f i c a l a a l i n e a c i o n d e l p d r r a f o c o r r e s p o n d i e n t e
c a s e RichEdit.Paragraph.Alignment o f
taLeftJustify: acLeftAligned.Checked : = True;
taRightJustify: acRightAligned.Checked : = True;
tacenter: acCentered.Checked : = True;
end;
// v e r i f i c a e l e s t a d o d e l a t e c l a BloqMayus
Checkcapslock;
end ;

Cuando se selecciona uno de estos botones, el controlador compartido utiliza


el valor de Tag, definido como el valor correspondiente de la enumeracion
TAl ignme nt , para determinar la justification correcta:
procedure TFormRichNote.ChangeAlignment(Sender: TObject);
begin
RichEdit.Paragraph.Alignment : = TAlignment ((Sender a s
T A c t i o n ) .Tag) ;
end;

Los contenedores de barra de herramientas


Muchas de las aplicaciones modernas tienen varias barras de herramientas
alojadas normalmente en un contenedor especifico. Microsoft Internet Explorer,
algunas aplicaciones empresariales estandar y el IDE de Delphi usan esta tecnica.
Sin embargo, cada uno de ellos la ha implementado de forma diferente. Delphi
tiene dos contenedores listos para usar:
El componente CoolBar: Es un control comun de Win32 introducido por
Internet Explorer y usado por algunas aplicaciones de Microsoft.
El componente ControlBar: Esta totalmente basado en la VCL, sin de-
pendencias de bibliotecas externas.
Ambos componentes pueden almacenar controles de barra de herramientas asi
como algunos elementos adicionales, como cuadros combinados y otros contro-
les. En realidad, una barra de herramientas puede reemplazar tambien a1 menu de
una aplicacion.
Ya que el componente CoolBar no se suele usar en las aplicaciones Delphi,
hablaremos brevemente de el a continuacion.
- .-- .

Una bonita barra de herramientas


El componente CoolBar de Win32 es. basicamente. un coniunto .- de obietos
T C c o l B a n d que se pueden activar a1 usar el editor de la propiedad Band.
disponible tambien en el menu del editor del componente o mediante la
. C . .
Object Tree View. Se puede personalizar el componente CoolBar de
* ..
4 .
mucnas rormas: se pueae esramecer un mapa ae bits para el ronao, anaair
.,. . r ..
algunas bandas a la coleccion B a n d s y asignarles despues a cada una un
componente existente o un contenedor de componentes a cada banda. Se
puede usar cualquier control basado en una ventana (no controles graficos)
per0 solo algunos dc ellos se mostrarhn del mono apropiado. Si sc quiere
tonor . a n m n n g AD h;tn onmn f n n A n AD I n n r r n m n n n n n t m
. r u r a u u r r l u p u uu u l c a r w l r l w r w m u w ur u u u w l l r p w u r l l r u
Pnn 1R
LL' uL
3 r nnr
L ~ ~ pL1 ,1
D ; D ~ ~ ] ~ .
rjr111

1lay que utilizar controlcs parcialmentt: transparentcs. El componentc ti1pic0


1~tilizadoen una CoolBar es el ToolBar per0 10s cuadros combinados. (:ua-
a,,, A., -A:,:-, .. 1.,-, A, ,
a:
-,
,.
-,
. , .,,t:..L , ,, t,,,,,*, ",.,
0 1 ~ U5C GUIGIUII y GUIILIUIG3 U C i l l l l l l l i l G I U 1 1 L;d1IIUICII 5 U I I USISLillllC GUIIIUIICS.

Se puede colocar una banda en cada linea o todas ellas en la misma. Cada
una utilizara una parte de la superficie disponible y aumentara de tamaiio
automaticamente cuando el usuario pinche sobre su titulo. Resulta mas
facil utilizar este componente que explicarlo. Se puede probar con el ejem-
plo CoolDemo:

his IS the text of the label, very bare if you compare ii

El formulario del ejemplo CoolDemo tiene un componente Tco o 1Bar con


cuatro bandas, dos por cada una de las dos lineas. La primera banda incluye
un subconjunto de la barra de herramientas del ejemplo anterior, akdiendo
ahora una ImageList para las imageries resaltadas. La segunda tiene un cua-
dro de edicion utilizado para establecer la fuente del texto; la tercera tiene un
componente C o l o r G r i d , usado para escoger el color de la fuente y el de
fondo. La ultima banda tiene un control ComboBox con las fuentes disponi-
es. La interfaz de usuario del componente ~ o o l w
Microsoft la utiliza en sus aplicaciones, pero alternativas como el compo-
nente C o n t r o l B a r ofrecen una interfaz de usuario similar sin ningtin
tip0 de problema ahdido. El control CoolBar de Windows ha tenido mu-
chas versiones distintas e incompatibles, ya que Microsoft ha hecho publi-
--" -
a"-
--" -- -----------dp
.--"-----"
r n c rlictintsrc v ~ r e i n n ~dp -- rnntrnlpr
-------" r-----"
c 1% h i h l i n t p r n --- diqtintac
n m i r n t w mn -"w-

versiones de hternet Explorer. AIgunas de estas versiones "estropean" 10s


programas existentes creados con Delphi, lo c u a es una b u m razon para
no usarlo ahora, induso aunque sea mis estable.

ControlBar
La barra de control (ControlBar) es un contenedor de controles y se crea sim-
plemente colocando otros controles dentro de ella, como si lo hicieramos en un
panel (en ella no hay lista de B a n d s ) . Cada control colocado en la barra consigue
su propia zona de arrastre o agarradera (un pequeiio panel con dos lineas vertica-
les, a la izquierda del control), incluso un boton solitario:

Por ello. generalmente, se deberia evitar colocar botones especificos dentro del
ControlBar, en su lugar se deberian colocar contenedores en 10s que se incluyan
botones. En lugar de un panel, como norma general, se deberia usar un control
ToolBar para cada seccion del ControlBar.
El ejemplo MdEdit2 es otra version de la prueba creada en este capitulo. Basi-
camente, se han agrupado 10s botones en tres barras de herramientas (en lugar de en
una) y dejado 10s dos cuadros combinados como controles independientes. Todos
estos componentes estan dentro del componente C o n t r o l B a r , para que el usua-
rio 10s pueda organizar en tiempo de ejecucion, como muestra la figura 6.12.
El siguiente fragment0 de listado DFM del ejemplo MdEdit2 muestra la forma
en que se incluyen varias barras de herramientas y controles en un componente
ControlBar:
object ControlBarl: TControlBar
A l i g n = alTop
AutoSize = True
ShowHint = True
PopupMenu = BarMenu
object ToolBarFile: TToolBar
Flat = True
Images = Images
Wrapable = False
object ToolButtonl: TToolButton
Action = acNew
end
/ / mds botones. . .
end
object ToolBarEdit: TToolBar . . .
object ToolBarFont: TToolBar . . .
object ToolBarMenu: TToolBar
AutoSize = True
Flat = True
Menu = MainMenu
end
object ComboFont : TComboBox
Hint = ' F a m i l y fonts'
Style = csDropDownList
OnClick = ComboFontClick
end
object ColorBoxl: TColorBox.. .
end

Figura 6.12. El ejemplo MdEdit2 en tiempo de ejecucion, mientras que un usuario


reordena las barras de herramientas.

Para conseguir el efecto estandar, hay que desactivar 10s bordes de 10s contro-
les de la barra de herramientas y definir su estilo como plano. Ajustar el tamaiio
de 10s controles del mismo modo, para poder obtener una o dos filas de elementos
con la misma altura, no es tan facil como parece. Algunos controles tienen ajuste
de tamaiio automatico o diversas restricciones. Concretamente, para que el cua-
dro combinado tenga la misma altura que la barra de herramientas, hay que ajus-
tar el tip0 y tamaiio de su fuente. Reajustar el tamaiio del control no tiene ningun
efecto.
La barra de control tiene tambien un menu de metodo abreviado que permite
mostrar u ocultar cada uno de 10s controles que contiene. En lugar de escribir un
codigo especifico para este ejemplo, hemos implementado una solucion mas gene-
rica (y reutilizable). El menu de metodo abreviado, llamado BarMenu, esta vacio
en tiempo de diseiio y se llena cuando arranca el programa:
procedure TFormRichNote.FormCreate(Sender: TObject);
var
I: Integer;
mItem: TMenuItem;
begin
...
// l l e n a e l m e n u d e l a b a r r a d e c o n t r o l
for I : = 0 to ControlBar .Controlcount - 1 do
begin
mItem : = TMenuItem-Create (Self);
mItem.Caption : = ControlBar.Controls [I].Name;
mItem.Tag : = Integer (ControlBar.Controls [I]) ;
mItem-OnClick : = BarMenuClick;
BarMenu.Items.Add (mItem);
end;

El procedimiento BarMenuCl i c k es un controlador de eventos sencillo, uti-


lizado por todos 10s elementos de menu. Usa la propiedad T a g del elemento de
menu Sender para referirse a1 elemento de la barra de control asociado a1 ele-
mento en el metodo F o r m c r e a t e :
procedure TFormRichNote.BarMenuClick(Sender: TObject);
var
aCtrl: TControl;
begin
aCtrl : = TControl ( (Sender as TComponent) .Tag);
aCtrl-Visible : = not aCtrl.Visible;
end;
Por ultimo, el evento OnPopup del menu se usa para refrescar la marca de
verificacion de 10s elementos del menu:
procedure TFormRichNote.BarMenuPopup(Sender: TObject);
var
I: Integer;
begin
// a c t u a l i z a l a s r n a r c a s d e v e r i f i c a c i o n d e l m e n u
for I : = 0 to BarMenu.Items .Count - 1 do
BarMenu. Items [I].Checked : = TControl (BarMenu.Items
[I] .Tag) .Visible;
end ;
Un menu en una barra de control
Si miramos la interfaz de usuario de la aplicacion MdEdit2 en la figura 6.12,
veremos que el menu del formulario en realidad aparece dentro de una barra de
herramientas, que, a su vez, esta dentro de la barra de control y bajo el titulo de la
aplicacion. Todo lo que hay que hacer es fijar la propiedad Menu de la barra de
herramientas. Tambien hay que eliminar el menu principal de la propiedad Menu
del formulario (manteniendo el componente MainMenu en el formulario), para
no tener dos menus.

Soporte de anclaje en Delphi


Otra caracteristica disponible en Delphi es el soporte para barras de herra-
mientas y controles que se pueden anclar. Es decir, se puede crear una barra de
herramientas y llevarla hacia cualquier lado de un formulario o moverla libremen-
te por la pantalla, sin anclarla. Sin embargo, configurar un prograrna adecuada-
mente para obtener este efecto no resulta tan facil como suena.
En primer lugar, el soporte de anclaje de Delphi esta conectado a controles de
contenedores, no a formularios. Se puede definir como destino del anclaje un
panel, una barra de control y otros contenedores (tecnicamente, cualquier control
derivado de TW i n C o n t r o 1 ) activando su propiedad Doc k S i t e . Tambien se
puede definir la propiedad A u t o S i z e de dichos contenedores para que aparez-
can solamente si contienen un control.
Para poder arrastrar un control (un objeto de cualquier clase derivada de
TCon t r o 1 ) hacia el lugar de anclaje, simplemente hay que definir la propiedad
D r a g K i n d como dkDock y su propiedad DragMode como d m A u t o m a t i c .
De esta forma, se puede arrastrar el control desde su posicion actual a un nuevo
contenedor de anclaje. Para desanclar un componente y llevarlo a un formulario
especial, se puede definir su propiedad F l o a t i ngDoc k S i t e C l a s s como
TCustomDoc kForm (para utilizar un formulario independiente predefinido con
un pequeiio titulo).
Se puede realizar un seguimiento de todas las operaciones de anclaje y desanclaje
utilizando eventos especiales del componente arrastrado ( o n S t a r t D o c k y
OnEndDoc k) y del componente que recibira el control anclado (OnDragOver
y OnDrag Drop). Estos eventos de anclaje son muy similares a 10s eventos de
arrastre en anteriores versiones de Delphi.
Tambien hay ordenes para realizar operaciones de anclaje mediante codigo y
explorar el estado del contenedor de anclaje. Se puede mover cada control a una
posicion diferente usando 10s metodos Dock, ManualDock y M a n u a l F l o a t .
Un contenedor tiene una propiedad Doc k c 1 i e n t Coun t , que indica el numero
de controles anclados, y otra Doc k c 1i e n t s , que contiene la matriz de dichos
controles.
Ademas, si el contenedor de anclaje tiene la propiedad UseDockManager
definida como T r u e , se puede utilizar la propiedad Doc k M a n a g e r , que
implementa la interfaz IDockManager. Esta interfaz tiene muchas finciones
para personalizar el comportamiento de un contenedor de anclaje, como el soporte
para streaming de su estado.
Como se puede ver en esta pequeiia descripcion, el soporte de anclaje en Delphi
se basa en un extenso numero de propiedades, eventos y metodos. El siguiente
ejemplo introduce las principales caracteristicas que necesitaremos normalmente.

Anclaje de barras de herramientas en barras


de control
Hemos incluido soporte de anclaje en el ejemplo MdEdit2. El programa tiene
una segunda barra de control en la parte inferior del formulario, que acepta el
arrastre de una de las barras de herramientas de la barra de control situada en la
parte superior. Como ambos contenedores de barra de herramientas tienen la
propiedad A u t o s i z e definida como T r u e , si no contienen ningun control se
eliminan automaticamente. Tambien hemos definido como T r u e la propiedad
A u t o D r a g y Aut oDoc k de ambas barras de control.
Hemos colocado la barra de control inferior dentro de un panel, junto con el
control RichEdit. Sin este truco, la barra de control seguiria moviendose por
debajo de la barra de estado cuando se activara y ajustara su tamaiio
automaticamente, lo que no supone un comportamiento correcto. En el ejemplo, la
barra de control es el unico panel alineado con la parte inferior, asi que no existe
ninguna confusion posible.
Para permitir a 10s usuarios arrastrar las barras de herramientas fiera de su
contenedor original, todo lo que hay que hacer es definir, una vez mas, su propie-
dad DragKind como dkDoc k y su propiedad DragMode como drnAutomat i c .
Las dos unicas excepciones son la barra de herramientas del menu, que se ha
mantenido cerca de una posicion tipica para una barra de menu, y el control
ColorBox, ya que, a diferencia del cuadro combinado, este componente no mues-
tra las propiedades DragMode y D r a g K i n d . (En el metodo F o r m c r e a t e del
ejemplo, se puede encontrar codigo encargado de activar el anclaje del componen-
te, basado en el truco de la palabra clave p r o t e c t e d ya comentado con anterio-
ridad.) El cuadro combinado de fientes se puede arrastrar, per0 no se va a permitir
que el usuario lo ancle en la barra de control inferior. Para implementar esta
restriccion, hemos usado el controlador de eventos OnDockOver de la barra de
control, que acepta la operacion de anclaje solo para barras de herramientas:
procedure TFormRichNote.ControlBarLowerDockOver(Sender:
TObject;
Source: TDragDockObject; X, Y: Integer; State: TDragState;
v a r Accept: Boolean) ;
begin
Accept : = Source.Contro1 is TToolbar;
end;
-
-TENCIA: h a s t a r directamente una ba<a de h e n a m i e n t a s m
, de la barra de c o n t d superior a la inferior no fimciona. La barra de control
no ajusta su tamaiio @ra dojar la barra de herramientas durante la opera-
ci6n de arra&re, corn hace si se arrastra la barra de herramientas a una
posicion flotantd y d&ub a la barra de conttol inferior. Se trata de un
fa110 en la VCL,,y &I muy dificil encontrar un rodeo.Como se vera en el
ejemplo MdEdit3. sepuede conseguir el efecto correct0 con un codigo dis-
tinto de soporte a la Vm.

Cuando se saca una de las barras de herramientas del contenedor, Delphi crea
automaticamente un formulario flotante. Podriamos sentirnos tentados a recupe-
rarla cerrando el formulario flotante. Pero, eso no funciona, porque el formulario
flotante se elimina junto con la barra de herramientas que contiene. Sin embargo,
sc puede utilizar el menu de metodo abreviado de la barra de control superior,
unido tambitn a la otra barra de control, para mostrar esta barra de herramientas
oculta.
El formulario flotante creado por Delphi para albergar controles no anclados
tienc un titulo muy pequeiio, llamado "titulo de barra de herramientas", que por
defect0 no tiene ningun testo. Por ello, hemos aiiadido algo de codigo a1 evento
OnEndDock de cada control anclable para fijar el titulo del formulario recien
creado en que se ancla el control.
Para evitar una estructura de datos personalizada para esta informacion, he-
mos usado el texto de la propiedad Hint de estos controles (que basicamente no
se utiliza) para proporcionar un titulo aceptable:
procedure TFormRichNote.EndDock(Sender, Target: TObject; X, Y:
Integer) ;
begin
i f Target i s TCustomForm then
TCustomForm(Target) .Caption : = GetShortHint ( (Sender as
TCont rol) .Hint) ;
end;

Se puede ver el resultado del ejemplo MdEdit2 en la figura 6.13. Otra posible
ampliacion de este ejemplo podria ser aiiadir zonas de anclaje en ambos laterales
del formulario. El unico esfuerzo adicional necesario seria una rutina para orien-
tar las barras de herramientas en vertical en lugar de en horizontal. Hacer esto
requiere conmutar las propiedades L e f t y Top de cada boton despues de inhabi-
litar el dimensionamiento automatico.
Control de las operaciones de anclaje
Delphi ofrece muchos eventos y metodos para controlar las operaciones de
anclaje, entre ellas un administrador de anclaje. El ejemplo DockTest es una
prueba para operaciones de anclaje, y se muestra en la figura 6.14.
Figura 6.13. El ejemplo MdEdit2 permite anclar las barras de herramientas (pero no el
menu) en la parte superior o inferior del formulario, o hacerlas flotar.

Figura 6.14. El ejemplo DockTest con tres controles anclados en el formulario principal.

El programa controla 10s eventos O n D o c k O v e r y O n D o c k D r o p de un panel


de anclaje anfitrion para mostrar mensajes a1 usuario, como el numero de contro-
les anclados en ese momento.
procedure TForml.PanellDockDrop(Sender: TObject; Source:
TDragDockObject;
X, Y: Integer);
begin
Caption : = 'Docked: ' + IntToStr (Panell.DockC1ientCount);
end;

De la misma forma, el programa controla tambien 10s eventos de anclaje del


formulario principal. Los controles tienen un menu de metodo abreviado a1 que se
puede recurrir para realizar operaciones de anclaje y desanclaje, sin necesidad del
arrastre con el raton, con un codigo como este:
procedure TForml.menuFloatPanelClick (Sender: TObject);
begin
Panel2 .ManualFloat (Rect (100, 100, 200, 300) ) ;
end ;

procedure TForml.FloatinglClick(Sender: TObject);


var
aCtrl: TControl;
begin
aCtrol : = Sender as TControl;
/ / conmuta a estado flotante
if aCtrl. Floating then
aCtrl .ManualDock (Panell, nil, alBottom) ;
else
aCtrl.ManualFloat (Rect (100, 100, 200, 300));
end :

Para hacer que el programa se ejecute de manera correcta en el arranque,


deberian anclarse 10s controles a1 panel principal en el codigo inicial; de no ser asi
se observaria un efecto algo extraiio. Aunque resulte raro, para que el programa
se comporte de manera adecuada, se necesita aiiadir controles a1 administrador de
anclaje y anclarlos tambien a1 panel (una operacion no activa automaticamente la
otra):
// anclar memo
Memol.Dock (Panell, Rect (0, 0, 100, 100) ) ;
Panell.DockManager.InsertControl(Memol, alTop, Panell);
/ / anclar cuadro de lista
ListBoxl-Dock(Panell, Rect (0, 100, 100, 100) ) ;
Panell.DockManager.Inse~:tContr01(ListBoxl, alleft, Panell);
// anclar panel2
Panel2Dock (Panell, Rect (100, 0, 100, 100) ) ;
Panell.DockManager.InsertControl(Panel2, alBottom, Panell);

La caracteristica final del ejemplo es, probablemente, la mas interesante (y la


mas complicada de implementar correctamente). Cada vez que se cierra el progra-
ma, se guardan 10s estados actuales de anclaje del panel, utilizando el soporte del
administrador de anclaje. Cuando se vuelve a abrir el programa, se vuelve a
aplicar la informacion de anclaje, restaurando la configuracion previa de la ven-
tana. Este es el codigo que se podria escribir para guardar y cargar este estado:
procedure TForml .Fordestroy (Sender: TObject) ;
var
FileStr: TFileStream;
begin
if Panell.DockClientCount > 0 then
begin
FileStr : = TFileStream.Create (DockFileName, fmCreate or
fmOpenWrite) ;
try
Panel1.DockManager.SaveToStream (FileStr);
finally
FileStr-Free;
end ;
end
else
// e l i m i n a e l a r c h i v o
DeleteFile (DockFileName);
end;

procedure TForml. Formcreate (Sender: TObject) ;


var
FileStr: TFileStream;
begin
// c o d i g o d e i n i c i a l i z a c i o n . ..

// v u e l v e a c a r g a r l a c o n f i g u r a c i o n
DockFileName : = ExtractFilePath (Application-Exename) +
'dock-dck';
if FileExists (DockFileName) then
begin
FileStr := TFileStream. Create (DockFileName, fmOpenRead) ;
try
Panell.DockManager.LoadFromStream (FileStr);
finally
FileStr.Free;
end:
end ;
Panel1.DockManager.ResetBounds (True);
end;

Este codigo funciona bien mientras que todos 10s controles estan anclados
inicialmente. Cuando se guarda el programa, si algun control permanece flotante,
no se vera cuando se vuelvan a cargar 10s parametros. Sin embargo, debido a1
codigo de inicializacion insertado con anterioridad, el control aparecera de todos
modos anclado a1 panel, y aparecera cuando se arrastren otros controles. No es
necesario decir que se trata de una situacion complicada. Por este motivo, des-
pues de cargar 10s parametros, hemos aiiadido este codigo:
for i : = Panell.DockClientCount - 1 downto 0 do
begin
aCtrl : = Panell.DockClientes[i];
Panell.DockManager.GetControlBounds(aCtr1, aRect);
if (aRect.Bottom - aRect .Top <= 0 ) then
begin
aCtrl-ManualFloat (aCtrl-ClientRect);
Panell.DockManager.RemoveControl(aCtrl);
end ;
end;

El listado cornpleto incluye codigo mas comentado, que se ha usado durante el


desarrollo de este programa; podria usarse para comprender lo que sucede (que
suele ser algo distinto de lo esperado). En pocas palabras, 10s controles que no
tienen un tamaiio especificado en el administrador de anclaje (el unico mod0 en
que se pucde detectar que no estan anclados) se muestran en una ventana flotante
y se eliminan de la lista del administrador de anclaje.
Si se analiza el codigo completo del controlador del cvento oncreate, se
vera una gran cantidad de codigo complejo, solo para conseguir un comporta-
miento sencillo. Se podrian aiiadir mas caracteristicas a un programa de anclaje,
per0 para hacer eso deberian eliminarse otras caracteristicas, ya que algunas
podrian entrar en conflicto. Aiiadir un formulario de anclaje personalizado choca
con las caracteristicas del administrador de anclaje. Los alineamientos automati-
cos no se llevan bien con el codigo del administrador de anclaje para recuperar el
estado. Lo me-jor es tomar este programa y explorar su comportamiento, amplian-
dolo para soportar el tip0 de interfaz de usuario que se prefiera.
.- - --- --
I N0TA:May quc recordar que, awque 10s panebs de h & j e hacen que una

que sur banas de herramientas puedan desaparecer o ~ s t n ren unsporic ih


diferente a la que e s h acostumbrados. No conviene abusar de 1;iS oarac-
tensticas de anclaje, ya que a l g h usuario inexpcrto podrh-pcrderse,

Anclaje a un PageControl
Otra caracteristica importante de 10s controles de ficha es su soporte especifi-
co para anclaje. Al anclar un nuevo control sobre un PageControl, automaticamente
se aiiade una nueva ficha que lo alberga, como se puede ver en el entorno Delphi.
Para realizar esto, simplemente hay que designar el PageControl como anclaje
anfitrion y activar el anclaje para 10s controles clientes. Esto funciona mejor si
tenemos formularios secundarios que queremos albergar. Ademas, para mover el
PageControl cornpleto a una ventana flotante y despues anclarlo otra vez, sera
necesario un panel de anclaje en el formulario principal.
Esto es exactamente lo que hemos hecho en el ejemplo Dockpage, que tiene
un formulario principal con 10s siguientes valores:
object Forml: TForml
Caption = ' Docking p a g e s '
object Panell: TPanel
Align = alLeft
DockSite = True
OnMouseDown = PanellMouseDown
object Pagecontroll: TPageControl
Activepage = TabSheetl
Align = alClient
DockSite = True
DragKind = dkDock
object TabSheetl: TTabSheet
Caption = ' List'
object ListBoxl: TListBox
Align = alClient
end
end
end
end
object Splitterl: TSplitter
Cursor = crHSplit
end
object Memol: TMemo
Align = alClient
end
end

Observe que el panel tiene la propiedad UseDockManage definida como


True y que el PageControl siempre alberga una pagina con un cuadro de lista
porque a1 quitar todas las fichas, el codigo utilizado para el ajuste automatico del
tamaiio de 10s contenedores de anclaje podria causar algun problema.
Ahora el programa tiene otros dos formularios mas, con valores similares
(aunque albergan controles diferentes):
object Form2: TForm2
Caption = 'Small Editor'
DragKind = dkDock
DragMode = dmAutomatic
object Memol: TMemo
Align = alClient
end
end

Se puede arrastrar estos fomularios a1 control de ficha para aiiadirle nuevas


fichas, con 10s titulos correspondientes a cada formulario. Se puede incluso des-
anclar cada uno de estos controles e incluso el PageControl completo. Para ello,
el programa no activa el arrastre automaticamente, lo cual haria que el carnbio
entre fichas ya no fuese posible. En carnbio, la caracteristica se activa cuando el
usuario hace clic en la zona sin solapas del PageControl, es decir, en el panel
subyacente:
procedure TForml.PanellMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X , Y: Integer);
begin
PageControl1.BeginDrag (False, 10);
end;

Si se ejecuta el ejemplo DockPage, se puede comprobar este comportamiento,


que trata de mostrar la figura 6.15. Observe que a1 quitar el Pagecontrol del
formulario principal, no se pueden anclar directamente 10s demas formularios al
panel, ya que lo impide el codigo especifico del programa (simplemente porque en
ocasiones no se tratara de un comportamiento correcto).

eat

lun mar n d iue we .ab dom


12 3 4
5 6 7 8 9 1 0 1 1
12 13 14 15 16 18
19 20 21 22 23 24 25
26 27 28 29 30 31

tun mar nw lue vle db durn


1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
n 24 25 26 27 28 29
30
+3Hoy: 17/05/2003

Figura 6.15. El formulario principal del ejemplo DockPage despues de que se haya
anclado un formulario al control de ficha de la izquierda.

La arquitectura de ActionManager
Hemos visto que las acciones y el componente A c t ionManager pueden
representar un papel principal en el desarrollo de las aplicaciones Delphi, ya que
permiten separar mejor la interfaz de usuario del codigo real de la aplicacion. Asi,
la interfaz de usuario puede cambiar ahora sin que eso tenga un gran impact0 en
el codigo. El inconveniente de esta tecnica es que el programador tiene mas traba-
jo. Para crear un nuevo elemento de menu, hay que aiiadir primer0 la accion
correspondiente, moverse a1 menu, aiiadir el elemento de menu y conectarlo con la
accion.
Para resolver este asunto y para ofrecer a 10s desarrolladores y usuarios fina-
les algunas caracteristicas avanzadas, Delphi 6 introdujo un nuevo tip0 de estruc-
tura, basada en el componente ActionManager, que amplia sobremanera la funcion
de las acciones. De hecho, el ActionManager no solo posee una coleccion de
acciones, sino tambien una coleccion de barras de herramientas y menus asocia-
dos a ellas. El desarrollo de estas barras de herramientas y menus es completa-
mente visual: se arrastran las acciones desde un editor de componente especial del
ActionManager hacia las barras de herramientas para acceder a 10s botones nece-
sarios. Ademas, se puede permitir a1 usuario final de 10s programas realizar la
misma operacion y reagrupar sus propias barras de herramientas y menus, empe-
zando por las acciones que se le ofrezcan.
En otras palabras, utilizar esta arquitectura permite construir aplicaciones con
una interfaz de usuario moderna y personalizable por el propio usuario. El menu
puede mostrar solo 10s elementos usados mas recientemente (como muchos pro-
gramas de Microsoft), permitir animaciones, y muchos detalles mas. Esta estruc-
tura se centra en el componente ActionManager, per0 incluye tambien otros
componentes que se encuentran al final de la ficha Additional de la paleta:
El componente ActionManager: Es un sustituto de ActionList (pero pue-
de utilizar tambien uno o mas ActionList existentes).
El control ActionMainMenuBar: Es una barra de herramientas usada
para mostrar el menu de una aplicacion basada en las acciones de un com-
ponente ActionManager.
El control ActionToolBar es una barra de herramientas utilizada para
albergar botones basados en las acciones de un componente ActionManager.
El componente CustomizeDlg: Contiene el cuadro de dialog0 que se pue-
de utilizar para permitir a 10s usuarios personalizar la interfaz de una
aplicacion basada en el componente A c t ionManager.
El componente PopupActionBarEx: Es un componente adicional que de-
beria usarse para permitir que 10s menus desplegables sigan la misma
interfaz de usuario que 10s menus principales. Este componente no se in-
cluye con Delphi 7, sino que se encuentra disponible como una descarga
separada.

bitn llamado AC t i o n ~ o ~ u ~ ~ e n uel)repositorio


ka Web CodeCentraI de
Borland (numero 1 8870). Ademis, se puede cncontrar mas infomacion en
el sitio Web del autor deI componente (homepages.borland.com/strefhen).
Es un miembro deI equipo de I+D de Delphi en Borland. El componente se
encuentra en el sitio Web, pero no esta oficialmente soportado.

Construir una sencilla demostracion


Debido a que esta estructura es principalmente visual, una demostracion sera
probablemente de mayor ayuda que una exposicion general (aunque un libro no es
el mejor soporte para este tip0 de descripcion). Para crear un programa de ejem-
plo basado en esta estructura, hay que poner en un formulario un componente
A c t ionManager,hacer doble clic sobre 61 para abrir su editor de componente,
que se muestra en la figura 6.16. Observe que este editor no es modal, asi que se
puede mantener abierto mientras realizamos otras operaciones en Delphi. Ademas
hay que tener en cuenta que este cuadro de dialogo lo muestra tambien el compo-
nente C u s tomizeDlg,aunque con algunas caracteristicas limitadas (por ejem-
plo, aAadir nuevas acciones esta desactivado).

Figura 6.16. Las tres paginas del cuadro de dialogo del editor de ActionManager.

rn
Las tres paginas del editor son asi:
La primera ficha proporciona una lista visual de contenedores de acciones
(barras de herramientas o menus). Para aiiadir nuevas barras de herra-
mientas, se hace clic en el boton New. Para aiiadir nuevos menus, hay que
aiiadir el componente correspondiente a1 formulario, despues abrir la co-
leccion A c t i o n B a r s del ActionManager, seleccionar una barra de ac-
ciones o aiiadir una nueva y engancharle el menu usando la propiedad
A c t i o n B a r . Estos son 10s mismos pasos que podriamos seguir para co-
nectar una nueva barra de herramientas a esta estructura en tiempo de
ejecucion.
La segunda ficha del editor de ActionManager es muy similar a la del
editor de ActionList, que ofrece una forma estandar de aiiadir acciones
nuevas o personalizadas, organizarlas en categorias y modificar su orden.
Sin embargo, la caracteristica importante de esta ficha consiste en que se
puede arrastrar una categoria o una simple accion desde la misma y soltar-
la en un control de una barra de acciones. Si se arrastra una categoria a un
menu, se consigue un menu desplegable con todos 10s elementos de la
categoria. Si se arrastra a una barra de herramientas, cada una de las
acciones de la categoria genera un boton en la barra de herramientas. Si se
arrastra una orden sencilla a una barra de herramientas, se obtiene el co-
rrespondiente boton, si se arrastra al menu, se obtiene una orden directa de
menu, algo que como norma general se deberia evitar.
La ultima pagina del editor ActionManager permite (a1 programador y
opcionalmente a un usuario final) activar el visor de 10s elementos de menu
usados recientemente y modificar algunas de las propiedades visuales de la
barra de herramientas.
El programa AcManTest es un ejemplo que usa algunas de las acciones
estandar y un control RichEdit para explicar el uso de esta estructura (no se ha
escrito nada de codigo personalizado para hacer que las acciones funcionen me-
jor, porque el objetivo era solo el administrador de acciones). Se puede experi-
mentar con el en tiempo de diseiio o ejecutarlo, hacer clic en el boton Customize
y ver lo que el usuario final puede hacer para personalizar la aplicacion. (Vease la
figura 6.17.) En realidad, en el programa se puede evitar que el usuario realice
algunas operaciones sobre acciones. Cualquier elemento especifico de la interfaz
de usuario (un objeto T ~ c t i o n ~ l i e ntiene
t ) una propiedad C h a n g e A l l o w e d
que se puede usar para desactivar la modificacion, el desplazamiento y la elimina-
cion de operaciones. Cualquier contenedor de clientes de accion (las barras visua-
les) tiene una propiedad para inhabilitar su ocultacion (A1 l owH i d i ng , fijada
por defect0 a T r u e ) . Cada coleccion de It e m s de una barra de accion tiene una
opcion C u s t o m i z a b l e que se puede inhabilitar para desactivar todos 10s cam-
bios de usuario en toda la barra.
Figura 6.17. Mediante el componente CustomizeDlg se puede permitir que un usuario
personalice las barras de herramientas y el menu de una aplicacibn arrastrando
elementos desde el cuadro de dialog0 o moviendolos en las barras de accion.

1. TRUCO: ~ & d o mencionarnos ActionBar no nos referimos a las b a n s


de herramientas visuales que contienen elementos de accion del componente
ActionManager, que a su vez dispone de una coIeccion Items. El mejor
modo de comprender esta estnrctura es fijarse en el subarbol mostrado por
el Object TreeView para un componente ActionManager. Cada elemento
de la coleccion TAc t i o n B a r tiene un componente visual Tcus tom-
Act ionBar conectado, pero lo contrario no ocurre (por eso, por ejemplo,
no se puede alcanzar la propiedad Customizable, si se inicia seleccio-
nando la barra de herramientas visual). Debido a la sirnilitud de 10s dos
pombres, puede llevar un tiempo entender a q u e se refiere realmente la
1 ayuda de Delphi.

Para que 10s valores de usuario sean permanentes, hemos conectado un archivo
(llamado settings) a la propiedad Fi leName del componente A c t ionManager.
Cuando se asigna esta propiedad, hay que introducir el nombre del archivo que se
quiere usar; a1 iniciar el programa, el ActionManager creara el archivo. La perma-
nencia se consigue mediante streaming de cada ActionClientItem conectado con el
administrador de acciones. Como estos elementos de cliente de accion estan basa-
dos en la configuracion de usuario y mantienen la informacion de estado, un
simple archivo recoge tanto 10s cambios que el usuario ha realizado en la interfaz
como 10s datos de uso.
Dado que Delphi almacena valores de usuario e informacion de estado en un
archivo que nosotros ofrecemos, se puede hacer que la aplicacion soporte varios
usuarios en un solo ordenador. Simplemente, hay que usar un archivo de configu-
raciones para cada uno de ellos (dentro de la carpeta Mi s document 0s) y
conectarlo con el administrador de accion cuando el programa arranque (utilizan-
do el usuario actual del ordenador o despues de algun nombre de usuario que se
solicite). Otra posibilidad es almacenar estas configuraciones en la red, de forma
que si un usuario esta en otro ordenador, su configuracion personal viaje con el.
En el programa hemos decidido guardar 10s valores en un archivo dentro de la
misma carpeta que el programa, asignado la ruta relativa (el nombre de archivo)
a la propiedad FileName del ActionManager. El componente rellenara el nom-
bre de archivo completo con la carpeta del programa, encontrando sin problemas
el archivo que cargar. Sin embargo, el archivo incluye entre sus datos su propio
nombre, con una ruta absoluta. Por eso, cuando llegue el momento de guardar el
archivo, la operacion puede referirse a la ruta antigua. Esto impediria que se
copiara este programa con sus configuraciones a una carpeta distinta (por ejem-
plo, esto es un problema para la prueba AcManTest). Se puede devolver su valor
a la propiedad FileName despues de cargar el archivo. Como otra alternativa
mejor, podria establecerse el nombre de archivo en tiempo de ejecucion, en el
evento oncreate del formulario. En este caso tambien habria que obligar a que
el archivo se recargase, ya que se asigna despues de que ya se hayan creado e
inicializado 10s componentes ActionManager y 10s distintos ActionBar. Sin
embargo, podria desearse forzar el nombre de usuario despues de la carga:
procedure TForml. Formcrate (Sender:TObject) ;
begin
ActionManagerl.Fi1eName : = ExtractFilePath
(Application.ExeName) + ' s e t t i n g s ' ;
ActionManagerl.LoadFromFi1e(ActionManagerl.FileName);
// devolvemos el nombre a 1 fichero d e configuracion despues
d e cargarlo (ruta relativa)
ActionManagerl.FileName : = ExtractFilepath
(Application.ExeName) + 'settings';
end ;

Objetos del menli utilizados con menos


frecuencia
Cuando tengamos el archivo para las configuraciones de usuario, el
ActionManager guardara en el las preferencias del usuario y tambien lo usara
para realizar un seguimiento de la actividad del usuario. Esto es esencial para que
el sistema pueda eliminar elementos del menu que no se hayan utilizado durante
algun tiempo, desplazandolos a un menu extendido, de manera que se use la
misma interfaz de usuario adoptada por Microsoft (vease la figura 6.18).

Figura 6.18. El ActionManager desactiva 10s elementos de menu menos utilizados


recientemente. Alin pueden verse mediante el comando de extension del menu.

El ActionManager no solo muestra 10s elementos utilizados con menos fre-


cuencia, tambien permite personalizar este comportamiento de una forma mu?;
precisa. Cada barra de accion tiene una propiedad S e s s i o n c o u n t que realiza
el seguimiento dcl numero de veces que se ha ejecutado la aplicacion. Cada
A c t i o n C l i e n t e I t e m tiene una propiedad L a s t S e s s i o n y una propiedad
u s a g e c o u n t utilizada para el seguimiento de las operaciones del usuario. Ob-
serve que el usuario puede volver a poner a cero toda esta informacion dinamica
usando el boton Reset Usage Data del dialog0 de personalizacion.
El sistema calcula el numero de sesiones en las que no se ha utilizado la accion
y procesa la diferencia entre el numero de veces que se ha ejecutado la aplicacion
( s e s s i o n c o u n t ) y la ultima sesion en la que se us6 dicha accion ( L a s t -
s e s s i o n ) . El valor de U s a g e c o u n t se usa para mirar en el P r i o r i t y -
S c h e d u l e el numero de sesiones en las que no se usa el elemento que hay
establecidas para eliminarlo. En otras palabras, modificando el P r i o r i t y -
S c h e d u l e se puede determinar la velocidad con la que se eliminan 10s elemen-
tos, en caso de que no se usen. Tambien se puede evitar que se active este sistema
en el caso de acciones especificas o grupos de acciones. La propiedad I terns de
A c t i o n B a r s del ActionManager tiene una propiedad H i d e u n u s e d que pode-
mos cambiar para desactivar esta caracteristica para todo un menu. Para que un
elemento especifico sea siempre visible, no importa cual sea su uso real, tambien
se puede fijar su propiedad U s a g e c o u n t como - 1.Sin embargo, las configura-
ciones de usuario pueden sobrescribir este valor.
Para entender un poco mejor el funcionamiento de este sistema, hemos aiiadido
una accion personalizada (Act i o n s h o w s t a t u s ) a1 ejemplo AcManTest. La
accion tiene el siguiente codigo que guarda la configuration actual del adminis-
trador de accion en un stream de memoria, lo convierte en texto y lo muestra
dentro del campo de memo:
procedure TForml.ActionShowStatusExecute(Sender: TObject);
var
memStr, memStr2: TMemoryStream;
begin
memStr : = TMemoryStream.Create;
try
memStr2 : = TMemoryStream.Create;
try
ActionManagerl.SaveToStream(memStr);
memStr.Position : = 0;
ObjectBinaryToText(memStr, memStr2);
memStr2.Position : = 0;
RichEditl.Lines.LoadFromStream(memStr2);
finally
memStr2. Free;
end ;
finally
memStr. Free;
end ;
end;

El resultado obtenido es la version textual del archivo settings actualizado


automaticamente con cada ejecucion del programa. A continuacion, aparece una
pequeiia parte del archivo, con 10s datos de uno de 10s menus desplegables y
muchos comentarios adicionales:
item / / desplegable File de la barra de acciones del menu
principal
Items = <
item
Action = Forml.FileOpen1
LastSession = 19 // utilizado en la ultima sesion
Usagecount = 4 // utilizado cuatro veces
end
item
Action = Forml.FileSaveAs1 / / no usado nunca
end
item
Action = Forml.FilePrintSetup1
LastSession = 7 / / usado hace algun tiempo
UsageCount = 1 // s o l o una vez
end
item
Action = Forml.FileRun1 // no u s a d o nunca
end
item
Action = Forml.FileExit1 // no usado nunca
end>
Caption = ' & F i l e 1
Lastsession = 19
UsageCount = 5 / / la suma del r e c u e n t o d e uso d e 10s e l e m e n t o s
end

Modificar un programa existente


Si esta arquitectura es util, probablemente sera necesario rehacer la mayoria
de las aplicaciones para poder sacarle partido. Sin embargo, si ya se han utilizado
acciones (con el componente ActionList), la conversion sera mucho mas sencilla.
De hecho, el ActionManager posee su propio conjunto de acciones, per0 tambien
puede usar acciones de otro ActionManager o ActionList. La propiedad
L i n k A c t i o nL i s t del ActionManager es un conjunto de otros contenedores de
acciones (componentes ActionList o ActionManager), que se pueden asociar con
el ActionManager actual. Asociar todos 10s diversos grupos de accion es util
porque se puede permitir a un usuario personalizar toda la interfaz de usuario con
un sencillo cuadro de dialogo.
Si enganchamos acciones externas y abrimos el editor del ActionManager,
veremos en la ficha Actions, un cuadro combinado que lista el ActionManager
actual mas 10s otros contenedores de acciones asociados a el. Se puede escoger
uno de estos contenedores para ver su conjunto de acciones y cambiar sus propie-
dades. La opcion A l l A c t i o n de este cuadro combinado permite trabajar en
todas las acciones de 10s diversos contenedores a1 mismo tiempo, per0 se ha
observado que se selecciona automaticamente a1 arrancar y no siempre resulta
eficaz. Hay que seleccionarlo de nuevo, para poder ver todas las acciones.
Como ejemplo de modificacibn de una aplicacion existente, hemos ampliado el
programa desarrollado a lo largo de este capitulo, en forma de ejemplo MdEdit3.
Este ejemplo utiliza la misma lista de acciones que la version anterior, engancha-
da a un ActionManager con propiedades personalizadas adicionales para permi-
tir a 10s usuarios reorganizar la interfaz. A diferencia del anterior programa
AcManTest, el ejemplo MdEdit3 utiliza un ControlBar como contenedor para las
barras de acciones (un menu, tres barras de herramientas y 10s cuadros combina-
dos habituales) y posee total soporte para arrastrarlos fuera del contenedor como
barras flotantes y dejarlas en la barra de control inferior. Para esto, solo hub0 que
modificar ligeramente el codigo fuente para hacer referencia a las nuevas clases
de contenedores (esto es, TCus t o m A c t i o n T o o l B a r , en lugar de T t o o l B a r )
en el metodo ControlBarLowerDockOver.El evento OnEndDock del com-
ponente ActionToolBar, pasa como uno de sus parametros un destino en
blanco cuando el sistema crea un formulario flotante para albergar el control, por
ese motivo no fue facil dotar a estos formularies de un nuevo titulo personalizado.
(Vease el metodo EndDock del formulario.)

Emplear las acciones de las listas


Como ejemplo adicional, se muestra el mod0 de empleo de un grupo bastante
complejo de acciones estandar: las acciones de lista. Las acciones de lista engloban
dos grupos diferentes. Algunas de ellas (como Move, Copy, Delete, Clear y Select
All) son acciones normales que funcionan en cuadros de lista u otras listas. Sin
embargo, VirtualListAction y StaticListAction definen acciones
que proporcionan una lista de elementos que se van a visualizar en una barra de
herramientas como cuadro combinado.
El ejemplo ListActions destaca ambos grupos de acciones, ya que su
ActionManager tiene cinco, visualizadas en dos barras de herramientas separa-
das. Este es un resumen de las acciones del administrador de acciones (omitiendo
las seccion de las barras de accion del archivo DFM del componente):
object ActionManagerl: TActionManager
ActionBars.SessionCount = 1
ActionBars = < . . . >
object StaticListActionl: TStaticListAction
Caption = 'Numbers'
1tems.CaseSensitive = False
1tems.SortType = stNone
Items = <
item
Caption = ' one'
end
item
Caption = ' two'
end
. . .>
OnItemSelected = ListActionItemSelected
end
o b j e c t VirtualListActionl: TVirtualListAction
Caption = ' Items'
OnGetItem = VirtualListActionlGetItem
OnGetItemCount = VirtualListActionlGetItemCount
OnItemSelected = ListActionItemSelected
end
object ListControlCopySelectionl: TListControlCopySelection
Caption = ' Copy'
Destination = ListBox2
Listcontrol = ListBoxl
end
object ListControlDeleteSelectionl: TListControlDeleteSelection
Caption = 'Delete'
end
object ListControlMoveSelection2: TListControlMoveSelection
Caption = 'Move'
Destination = ListBox2
Listcontrol = ListBoxl
end
end

El programa tiene tambien dos cuadros de lista en su formulario, utilizados


como objetos de accion. Las acciones Copy y Move estan ligadas a estos dos
cuadros de lista mediante sus propiedades Listcontrol y Destination.
Sin embargo, la accion Delete trabaja automaticamente con el cuadro de lista que
tiene el foco de entrada.
En su coleccion Items, la StaticListAction define una serie de elementos
alternativos. Esta no es una simple lista de cadena, ya que cada elemento tambien
t i m e un ImageIndex que permite aiiadir elementos graficos a1 control que
muestra la lista. Por supuesto. se pueden aiiadir mas elementos mediante progra-
macion a esta lista. Sin embargo, en el caso de una lista altamente dinamica.
tambien s e puede utilizar 1aVirtualListAct ion.Este accion no solo define
una lista de elementos. sino que tiene dos eventos que se pueden usar para propor-
cionar cadenas e imagenes para la lista. El evento OnGetItemCount permite
indicar el numero de elementos a mostrar y OnGe t Item se llama entonces para
cada elemento especifico.
En el ejemplo ListActions, la VirtualListAction t h e 10s siguientes
controladores de eventos para su definicion y produce la lista que se aparece en el
cuadro combinado de la figura 6.19.
procedure TForml.VirtualListActionlGetItemCount(Sender:
TCustornListAction;
var Count: Integer) ;
begin
Count : = 100;
end;

procedure TForml.VirtualListActionlGetItem(Sender:
TCustornListAction;
const Index: Integer; var Value: String;
var ImageIndex: Integer; var Data: Pointer) ;
begin
Value : = 'Item' + IntToStr (Index);
end;

NOTA: En lugar de que 10s elementos de acci~nvirtuales sean solicitados


solo cuando. se necesita mostrarlos, se crean de todas fonnas. Se puede
probar si se habilita el &go comentado en el metodo VirtualLis t -
A c t ionlGe tI t e m (no incluido en el listado anterior), que aiiade a cada
elemento la hora en que se solicita su cadena.
Figura 6.19. La aplicacion ListActions tiene una barra de herrarnientas que hospeda
una lista estatica y una lista virtual.

Ambas listas, la estatica y la virtual, tienen un controlador de eventos


On1 temSe lected.En el controlador de eventos compartido, hemos escrito el
codigo siguiente, para aiiadir el elemento actual a1 primer cuadro de lista del
formulario:
procedure TForml.ListActionItemSe1ected(Sender:
TCustomListAction; .
Control: TControl) ;
begin
ListBoxl.Items.Add ((Control as
TCustornActionCombo) .SelText) ;
end:

En este caso, el remitente es la lista de accion personalizada, pero la propiedad


I temIndex de esta lista no se actualiza con el elemento seleccionado. Sin em-
bargo, accediendo a1 control visual que muestra la lista, obtenemos el valor del
elemento seleccionado.
0 Trabajo
con formularios

Si se han leido 10s capitulos anteriores, ahora deberia poder utilizar 10s com-
ponentes visuales de Delphi para crear la interfaz de usuario de una aplicacion.
Es hora de fijarse en otro elemento central del desarrollo en Delphi: 10s formula-
rios. Se han venido usando desde el principio del libro, per0 nunca se ha descrito
en detalle lo que se puede hacer con un formulario, que propiedades puede usar o
que metodos de la clase TForm resultan de interes particular.
Este capitulo analiza algunas de las propiedades y estilos de formularios y las
tecnicas de dimcnsionamiento y position, a1 igual que su escalado y desplaza-
miento. Tambien hablaremos de las aplicaciones con varios formularios, el uso de
10s cuadros de dialog0 (personalizados y predefinidos), marcos y herencia visual
de formularios. Por ultimo, dedicaremos algo de tiempo a1 sistema de entrada en
un formulario, tanto mediante el teclado como mediante el raton.
Este capitulo trata 10s siguientes temas:
Estilos de formularios, de bordes e iconos de bordes.
Entrada de raton y teclado.
Dibujo direct0 sobre el formulario y efectos especiales.
Posicion, escala y desplazamiento de formularios.
Creacion y cierre de formularios.
Cuadros de dialogo y formularios modales y no modales.
Creacion dinamica de formularios secundarios
Cuadros de dialogo predefinidos.
Construccion de una pantalla de inicio.

La clase TForm
La clase T Form,incluida en la unidad Forms de la VCL, define 10s formula-
rios en Delphi. Ahora, existe tambien una segunda definicion de 10s formularios
en la biblioteca VisualCLX. Aunque a lo largo del presente capitulo, nos referire-
mos principalmente a la clase de la VCL, intentaremos resaltar tambien las dife-
rencias con la version multiplataforma que proporciona la biblioteca CLX.
La clase TForm forma parte de la jerarquia de controles de ventana, que
comienza con la clase TWinControl (o TWidgetControl). En realidad,
TForm hereda de la "casi completa" TCustomForm,que a su vez hereda de
TScrollingWinControl (o TScrollingWidget). A1 tener todas las
funciones de sus clases basicas, 10s formularios tienen una gran serie de metodos,
propiedades y eventos. Por este motivo, no vamos a intentar mostrar una lista de
todos ellos. En su lugar, a lo largo del capitulo, explicaremos una serie de tecni-
cas interesantes relacionadas con 10s formularios. Remarcaremos las pocas dife-
rencias existentes entre 10s formularios VCL y 10s formularios CLX. Para la
mayoria de 10s ejemplos existe una version CLX, para que se pueda comenzar a
experimentar a1 instante con formularios y cuadros de dialogo en la CLX, a1 igual
que con la VCL. La inicial de estas versiones CLX de cada ejemplo sera la Q.

Usar formularios normales


Normalmente, 10s desarrolladores en Delphi suelen crear formularios en tiem-
po dc diseiio, lo cual implica la derivacion de una nueva clase a partir de la basica
y la creacion del contenido del formulario de un mod0 visual. Aunque esta es una
tecnica muy comun, no es obligatorio crear un descendiente de la clase TForm
para mostrar un formulario, sobre todo cuando se trata de uno sencillo.
Pensemos en el siguiente ejemplo: tenemos que mostrar un mensaje largo a1
usuario (basado en una cadena), per0 no queremos usar el sencillo cuadro de
mensaje predefinido, porque sera demasiado grande y no tendra barras de despla-
zamiento. Podemos crear un formulario con un componente de memo y mostrar la
cadena en su interior. Nada impide crear este formulario del mod0 visual acos-
tumbrado, per0 podria tomarse en consideracion hacerlo mediante codigo, en par-
ticular si se necesita un amplio grado de flexibilidad. Los ejemplos DynaForm y
QDynaForm, que son algo radicales, no tienen un formulario definido en tiempo
de diseiio, sino que incluyen una unidad con esta funcion:
procedure ShowStringForm (str: string) ;
var
form: TForm;
begin
Application.CreateForm (TForm, form);
form.caption : = 'DynaForrn';
form-Position : = poScreenCenter;
with TMemo.Create (form) do
begin
Parent : = form;
Align : = alclient;
Scrollbars : = ssvertical;
ReadOnly : = True;
Color : = form.Color;
Borderstyle : = bsNone;
WordWrap : = True;
Text : = str;
end;
form.Show;
end;

Se crea el formulario usando el metodo CreateForm del objeto global


A p p 1i cation (una caracteristica necesaria para las aplicaciones de Delphi);
ademas de esto, este codigo realiza de forma dinarnica algo que, por lo general, se
hace con el disefiador de formularios.
Escribir este codigo es sin duda alguna pesado, per0 permite una gran flexibi-
lidad, porque cualquier parametro puede depender de las configuraciones
externas.
La funcion Showstring Form anterior no la ejecuta un evento de otro for-
mulario, puesto que en este programa no hay formularios tradicionales. En cam-
bio, hemos modificado el codigo fuente del proyecto del siguiente modo:
program DynaForm;

uses
Forms,
DynaMemo in ' DynaMerno .pas ' ;

var
str: string;

begin
str : = 1 1 .
Randomize;
while Length (str) < 2000 do
str : = str + Char (32 + Random (74));
ShowStringForm (str);
Application.Run;
end.
A1 ejecutar el programa DynaForm, se obtiene un formulario de extraAa apa-
riencia cubierto con caracteres aleatorios (como muestra la figura 7.1).

Figura 7.1. El formulario dinamico generado por el ejemplo DynaForm se crea


completamente en tiempo de ejecucion.
--
TRUCO: Una ventaja indirecta de esta tecnica, comparada con el uso de
archivos DFM para formularios en tiempo de disefio, es que supondria una
mayor dificultad para un programador externo conseguir informacion so-
L-- l- 3- l- --l:---:A-
r a la ayl1c;ac;lutl. p..--
VIC la ~ s n u c ~ u u~
--&---A *--- L ---- -- ----A- ---- -1
LUIIIU nernus vlsrv, sr; pur;ur; r;xrrar;i GI
-.:-A-

DFM del archivo ejecutable real de Delphi, per0 tambien se puede hacer lo
mismo con cualquier archivo ejecutable compilado con Delphi del que no
tengamos el c6digo fuente. Si es importante guardarse un conjunto especifi-
co de componentes que se utilicen (quizas en un formulario especifico),
junto con 10s valores predefinidos para sus propiedades, escribir el codigo
adicional puede merecer la pena.

El estilo del formulario


La propiedad F o r m S t y l e permite escoger entre un formulario normal
(f s N o r m a l ) y las ventanas que componen una aplicacion de hterfaz de Docu-
mentos Multiples (Multrple Doctment Interface, MD1). En este caso, usaremos
cl estilo fsMDIForm para la ventana padre MDI (es decir, la ventana marco de
la aplicacion MDI) y el estilo f s M D I C h i l d para la ventanas MDI hijo.
Una cuarta opcion es el estilo fs S t a y O n T o p , que establece si el formulario
ha de permanecer siemprc sobre todas las demas ventanas, a escepcion de algunas
que puedan ser ventanas "fijadas por encima". Para crear un formulario superior
(un formulario cuya ventana permanece siempre por encima), es necesario definir
la propiedad F o r m S t y l e . como se indico anteriormente. Dicha propiedad causa
dos efectos distintos, dependiendo del tip0 de formulario a1 que se aplique:
El formulario principal de una aplicacion permanecera por encima de to-
das las demas aplicaciones (a menos que las demas aplicaciones tengan
tambien este mismo estilo de ventana). A veces. esto genera un efecto
visual bastante desagradable, por lo que solo tiene sentido en programas de
alerta para usos especiales.
Un formulario secundario permanecera por encima de 10s demas formula-
rios de la aplicacion a la que pertenecc. Pero las ventanas de otras aplica-
ciones no se veran afectadas. Esto se usa normalmente para barras de
herramientas flotantes que deberian estar sobre la ventana principal.

El estilo del borde


Otra propiedad importante de un formulario es su propiedad B o r d e r S t y l e .
Esta se refiere a un elemento visual del formulario, per0 tiene una influencia
mucho mayor en el comportamiento de la ventana, como muestra la figura 7.2.
En tiempo de diseiio, el formulario siempre aparece utilizando el valor
predefinido de la propiedad B o r d e r S t y l e , b s s i z e a b l e . Este corresponde a
un estilo Windows que se conoce como "marco grueso". Cuando una ventana
principal esta rodeada por un marco grueso, el usuario puede adaptar su tamaiio
arrastrando el borde. Esto se consigue con 10s cursores especiales de ajuste del
tamaiio que tienen forma de flecha de doble punta y aparecen cuando el usuario
mueve el raton sobre ese borde grueso de la ventana.

Figura 7.2. Forrnularios de ejemplo con diversos estilos de borde, creados por el
ejernplo Borders.
En tiempo de diseiio, el formulario siempre se muestra con el valor predetermi-
nado para la propiedad BorderSylte, bs Si zeab le. Este valor se corresponde
con un estilo de Windows conocido como "marco fino". Cuando una ventana
principal tiene un marco fino a su alrededor, un usuario puede ajustar su tamaiio
arrastrando su borde. Este estado se manifiesta mediante unos cursores de
redimensionamiento especiales (con la forma de una flecha de dos puntas) que
aparecen cuando el usuario mueve el raton sobre el borde de esta ventana.
Una segunda opcion bastante importante de esta propiedad es bsDialog. Si
la seleccionamos, el formulario utiliza como borde el tipico marco de cuadro de
dialogo (un marco grueso que no permite que se reajuste su tamaiio).
Ademas de este elemento grafico, observe que si seleccionamos este valor
bsDialog, el formulario se transforma en un cuadro de dialogo. Esto implica
una serie de cambios: por e.jemplo, 10s elementos de su menu de sistema son
distintos y el formulario ignora algunos de 10s elementos de la propiedad
Border Icons.

ADVERTENCIA: Definir la propiedad Border style en tiempo de di-


seiio no ~ r o d u c ninein
eY
efecto visible. De hecho. hav
za .
diversas ~ r or ~ i e d a d e s
de componentes que no tienen n i n g h efecto en tiempo de diseilo, porque
evitarian que pudiesemos trabajar en el ~ o m ~ o n e n ~ m i e n tdeshrofia-
ras
-
mos -I
el -:-..--- ..-- _ > - r
programa. rno-r-ejemplo,i -1
no poariamos reajusrar el ramano aer ror-
~.-:---L-- A --
P-- 3-1

mulario con el rat6n si se convirtiera en un cuadro de c2ihlogo. Pero hay que


recordar que cuando se ejecuta la aplicaci6n, el fomulario usarh el borde
indicado.

Podemos asignar cuatro valores a la propiedad Borders t yle :


El estilo bssingle: Se puede usar para crear una ventana principal en la
que no podamos modificar el tamaiio. Muchos juegos y aplicaciones basa-
dos en ventanas con controles (corno 10s formularios para introducir datos)
utilizan este valor, simplemente porque no tiene sentido ajustar el tamaiio
de estos formularios. Aumentar un formulario para ver un area vacia o
reducir su tamaiio para que algunos componentes sean menos visibles no
suele ayudar al usuario de un programa (aunque las barras de desplaza-
miento automaticas de Delphi resuelvan en parte este problema.
El estilo bsNone: Se usa solo en situaciones muy especiales y dentro de
otros formularios. Jamas se vera una aplicacion con una ventana principal
que no tenga borde o titulo (except0 quizas como ejemplo en un libro de
programacion para demostrar que no tiene sentido).
Los valores, bsToolWindow y bsSizeToolWin: Estan relacionados con
el estilo especifico ampliado de Win32, ws ex Toolwindow. Este es-
tilo transforma la ventana en un cuadro deherramientas flotante, con un
titulo en una fuente pequeiia y un boton de cierre. Para la ventana principal
de una aplicacion no se deberia usar este estilo.

lag &nu -fbs Iform border style, estilo del h d e del formuhrio). Asi
tdremos 'Pbssingle, fbsDialog, etc.

Para comprobar el efecto y comportamiento de 10s distintos valores de la pro-


piedad Borderstyle, hemos creado un sencillo programa llamado Borders,
tambidn disponible en version CLX como QBorders. En la figura 7.2 ya hemos
visto su resultado, per0 si se e.jecuta el e.jemplo y se observa su funcionamiento
durante algun tiempo, se entenderan mejor las diferencias entre 10s formularios.
El formulario principal dc este programa contiene solo un grupo de radio y un
boton. Tambien hay un formulario secundario, sin componentes y con la propie-
dad Posit ion predefinida como poDef a u l t PosOnly. Esto afecta a la po-
sicion inicial del formulario secundario que crearemos a1 hacer clic sobre el boton.
El codigo del programa es muy sencillo. Al hacer clic sobre el boton, se crea
un nuevo formulario de forma d i n h i c a , dependiendo del elemento seleccionado
del grupo de radio:
p r o c e d u r e TForml.BtnNewFormClick(Sender: TObject);
var
NewForm: TForm2 ;
begin
NewForm : = TForm2.Create (Application);
NewForm.BorderStyle : = TFormBorderStyle
(BorderRadioGroup.ItemIndex);
NewForm. Caption : =
BorderRadioGroup.Items[BorderRadioGroup.ItemIndex];
NewForm. Show;
end;

Este codigo usa en realidad un truco: convierte el numero del elemento selec-
cionado en la enumeracion T FormBorderSt yle. Esta tecnica funciona por-
que hemos colocado 10s botones de radio en el mismo orden que 10s valores de
esta enumeracion.
El metodo BtnNew FormClic k copia a continuation el testo del boton de
radio en el titulo del formulario secundario. Este programa remite a1 T Form2, el
formulario secundario definido en una unidad secundaria del programa, guardado
como SECOND.PAS. Por esa razon, para compilar el ejemplo, habra que aiiadir
las siguientes lineas a la seccion de implernentacion de la unidad del formulario
principal:
uses
Second;
TRUCO: Siempre que bay6 quq fiferirse a otra unidRd de un pi@mha,
hay que .colocq 1a correspamdiente sentencia udes en lrr3ec&n
irnp;lemsn$ation y no en la secci6n inter face, 3 se; podble, E s t ~
rrcelerie#pptoceso de compilaci6n. origins un cbdigo miis limpio (porque
laa pnfdadses'que se incluyen esdn sep&adas de lasque incluie ~ e l ~ hyi )
evita emks de @qilacion ~irculaPdeunidades. Para hacer referencia ti
otr& archivos del Ynismo proyecto, tambitn se puede usar la opcion de
m d FileNse UNt.

Los iconos del borde


Otro importante elemento de un formulario es la presencia de iconos en su
borde. De manera predeterminada, una ventana tiene un pequeiio icono conectado
a1 menu del sistema y unos botones Minimizar, Maximizar y Cerrar a la derecha
del todo. Se pueden fijar distintas opciones mediante la propiedad Border Icons
que tiene cuatro valores posibles: b i S y s t e m M e n u , b i M i n i m i z e ,

NOTA:El icana de b a r d e - h i ~ edi i~v a laayuda ni


jQvi es esto?". Cuan-
do se b c h y e ester estilo y se .excluyen 10s estilos biMinirnize y
biMaximi ze, w c 4 Una interrogation en la barra de fitula del f~&-
lark. Si st h w e &mbre ellag,9 continuacion.~obrewwmponenfe que
est6 dentr~dd f'uhrio, belphi.&tiva la ayuda sobre el objeto (e*una
ventaaa eaftteXW en Windows 9x o ?enuna tipica ventana de a y d a b
Windows cn Windows 2WOlXP). Este comportarniento se demuestra en e!
ejemplo BXams. que tkne uri Simple archivo de ayuda con una pagina co-
nectada a hpropied&)-Ie~p~on t e x t del boton que se encuentra en me-
dio w fcmrddo.

El ejemplo Blcons demuestra el comportamiento de un formulario con dife-


rentes iconos en el borde y muestra el mod0 de cambiar esta propiedad en tiempo
de ejecucion. El formulario de este ejemplo es muy sencillo: solo tiene un menu,
con un desplegable que contiene cuatro elementos de menu, uno para cada ele-
mento posible del conjunto de iconos de borde. Hemos creado un unico metodo,
conectado con las cuatro opciones, que lee las marcas de verificacion de 10s
elementos del menu para establecer el valor de la propiedad BorderIcons.Por
tanto, este codigo sirve tambien para practicar con el trabajo con conjuntos:
procedure TForml.SetIcons(Sender: TObject);
var
BorIco: TBorderIcons;
begin
(Sender a s TMenuItem) .Checked : = not (Sender a s
T M e n u I t e m ) .Checked;
i f SystemMenul.Checked t h e n
BorIco : = [biSystemMenu]
else
BorIco : = [ I ;
i f MaximizeBoxl.Checked t h e n
Include (BorIco, biMaximize) ;
i f MinimizeBoxl.Checked t h e n
Include (BorIco, biMinimize) ;
i f Helpl.Checked t h e n
Include (BorIco, biHelp) ;
BorderIcons : = BorIco;
end;

Mientras se ejecuta el ejemplo Blcons, se pueden fijar y eliminar facilmente


10s diversos elementos visuales del borde del formulario. Enseguida se vera que
algunos de estos elementos se encuentran intimamente relacionados. Si elimina-
mos el menu de sistema, desapareceran todos 10s iconos del borde. Si eliminamos
el boton de minimizar o el de masimizar, se pondran en gris. Si eliminamos ambos
botones, desapareceran. Fijese en que ademas, en estos dos ultimos casos, 10s
elementos correspondientes del menu de sistema se desactivan automaticamente.
Este es el comportamiento estandar de cualquier aplicacion Windows. Cuando se
han desactivado 10s botones de maximizar y minimizar, se puede activar el boton
de ayuda. Actualmente en Windows 2000, si solo se ha inhabilitado uno de 10s
botones de maximizar o minimizar, aparecera cl boton de ayuda per0 no sera
funcional. Como metodo abreviado para conseguir este efecto, se puede hacer clic
sobre el boton que esta dentro del formulario. Ademas, tambien se puede hacer
clic sobre el boton despues de habcr hecho clic sobre el icono del menu de ayuda
para que aparezca un mensaje de ayuda, como muestra la figura 7.3. Como fun-
cion adicional, el programa muestra tambikn en el titulo el momento en que se
activa la ayuda, controlando el evento O n H e l p del formulario.

Figura 7.3. El ejemplo Blcons. Al seleccionar el icono de ayuda dt borde y hacer clic
sobre el boton. aparece la ayuda.

rn
ADVERTENCIA: Si se analiza la versi6n QBIcons, creada con CLX, se
puede comprobar que un fallo de la biblioteca impide modificar 10s iconos
de 10s bordes en tiempo de ejecuci6n. Las distintas configuraciones en tiem-
po de diseiio h n c i o n a r b completamente, asi que sera necesario modificar
el programa antes de ejecutarlo si se quiere ver algun tipo de efecto. Este
programa no hace nada en tiempo de ejecucion.

Definicion de mas estilos de ventana


El cstilo y 10s iconos del borde estan indicados en dos propiedades de Delphi
distintas, quc sc pueden utilizar para filar el valor inicial de 10s correspondientes
clementos de la interfaz de usuario. Ademas de cambiar la interfaz de usuario, las
propiedades de 10s iconos de borde afectan a1 comportamiento de la ventana. Es
importante saber que en la VCL (y obviamente no en la CLX), estas propiedades
relacionadas con el borde y con la propiedad Formstyle corresponden princi-
palmente a diferentes configuracioncs del "estilo" y del "estilo ampliado" de la
ventana. Estos dos terminos reflejan dos parametros de la funcion create-
WindowEx de la API que Delphi usa para crear formularios.
Es importante saber esto, porque Delphi permite modificar estos dos parametros
libremente, sobrescribiendo el n~etodovirtual createparams:
p u b 1 ic
procedure CreateParams ( v a r Params: T C r e a t e P a r a m s ) ; override;

Este es el unico mod0 de usar 10s peculiares estilos de ventana que no cstan
directamente disponibles mediante las propiedades del formulario. Para ver una
lista de 10s estilos y estilos ampliados de ventana, se pueden estudiar en la ayuda
de la API temas como "CreateWindowMy "CreateWindowEs". Se vera que la API
de Win32 tiene estilos para estas funciones, incluidos aquellos relacionados con
las ventanas de herramientas.
Para mostrar la utilization de este metodo, hemos creado el ejemplo NoTitle,
que pcrmite crear un programa con un titulo personalizado. Primero tenemos que
eliminar el titulo estandar. per0 mantener el marco que permite ajustar el tamaiio,
definiendo 10s estilos correspondientes:
p r o c e d u r e TForml.CreateParams ( v a r Params: T C r e a t e P a r a m s ) ;
begin
i n h e r i t e d C r e a t e P a r a m s ( P a r a m s );
P a r a m s . S t y l e : = (Params.Style or ws-Popup) a n d n o t ws-Caption;
end;

Para eliminar el titulo, es necesario cambiar el estilo solapado por un estilo


contextual, puesto que de otro modo, el titulo se quedara adherido. Para aiiadir un
titulo personalizado, hemos colocado una etiqueta alineada con el borde superior
del formulario y un pequeiio boton en la esquina superior. Se puede ver en tiempo
de ejecucion en la figura 7.4

Dmg Oh ba to move widow

Figura 7.4. El ejemplo NoTitle no posee un titulo real sin0 uno falso creado con una
etiqueta.

Para que el titulo falso funcione, debemos decirle a1 sistema que una operacion
dc raton en esta zona se corresponde con una operacion de raton sobre el titulo.
Para ello, sc puede interceptar el mensaje de Windows wm N C H i t T e s t , que
normalmente se le envia a Windows para establecer el lugar en el que esti en ese
momcnto cl raton. Cuando la accion de pulsado se realiza en la zona del cliente y
cn la etiqueta, podemos simular que el raton esta sobre el titulo definiendo el
rcsultado adecuado:
procedure TForml.HitTest ( v a r Msg: TWrnNCHitTest);
// mensaje w m _ N c H i t T e s t
begin
inherited;
i f (Msg.Result = htclient) and
(Msg.YPos < Labell.Height + Top + GetSystemMetrics
(sm-cyFrame) ) then
Msg.Result : = htcaption;
end;

La funcion G e t s y s temMetrics de la API utilizada en el listado anterior


es una consulta al sistema operativo sobre el grosor vertical ( c y ) en piseles del
borde que hay alrededor de una ventana con un titulo no redimensionable. Es
importante realizar esta peticion cada vez porque 10s usuarios pueden personali-
zar la mayoria de estos elementos utilizando la ficha Apariencia de las opciones
Propiedades de pantalla (en el Panel de Control) y otros parametros de
Windows.
En cambio, el boton pequeiio tiene una llamada a1 metodo C l o s e en su con-
trolador del evento O n c l i c k . El boton se mantiene en su posicion aunque se
ajuste el tamafio de la ventana, usando el valor LakTop, a k R i g h t ] para la
propiedad A n c h o r s .
El formulario tiene tambien restricciones de tamaiio, para que un usuario no
pueda reducirlo en exceso.
Entrada directa en un formulario
Pasaremos ahora a tratar un tema muy importante: la entrada del usuario en un
formulario. Si decidimos hacer un uso limitado de 10s componentes, podriamos
escribir tambien programas complejos que reciban la entrada del raton y del teclado.

Supervision de la entrada del teclado


Por lo general, 10s formularios no controlan directamcnte la cntrada del tecla-
do. Si un usuario tiene que teclear algo, el formulario deberia incluir un compo-
nente de edicion u otro componente de entrada. Si queremos controlar 10s metodos
abreviados del teclado, se pueden usar 10s que estan conectados a menus (utili-
zando probablemente un menu contestual oculto).
Sin embargo, otras veces para controlar la entrada de un mod0 especifico para
un proposito particular se puede activar la propiedad KeyPreview del formula-
rio. Entonces, aunque tengamos algunos controles de entrada, el evento
OnKeyPress del formulario se activara siempre para cada operacion de entra-
da mediante el teclado (except0 las teclas de metodo abreviado y de sistema). La
entrada de teclado llegara entonces hasta el componente destino, a menos que se
detenga en el formulario, fijando el valor del caracter como cero (no el caracter 0,
sino el valor 0 del conjunto de caracteres, indicado como #O).
El ejemplo creado para ilustrar esta tecnica es KPreview, que ticne un formu-
lario sin propiedades especiales (ni siquiera KeyPreview), un grupo de radio
con cuatro opciones y algunos cuadros de edicion, como se muestra la figura 7.5.

. . . . . . . Aid,
..
. .
..
..
..
.... . . . . . . . . . . . . . . . . .
: , I '- ,"On= . . . .l~dill
......................
. .
. .
..I
.. ::::I
. , . . Edit2
.
. . . . . . . .
. . . . . . . . . . .
. .
.. .... . . . . . . . . . . .
, .... . . . . . . . . . . . . . . . .
, . . , Edit3 . . . . . .
.... . . . . . . . . . . . .
I . . . . . . . . .: : : : : . : : : : . . .
I . . . . .
.. . . . .
.. I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Figura 7.5. El programa KPreview en tiempo de diseiio.

Por defect0 el programa no hace nada especial, a escepcion de que 10s diversos
botones de radio que se usan para activar la vista previa de la tecla:
procedure TForml.RadioPreviewClick(Sender: TObject);
begin
KeyPreview : = RadioPreview.ItemIndex <> 0;
end;
Ahora empezaremos a recibir 10s eventos OnKeyPress y podremos realizar
una de las tres acciones solicitadas por 10s tres botones especiales del grupo de
radio. La accion depende del valor de la propiedad ItemIndex del componente
grupo de radio. Esta es la razon por la que el controlador de eventos se basa en
una sentencia case:
p r o c e d u r e TForml.FormKeyPress(Sender: TObject; var Key: Char);
begin
c a s e RadioPreview.Item1ndex of

En el primer caso, si el valor del parametro Key es #13, valor que correspon-
de a la tecla Intro, desactivamos la operacion (definiendo Key como cero) y, a
continuacion, imitamos la activacion de la tecla Tab. Existen muchas formas de
hacer esto, per0 en este caso hemos escogido una bastante particular. Hemos
enviado el mensaje CM -DialogKey al formulario, pasando el codigo para la
tecla Tab (VK-TAB):
1: // I n t r o = T a b
i f Key = #13 t h e n
begin
Key : = #O;
Perform (CM-DialogKey, VK-TAB, 0 );
end;

documentado. Existen unos cuantos de estos memsajes, y resulta bastante


: interesante
- .- .
-- --- .-. - - - ..- ir componentes avanzados parra cllos y usarlos para rea-
.- constru
--

lizar una codificaicibn especial, pero Borland ja d s 10s ha descrito. Hay


que resaltar que este estilo exacto de codificaciclu u a ~ a GUa lucuaqsJca uu
esta disponible bqjo CLX.

Para escribir texto en el titulo del formulario mediante el teclado, el programa


aiiade sencillamente el caracter a1 Caption actual. Existen dos casos especiales
mas. Cuando se pulsa la tecla Retroceso, se elimina el ultimo caracter de la
cadena (a1 copiar al Caption todos 10s caracteres del Caption actual menos
el ultimo). Cuando se pulsa la tecla Intro, el programa detiene la operacion,
redefiniendo la propiedad ItemIndex del control del grupo de radio. Veamos el
codigo:
2 : // e s c r i b i r e n e l t i t u l o
begin
i f Key = # 8 t h e n // r e t r o c e s o : e l i m i n a r u l t i m o c a r a c t e r
Caption : = Copy (Caption, I, Length (Caption) - 1)
e l s e i f Key = #13 t h e n // i n t r o : d e t i e n e la o p e r a c i o n
RadioPreview.ItemIndex : = 0
e l s e // o t r a c o s a : a d a d e c a r a c t e r
Caption : = Caption + Key;
Key : = #0;
end:

Por ultimo, si sc selecciona el ultimo elemento de radio, el codigo verifica si el


caracter es una vocal (buscando su inclusion en un "conjunto de vocales" fijo). En
cste caso, el caracter se omitc:
3 : / / omite las vocales
if Key i n ['A', 'E', ' I t , 'O', 'U'] then
Key : = # O ;

Obtener una entrada de raton


Cuando un usuario hace clic con uno de 10s botones del raton sobre un formu-
lario (o sobre un componente), Windows envia a la aplicacion algunos mensajes.
Delphi define algunos eventos que sc pueden utilizar para escribir codigo que
responda a dichos mensajcs. Los dos cvcntos basicos son OnMouseDown,reci-
bid0 cuando se pulsa un boton del raton, y OnMouseUp, recibido cuando se
suelta cl boton. Otro mensaje fundamental del sistema csta relacionado con un
movimiento del raton. el evento es OnMouseMove.Aunque deberia ser sencillo
comprender cl significado de 10s tres mensajes (abajo. arriba y mover), podriamos
preguntarnos como se relacionan con el evento OnClick utilizado hasta ahora.
Hemos utilizado el evento OnClic k para 10s componentes, pero tambien esta
disponible para el formulario. Su significado general es que el boton izquierdo del
raton se ha pulsado y soltado en la misma ventana o componente. Sin embargo,
entre las dos acciones. el cursor podria haberse movido fuera de la zona de la
ventana o componente, mientras sc mantenia pulsado el boton izquierdo del raton.
Otra diferencia entre 10s eventos OnMouseXX y OnClick consiste en que el
ultimo se refiere so10 al boton izquierdo del raton. La mayoria de 10s ratones
conectados a un PC con Windows tienen dos botones dc raton y algunos incluso
tres. Normalmente se hace referencia a estos botones como boton izquierdo (el
que se suele utilizar para las selecciones), boton derecho (para acceder a 10s
menus contextuales) y boton central (quc se usa rara vez).
% .

Uso de Windows sin ratdn


Un usuario deberia se siempre capaz de utilizar cualquier aplicacion de
Windows sin el raton. No se trata de una opcion; es una regla de la progra-
maci6n para Windows. Por supuesto, una aplicacion podria resultar m b
ficil de Lsar con un raton, jambs deberia ser una obligaci6n. Algunos
usuarios puede que no tengan conectado un ratrjn, como la gente que viaja
.n..nhr, ,
IIIULUV w-
u, ..-
-a,..,SL.
wpqu~uv
-AAX+;I
yv~ulur ry. n31:1r1 a
r..srnr
paw Gs n r r A r , +rrL....:..Ar-a,
~ J ~ Wu
,
Va. u a j a u w ~
,altr-r,,
G~~ I
GIILVIIIVD

industrides y empleados de Lianca con muchos otros perifericos a su alre-


I dedor.
&
m- I
- - ~

oars sop&.a;-
el uso 6el teclado' usar if ratbn es& '
bien, per0 suele ser m b lento. Si se es habil con el teclado, no se querra
utilizar el raton para arrastrar una palabra de un texto; se utilizarb las
teclas de rn~toddabreviado para copiar y pegar el texto sin separar las
manos del teclado.
-Par estas razones, siempre deberia establecer un orden de tabulacion co-
rrecto para 10s componentes de un formulario. Hay que recordar aiiadir
teclas para 10s botones y para acceder a 10s elementos de menu mediante el
--
.^^l^l^&:I:-.... .^^I^- -1- -A*^*-
LGGI~UU, U I - w ~ a rL C G I ~ S UG IIIGLVUU
-L-.^-.:^l^ ---- --^:^-^-
~ U I G V I ~ U U para U~GIUIIGS
J^ ---- L -.
uc IIIG~U y w s a s
asi.

Los parametros de 10s eventos de raton


Todos 10s eventos del raton de bajo nivel poseen 10s mismos parametros: el
habitual parametro Sender, un parametro Butt o n que indica cual de 10s tres
botones de raton se ha pulsado (mbRight, m b L e f t o m b c e n t e r ) , el parametro
Shift que indica cual de las teclas relacionadas con el raton (Alt, Control y
Mayus, ademas de 10s tres botones) se encontraba pulsada cuando ocurrio el
evento y las coordenadas x e y de la posicion del raton, en las coordenadas de la
zona de clicnte de la ventana actual.
Utilizando dicha informacion, es muy sencillo dibujar un pequeiio circulo en la
posicion de un evento de pulsacion del boton izquierdo del raton:
.
procedure TForml FormMouseDown (Sender: TObj ect;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = &Left then
Canvas . E l l i p s e (X-10, Y-10, X+10, Y+10) ;
end;

NOTA: Para dibujar en el formulario, usamos una propiedad muy espe-


,+,I.
V'UI. bC111VC1a. "L I T ? obieto TCanvas tiene dos caracteristicas distintivas:

contiene una colec ion de herramientas de dibujo (como un lhpiz, una bro-
cha y una fuente) Y posee algunos metodos de dibujo, que usan las herra-
,-,..,I,, I A, f
:L
..
:, A, - ,,,
w u a l c a . El L I ~ U uo ~uulgu
,
a
,
,,
:
, b:,, A, -LA:-- A:,-+, ,:-,Ih
~ l u c u ~ ia
r s uc u v u j u UIIGGLU uc csw GJGIU~IU uu GS
correcto, porque la imagen en pantalla no es pennanente: a1 mover otra
ventana sobre la actual se eliminara su efecto.

Arrastrar y dibujar con el raton


Para demostrar algunas de las tecnicas de raton esplicadas hasta ahora, hemos
creado un ejemplo sencillo basado en un formulario sin ningun componente, lla-
mado MouseOne en la version VCL y QMouseOne en la version CLX. Muestra
en el titulo del formulario la posicion actual del raton:
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer);
begin
// r n u e s t r a l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x=%d, y = % d l , [ X , Y]) ;
end;

Se puede usar esta caracteristica del programa para entender mejor como fun-
ciona el raton. Se puede hacer esta prueba: se ejecuta el programa (esta version
simple o la version completa) y se ajusta el tamafio de las ventanas del escritorio
para que el formulario del programa MouseOne o QMouseOne quede detras de
otra ventana e inactivo per0 con el titulo visible. Si ahora se mueve el raton sobre
el formulario, se podra ver que las coordenadas cambian. Este comportamiento
significa que se envia el evento OnMouseMove a la aplicacion incluso aunque su
ventana no se encuentre activa, y demuestra lo ya comentado: 10s mensajes de
raton siempre se dirigen a la ventana que se encuentra bajo el cursor del raton. La
unica excepcion a esto es la operacion dc captura de raton que enseguida comen-
taremos .
Adcmas de mostrar la posicion en cl titulo de la ventana, el ejemplo Mouseonel
QMouseOne puede realizar un seguimiento de 10s movimientos del raton, pintan-
do pequeiios pixeles en el formulario si el usuario mantiene pulsada la tecla Mayus.
(De nuevo estc codigo de dibujo directo produce un resultado no permanente.)
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer);
begin
// rnuestra l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x = % d , y = % d l , EX, Y]) ;
if ssShift in Shift then
// m r c a p u n t o s e n a m a r i l l o
Canvas.Pixels [ X I Y] : = clYellow;
end ;

6 no incluye una matriz Pixels. En carnbio, se puede llarnar a1 mttodo


. - como hemos
Drawpoint tras haber fijado el color adecuado para el Iaviz.
becho en el ejemplo QMouseOne. Kylix 2 y Dellphi 7 vuelven a introducir
la propiedad de matriz Pixels.
-

Sin embargo, la caracteristica mas importante de este ejemplo es el soporte de


arrastre directo del raton. A1 contrario de lo que se podria pensar, Windows no
posee soporte del sistema para el arrastre, que esta implementado en la VCL
mediante 10s eventos y operaciones de raton de bajo nivel. En la VCL, 10s formu-
larios no pueden originar operaciones de arrastre, por lo que en este caso tendre-
mos que usar una tecnica de bajo nivel. El objetivo de este ejemplo consiste en
dibujar un rectangulo desde la posicion original de la operacion de arrastre a la
final, aportando a 10s usuarios indicaciones visuales sobre la operacion que estan
realizando. La idea que se encuentra tras la operacion de arrastre es sencilla. El
programa recibe una secuencia de mensajes sobre la pulsacion del boton, su mo-
vimiento y el soltado del boton. Cuando se pulsa el boton, comienza el arrastre,
aunque las acciones ocurren en realidad solo cuando el usuario mueve el raton
(sin soltar el boton del raton) y cuando termina el arrastre (cuando llega el mensa-
je de soltado del boton). El problema de esta tecnica basica es que no es fiable.
Una ventana normalmente recibe eventos solo cuando el raton esta sobre la zona
de cliente, por lo que si el usuario pulsa el boton del raton, mueve el raton sobre
otra ventana y, a continuacion, suelta el boton, la segunda ventana recibira el
mensaje de soltado del boton.
Existen dos soluciones a este problema. Una (poco usada) es el recorte del
raton. Utilizando una funcion de la API de Windows (Clipcursor), se puede
obligar a que el raton no abandone una cierta zona de la pantalla. Cuando intenta-
mos moverlo fuera de dicha zona, choca contra una barrera invisible. La segunda
solution, mas comun, consiste en capturar el raton. Cuando una ventana captura
el raton, todas las entradas de raton subsiguientes se envian a dicha ventana. Esta
es la tecnica que utilizaremos en el ejemplo MouseOne/QMouseOne.
El codigo del ejemplo se organiza en torno a tres metodos: FormMouseDown,
FormMouseMove y FormMouseUp. A1 pulsar con el boton izquierdo del ra-
ton sobre el formulario comienza el proceso, activando el campo booleano
fDrag g ing del formulario (que indica que el arrastre esta activo dentro de 10s
otros dos metodos). El metodo usa una variable TRect para realizar un segui-
miento de la posicion inicial y la actual de arrastre. Veamos el codigo:
procedure TMouseForm.FormMouseDown(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
i f Button = mbLeft then
begin
£Dragging : = True;
Mouse.Capture : = Handle;
fRect.Left : = X;
fRect.Top : = Y;
fRect-BottomRight : = fRect.TopLeft;
dragstart : = fRect.TopLeft;
Canvas .DrawFocusRect (fRect);
end ;
end ;

Una accion importante de este metodo es la llamada a la funcion setcapture


de la API, obtenida a1 activar la propiedad Capture del objeto global Mouse.
Ahora, aunque el usuario mueva el raton fuera de la zona de cliente, el formulario
recibira igualmente todos 10s mensajes relacionados con el raton. Se puede com-
probar moviendo el raton hacia la esquina superior izquierda de la pantalla. El
programa muestra coordenadas negativas en el titulo.
-

TRUCO: El objeto global Mouse permite obtener infonnaci6n global so-


bre el rat6n, como su presencia, tipo y posicibn actudes, ademh & definir
algunas de sus caracteristicasglobales. Este objeto global oculta unas cuan-
tas funciones de la API, que simplifican el c6digo y lo hacen de miis fhcil
adaptation. En la VCL, la propiedad Capture es de tipo Handle, mien-
tras que en Iet'CLX es de tipo TControl (el objeto del componente que
captura d rat6n). Por eso, el c6digo de esta secci6n se convertirh en
Mouse.Capture := self, como se puede cornprobar en el ejemplo
QMoweOnc.

Cuando el arrastre esta activo y el usuario mueve el raton, el programa dibuja


un rectangulo con una linea de puntos que se corresponde a la posicion del raton.
En realidad, el programa llama a1 metodo D r a w F o c u s R e c t dos veces. La pri-
mera vez que se llama a este mktodo, borra la imagen actual, gracias a que dos
Ilamadas consecutivas a D r a w F o c u s Rec t recuperan la situacion original. Des-
puts de actualizar la posicion del rectangulo, el programa llama a1 metodo por
segunda vez:
procedure TMouseForm.FormMouseMove(Sender: TObject; Shift:
TShiftState;
X, Y: Integer) ;
begin
// rnuestra l a p o s i c i o n d e l r a t o n e n e l t i t u l o
Caption : = Format ( ' M o u s e i n x = % d , y = % d ' , [X, Y]) ;
i f fDragging then
begin
// elirnina y d i b u j a d e n u e v o e l r e c t i n g u l o d e a r r a s t r e
Canvas.DrawFocusRect (fRect);
i f X > dragStart.X then
fRect.Right := X;
else
fRect.Left : = X;
i f Y > dragStart.Y then
fRect.Bottom : = Y;
else
fRect.Top : = Y;
Canvas.DrawFocusRect ( f R e c t ) ;
end
else
i f ssShift i n Shift then
// marca 1 0 s p u n t o s e n a m a r i l l o
Canvas .Pixels [ X , Y] : = clYellow;
end;
En Windows 2000 (y otras versiones), la funcion D r a w F o c u s R e c t no
dibuja rectangulos con un tamaiio ncgativo, asi que el codigo del programa se
ha preparado para comparar la posicion actual con la posicion inicial del
arrastre, guardada en el punto d r a g s t a r t . Cuando se suelta el boton del
raton, el programa termina la operacion de arrastre redefiniendo la propiedad
C a p t u r e del objeto M o u s e , que llama internamente a la funcion
R e l e a s e c a p t u r e de la API y definiendo el valor del campo fD r a g g i n g
como F a l s e :
procedure TMouseForm.F'ormMouseUp(Sender: TObject; Button:
TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if fDragging then
begin
Mouse.Capture : = 0; / / l l a m a a R e l e a s e C a p t ~ i r e
fDragging : = False;
Invalidate;
end ;
end ;

La llamada final, I n v a l i d a t e , desencadena una operacion de pintado y


ejecuta el siguiente controlador de eventos O n p a i n t :
procedure TMouseF'orm.FormPaint(Sender: TObject);
begin
C a n v a s - R e c t a n g l e (fRect.Left, fRect.Top, f R e c t - R i g h t ,
fRect .Bottom) ;
end;

Esto transforma el resultado del formulario en permanente, aunque se oculte


tras otro formulario. En la figura 7.6 aparece la version anterior del rectangulo y
una operacion de arrastre en marcha.

Figura 7.6. El ejernplo MouseOne usa una linea de puntos para dibujar, durante la
operacion de arrastre, un rectangulo.
Pintar sobre formularios
~ Q u Ces lo que hace necesario controlar el evento onpaint para producir un
resultado correcto, y por que no se puede dibujar directamente sobre la superficie
del formulario? Depende del comportamiento predefinido de Windows. A1 pintar
sobre una ventana, Windows no almacena la imagen resultante. Cuando se cubre
la ventana, normalmente se pierde su contenido.
La razon de este comportamiento es sencilla: el almacenamiento en memoria.
Windows asume que resulta "mas barato" a largo plazo dibujar de nuevo la ven-
tana mediante codigo que dedicar la memoria de sistema a conservar el estado de
una ventana. Se trata de un compromiso clasico entre la memoria y 10s ciclos de
CPU. Un mapa de bits a color de una imagen a 600x800 en 256 colores necesita
unos 480 KB. A1 aumentar la calidad del color o el numero de pixeles, se pueden
alcanzar facilmente 10s 4 MB de memoria para una resolucion de l28Ox 1024 con
16 millones de colores.
En caso de que se quiera tener una salida coherente en las aplicaciones, se
pueden usar dos tecnicas. La solucion general consiste en almacenar suficientes
datos sobre la salida para poder reproducirla cuando el sistema realiza una solici-
tud para pintar. Una tkcnica alternativa consiste en guardar la salida del formula-
rio en un mapa de bits mientras se crea, colocando un componente Image sobre
el formulario y dibujando sobre el lienzo de este componente imagen.
La primera tecnica, pintar, es la tecnica comun para controlar la salida en la
mayoria de 10s sistemas de ventanas, a parte de 10s programas orientados a grafi-
cos especificos que almacenan la imagen del formulario completa en un mapa de
bits. La tecnica utilizada para implementar el pintado tiene un nombre muy des-
criptivo: almacenar y pintar. Cuando el usuario pulsa un boton del raton o realiza
alguna otra operacion, hay que almacenar la posicion y otros elementos. A conti-
nuacion, en el metodo de pintado, se usa esta informacion para pintar la imagen
correspondiente.
La idea de esta tecnica es permitir que la aplicacion pinte de nuevo su superfi-
cie completa en cualquier situacion posible. Si ofrecemos un metodo para dibujar
de nuevo el contenido del formulario y se llama a1 metodo automaticamente cuan-
do se ha ocultado una parte del formulario y es necesario pintarla de nuevo,
podremos volver a crear la salida de forma adecuada.
Como esta tecnica se realiza en dos etapas, debemos ejecutar esas dos opera-
ciones seguidas, solicitando a1 sistema que pinte de nuevo la ventana (sin esperar
a que el sistema lo pida). Se pueden usar diversos metodos para originar un nuevo
pintado: Invalidat e l Update, Repaint y Refresh. Los dos primeros
se corresponden a las funciones de la API de Windows, mientras que el ultimo lo
ha introducido Delphi:
El mttodo Invalidate: Informa a Windows de que hay que volver a pintar
la superficie total del formulario. Lo mas importante es que Invalidate
no desencadena una operacion de pintado de forma inmediata. Windows
almacena simplemente la solicitud y respondera solo despues de que se
haya ejecutado por completo el procedimiento actual (a no ser que se llame
a Application. ProcessMessages oUpdate) y e n cuanto no haya
otros eventos pendientes en el sistema. Windows retrasa deliberadamente
la operacion de pintado porque es una de las operaciones para las que mas
tiempo se necesita. A veces, con dicho retraso, solo es posible pintar el
formulario despues de que se hayan producido diversos cambios, lo cual
evita que haya muchas llamadas consecutivas a1 metodo (lento) de pintar.
El mCtodo Update: Solicita a Windows que actualice el contenido del
formulario, pintandolo de nuevo inmediatamente. Sin embargo, hay que
recordar que esta operacion se realizara solo si existe una zona no valida.
Esto ocurre si se acaba de llamar a1 metodo Invalidate o como resul-
tad0 de una operacion realizada por el usuario. Si no existe una zona no
valida, una llamada a Update no tiene ningun efecto. Por esa razon, es
frecuente ver una llamada a Update inmediatamente despues de una Ila-
mada a Invalidate.Como hacen exactamente 10s dos mktodos de Delphi:
Repaint y Refresh.
El mCtodo Repaint: Llama a Invalidate y, a continuacion, a Updat e.
Como consecuencia, activa el evento onpaint inmediatamente. Existe
una version ligeramente diferente de este metodo denominada Refresh.
El hecho de que existan dos metodos para la misma operacion se remonta a
10s dias de Delphi 1, cuando ambos eran sutilmente distintos entre si.
Cuando hay que pedir a1 formulario que se vuelva a pintar, normalmente debe-
riamos llamar a Invalidate,siguiendo el enfoque estandar de Windows. Esto
es importante sobre todo cuando hay que solicitar dicha operacion con bastante
frecuencia, porque si Windows emplea demasiado tiempo en actualizar la panta-
lla, las solicitudes de pintado se pueden acumular en una sencilla accion de pinta-
do. El mensaje wm Paint de Windows es un mensaje de baja prioridad; si hay
una solicitud pend&e, per0 hay mas mensajes esperando, 10s otros mensajes se
controlan antes de que el sistema lleve a cab0 la accion de pintado.
Por otra parte, si se llama varias veces a1 metodo Repaint, habra que volver
a pintar la pantalla cada vez antes de que Windows pueda procesar otros mensa-
jes y debido a que para las operaciones de pintado se realizan calculos de forma
exhaustiva, la capacidad de respuesta de la aplicacion sera menor. Sin embargo.
si queremos que la aplicacion pinte de nuevo una superficie lo antes posible hay
que llamar a Repaint .

NOTA: Otra consideracih imprtantq es que durante una.operacih de


pintado Windows vuelve a dibujs solo b denominada 'ked6riactudii-
dam,para acelerar dicha uperaeib. Pbr es& i$i;bn, si aiempre se inv562ida
m a part"eTE'~iniimil, jintara de nuevo dicha z o h . Para ello, se
pueden usar las funciones Inva:
En realidad, esta funci6n es un a m ae oome n ~ o a. s una recnlca muy
potente, que puede mejorar la velocidad y reducir el Imrpadeo causado por
operaciones de pintado frecuentes. Por otra parte, tam b i h puede originar
.. ,
una salida incorrecta. Un problema bastante comun consiste en que solo se
.
modifican en realidad algunas de las zonas afectadas pcIr las operaciones de
usuario, mientras que otras siguen como estaban aunql~eel sistema ejecute
el codino fuente aue su~uestamentedeberia actualizarl~.51
- ~- -
!.Ac: .
, ,
,.
,,
,a
,,
UIU vywawvu
.,
de pintho se da &era de la regi6n actualizada, el r:istermla ignora, como si
estuviera fuera de la zona visible de la ventana.

Tecnicas inusuales: Canal Alpha, Color Key y


la API Animate
Una de las caracteristicas mas recientes de Delphi relacionada con 10s formu-
larios es el soporte de algunas nuevas API de Windows que afectan a1 mod0 en
que se muestran 10s formularios (en Windows 2000/XP, per0 no disponibles en
QtICLX). En el caso dc un formulario, la tecnica de Canal Alpha permite
mezclar el contenido de un formulario con lo que se encuentra tras el en pantalla,
algo que rara vez resulta necesario, a1 menos en aplicaciones empresariales. La
tecnica es realmente mas interesante cuando se aplica a un mapa de bits (con las
nuevas funciones AlphaBlend y AlphaDIBBlend de la API) que en el caso
del propio formulario. En cualquier caso, a1 establecer la propiedad AlphaBlend
de un formulario como True y proporcionar a la propiedad ~1phaBlendValue
un valor inferior a 25 5 . se puede ver, en transparencia, lo que esta tras el formu-
lario. Cuanto menor sea el valor de AlphaBlendValue,mas transparente sera
el formulario. Se puede ver un ejemplo de la tecnica alpha blending en la figura
7.7. tomada a partir del ejemplo CkKeyHole.
Otra nueva caracteristica de Delphi es la propiedad booleana Transpa-
rentcolor,que permite indicar un color transparente, que sera sustituido por
el fondo, creando una especie de agujero en un formulario. El color transparente
lo indica la propiedad TransparentColorValue.De nuevo, se puede ver un
ejemplo de este efecto en la figura 7.7. Por ultimo, se puede usar una tecnica
originaria de Windows, la animacion de pantalla, que Delphi no soporta directa-
mente (mas alla de la presentacion de sugerencias). Por ejemplo, en lugar de
llamar a1 metodo show de un formulario, se puede escribir:
Form3.Hide;
Animatewindow (Form3.Handle, 2000, AW-BLEND);
Form3.Show;
Figura 7.7. El resultado de CkKeyHole, que rnuestra el efecto de las nuevas
propiedades Transparentcolor y AlphaBlend y de la API AnimateWindow.

Fijese en que hay que llamar a1 metodo show a1 final para que el comporta-
miento del formulario sea el adecuado. Tambien se puede conseguir un efecto de
animation similar al modificar la propiedad AlphaBlendValue en un bucle.
La API AnimateWindow se puede usar tambien para controlar el mod0 en que
se presenta el formulario: empezando desde el centro (con el indicador A w -
C E N T E R ) o desde uno de sus lados (AW H O R P O S I T I V E , AW -H O R -
NEGATIVE, AW V E R POSITIV, o AW VER NEGATIVE).
Esta misma fuhci6nie puede aplicar tambitin-a 10s controles de ventana para
darles un aspect0 transparente en lugar de su habitual apariencia directa. No
queda muy claro el gasto de ciclos de CPU que causan estas animaciones, pero si
se aplican correctamente y en el programa apropiado, pueden mejorar la interfaz
de usuario.

Posicion, tamaiio, desplazamiento y ajuste


de escala
En Delphi, tras haber diseiiado un formulario, se ejecuta el programa y se
espera que el formulario aparezca exactamente del mod0 en que se ha preparado.
Sin embargo, un usuario de la aplicacion podria usar una resolucion de pantalla
diferente o querer ajustar el tamaiio del formulario (si es posible, segun el estilo
del borde), lo cual afectaria en ultimo tkrmino a la interfaz de usuario. Ya hemos
comentado algunas tecnicas relacionadas con 10s controles, como la alineacion y
el anclaje.
Ademas de las diferencias en el sistema del usuario, existen muchas razones
para querer cambiar las configuraciones predefinidas de Delphi en este sentido.
Por ejemplo, tal vez queramos ejecutar dos copias del programa y evitar todos 10s
formularios aparezcan en el mismo lugar. De ello hablaremos a continuacion.
La posicion del formulario
Existen una serie de propiedades que se pueden usar para definir la posicion de
un formulario. La propiedad Position indica el mod0 en que Delphi determina
la posicion inicial del formulario. El valor predefinido poDes igned indica que
el formulario aparecera en el lugar en el que se diseiia y en el que se usan las
propiedades de posicion (Left y Top) y de tamaiio (Width y Height) del
formulario. Otras opciones (poDefault, poDef ault PosOnly y poDe-
fault Sizeon1y) dependen de una caracteristica del sistema operativo: usan-
do un indicador especifico, Windows puede establecer la posicion y/o tamaiio de
nuevas ventanas utilizando una disposicion en cascada. De este modo, las propie-
dades de posicion y tamaiio que definamos en tiempo de diseiio seran ignoradas,
per0 si se ejecuta la aplicacion dos veces, las ventanas no se solaparan. Cuando el
formulario tiene un estilo de borde de dialogo, se ignoran las posiciones
predefinidas. Por ultimo, con el valor po Screence nter,el formulario apare-
cera en el centro de la pantalla, con el tamaiio que definamos en tiempo de diseiio.
Se trata de una configuracion muy comun para cuadros de dialogo y otros formu-
larios secundarios. Otra propiedad que afecta a1 tamaiio y posicion iniciales de
una ventana es su estado. Se puede usar la propiedad Windowstate en tiempo
de diseiio para que una ventana aparezca maximizada o minimizada a1 iniciar.
Esta propiedad solo puede tener tres valores: wsNormal , wsMinimized y
wsMaximi ze d. Si se define un estado de ventana minimizada, a1 arrancar el
formulario aparecera en la barra de tareas de Windows. En el caso del formulario
principal de una aplicacion, esta propiedad se puede definir automaticamente a1
especificar 10s atributos correspondientes en un metodo abreviado que se refiera a
la aplicacion. Por supuesto, tambien se puede maximizar o minimizar una venta-
na en tiempo de ejecucion. Simplemente cambiando el valor de la propiedad
Windowstate a wsMaximized o wsNormal se produce el efecto esperado.
Sin embargo, definir la propiedad como wsMinimized crea una ventana mini-
mizada que se coloca sobre la barra de tareas, no en su interior. Esta no es la
accion esperada en el caso de un formulario principal, per0 si en el caso de uno
secundario. Una solucion sencilla a este problema consiste en llamar a1 metodo
Minimize del objeto Application.Tambien existe un metodo Restore en
la clase TApplication que se puede usar cuando es necesario restaurar el
tamaiio de un formulario, aunque normalmente el usuario realizara esta operacion
mediante la orden Restore del menu de sistema.

Ajuste a la ventana (en Delphi 7)


Los formularios tienen dos nuevas propiedades en Delphi 7:
La propiedad booleana Screensnap: Determina si el formulario deberia
ajustarse (como atraido por un iman) a1 area de representacion de la panta-
lla cuando se encuentre cerca de uno de sus bordes.
La propiedad entera SnapBuffer: Determina la distancia con respecto a
10s bordes que se considera cercana. Aunque no es una caracteristica par-
ticularmente vistosa, es practica para permitir que 10s usuarios ajusten 10s
formularios en un lateral de la pantalla y aprovechen toda la superficie de
la pantalla; resulta particularmente practico para aplicaciones con multi-
ples formularios visibles a1 mismo tiempo. Pero hay que tener cuidado y no
usar un valor demasiado grande para la propiedad SnapBuff e r (algo
tan alto como toda la pantalla) o se confundira a1 sistema.

El tamafio de un formulario y su zona de cliente


En tiempo de diseiio, existen dos formas de definir el tamaiio de un formulario:
dcfiniendo el valor de las propiedades W i d t h y H e i g h t o arrastrando sus bor-
des. En tiempo de ejecucion, si el formulario posee un borde con un tamaiio
ajustable, el usuario puede modificarlo (originando el evento OnResize. en el
que se pueden realizar acciones personalizadas para adaptar la interfaz de usua-
rio a1 nuevo tamaiio del formulario).
Sin embargo, si miramos a las propiedades de un formulario en el codigo
fuente o en la ayuda electronica, se puede ver que hay dos propiedades que se
refieren a su ancho y otras dos que se refieren a la altura. H e i g h t y W i d t h se
refieren a1 tamaiio del formulario, incluidos sus bordes. C l i e n t H e i g h t y
C l i e n t W i d t h hacen referencia al tamaiio de la zona interna del formulario,
excluyendo bordes, titulo, barras de desplazamiento (si las hay) y barra de menu.
La zona de cliente del formulario es la superficie que se puede usar para colocar
coinponentcs en el formulario, para crear la salida y para recibir entradas de
usuario. Hay que resaltar que en CLX, incluso H e i g h t y Width se refieren a1
tamaiio del area interna del formulario.
Dado que podria interesarnos tener un espacio dado disponible para 10s com-
ponentes. normalmente es mejor definir el tamaiio de cliente de un formulario en
lugar de su tamaiio global. Hacer esto es sencillo, ya que cuando se fija una de las
dos propiedades del area de cliente, la propiedad correspondicnte para el formula-
rio se modifica de manera correcta.

TRUCO: En Windows, tambikn se puede producir resultados y capturar


entradas desde el Area no de cliente del formulario (es decir, de su borde).
Pintar sobre el borde y recibir una entrada cuando se hace clic sobre el son
asuntos complejos. Si interesa, se puede buscar en el archivo de ayuda la
descripcion de mensajes de Windows como w m N C P a i n t , w m
N C C a l c S i z e y wm N C H i t T e s t , y la serie de mekajes no de clienG
relacionados con la gntrada de raton, corno wrn NCLButtonDown. La
dificultad de esta ttcnica reside en combinar el cgdigo propio con el com-
portarniento predefinido de Windows.
Restricciones del formulario
Cuando se escoge un borde de tamaiio ajustable para un formulario, 10s usua-
rios generalmente pueden ajustar el tamaiio del formulario como deseen y tambien
maximizarlo para que ocupe la pantalla completa. Windows informa de que el
tamaiio del formulario ha cambiado con el mensaje wm-Size, que genera el
evento OnRe si ze. OnRe si ze tiene lugar despues de que el tamaiio del formu-
lario ya haya cambiado. Modificar de nuevo el tamaiio dentro de este evento (si el
usuario ha reducido o aumentado el formulario en exceso) resultaria poco practi-
co. Un enfoque preventivo se adapta mejor a este problema.
Delphi proporciona una propiedad especifica para 10s formularios y tambien
para todos 10s controles: la propiedad c o n s t r a i n t s . A1 definir las
subpropiedades de esta propiedad con 10s adecuados valores maximos y minimos
se genera un formulario que no se puede cambiar de tamaiio mas alla de 10s
limites establecidos. Veamos un ejemplo:
object Form1 : TForml
Constraints.MaxHeight = 300
C~nstraints~MaxWidth = 300
Constraints-MinHeight = 150
Constraints.MinWidth = 150
end

Conviene tener en cuenta que el efecto de la propiedad constraints,des-


pues de haberla definido, es inmediato incluso en tiempo de diseiio, cambiando el
tamaiio del formulario si esta fuera de la zona permitida.
Delphi utiliza tambien las restricciones maximas para las ventanas maximizadas,
produciendo un efecto algo extraiio. Por este motivo, generalmente deberia
inhabilitarse el boton de maximizar de una ventana que tenga un tamaiio maximo.
En algunos casos las ventanas maximizadas con un tamaiio limite pueden tener
sentido (este es el comportamiento de la ventana principal de Delphi). Si se nece-
sita modificar las restricciones en tiempo de ejecucion, tambien se puede conside-
rar usar dos eventos especificos, OnCanRes ize y OnConstrainedRes ize.
El primer0 de 10s dos tambien puede usarse para inhabilitar el ajuste de tamaiio de
un formulario o control bajo determinadas circunstancias.

Desplazar un formulario
Cuando se crea una aplicacion simple, un solo formulario podria albergar
todos 10s componentes necesarios. Sin embargo, a medida que crece la aplicacion,
tal vez haya que reducir el espacio para 10s componentes y juntarlos mas, aumen-
tar el tamaiio del formulario o aiiadir formularios nuevos. Si se juntan mas 10s
componentes, se podria aiiadir la capacidad de modificar su tamaiio en tiempo de
ejecucion, posiblemente dividiendo el formulario en dos zonas diferentes. Si deci-
dimos aumentar el tamaiio del formulario, podriamos usar las barras de desplaza-
miento para permitir que el usuario se mueva por un formulario que sera mas
grande que la pantalla (o a1 menos mas grande que su zona visible en la pantalla).
Aiiadir una barra de desplazamiento a un formulario es sencillo. De hecho, no
hay que hacer nada. Si se colocan varios componentes dentro de un gran formula-
rio y se reduce su tamaiio, automaticamente se aiiadira una barra de desplaza-
miento a1 formulario, siempre que no se haya cambiado el valor de la propiedad
AutoScroll predefinida como True.
Junto con A u t o s c r o 1 1 , 10s formularios tienen dos propiedades,
HorzScrollBar y VertScrollBar, que se pueden usar para definir diver-
sas propiedades de 10s dos objetos T FormScro 11Bar asociados con el formu-
lario.
La propiedad Visible indica si esta presente la barra de desplazamiento, la
propiedad Posit ion determina el estado inicial del control de desplazamiento y
la propiedad Increment determina el efecto que se obtiene a1 hacer clic sobre
una de las flechas situadas en 10s extremos de la barra de desplazamiento. Sin
embargo, la propiedad mas importante es Range.
La propiedad Range de una barra de desplazamiento establece el tamaiio
virtual del formulario, no el rango real de valores de la barra de desplazamiento.
Supongamos que se necesita un formulario que aloje diversos componentes y que
por tanto necesite ser de 1000 pixeles de ancho. Podemos usar este valor para
definir el "rango virtual" del formulario, cambiando el Range de la barra de
desplazamiento horizontal.
La propiedad Position de la barra de desplazamiento variara entre 0 y
1000 menos el tamaiio actual de la zona de cliente. Por ejemplo, si la zona de
cliente de un formulario tiene 300 pixeles de ancho, podemos desplazarnos 700
pixeles para ver el extremo mas alejado del formulario (el pixel milesimo).

Un ejemplo de prueba de desplazamiento


Para demostrar el caso concreto que acabamos de exponer, hemos construido
el ejemplo Scroll, que tiene un formulario virtual de 1000 pixeles de ancho. Para
ello, hemos definido el rango de la barra de desplazamiento horizontal como 1000:
object Form1 : TForml
HorzScrollBar.Range = 1000
VertScrollBar-Range = 305
AutoScroll = False
OnResize = FormResize

En el formulario del ejemplo se han colocado dos cuadros de lista sin ninguna
funcion y se podria haber obtenido el mismo rango de barra de desplazamiento
colocando el cuadro de lista fijo a la derecha de tal manera para que su posicion
(Left) mas su tamaiio (Width) fuese igual a 1000.
La parte interesante del ejemplo es la presencia de una ventana de cuadro de
herramientas que muestra el estado del formulario y de su barra de desplazamien-
to horizontal. Este segundo formulario tiene cuatro etiquetas, dos con texto fijo y
dos con la salida. Ademas de eso, el formulario secundario (Ilamado st at us)
tiene un estilo de borde bsToolWindow y es una ventana que siempre estara
por encima. Tambien deberiamos definir su propiedad V i s ible como True,
para que su ventana se muestre automaticamente a1 arrancar:
object Status: TStatus
BorderIcons = [biSystemMenu]
Borderstyle = bsToolWindow
Formstyle = fsStayOnTop
Visible = True
object Labell: T L a b e l ...
...
El unico objetivo de este programa es actualizar 10s valores del cuadro de
herramientas cada vez que se modifica el tamaiio del formulario o que este se
desplaza (como muestra la figura 7.8). La primera parte es muy sencilla. Se
puede controlar el evento O n R e s i z e del formulario y copiar simplemente un par
de valores en ambas etiquetas. Estas etiquetas forman parte de otro formulario,
por lo que sera necesario aiiadirles como prefijo el nombre de la instancia del
formulario, S t a t us :
procedure TForml. FormResize (Sender: TObject) ;
begin
Status.Label3.Caption : = IntToStr(C1ientWidth);
Status.Label4.Caption : = IntToStr(HorzScrollBar.Position);
end;

Form Size @):


Scroll Position M: 0
450 I

r1 I -
b

Figura 7.8. El resultado del ejemplo Scrolll.

Si queremos cambiar el resultado cada vez que el usuario desplace el conteni-


do del formulario, no podemos usar un controlador de eventos de Delphi, porque
no esiste un evento O n S c r o l l para formularios (aunque 10s componentes
ScrollBar independientes tengan uno). Omitir este evento time sentido, porque
10s formularios de Delphi gestionan las barras de desplazamiento de un mod0
muy potente. En Windows, por contraste, las barras de desplazamiento son ele-
mentos de un nivel muy bajo, lo que requiere mucha codificacion. Manejar el
evento de desplazamiento solo tiem sentido en casos especiales, como cuando se
desea seguir la pista con precision de las operaciones de desplazamiento realiza-
das por un usuario.
Veamos el codigo que hay que escribir. Primero, aiiadimos una declaracion de
metodo a la clase y la asociamos con el mensaje de desplazamiento horizontal de
Windows (wm-H S c r o l l ) . A continuacion, escribimos el codigo de este procedi-
miento, que es casi el mismo que el del metodo F o r m R e s i z e visto antes:
public
procedure FormScroll (var ScrollData: TWMScroll) ; message
wm_HScroll;

procedure TForml.FormScroll (var ScrollData: TWMScroll);


begin
inherited;
Status.Label3.Caption : = IntToStr(C1ientWidth);
Status.Label4.Caption : = IntToStr(HorzScrollBar.Position);
end;

Es importante aiiadir la llamada a i n h e r i t e d , que activa el metodo relacio-


nado con el mismo mensaje en el formulario de clase basica. La palabra clave
i n h e r i t e d en 10s controladores de mensajes de Windows llama a1 metodo de la
clase basica que estamos sobrescribiendo, que se encuentra asociado con el co-
rrespondiente mensaje de Windows (incluso aunque el nombre del procedimiento
sea distinto). Sin esta llamada, el formulario no se desplazara en absoluto.

NOTA:Debido a que en CLX no podems mtrolar 10s rnensajes de des-


p l a z ~ e n t oa bajo nivel, no puece que haya un modo fslcil de crear un
program similar a Scroll 1. En las aplimiones del mundo real, esto no
resulta extremadamente importante, puem que el siseema de desplazamimta
es a u t d t i c o y probablernente se pwde realizar conectando la biblioteca
CLX a un nivel inferior.

Desplazamiento automatic0
La propiedad R a n g e de la barra de desplazamiento puede parecer extraiia
hasta que se comienza a usar continuamente. Entonces, se empieza a pensar en las
ventajas de la tecnica del "rango virtual". En primer lugar, la barra de desplaza-
miento se elimina automaticamente del formulario cuando la zona de cliente del
formulario es lo suficientemente amplia como para acomodar el tamaiio virtual y
cuando se reduce el tamaiio del formulario, la barra de herramientas vuelve a
aparecer.
Esta caracteristica resulta sobre todo interesante cuando la propiedad
A u t o s c r o l l del formulario se establece como T r u e . En este caso, las posicio-
nes extremas de 10s controles situados mas a la derecha y mas abajo se copian
automaticamente en las propiedades Range de las dos barras de desplazamiento
del formulario. El desplazamiento automatic0 funciona bien en Delphi. En el
ejemplo anterior, el tamaiio virtual del formulario se estableceria en el borde
derecho del ultimo cuadro de lista. Esto se definia con 10s siguientes atributos:
object ListBox6: TListBox
Left = 832
Width = 145
end

Por lo tanto, el tamaiio virtual horizontal del formulario seria de 9 7 7 (la suma
de 10s dos valores anteriores). Este numero se copia automaticamente en el campo
Range de la propiedad Horz S c r o l l B a r del formulario, a menos que se cam-
bie manualmente para conseguir un formulario mas grande (corno en el ejemplo
Scroll 1, en el que se usaban un valor de 1 0 0 0 para que hubiera algo de espacio
entre el ultimo cuadro de lista y el borde del formulario). Podemos ver dicho valor
en el Object Inspector o realizar la siguiente prueba: ejecutar el programa,
establecer el tamaiio deseado para el formulario y mover el control de desplaza-
miento hasta el extremo derecho. Cuando aiiadimos el tamaiio del formulario y la
posicion del control, siempre obtendremos 1 0 0 0, la coordenada virtual del pixel
situado mas a la derecha, sea cual sea su tamaiio.

Desplazamiento y coordenadas del formulario


Ya hemos visto que 10s formularios pueden desplazar automaticamente sus
componentes. Lo que sucede si se pinta directamente sobre la superficie del for-
mulario es que surgen ciertos problemas, aunque con una solucion sencilla. Su-
pongamos que queremos dibujar algunas lineas en la superficie virtual de un
formulario, como muestra la figura 7.9. ya que probablemente no se disponga de
un monitor capaz de mostrar 2000 pixeles a lo largo de cada eje, se puede crear un
formulario mas pequeiio, aiiadir dos barras de desplazamiento y definir la propie-
dad Range, como en el ejemplo Scroll2.
Si simplemente dibujamos las lineas usando las coordenadas virtuales del
formulario, la imagen no aparecera de forma adecuada. En el metodo de respuesta
o n p a i n t , es necesario que calculemos nosotros mismos las coordenadas virtuales.
Afortunadamente, esto es facil, puesto que sabemos que las coordenadas virtuales
x l e Y 1 de la esquina superior izquierda de la zona de cliente corresponden a las
posiciones actuales de las dos barras de desplazamiento:
procedure TForml.FormPaint(Sender: TObject);
var
XI, Y1: Integer;
begin
X1 : = HorzScrollBar.Position;
Y1 : = VertScrollBar.Position;

// d i b u j a una l i n e d a m a r i l l a
Canvas.Pen.Width : = 30;
Canvas.Pen.Color : = clYellow;
Canvas .MoveTo (30-XI, 30-Y1) ;
Canvas .LineTo (1970-XI, 1970-Y1) ;

// y a s i s u c e s i v a m e n t e ...
2000 Pixeles

2000
'ixeles

500 Pixeles I
Figura 7.9. Las lineas a dibujar sobre la superficie virtual del formulario

Como una mejor alternativa, en lugar de calcular la coordenada correcta para


cada operacion de salida, podemos llamar a SetWindowOrg Ex de la API para
desplazar el origen de las coordenadas de la propia Canvas. De este modo,
nuestro codigo de dibujo se referira directamente a las coordenadas virtuales per0
las lineas se mostraran correctamente:
procedure TForm2.FormPaint(Sender: TObject);
begin
SetWindowOrgEx (Canvas.Handle, HorzScrollbar.Position,
VertScrollbar.Position, nil);

// d i b u j a una l i n e a a m a r i l l a
Canvas.Pen.Width : = 30;
Canvas.Pen.Color : = clYellow;
Canvas .MoveTo (30, 3 0 ) ;
C a n v a s .LineTo ( 1 9 7 0 , 1970) ;

// y a s i s u c e s i v a m e n t e ...
Esta es la version del programa que encontrara en el codigo fuente del libro. Se
puede probar el programa y comentar la llamada a SetWindowOrgEx para ver
lo que sucede si no se usan las coordenadas virtuales. Se puede ver que el resulta-
do del programa no es correct0 (no se desplazara, y siempre permanecera la
misma imagen en la misma posicion, sin importar las operaciones de desplaza-
miento). Observe tambien que en la version QtICLX del programa, denominada
QScroll2, no se usan las coordenadas virtuales sino que simplemente se restan las
posiciones de desplazamiento a cada coordenada codificada manualmente.

Escalado de formularios
Cuando sc crea un formulario con multiples componentes, se puede escoger un
borde de tamaiio fijo o permitir que el usuario ajuste el tamaiio del formulario y se
aiiadan automaticamente barras de desplazamiento para poder acceder a 10s com-
poncntes que se encuentren fuera de la parte visible del formulario, como ya se ha
visto. Tambien podria suceder esto porque un usuario de la aplicacion utilizara
un controlador de pantalla con un numero de pixeles mucho menor que en el
desarrollo. En lugar de reducir el tamaiio del formulario y desplazar el contenido,
podria quercrse reducir el tamaiio de cada uno de 10s componentes al unisono.
Esto ocurre automaticamente si el usuario tiene una fuente de sistema con una
tasa dc piselcs por pulgada distinta que la usada para el desarrollo. Para enfren-
tarse a estos problemas, Delphi disponc de unas apreciables caracteristicas de
escalado, per0 no son completamente intuitivas.
El metodo S c a l e B y de formulario permite ajustar la cscala del formulario y
de cada uno dc sus componentes. Las propiedadcs P i x e l s P e r I n c h y S c a l e d
permiten que Delphi modifique el tamaiio de una aplicacion de forma automatica,
cuando esta se ejecuta con un tamaiio de fuente de sistema diferente, normalmente
debido a una resolution de pantalla distinta. En ambos casos, para que el formu-
lario establezca la escala de su ventana, hay que asegurarse de definir tambien la
propiedad A u t o s c r o l l como F a l s e . De otro modo, el contenido del formula-
rio se ajustara a la escala, pero el borde de dicho formulario no.

NOTA: El ajuste a escala del formulario se cafcula segun la diferencia


entre la altura de la fuente en tiempo de ejecucion y la altura de la fuente en
tiempo de disefio. La escala garantiza que 10s controles de edicion y otros
controles sean lo suficientemente grandes como para mostrar su texto, uti-
lizando las preferencias del usuario sobre fuentes sin recortar el texto. La
- - - - 1- uel
escala 2-1 r 1-2-
rvrrnu~ar~o --
A--L:L-
rarnulen se auapra, ----
->--A-1- - - z - : ----
peru lo ---A- - - que 10s
mas 1rnponanLe es
controles de edicion y otros controles resulten legibles.
Escalado manual del formulario
Siempre que se quiere ajustar la escala de un formulario, asi como sus compo-
nentes, se puede usar el metodo S c a l e B y , que tiene dos parametros enteros, un
dividend0 y un divisor (es una fraccion). Por ejemplo, con esta sentencia el tama-
fio del formulario actual se reduce a tres cuartas partes de su tamaiio original:
ScaleBy (3, 4);

Normalmente, resulta mas sencillo usar porcentajes. Se obtiene el mismo efec-


to usando:
ScaleBy (75, 1 0 0 ) ;

Cuando se ajusta la escala de un formulario, se mantienen todas las proporcio-


nes, per0 si se superan ciertos limites minimos o maximos, las cadenas de texto
pueden modificar ligeramente todas sus proporciones. El problema es que en
Windows, 10s componentes se pueden colocar y se puede ajustar su tamafio solo
en pixeles enteros, mientras que el ajuste de la escala casi siempre implica una
multiplication por numeros fraccionarios. Por lo tanto, cualquier parte fraccionaria
del origen o tamaiio de componente se vera truncada.
Hemos creado un sencillo ejemplo, Scale (o QScale), para mostrar como se
puede ajustar manualmente la escala de un formulario, respondiendo a una solici-
tud realizada por el usuario. El formulario de esta aplicacion tiene dos botones,
una etiqueta, un cuadro de edicion y un control UpDown conectados a el (median-
te la propiedad A s s o c i a t e ) . Con esta configuration, un usuario puede escribir
numeros en el cuadro de edicion o pinchar sobre las dos pequeiias flechas para
aumentar o disminuir el valor (en la cantidad indicada por la propiedad
I n c r e m e n t ) . Para extraer el valor de entrada, se puede usar la propiedad T e x t
del cuadro de edicion o la propiedad P o s i t i o n del control UpDown. Cuando se
hace clic sobre el boton Do Scale, el valor de la entrada actual se utiliza para
determinar el porcentaje de escalado del formulario:
procedure TForml.ScaleButtonC1ick(Sender: TObject);
begin
AmountScaled : = UpDownl.Position;
ScaleBy (AmountScaled, 100) ;
UpDownl.Height : = Editl.Height;
ScaleButton. Enabled : = False;
RestoreButton.Enab1ed : = True;
end;

Este metodo almacena el valor de entrada actual en el campo privado


A m o u n t s c a l e d del formulario y activa el boton Restore, desactivando el que
estaba pulsado. A continuacion, cuando el usuario pulsa el boton Restore, se
ajustara la escala a1 contrario. A1 tener que restaurar el formulario antes de reali-
zar otra operacion de ajuste de escala, evitamos una acumulacion de errores de
redondeo. Tambien hemos aiiadido una linea para definir la altura del componente
UpDown como igual a la del cuadro de cdicion a1 que se encuentra conectado.
Esto cvita pequcfias difercncias entrc ambos. debido a problemas dc escalado del
control UpDown.
- -- - -
NOTA: Si queremos ajustar la escala del texto del formulario correcta-
mente, y tambien de 10s titulos de 10s componentes, 10s elementos de 10s
cuadros de lista, etc.. ., deberiamos utilizar exclusivamente fhentes TrueType.
La fuente de sistema (MS Sans Serif) no se ajusta a la escala correctamen-
te. El problema de la fhente resulta importante porque el tamaiio de muchos
componentes depende de la altura del texto de sus titulos y, si el titulo no se
ajusta bien a la escala, el componente podria no fun~ionar~correctamente.
Por esa razon, en el ejemplo Scale hemos usado una fuente Arial.

Esta misma tecnica de ajuste de escala funciona tambien en CLX, como se


pucde ver al ejecutar cl ejemplo QScale. La imica diferencia real es que hemos
sustituido el componente UpDown (y cl cuadro de edicion relacionado) por un
control SpinEdit, puesto quc el primer0 no cxiste en Qt.

Ajuste automatico de la escala del formulario


En lugar dc trabajar con cl metodo ScaleBy,podemos pedirle a Delphi que
se cncargue del trabajo. Cuando se inicia Delphi, pide al sistema la configuracion
de la pantalla y guarda el valor en la propiedad Pixels PerInch dcl objeto
Screen,un objeto especial de la VCL, que esta disponible en cualquier aplica-
cion.
Pixels Per Inch sucna como si tuviese algo que ver con la resolucion cn
piscles dc la pantalla (que en realidad se encuentra en Screen.Height y
Screen.Width). per0 desafortunadamente no es asi. Si cambiamos la resolu-
cion de la pantalla de 6 4 0 ~ 4 8 0a 800x60, o 1 0 2 4 ~ 7 6 8o incluso a 1600x1280,
vercmos que Windows indica el mismo valor PixelsPerInch en todos 10s
casos, a menos quc cambiemos la fuente del sistema. PixelsPerInch se refie-
re en realidad a la resolucion en pixeles dc la pantalla para la que se diseiio
originalmentc la fuente de sistema instalada actualmentc. Cuando el usuario cam-
bia la escala de la fuente de sistema, normalmente para que 10s menus y otros
testos Sean mas faciles de lecr. se espera que todas las aplicaciones respeten csa
configuracion. Una aplicacion que no refleja las preferencias de apariencia de la
pantalla del usuario parecera estar fuera de lugar y. en casos muy estremos.
resultara completarnente inutil para 10s usuarios con problemas visuales a 10s que
les resultan muy practicas las fuentes muy grandes y 10s esquemas de color de
gran contraste.
Los valores PixelPer Inch mas comunes son 96 (fuentes pequeiias) y 120
(fuentes grandes), per0 son posibles otros. Las versiones mas recientes de Windows
permiten incluso que el usuario defina el tamaiio de la fuente de sistema segun una
escala arbitraria. En tiempo de diseiio, el valor P i x e l s P e r I n c h de la pantalla,
que es una propiedad so10 de lectura, se copia en cada formulario de la aplicacion.
Delphi usa entonces el valor de P i x e l s P e r I n c h , si la propiedad S c a l e d esta
definida como T r u e , para ajustar el tamaiio del formulario cuando se inicia la
aplicacion .
Como hemos dicho, tanto el ajuste automatico de la escala como el realizado
por el metodo S c a l e B y modifican el tamaiio de la fuente de 10s componentes. El
tamaiio de cada control, en realidad, depende de la fuente que se use. Con el ajuste
de escala automatico, el valor de la propiedad P i x e l s P e r I n c h del formulario
(el valor en tiempo de diseiio) se compara con el valor del sistema en ese momento
(indicado por la propiedad correspondiente del objeto S c r e e n ) y se usa ese
resultado para modificar la fuente de 10s componentes del formulario. Para mejo-
rar la precision de este codigo, la altura final del texto se compara con la altura
del texto en tiempo de diseiio y se ajusta su tamaiio si ambas alturas no se corres-
ponden.
Gracias a1 soporte automatico de Delphi, una misma aplicacion ejecutada en
un sistema con un tamaiio de fuente de sistema distinto ajustara su escala de
forma automatica, sin ningun codigo especifico. Los controles de edicion de la
aplicacion tendran el tamaiio adecuado para mostrar su texto en el tamafio de
fuente preferido por el usuario y el formulario tendra el tamaiio adecuado para
alojar dichos controles. Aunque el ajuste automatico de escala tiene problemas en
algunos casos especiales, si se respetan las siguientes normas, 10s resultados de-
berian ser 10s correctos:
Definir la propiedad S c a l e d de 10s formularios como T r u e . (Es el valor
predefinido.)
Usar solo fuentes TrueType
Usar fuentes pequeiias de Windows (96 dpi) en el ordenador que se use
para desarrollar 10s formularios.
Definir la propiedad A u t o s c r o l l como F a l s e si se quiere ajustar a
escala del formulario y no solo sus controles. ( A u t o S c r o l l esta
predefinida como T r u e , por lo que conviene recordar este paso.)
Definir la posicion del formulario o bien proxima a la esquina superior
izquierda o en el centro de la pantalla (con el valor p o s c r e e n c e n t e ~ )
para evitar tener un formulario fuera de la pantalla.

Crear y cerrar formularios


Hasta ahora no hemos hablado de la cuestion de la creacion de un formulario.
Sabemos que cuando se crea el formulario, se recibe el evento o n c r e a t e y
podemos cambiar o comprobar algunas de las propiedades o campos iniciales del
formulario. La sentencia responsable de la creacion del formulario esta en el
archivo fuente del proyecto:
begin
Application-Initialize;
Application. CreateForm(TForm1, Forml) ;
Application.Run;
end.

Para saltarse la creacion automatica del formulario, se puede modificar este


codigo o utilizar la ficha Forms del cuadro de dialogo Project Options (vCase
figura 7.10). En este cuadro de dialogo, se puede decidir si el formulario deberia
crearse de manera automatica. Si desactivamos la creacion automatica, el codigo
de inicializacion del proyecto se transforma en el siguiente:
begin
Applications.Initialize;
Application.Run;
end.

r --Man
- -

-
fam l~olrn~

Figura 7.10. La ficha Forms del cuadro de dialogo Project Options de Delphi.

Ahora, si ejecutamos este programa, no pasara nada. Finaliza de forma inme-


diata porque no se crea ninguna ventana principal. El efecto de la llamada a1
metodo C r e a t e F o r m de la aplicacion crea una nueva instancia de la clase de
formulario que se pasa como primer parametro y la asigna a la variable pasada
como segundo parametro.
En el ambito interno sucede algo mas. Cuando se llama a C r e a t e F o r m , si en
ese momento no hay un formulario principal, se asigna el formulario actual a la
propiedad M a i n F o r m de la aplicacion. Por esa razon, el formulario indicado
como Main Form en el cuadro de dialog0 que aparece en la figura 7.10 se corres-
ponde con la primera llamada a1 metodo CreateForm de la aplicacion (es decir,
cuando se crean diversos formularios a1 arrancar).
A1 cerrar la aplicacion, ocurre lo mismo. Si se cierra el formulario principal,
finaliza la aplicacion, sin tener en cuenta 10s otros formularios. Para realizar esta
operacion desde el codigo del programa, sencillamente hay que llamar a1 metodo
close del formulario principal, como hemos hecho en diversas ocasiones en 10s
ejemplos anteriores.

Eventos de creacion de formularios


Sea cual sea el tipo de creacion de formularios (manual o automatica), cuando
se crea uno; hay muchos eventos que podemos interceptar. Los eventos de crea-
cion de formulario se dan en el siguiente orden:
1. Oncreate indica que se esta creando el formulario.
2. OnShow indica que se esta mostrando el formulario. Ademas de 10s for-
mularios principales, este evento tiene lugar despues de que se define como
True la propiedad Visible del formulario o se llama a 10s metodos
Show o ShowModal. Este evento ocurre de nuevo si el formulario se
oculta y aparece de nuevo.
3. OnActivate indica que el formulario se transforma en el formulario
activo de la aplicacion. Este evento ticne lugar cada vez quc nos movemos
desde otro formulario de la aplicacion a1 actual.
4. Otros eventos, como OnRes i ze y On Paint,indican operaciones reali-
zadas siempre a1 arrancar pero repetidas, a continuacion, varias veces.
, - - - - - -
- - . - -

NOTA: En Qt, el evento OnRes i ze no se lanza como en Windows cuan-


do se crea el formulario. Para que el &go resulte rnh adaptable de Delphi
a Kylix, CLX simula este evento, aunque tendria mas sentido retocar la
VLC para evitar que se diera este e x t r s o comportamiento (un comentario
en el c ~ d i g ofuente de CLX comenta esta situacion).

Como se puede ver, cada evento tiene una funcion especifica ademas de la
inicializacion del formulario, a escepcion del evento Oncreate,a1 que se llama
solo una vez de manera garantizada cuando se crea el formulario.
Sin embargo, existe un enfoque alternativo para aiiadir codigo de inicializacion
a un formulario: sobrescribir el constructor. Esto se hacc del siguiente modo:
constructor TForml.Create(A0wner: TComponent);
begin
inherited Create (AOwner);
// c o d i g o d e i n i c i a c i o n a d i c i o n a l
end;

Antes de la llamada a1 m e t o d o c r e a t e de la clase basica, las propiedades del


formulario no se han cargado todavia y 10s componentes internos no estan dispo-
nibles. Por esa razon, la tecnica estandar consiste en llamar a1 constructor de la
clase basica primcro y, a continuacion, hacer las operaciones personalizadas.
- -- - - .- - - - .

NOTA: Hasta la version 3, Delphi usaba un orden de creacion distinto, que


ha llevado a la propiedad de compatibilidad OldCreateOrder de la
VCL. Cuando se fija esta propiedad a1 valor predeteminado de False,
todo el codigo de un constructor de fomulario se ejecuta antes que el codi-
- -. - ". \ .. .. .
go del controlador del evento oncreate (que lanza el metodo especial
. . ....
A E tercons truct ion). 31se naml1t.a el antlguo oraen ae creaclon, la
. I . I .
llamada inherited del constructor lleva a la llamada a1 controlador del
evento Oncreate. Se puede examinar el comportamiento del ejemplo
CreateOrd utilizando 10s dos valores para la propiedad OldCreateOrder.

Cerrar un formulario
Cuando cerramos un formulario utilizando el metodo c l o s e o mediante el
tipico mdtodo (Ah-F4, el mcnu de sistcma o el boton Close), llamamos a1 evento
O n C l o s e Q u e r y . En este caso, se puede pedir a1 usuario que confirme la ac-
cion, sobrc todo si hay datos sin guardar en el formulario. Veamos la sencilla
cstructura dcl codigo que podemos escribir:
procedure TForml.FormCloseQuery(Sender: TObject; var CanClose:
Boolean) ;
begin
if MessageDlg ( ' A r e y o u s u r e y o u w a n t t o e x i t ? ' ,
mtconfirmation, [&Yes, &No] , 0 ) = idNo then
CanClose : = False;
end:

Si O n C l o s e Q u e r y indica quc el formulario deberia cerrarse, se efectua una


llamada a1 evento o n c l o s e . El tercer paso consiste en llamar a1 evento
OnDes t r o y , que es el contrario del evento OnCrea t e y se usa por lo general
para eliminar objetos relacionados con el formulario y liberar la memoria corres-
pondientc.
-- . - - - ..- -- - -- - -- - - . -- -

NOTA: Para ser m b precisos, el metodo Be foreDestruct ion genera


un evento OnDestroy antes de llamar a1 destructor Destroy.Es deck,
-
a -Ai-- L
m m o ~que nayamos J-P--:>-
aerln~ao 1-
la propieaaa A. -.-_.-
---- l - 2 - J u1aLrear;euraer como
_L--.._.___ -_-a-

True,en cuyo caso Delphi usa una secuencia de cierre distinta.


El uso dcl evento intermedio onclose esta en que nos ofrece otra oportuni-
dad para no cerrar la aplicacion o podemos especificar "acciones de cierrc" altcr-
nativas. De hecho, el metodo ticnc un parametro Action que se pasa mcdiante
rcferencia. Podemos asignar 10s siguientcs valorcs a dicho parametro:
caNone: No sc pcrmitc a1 formulario que se cicrrc. Corrcsponde a la con-
figuration del paramctro Canclose del metodo OnCloseQuery como
False.
caHide: No se cierra cl formulario, solo se oculta. Esto solo sc dcbe haccr
si hay otros formularios cn la aplicacion, si no, el programa finaliza. En cl
caso de 10s formularios secundarios es el comportamiento predefinido y
csa es la razon de quc hapamos tenido que controlar el evento onclose
en el ejemplo antcrior para poder cerrar 10s formularios secundarios.
caFree: Se cierra el forn~ulario~ sc libcra su memoria y por ultimo finaliza
la aplicacion si ese era el formulario principal. Esta es la accion predefinida
cn el caso del formulario principal y la accion que deberiamos usar cuando
sc crean varios formularios dc forma dinamica (si se desea eliminar las
ventanas y destruir cl corrcspondiente objeto Delphi cuando sc cicrrc cl
formulario).
cahlinimize: No se cierra el formulario, solo se minimiza. Esta es la ac-
cion predefinida en el caso de formularios hijo MDI.
- -

NOTA: C u a n d o un usuario cierra Windows. s e activa el evento


7

OnCloseQuery que puede usar un programa para detener el proceso de


c i e r r e . En ese c a s o , n o se llama a1 evento O ~ l C
- -. 3."
uncloseyuery aerina
A , . . canclose
1
el pararnerro - como 'Lrue.
- 3
o s e aunque
-

Cuadros de dialogo y otros formularios


secundarios
Cuando escribimos un programa, no existe una gran diferencia entre un cua-
dro de dialogo y otro formulario secundario, a parte del borde, 10s iconos del
borde y elementos similares de la interfaz de usuario que se pueden personalizar.
Los que 10s usuarios asocian con un cuadro de dialogo es el concept0 de una
ventana modal (una ventana que obtiene el foco y habra de cerrarse antes de que
el usuario pueda volver a la ventana principal). Esto ocurre en el caso de 10s
cuadros de mensaje y normalmente tambien en el de 10s cuadros de dialogo. Sin
embargo. tambien podemos tener cuadros de dialogo no modales. Entonces, si
piensa en 10s cuadros de dialogo como simplemente en formularios modales, esta
en el buen camino, aunque la descripcion no es precisa. En Delphi (igual que en
Windows), podemos tener tambien cuadros de dialogo no modales y formularios
modales. Tenemos que considerar dos elementos diferentes: el borde del formula-
rio y su interfaz de usuario establecen si su apariencia es la de un cuadro de
dialogo; y el uso de dos metodos diferentes (Show o ShowModal) para mostrar
el formulario secundario determina su comportamiento (no modal o modal).

Afiadir un formulario secundario a un programa


Para aiiadir un formulario secundario a una aplicacion, simplemente pulsamos
el boton New Form de la barra de herramientas de Delphi o la orden del menu
File>New Form. Como alternativa podemos seleccionar File>New, ir a la ficha
Forms o Dialogs y seleccionar una de las plantillas de formulario o asistentes
para formularios disponibles.
Si tenemos dos formularios en un proyecto, podemos usar el boton View Form
o View Unit de la barra de herramientas de Delphi para movernos por ellos en
tiempo de diseiio. Tambien podemos seleccionar quC formulario es el principal y
cuales debcrian de crearse automaticamente a1 arrancar, utilizando la ficha Forms
del cuadro de dialogo Project Options. Esta informacion se refleja en el codigo
fuente del archivo de proyecto.

TRUCO: Los formularios secundarios se crean automaticamente en el ar-


chive de codigo fuente del proyecto dependiendo del estado del cuadro de
comprobacidn Auto Create Forms de la phgina Designer del cuadro de
dialogo Environment Options. Aunque la creation automatica es la tec-
nica mas sencilla y mas fiable para 10s desarrolladores noveles y proyectos
sin perfeccionar, conviene desactivar esta casilla de verification en todos
aquellos proyectos de desarrolIo importantes. Si la aplicaci6n contiene cientos
de formularios, no se deberian crear todos a1 iniciar la aplicacion. Lo mejor
es crear las instancias de fonnularios secundarios cuando y donde Sean
necesarios y liberarlos cuando no se necesiten.

Cuando hayamos preparado el formulario secundario, simplemente podemos


definir su propiedad V i s i b l e como T r u e y ambos formularios apareceran a1
arrancar el programa. En general, 10s formularios secundarios de una aplicacion
se dejan "invisibles" y, mas tarde, se muestran llamando a1 metodo Show (o
definiendo la propicdad V i s i b l e en tiempo de ejecucion). Si utilizamos la fun-
cion Show, el segundo formulario aparecera como no modal, de mod0 que pode-
mos movernos de nuevo a1 primer0 mientras el segundo esta visible. Para cerrar el
segundo formulario, podriamos usar su menu de sistema o pulsar el boton o ele-
mento del menu que llama a1 metodo C l o s e . Tal y como acabamos de ver, la
accion de cierre predefinida (vease el evento O n c l o s e ) en el caso de un formu-
lario secundario consiste sencillamente en ocultarlo, por lo que el formulario
secundario no se destruye cuando se cierra, sino que se mantiene en memoria (no
es el mejor enfoque) y esta disponible si queremos volver a mostrarlo.

Crear formularios secundarios en tiempo de


ejecucion
A menos que creemos todos lo formularios a1 arrancar el programa, sera nece-
sario verificar si existe un formulario y, si es necesario, crearlo. El caso m b
sencillo es aquel en el que queremos crear diversas copias del mismo formulario
en tiempo de ejecucion. En el ejemplo MultiWin/QMultiWin, lo hemos hecho asi
con el siguiente codigo:
with TForm3.C r e a t e ( A p p l i c a t i o n ) do
Show;

Cada vez que hacemos clic sobre el boton, se crea una nueva copia del formu-
lario. Hay que darse cuenta de que no utilizamos la variable global F o r m 3 por-
que no tiene mucho sentido asignar a esta variable un nuevo valor cada vez que se
crea un nuevo objeto de formulario. Sin embargo, lo importante es no referirse a1
objeto global F o r m 3 en el codigo del formulario o en otras partes de la aplica-
cion. La variable F o r m 3 sera invariablemente un punter0 a n i l . Lo mas reco-
mendable es que en un caso como este se elimine de la unidad para evitar cualquier
confusion posible.
-- -.. -- - . -

TRUCO:En el c M g o de un formulario que puede tener mmiltiples instan-


cias, nunca se deben'a hacer referencia explicita al formulario utihzando la
variable global que Delphi define para 61. Por ejemplo, supongarnos que en
.
el codigo de T Form3 hacemos referencia a Form3 C a p t i o n . Si crea-
mos un segundo objeto del mismo tipo (la clase TForm3), la expresion
.
Form3 C a p t i o n se referira siempre a1 titulo del objeto de formulario al
que hace referencia la variable Form3, que podria no ser el objeto actual
que ejecuta el codigo. Para evitar dicho problerna, hay que referirse a la
propiedad C a p t i o n en el m6todo del formulario para indicar el titulo del
objeto de formulario actual y usar la palabra clave self cuando se necesi-
- .- - -. - - - - . -
te hacer reterencla especifica a1 0bjet0 del formulario en uso. Para evltar
cualquier problema al crear diversas copias de un formulario, se puede
elirninar el objeto global formulario de la parte de interfaz de la unidad que
declara el formulario.

Cuando se crean varias copias de un formulario de manera dinamica, hay que


recordar destruir cada objeto de formulario cuando se cierra, controlando el even-
to correspondiente:
procedure TForm3.FormClose(Sender: TObject; var Action:
TCloseAction) ;
begin
Action : = caFree;
end :

No hacer esto conllevara un gran consumo de memoria, ya que todos 10s for-
mularios que se creen (tanto las ventanas como 10s objetos Delphi) se mantendran
en memoria y se ocultaran.

Crear un unica instancia de formularios secundarios


Centremonos ahora en la creacion dinamica de un formulario, en un programa
que cuenta solo con una copia del formulario cada vez. Crear un formulario
modal es bastante sencillo, porque se puede destruir el cuadro de dialogo cuando
se cierra, con un codigo como este:
var
Modal: TForm4;
begin
Modal : = TForm4 .Create (Application);
try
Modal.ShowModa1;
finally
Modal.Free;
end ;

Como la llamada a ShowModal puede producir una excepcion, deberiamos


escribirla dentro de un bloque try seguido de un bloque finally para asegu-
rarnos de que se libera la memoria asignada para el mismo. Normalmente dicho
bloque incluye tambien codigo que inicializa el cuadro de dialogo antes de mos-
trarlo y codigo que extrae 10s valores definidos por el usuario antes de destruir el
formulario. Los valores finales son de solo lectura si el resultado de la funcion
ShowModal es mrOK.
La situacion es algo mas compleja cuando queremos mostrar una sola copia de
un formulario no modal. Tenemos que crear el formulario, si no existe todavia, y
despues mostrarlo:
if not Assigned (Form2) then
Form2 : = TForm2 .Create (Application);
Form2.Show;

Con este codigo, se crea el formulario la primera vez que se necesita y despues
se guarda en memoria, visible en pantalla u oculto. Para evitar consumir memoria
y recursos del sistema de forma innecesaria, debemos destruir el formulario se-
cundario cuando se cierre. Para ello podemos escribir un controlador del evento
OnClose:
procedure TForm2.FormClose(Sender: TObject; var Action:
TCloseAction) ;
begin
Action : = caFree;
/ / i m p o r t a n t e : d e f i n i r p u n t e r 0 como n i l
Form2 : = n i l ;
end ;

Fijese en que despues de destruir el formulario, la variable global Form2 se


define como n i l . Sin este codigo, a1 cerrar el formulario se destruiria su objeto,
per0 la variable Form2 remitiria aun asi a la posicion de memoria original. En
cste punto, si se intenta mostrar el formulario una vez mas con el metodo
b t n S i n g l e C l i c k , funcionara la prueba i f n o t A s s i g n e d ( ) , puesto que
sencillamente verifica si la variable Form2 es n i l . El codigo no crea un objeto
nuevo y el metodo show (invocado sobre un objeto inexistente) producira un
error de memoria del sistema.
Como esperimento, se puede generar este error eliminando la ultima linea del
listado anterior. Como se ha visto, la solucion es asignar n i l a1 objeto Form2
antes de destruir el objeto, de manera que un codigo escrito correctamente sera
capaz de "ver" que se debe crear un nuevo formulario antes de utilizarlo. Una vez
mas. esperimentar con el ejemplo Mu1t i W i n / Q M u l t i W i n puede ser muy util
para comprobar distintas situaciones.

NOTA: Conviene definir la variable del fonnulario como nil (y funciona)


si solo va a haber una instancia del fonnulario presente en un momento
dado. Si queremos crear diversas copias de un formulario, tendremos que
Ae lac m i c m a c AAp-
11tilivar n t r a c t b ~ n i r a cn a r a m a n t e n e r wn c o r n ~ i m i e n t n
-
mas, hay que tener en cuenta que en este caso, no podemos usar el procedi-
miento FreeAndNil, porque no podemos llamar a Free sobre Form2.

I
1,A
,, a
, ,
.
a " ,
A
*,",, A,"+,.:, ,I C,-..l..,, ,
,
+a
, ,
,A,
,:
,a+
,..
, A,
~a I ~ L U I GJ
I ~ U uu
G ~ J U G I I I V J U G J C I U I ~GI 1 ~ 1 1 1 1 ~ 1 4 CULCGJ
1 1 ~ UG ~ U CGIIIIIUGII
G UG

ejecutarse sus controladores de eventos.

Creacion de un cuadro de dialogo


Ya hemos dicho que un cuadro de dialogo no es muy distinto de otros formula-
rios. Para crear un cuadro de dialogo en lugar de un formulario, solo hay que
selcccionar el valor b s D i a l o g para la propiedad B o r d e r s t y l e . Con este
sencillo cambio, la interfaz del formulario se convierte en la de un cuadro de
dialogo, sin icono de sistema, ni casillas para ininimizar o maximizar. Por su-
puesto, dicho formulario posee el borde p e s o tipico de cuadro de dialogo, que
no podremos modificar de tamaiio.
Cuando hayamos creado el formulario de cuadro de dialogo, podemos mos-
trarlo como una ventana modal o no modal que use 10s dos metodos habituales
para aparecer (Show y ShowModal). Sin embargo, 10s cuadros de dialogo mo-
dales son mas habituales que 10s no modales. Es justo a1 contrario que con 10s
formularios: generalmente deberian evitarse 10s formularios modales, porque no
es lo que espera un usuario.

El cuadro de dialogo del ejemplo RefList


En el ejemplo RefList2 (y en su homologo CLX QRefLsit2), hemos aiiadido a
la version basica del ejemplo (el ejemplo RefListIQRefList del capitulo sobre
controles visuales, que usaba un control ListView para mostrar referencias a
libros, revistas y cosas asi) un cuadro de dialogo que se utiliza en dos circunstan-
cias distintas: aiiadir elementos nuevos a la lista y editar elementos existentes.

ADVERTENCIA: El componente ListView de la CLX tiene un problema.


En caso de que se activen 10s cuadros de verification y despues se inhabiliten,
desaparecerhnlas imiygenes. Este es el comportamientodel ejemplo QRefList
-._ J _ n n - m '-&A L
-..-__ _-_>:A- - I > : _ _ - _ - -.-I
m e n m o . P-
__--_-A-
ya w I-
c n ra $1-
verslon ynerusu nemos anaaao coalgo para vower
a asignar la propiedad ImageIndex de cada elemento como solucion de
este fallo.

La unica caracteristica interesante de este formulario en el ejemplo VCL es el


uso del componente ComboBoxEx,que esta conectado a la misma ImageList
utilizada por el control ListView para controlar el formulario principal. Los ele-
mentos de la lista desplegable, usados para escoger un tip0 de referencia, incluyen
tanto una descripcion textual como la imagen correspondiente.
Como ya se ha comentado, este cuadro de dialogo se usa en dos casos distin-
tos. El primer0 ocurre cuando el usuario selecciona File>Add Items en el menu:
procedure TForml.AddItemslClick(Sender: TObject);
var
NewItem: TList Item;
begin
FormItem.Caption : = 'New Item';
FormItem.Clear;
if FormItem.ShowModal = mrOK then
begin
NewItem : = ListViewl.1tems.Add;
NewItem.Caption : = FormItem.EditReference.Text;
NewItem.ImageIndex : = FormItem.ComboType.ItemIndex;
NewItem.SubItems.Add (Form1tem.EditAuthor.Text);
NewItem.SubItems.Add (Form1tem.EditCountry.Text);
end ;
end ;

Ademas de definir el titulo correct0 para el formulario, este procedimiento


necesita iniciar el cuadro de dialogo porque introducimos un nuevo valor. Sin
embargo, si el usuario hace clic sobre OK, el programa aiiade un nuevo elemento
a la lista y define todos sus valores. Para vaciar 10s cuadros de edicion del dialo-
go, el programa llama a1 metodo personalizado c l e a r , que reinicia el texto de
cada cuadro de edicion:
procedure TFormItem.Clear;
var
I: Integer;
begin
// b o r r a c a d a c u a d r o d e e d i c i o n
for I : = 0 to Controlcount - 1 do
if Controls [I] is TEdit then
TEdit (Controls[I]) .Text : = '';
end;

Para editar un elemento ya existente, es necesaria una tecnica ligeramente


distinta. En primer lugar, 10s valores actuales se llevan tambien a1 cuadro de
dialog0 antes de que aparezca. En segundo lugar, si el usuario hace clic sobre
OK, el programa modifica la lista actual en lugar de crear una nueva. Veamos el
codigo:
procedure TForml.ListViewlDblClick(Sender: TObject);
begin
if ListViewl.Selected <> nil then
begin
// i n i c i o d e l d i d l o g o
FormItem.Caption : = ' E d i t I t e m ' ;
FormItem.EditReference.Text : =
ListViewl.Se1ected.Caption;
FormItem.ComboType.ItemIndex : =
ListViewl.Selected.Image1ndex;
Form1tem.EditAuthor.Text : = ListViewl.Se1ected.SubItems [O];
Form1tem.EditCountry.Text : = ListViewl.Selected.SubItems [I];

// l o r n u e s t r a
if FormItem.ShowModa1 = mrOK then
begin
// l e e 1 0 s v a l o r e s n u e v o s
ListViewl.Se1ected.Caption : =
FormItem.EditReference.Text;
ListViewl.Selected.ImageIndex : =
FormItem.ComboType.ItemIndex;
ListViewl.Selected.Sub1tems [O] : =
Form1tem.EditAuthor.Text;
ListViewl.Selected.SubItems [I] : =
Form1tem.EditCountry.Text;
end ;
end ;
end;

Se puede ver el efecto de este codigo en la figura 7.11. Observe que el codigo
utilizado para leer el valor de un elemento nuevo o modificado es similar. En
general, hay que evitar este tip0 de codigo duplicado y colocar las sentencias de
codigo compartidas en un metodo aiiadido a1 cuadro de dialogo. En este caso; el
metodo podria recibir como parametro un objeto TList I t e m y copiar en el 10s
valores adecuados.

Marca Canlu

Eapaiia

Figura 7.11. El cuadro de dialogo del ejernplo RefList2 utilizado en modo edicion.

- . .- - - ... . - . - .. - - .

NOTA: Lo que sucede internamente cuando el usuario hace clic sobre el


boton OK o Cancel del cuadro de dialogo es que un cuadro de dialogo
modal se cierra estableciendo su propiedad ModalResul t, y devolviendo
el valor de esta propiedad. Se puede indicar el valor de retorno usando la
propiedad ModalResul t del boton. Cuando el usuario haga clic sobre el
boton, su valor Modal Resul t se copiarh a1 formulario, que lo que cierra
el formulario y devuelve el valor como el resultado de la funcion
ShowModal.

Un cuadro de dialogo no modal


El segundo ejemplo de cuadros de dialogo muestra un cuadro de dialogo modal
mas complejo que utiliza el enfoque estandar, a1 igual que un cuadro de dialogo
no modal. El formulario principal del ejemplo DlgApply (y del ejemplo identico
basado en CLX, QDlgApply) tiene cinco etiquetas con nombres, como muestra la
figura 7.12 y un analisis del codigo fuente del ejemplo.
Si el usuario hace clic sobre un nombre, su color se vuelve rojo. Si hace doble
clic sobre el, el programa muestra un cuadro de dialogo modal con una lista de
nombres de entre la que escoger. Si el usuario hace clic sobre el boton Style,
aparece un cuadro de dialogo no modal, el cual permite que el usuario modifique
el estilo de la fuente de las etiquetas del formulario principal. Las cinco etiquetas
del formulario principal estan conectadas con dos metodos, uno para el evento
OnClic k y otro para el evento OnDoubleClic k. El primero hace que la
etiqueta pulsada por el usuario se vuelva roja y reinicia todas las demas (que
tienen una propiedad Tag configurada como 1, como una especie de indice de
grupo) en negro. Fijese en que se asocia el mismo metodo con todas las demas
etiquetas:
procedure TForml.LabelClick(Sender: TObject);
var
I: Integer;
begin
for I : = 0 to Componentcount - 1 do
if (Components[I] is TLabel) and (Components[I] .Tag = 1)
then
TLabel (Components[I]) .Font.Color : = clBlack;
/ / define el color de la etiqueta pulsada como rojo
[Sender as TLabel) .Font.Color := clRed;
end ;

.Bob
C njy Name
Jane
Jelf Name
John
J iha
Ma~k Name
Martha
Mn,,
Name
Name

I Sample label

Figura 7.12. Los tres forrnularios del ejernplo DlgApply en tiempo de ejecucion (un
formulario principal y dos cuadros de dialogo).

El segundo metodo comun a todas las etiquetas es el controlador del evento


OnDoubleClic k . El mitodo LabelDoubleClick selecciona el titulo de la
etiqueta en uso (indicada por el parametro Sender) en el cuadro de lista del
dialogo y despues muestra el cuadro de dialogo modal. Si el usuario cierra el
cuadro de dialogo haciendo clic sobre OK y hay seleccionado un elemento de la
lista, este se copia de nuevo en el titulo de la etiqueta:
procedure TForml.LabelDoubleClick(Sender: TObject);
begin
with ListDial .Listbox1 do
begin
/ / selecciona el nombre actual del cuadro de lista
ItemIndex := Items.IndexOf (Sender as TLabel) .Caption);
/ / muestra el cuadro de didlogo modal, verificando el
valor de retorno
if (ListDial.ShowModa1 = mrOk) and (ItemIndex >= 0 ) then
// copia el elemento seleccionado en la etiqueta
(Sender as TLabel) .Caption : = Items [ItemIndex];
end ;
end;
I TRUCO: Fijese en que todo el clidigo usad~parapgsoqlalizar el.cundro:de,
dialog0 modal se encuentra en el m6todo ~ a b e l ~ o & l iec k~ del
l for-
mula& principal. El formulario de este cuadro de diilogo no tiene cbdigo
aiiadido.

Por el contrario, el cuadro de dialogo no modal tiene mucho codigo en su


interior. El formulario principal simplemente muestra el cuadro de dialogo cuan-
do se pulsa el boton Style (fijese en que el titulo de este boton termina con tres
puntos para indicar que lleva a un cuadro de dialogo), llamando a su metodo
s h o w . El cuadro de dialogo aparece en la anterior figura 7.12.
Existen dos botones, Apply y Close, que sustituyen a 10s botones OK y Can-
cel en un cuadro de dialogo no modal. (El mod0 mas rapido de obtener dichos
botones es seleccionar el valor bkOK o b k C a n c e l para la propiedad K i n d y
despues editar la propiedad C a p t i o n . ) De vez en cuando, puede que vea un
boton Cancel que funciona como un boton Close, pero el boton OK no suele tener
significado en un cuadro de dialogo no modal. En su lugar, uno o mas botones
podrian realizar acciones especificas sobre la ventana principal, como Apply,
Change Style, Replace, Delete y muchos mas.
Si el usuario hace clic sobre una de las casillas de verificacion del cuadro de
dialogo no modal, el estilo del texto de la etiqueta de muestra que esta en la parte
inferior cambia en funcion de la eleccion. Para ello, se aiiade o elimina el indica-
dor especifico que muestra el estilo, como en el siguiente controlador del evento
O n C l i c k:
procedure TStyleDial.ItalicCheckBoxClick(Sender: TObject);
begin
if 1talicCheckBox.Checked then
LabelSample.Font.Style : = LabelSample.Font.Style +
[fsItalic]
else
LabelSarnple.Font.Sty1e : = LabelSarnple.Font.Sty1e -
[ f s I t a l i c ];
end;

Cuando el usuario hace clic sobre el boton Apply, el programa copia el estilo
de la etiqueta de muestra en cada una de las etiquetas del formulario, en lugar de
tener en cuenta 10s valores de las casillas de verificacion:
procedure TStyleDial.ApplyBitBtnClick(Sender: TObject);
begin
Forml.Labell.Font.Style : = LabelSarnple.Font.Sty1e;
Forrnl.Label2.Font.Style : = LabelSample.Font.Style;

Como alternativa, en lugar de hacer referencia directamente a cada etiqueta, se


puede buscar llamando a1 metodo F i n d c o m p o n e n t del formulario, pasando el
nombre de la etiqueta como parametro y convirtiendo despues el resultado al tipo
TLabel.
La ventaja de este enfoque es que se pueden crear 10s nombres de las diversas
etiquetas dentro de un bucle f o r :
p r o c e d u r e TStyleDial.ApplyBitBtnClick(Sender: TObject),
var
I: Integer;
begin
for I : = 1 to 5 d o
(Forml.Findcomponent ( 'Label' + IntToStr (I)) as
TLabel) .Font.Style : = LabelSample.Font.Style;
end;

TRUCOi El metmdb.Appf.yBit~tn~lick tambih jodria haberse es-


crito para.que.aa&ase .fa matriz Controls en un b :le, como en otros

Esta segunda version del codigo es realmente mas lenta, porque tiene que
realizar mas operaciones, per0 la diferencia no se podra percibir porque sigue
siendo muy rapido.
Por supuesto, este enfoque tambien es mas flesible: si se aiiade una nueva
etiqueta, solo se necesita cambiar el limite superior para el bucle f o r , siempre
que todas las etiquetas tengan numeros consecutivos.
Hay que darse cuenta de que cuando el usuario hace clic sobre el boton Apply,
no se cierra el cuadro de dialogo (solo el boton Close tiene este efecto). Hay que
considerar tambih que este cuadro de dialogo no necesita codigo de inicializacion
porque el formulario no se destruye, y sus componentes mantienen su estado cada
vez que se muestra el cuadro de dialogo. Sin embargo, en la version CLX del
programa, QDlgApply, el dialogo es modal, incluso aunque se llame con el meto-
do Show.

Cuadros de dialogo predefinidos


Ademas de crear cuadros de dialogo propios, Delphi permite usar cuadros de
dialogo predefinidos de varios tipos. Algunos estan predefinidos por Windows,
otros son cuadros de dialogo sencillos (corno un cuadro de mensaje) que muestra
una rutina de Delphi.
La Component Palette de Delphi contiene una ficha de componentes de
cuadro de dialogo. Cada uno de esos cuadros de dialogo, conocidos como dialo-
gos comunes de Windows, se define en la biblioteca de sistema ComDlg32.
DLL.
Dialogos comunes de Windows
Ya hemos utilizado algunos de estos cuadros de dialogo en varios ejemplos de
10s capitulos anteriores; asi que probablemente le resulten familiares. Basicamen-
te, es necesario poner el componente correspondiente en un formulario, definir
algunas de sus propiedades, ejecutar el cuadro de dialogo (mediante el metodo
E x e c u t e , que devuelve un valor booleano) y recuperar las propiedades que se
hayan definido mientras se ejecutaba. Vamos a experimentar con estos cuadros de
dialogo gracias a1 ejemplo CommDlgTest. Veamos algunas de las caracteristicas
claves y no obvias de 10s cuadros de dialogo comunes:
El componente OpenDialog: Se puede personalizar definiendo filtros de
diferentes extensiones de archivo, usando la propiedad F i l t e r , que tiene
un editor muy practico y se puede asignar directamente con una cadena
como T e x t F i l e ( * . t x t ) 1 * . t x t . Otra funcionmuy util es l a d e
permitir que el dialogo verifique si la extension del archivo seleccionado se
corresponde con la extension predefinida, utilizando el indicador
o f E x t e n s i o n D i f f e r e n t de la propiedad O p t i o n s tras ejecutar el
dialogo. Por ultimo, este dialogo permite la seleccion multiple, definiendo
su opcion o f A 1 lowMul t i s e l e c t . En este caso se puede obtener la
lista de archivos seleccionados fijandose en la propiedad de lista de cadena
Files.
El componente SaveDialog: Se usa de un mod0 similar y tiene propieda-
des tambien similares, aunque no se pueden seleccionar diversos archivos.
Los componentes OpenPictureDialog y SavePictureDialog: Ofrecen fun-
ciones similares per0 tienen un formulario personalizado, que muestra una
vista previa de una imagen. Por supuesto, solo tiene sentido utilizarlos
para trabajar con archivos graficos o para guardarlos.
El componente FontDialog: se puede usar para mostrar y seleccionar
todos 10s tipos de fuentes, fuentes que se pueden usar tanto en la pantalla
como en la impresora seleccionada (WYSIWYG), o solo fuentes TrueType.
Se puede mostrar u ocultar la parte relacionada con 10s efectos especiales
y obtener diferentes versiones definiendo su propiedad o p t i o n s . Tam-
bien se puede activar un boton Apply ofreciendo sencillamente un contro-
lador de eventos para su evento OnApp l y y usando la opcion
f dApplyBut t o n . Un cuadro de dialogo de fuentes con un boton Apply
(vease la figura 7.13) se comporta casi como un cuadro de dialogo no
modal (pero no lo es).
El componente ColorDialog: Se usa con distintas opciones para mostrar
el dialogo totalmente abierto a1 principio o evitar que se abra por comple-
to. Dichas configuraciones se corresponden a 10s valores c d ~ ul l~ p e n ' o
c d P r e v e n t F u l l O p e n de la propiedad O p t i o n s .
0 Tfehuchet US
0 Tunga
0 Verdana A IE
16

A
Elector - - I r Eismph - I

Figura 7.13. El cuadro de dialogo de selection de fuentes con un boton Apply


(Aplicar).

Los cuadros d e dialogo Find y Replace: Son verdaderos cuadros de dia-


logo no modales, per0 tenemos que implementar la funcionalidad de bus-
queda y reemplazo nosotros mismos, como se ha hecho en parte en el
ejemplo CommDlgTest. El codigo personalizado esta conectado a 10s boto-
nes de 10s dos cuadros de dialogo proporcionando 10s eventos O n F i n d y

--- --
NOTA: Qt ofiece un conjunto similar de cuadros de d i h g o predeibiidos,
pero el conjunto de opciones suele ser mis limitado. La versih Q C o d g
del ejemplo pemite experimentar con estas configuracianaa,El programa
CLX tiene menos elementos de mmu, ya que algunas de opeiones no se
encuentran disponibles. Adern&, hay otros cambioa miaim08 en el M g o
fuente.
- ---- ~ - ~- -~ -

Un desfile de cuadros de mensaje


Los cuadros de mensaje de Delphi jJ 10s cuadros de entrada son otro conjunto
de cuadros de dialogo predefinidos. Se pueden usar muchos procedimientos y
funciones Delphi para mostrar cuadros de dialogo simples:
L a funci6n MessageDlg: Muestra un cuadro de mensaje personalizado,
con uno o mas botones y normalmente un mapa de bits. La funcion
M e s s a g e D l g P o s es similar a la funcion M e s s a g e D l g , per0 el cuadro
de mensaje aparece en una posicion dada, no en el centro de la pantalla (a
no ser que se use la posicion -1,-1 para hacer que aparezca en el centro de
la pantalla).
El procedimiento ShowMessage: Muestra un cuadro de mensaje mas sen-
cillo, con el nombre de la aplicacion como titulo y solo un boton OK. El
procedimiento ShowMessagePos hace lo mismo, per0 tambien se puede
indicar la posicion del cuadro de mensaje. El procedimiento ShowMessa-
ge Fmt es una variation de ShowMes sage, que tiene 10s mismos
parametros que la funcion Format.Corresponde a una llamada a Format
dentro de una llamada a ShowMessage.
El mCtodo MessageBox del objeto Application: Permite especificar el
mensaje y el titulo. Tambien ofrece varios botones y funciones. Esto es un
encapsulado direct0 y sencillo de la funcion de la API de Windows
MessageBox,que pasa como parametro de ventana principal el contro-
lador del objeto Appl i cat ion.Este se necesita para que el cuadro de
mensaje se comporte como una ventana modal.
La funci6n InputBox: Pide a1 usuario que escriba una cadena. Tenemos
que proporcionar un titulo, una consulta y una cadena predeterminada. La
funcion InputQuery tambien pide al usuario que escriba una cadena.
La unica diferencia entre ambas funciones estriba en su sintaxis. La fun-
cion InputQuery tiene un valor de retorno booleano que indica si el
usuario ha hecho clic sobre OK o sobre Cancel.
Para mostrar algunos de 10s cuadros de mensaje disponibles en Delphi, hemos
escrito otro programa de muestra, con un enfoque similar a1 anterior ejemplo
CommDlgTest.
En el ejemplo MBParade, existe una gran cantidad de opciones (botones de
radio, casillas de verificacion, cuadros de edicion y controles de edicion e incre-
mento) para definir antes de hacer clic sobre uno de 10s botones que muestra un
cuadro de mensaje. El ejemplo QMbParade solo carece del boton de ayuda, que
no esta disponible en 10s cuadros de mensaje de CLX.

Cuadros "Acerca de" y pantallas iniciales


Las aplicaciones suelen tener un cuadro "Acerca de", en el que mostramos
informacion, como por ejemplo la version del producto, el copyright, etc. El mod0
mas sencillo de construir uno de estos cuadros es usar la funcion MessageDlg.
Con este metodo, se puede mostrar solo una cantidad limitada de texto y ningun
grafico especial.
Por eso, el metodo habitual para crear un cuadro "Acerca de" es usar un
cuadro de dialogo, como el generado con una de las plantillas predefinidas de
Delphi. En este cuadro "Acerca dew,podria afiadirse algo de codigo para mostrar
informacion sobre el sistema, como la version de Windows o la cantidad de me-
moria libre, u otra informacion de usuario, como el nombre del usuario regis-
trado.
Creacion de una pantalla inicial
Otra tecnica tipica en aplicaciones es mostrar una pantalla inicial antes de que
aparezca el formulario principal. A1 hacer esto se consigue que la aplicacion
parezca responder mejor, porque se muestra algo al usuario mientras que se carga
el programa, y tambien es un efecto visual bastante agradable. En ocasiones esta
misma ventana se muestra como el cuadro "Acerca dewde la aplicacion. Como
ejemplo; de una pantalla inicial especialmente util, hemos creado un programa
que muestra un cuadro de lista con ni~merosprimos.
Los numeros primos se calculan a1 arrancar el programa para que aparezcan
desde el momento en que el formulario se hace visible, mediante un bucle for
que va de 1 a 3 0 - 0 0 0. Como hemos usado (a proposito) una funcion lenta para
calcular numeros primos, este codigo de inicializacion requiere algo de tiempo.
Los numeros se aiiaden a1 cuadro de lista que cubre toda la zona de cliente del
formulario y permite mostrar diversas columnas, como muestra la figura 7.14.

Figura 7.14. El formulario principal del ejemplo Splash, con la pantalla inicial (se
trata de la version Splash2).

Existen tres versiones de este programa (ademas de las tres versiones corres-
pondientes para CLX). A1 ejecutar SplashO, el problema es que para la operacion
inicial, que se realiza en el metodo FormCreate, se emplea mucho tiempo.
Cuando arrancamos el programa, tarda varios segundos en mostrar el formulario
principal. Si el ordenador es muy rapido o muy lento, podemos cambiar el limite
superior del bucle for del metodo FormCreate para que sea mas rapido o mas
lento. Este programa tiene un cuadro de dialogo sencillo con un componente de
imagen, un titulo y un boton de mapa de bits, todo colocado en un panel que ocupa
toda la superficie del cuadro "Acerca de". Este formulario aparece cuando selec-
cionamos la opcion del menu Help>About. Pero lo que queremos en realidad es
mostrar el cuadro "Acerca den mientras arranca el programa. Podemos ver este
efecto a1 ejecutar Splashl y Splash2, que muestran una pantalla inicial mediante
dos tecnicas distintas.
En primer lugar, hemos afiadido un metodo a la clase TAboutBox. Este
metodo, llamado Make S p l a s h, cambia algunas propiedades del formulario para
que la pantalla inicial encaje como formulario de pantalla inicial. Basicamente
elimina el borde y el titulo, oculta el boton OK, hace que el borde del panel sea
grueso (para sustituir el borde del formulario) y, a continuacion, muestra el for-
mulario y lo pinta inmediatamente:
procedure TAboutBox.MakeSplash;
begin
Borderstyle : = bsNone;
BitBtnl-Visible : = False;
Panell.BorderWidth : = 3;
Show;
Update;
end;

A este metodo se llama tras haber creado el formulario en el archivo de pro-


yecto del ejemplo Splashl. Este codigo se ejecuta antes de crear 10s otros formu-
larios (en este caso solo el formulario principal) y entonces se elimina la pantalla
inicial antes de ejecutar la aplicacion. Dichas operaciones suceden en un bloque
t r y/fi na 11y. Veamos el codigo del bloque principal del archivo de proyecto
del ejemplo Splash2:
var
SplashAbout: TAboutBox;

begin
Application.Initialize;

// c r e a y m u e s t r a e l f o r m u l a r i o i n i c i a l
SplashAbout := TAboutBox .Create (Application);
try
SplashAbout.MakeSp1ash;
// c o d i g o e s t d n d a r ...
Application.CreateForm(TForml, Forml);
// e l i m i n a e l f o r m u l a r i o i n i c i a l
SplashAbout.Close;
finally
SplashAbout.Free;
end;

Application.Run;
end.

Esta tecnica solo tiene sentido solo si se tarda en crear el formulario principal
de la aplicacion, para ejecutar su codigo de arranque (como en este caso) o para
abrir tablas de bases de datos. Fijese en que la pantalla inicial es el primer formu-
lario que se crea, per0 como el programa no usa el metodo CreateForm del
objeto Application, este no se transforma en el formulario principal de la
aplicacion. En ese caso: cerrar la pantalla inicial finalizaria el programa.
Un enfoque alternativo es mantener el formulario inicial en pantalla algo mas
de tiempo y utilizar un temporizador para librarse de el. Esta tecnica se utiliza en
el ejemplo Splash2. Este ejemplo tambien utiliza un enfoque distinto para crear el
formulario inicial: en lugar de crear el formulario inicial en el codigo fuente del
proyecto, lo crea a1 comienzo del metodo FormCreate del formulario principal.
procedure TForml. FormCreate ( S e n d e r : T O b j e c t ) ;
var
I: Integer;
SplashAbout: TAboutBox;
begin
// c r e a y m u e s t r a e l f o r m u l a r i o i n i c i a l
SplashAbout : = TAboutBox.Create ( A p p l i c a t i o n ) ;
SplashAbout.MakeSp1ash;
// c o d i g o l e n t o ( o m i t i d o ) . . .
// e l i m i n a e l f o r m u l a r i o i n i c i a l , d e s p u e s d e u n t i e m p o
SplashAbout.Timerl.Enab1ed : = True;
end ;

El temporizador se activa justo antes de finalizar el metodo. Despues de que su


intervalo de tiempo haya pasado (en el ejemplo, tres segundos) se activa el evento
OnTimer y el formulario inicial lo controla cerrandose y destruytndose, me-
diante Close y despues Release.
- - . - -.. ---
- - - - -- .-
- .----~-. ~

NOTA: El mitodo Release de un formulario es similar a1 m M o Free


de 10s objetos, pero la destruction del formulario se retrasa hasta que todos
10s cont*olado;es de eventos haya completado su ejecuci6n. US& Free
dentro
. -. .
de un formularia
-. podria
. -
provocar una violaci6n de acceso,
- - - - .
ya que el
- .
codigo rnterno que disparo el controlador de eventos podria referirse de
nuevo a1 objeto fomulario.

Hay algo mas que solucionar. El formulario principal aparecera mas tarde y
delante del formulario inicial, a menos que lo convirtamos en un formulario fijo
por encima de la pantalla. Por esa razon, hemos aiiadido una linea a1 metodo
Makesplash del cuadro "Acerca de" en el ejemplo Splash2:
Parte II
Arquitecturas
orientadas
a objetos
en Delphi
La arauitectura de
las aplicaciones
Delphi

A pesar de que se han presentado ejemplos de programas Delphi desde el


principio de este libro, estos no han estado centrados en la estructura y arquitec-
tura de las-aplicaciones desarrolladas con las bibliotecas de clases de Delphi. Por
ejemplo, no se ha entrado en profundidad a explicar el objeto global
A p p l i c a t i o n , las tecnicas para realizar el seguimiento del desarrollo de 10s
formularios creados, el flujo de mensajes del sistema, ni otros elementos afines.
En un capitulo anterior se ha descrito como crear aplicaciones con multiples
formularios y cuadros de dialogo, per0 no como se pueden relacionar esos formu-
larios entre si, como se pueden compartir caracteristicas comunes entre formula-
rios, o como trabajar con varios formularios similares de una manera consistente.
El objetivo del presente capitulo es explicar todos estos conceptos, cubriendo
tanto tecnicas basicas como avanzadas, incluyendo la herencia de formularios
visuales, el uso de marcos, y el desarrollo MDI, asi como el uso de interfaces para
construir jerarquias complejas de clases de formularios.
Este capitulo trata 10s siguientes temas:
LosobjetosglobalesApplicationy Screen.
Mensajes y multitarea en Windows.
Procesamiento en segundo plano y multihilo,
Busqueda de las instancias previas de una aplicacion.
Aplicaciones MDI.
Herencia de formularios visuales
Marcos.
Formularios base e interfaces.

El objeto Application
Se ha mencionado el objeto global Application en multiples ocasiones,
per0 dado que este capitulo se centra en la estructura de las aplicaciones Delphi,
pasemos a describir en detalle este objeto global y su clase correspondiente.
App 1icat ion es un objeto global de la clase TApp 1icat ion,definido en la
unidad Forms y creado en la unidad Controls. La clase TApplication es un
componente, per0 no se puede utilizar en tiempo de diseiio. Algunas de sus pro-
piedades pueden definirse directamente en la ficha Application del cuadro de
dialog0 Project Options, otras deben asignarse en el codigo.
Para controlar estos eventos, en cambio, Delphi incluye un componente muy
comodo, App 1icat ionEvent s. Ademas de permitir asignar controladores en
tiempo de diseiio, la ventaja de este componente es que permite usar multiples
controladores. Si situamos una instancia del componente App 1icationEvents
en dos formularios diferentes, cada uno de ellos podra controlar el mismo evento
y se ejecutaran ambos controladores. En otras palabras, multiples componentes
App 1icat io nEve nt s pueden encadenar sus controladores.
Algunos eventos que afectan a toda la aplicacion, como OnAct ivat e,
OnDeactivate, OnMinimize y OnRestore, permiten realizar un segui-
miento del estado de la misma. En el caso de otros eventos, 10s controles que 10s
reciben 10s reenvian a la aplicacion, como en el caso de OnActionExecute,
OnActionUpdate,OnHelp,OnHint,OnShortCut y OnShowHint.Por
ultimo, existe un controlador global de excepciones, 0nEx ception,el evento
O n ~ d l eutilizado para ejecutar procesos en segundo plano y el evento
OnMessage, que se activa siempre que se envia directamente un mensaje a
cualquier ventana o control de ventana de la aplicacion.
Aunque su clase hereda directamente de T C o m p o n e n t , el objeto
Application tiene asociada una ventana. La ventana de la aplicacion esta
oculta per0 aparece en la barra de tareas. Por este motivo Delphi llama Form1 a
la ventana y Pro j e ct 1 a1 icono correspondiente en la barra de tareas.
La ventana relacionada con el objeto Application ( la ventana de la apli-
cacion) sirve para mantener todas las ventanas de una aplicacion juntas. El hecho
de que todos 10s formularios de alto nivel de un programa tengan invisible esta
ventana propietaria resulta fundamental, por ejemplo, cuando se activa la aplica-
cion. De hecho. cuando las ventanas de un programa estan detras de las de otros
programas. a1 hacer clic sobre una ventana de la aplicacion todas las ventanas de
la aplicacion apareceran en primer termino. Es decir, la ventana invisible se utili-
za para conectar 10s diferentes formularios de la aplicacion. En realidad, la venta-
na de la aplicacion no esta oculta; porque eso afectaria a su comportamiento, solo
tiene una altura y anchura nulas y, por lo tanto, no se ve.
-- --, -
TRUCO: En Windows, las operaciones de minimizar y maximizar e s t h
asociadas Dor defect0 con sonidos del sistema y con un efecto visual anima-
producen el sonido y muestran el

Cuando se crea una nueva aplicacion en blanco, Delphi genera un codigo para
el archivo de proyecto. que incluye lo siguiente:
begin
Application.Initialize;
Application.CreateForm(TForml, Forml);
Application.Run;
end.

Como se lfe en este codigo estandar, el objeto Application puede crear


formularios, definiendo el primero como MainForm (una de las propicdades de
Application), y cerrar toda la aplicacion cuando se destruye dicho formula-
rio principal. La ejecucion del programa esta incluida en el mCtodo Run, que
contiene el bucle del sistema que procesa 10s mensajes del sistema. Este bucle
continua hasta que la ventana principal (la ventana creada en primer lugar) esta
cerrada.
-
TRUCO: El formulario principal no es necesariarnente el que se crea en
primer lugar, per0 es el primero que se crea con la 1lamadaApplicat i o n .
C r e a t e Form.

El bucle de mensaje de Windows contenido en el metodo Run envia 10s mensa-


jes del sistema a la ventana de aplicacion adecuada. Cualquier aplicacion Windows
necesita un bucle de mensaje, per0 en Delphi no tenemos que escribirlo porque el
objeto Application ofrece uno predefinido.
Aunque Qta es la funcion principal del objeto Application, tambien controla
otras funciones interesantes:
Las sugerencias.
El sistema de ayuda, que incluye la capacidad de definir el tipo de visor de
ayuda.
La activacion de la aplicacion, su minirnizacion y restauracion.
Un controlador de escepciones global.
Information general sobre la aplicacion, como MainForm. el nombre del
archivo ejecutable y su ruta (ExeName), el icono y el titulo que aparecen
en la barra de tareas de Windows y cuando buscamos en las aplicaciones
en ejecucion con las teclas AIt-Tab.

TRUCO: Para evitar discrepancias entre 10s dos titulos, se puede cambiar
el titulo de la aplicacion en tiempo de disefio. En caso de que cambie en
tiempo de ejecucion, se puede copiar el titulo del formulario a1 de la aplica-
cion con el siguiente codigo: Application.Title := Forml.
Caption.

En la mayoria de las aplicaciones, no tenemos en cuenta la ventana de la


aplicacion, aparte de fijar su titulo e icono y controlar algunos eventos. Sin em-
bargo, podemos realizar operaciones sencillas. Si definimos la propiedad
ShowMainForm como False en el codigo fuente del proyecto, indicamos que
el formulario principal no deberia aparecer a1 arrancar. Dentro de un programa,
en cambio, se puede usar la propiedad MainForm del objeto Application
para accedcr a1 formulario principal.

Mostrar la ventana de la aplicacion


No hay mejor prueba de que esiste una ventana para el objeto Application
que mostrarla, como en el ejeinplo ShowApp. En realidad, no hay que.mostrarla,
sino, simplemente, ajustar su tamaiio y definir una serie de atributos de la venta-
na, como el titulo y el borde. Para ello podemos utilizar las funciones de la API de
Windows en la ventana que indica l a propiedad H a n d l e del objeto
Application:
procedure TForml.ButtonlClick(Sender: TObject);
var
OldStyle: Integer;
begin
// a d a d e un b o r d e y u n t i t u l o a l a v e n t a n a d e l a a p l i c a c i o n
OldStyle : = GetWindowLong (Application.Handle, gwl-Style);
SetWindowLong (Application.Handle, gwl-Style,
OldStyle o r ws-ThickFrame o r ws-Caption);
// d e f i n e e l t a m a d o d e l a v e n t a n a d e l a a p l i c a c i o n
SetWindowPos (Application.Handle, 0, 0, 0, 200, 100,
swp-NoMove o r swp-NoZOrder ) ;
end;

Las dos funciones GetWindowLong y SetWindowLong de la API acce-


den a la informacion del sistema relacionada con la ventana. En este caso, utiliza-
mos el parametro gwl Style para leer o escribir el estilo de la ventana, lo que
incluye su borde, tituloTmenfi de sistema, iconos del borde, etc. El c6digo anterior
obtiene 10s estilos actuales y aiiade (usando una sentencia or) un borde estandar
y un titulo a1 formulario. Generalmente no es necesario implementar algo como
esto en 10s programas. Pero saber que el objeto aplicacion tiene una ventana
conectada a el y que se puede modificar es un aspect0 importante para compren-
der la estructura por defect0 de las aplicaciones Delphi.

Activacion de aplicaciones y formularios


Para mostrar el mod0 en que se activan 10s formularios y las aplicaciones,
hemos creado un ejemplo autoexplicatorio sencillo, llamado ActivApp. Este ejemplo
tiene dos formularios. Cada formulario tiene un componente etiqueta
(Labe1 Form) utilizado para mostrar el estado del formulario. El programa usa
para ello texto y color, como demuestran 10s controladores de 10s eventos
OnAct ivate y OnDeact ivate del primer formulario:
p r o c e d u r e TForml.ForrnActivate(Sender: TObject);
begin
LabelForm.Caption : = 'Form2 Activo ';
LabelForm.Color : = clRed;
end;

p r o c e d u r e TForml.FormDeactivate(Sender: TObject);
begin
LabelForm.Caption : = 'Form2 N o Activo';
LabelForm.Color : = clBtnFace;
end;

El segundo formulario tiene una etiqueta y codigo similares. El formulario


principal tambien muestra el estado de toda la aplicacion. Utiliza un componente
A p p l i c a t i o n E v e n t s para controlar 10s eventos O n A c t i v a t e y
OnDeact ivate del objeto Appl icat ion.Existen dos controladores de eventos
similares a 10s dos anteriores, con la unica diferencia de que modifican el texto y
color de una segunda etiqueta del formulario y que uno de ellos emite un sonido.
A1 ejecutar este programa, veremos si la aplicacion esta activa y, de ser asi,
cual de sus formularios es el activo. Si nos fijamos en el resultado (vease la figura
8.1) y escuchamos el sonido, entenderemos como se desencadena cada uno de 10s
eventos de activacion en Delphi.

Seguimiento de formularios con el objeto Screen


El objeto Screen, cuya clase basica es T S cr ee n,nos ofrece cierta informa-
cion global de interes sobre una aplicacion. Este objeto guarda informacion sobre
la configuracion del sistema (el tamaiio de la pantalla y las fuentes de la pantalla)
y tambien sobre el conjunto actual de formularios de una aplicacion en ejecucion.
Por ejemplo, podemos mostrar el tamaiio de la pantalla y la lista de fuentes si
escribimos:
Label1 .Caption := IntToStr (Screen-Width) + ' x ' + IntToStr
( S c r e e n - H e i g h t );
ListBoxl.Items : = Screen. Fonts;

Figura 8.1. El ejemplo ActivApp muestra si la aplicacion esta activa y cual de sus
formularios esta activo.

Tscreen informa tambien sobre el numero y resolucion de 10s monitores de


un sistema de varios monitores. Sin embargo, nos centraremos en la lista de for-
mularios almacenada por la propiedad Forms del objeto screen,el formulario
superior que indica la propiedad ActiveForm y el evento relacionado
OnActiveFormChange. Observe que 10s forrnularios a 10s que se refiere el
objeto screen son 10s formularios de la aplicacion y no 10s del sistema.
Estas funciones se demuestran mediante el ejemplo Screen, que mantiene una
lista de 10s formularios actuales en un cuadro de lista. Esta deberia actualizarse
cada vez que se crea un nuevo formulario, se destruye un formulario existente o
cambia el formulario activo del programa. Para ver su funcionamiento, se pueden
crear formularios secundarios haciendo clic sobre el boton New:
procedure TMainForm.NewButtonClick(Sender: TObject);
var
NewForm: TSecondForm;
begin
// crea un forrnulario nuevo, define su titulo y lo ejecuta
NewForm := TSecondForm-Create (Self);
Inc (nForms);
NewForm-Caption : = 'Second ' + IntToStr (nForms);
NewForm-Show;
end ;

Hay que tener en cuenta que debe desactivarse la creacion del formulario se-
cundario mediante la pagina Forms del cuadro de dialog0 Project Options.
Una de las partes clave del programa es el controlador del evento oncreate del
formulario, que rellena la lista por primera vez y, a continuacion. conecta un
controlador a1 evento OnAc tiveFormchange:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FillFormsList (Self);
/ / define el contador de formularios secundarios a 0
nForms : = 0;
/ / define un controlador de eventos para el objeto en
pantalla
Screen.0nActiveFormChange : = FillFormsList;
end;

El codigo usado para cubrir el cuadro de lista Forms esta en un segundo


procedimiento, FillFormsList,que tambien esta instalado como controlador
de eventos para el evento OnAct iveFormChange del objeto Screen:
procedure TMainForm. FillFormsList (Sender: TObject) ;
var
I: Integer;
begin
/ / ornite codigo en la fase de destruccidn
if Assigned (FormsListBox) then
begin
FormsLabel.Caption : = 'Forms: ' + IntToStr
(Screen.Formcount) ;
FormsListBox.Clear;
// escribe un nombre de clase y un titulo de forrnulario en
el c~ladrode lista
for I : = 0 to Screen.FormCount - 1 do
FormsListBox. Items .Add (Screen.Forms [I] .ClassName + ' - ' +
Screen.Forms [I] .Caption) ;
ActiveLabel.Caption : = 'Active Form : ' +
Screen.ActiveForm.Caption;
end;
end;

ADVERTENCIA:Resulta muy importante no ejecutar este codigo mien-


tras se destruye el formulario principal. Como alternativa a comprobar que
el cuadro de lista no este definido como nil,tambitn podemos probar el
Component state del formulario para el indicador csDestroying.
Otra tkcnica seria destruir el controlador de eventos OnAc tiveForrn-
Change antes de salir de la aplicacion, es deck, controlar el evento
.
u -
nc; o -
7
s e oer Prormulario
3 - . .1 J . -
principal y asignar n. 1 1 a- s; c_ r
.-
-
n.
e e .-
-

OnActiveFormChange.

El metodo Fill FormsList rellena el cuadro de lista y define un valor para


las dos etiquetas que estan sobre 61 para mostrar el numero de formularios y el
nombre del activo. Cuando hacemos clic sobre el boton New, el programa crea
una instancia del formulario secundario, le aiiade un nuevo titulo y lo muestra. El
cuadro de lista Forms se actualiza automaticamente debido a1 controlador del
evento OnAc t i v e Fo r m C ha nge instalado. En la figura 8.2, aparece el resulta-
do de este programa cuando se han creado diversos formularios secundarios.

Aclive Farn : S e c d 3
MI
Farns: 4
TSecondForm . Second 3
TSecondForm - Second 2
TSecondForrn .Second 1
TMa~nForrn- Sc~eenInfo

I
Figura 8.2. El resultado del ejemplo Screen con algunos formularios secundarios.

Cada uno de 10s formularios secundarios tiene un boton Close que podemos
pulsar para eliminarlos. El programa controla el evento onclose,definiendo el
parametro Action como caFree,de mod0 que en realidad el formulario se
destruye cuando lo cerramos. Este codigo cierra el formulario, per0 no actualiza
la lista de ventanas como es debido. El sistema desplaza primer0 el foco a otra
ventana, activando el evento que actualiza la lista y destruye el antiguo formula-
rio solo despues de dicha operacion.
Una priinera aprosimacion puede plantear que para actualizar la lista de ven-
tanas adecuadamente puede introducirse un retraso, enviando un mensaje de
Windows definido por el usuario. Pero debido a que el mensaje enviado es encola-
do y no es tratado inmediatamente, aunque el envio se realice a1 final de la esis-
tencia del formulario secundario, el formulario principal lo recibira cuando el
otro formulario sea destruido. El truco esta en poder enviar el mensaje con el
controlador del evento OnDestroy del formulario secundario. Para ello, es ne-
cesario referirse al objeto MainForm,aiiadiendo una sentencia uses en la parte
de implernentacion de esta unidad. Hemos enviado un mensaje wm User,contro-
lado por un mCtodo message especifico del formulario como pode-
mos ver a continuacion:
public
procedure Childclosed (var Message: TMessage) ; message
-User;

procedure TMainForm.ChildC1osed (var Message: TMessage);


begin
FillFormsList ( S e l f );
end:

El problema es que si cerramos la ventana principal antes de cerrar 10s formu-


larios secundarios; el formulario principal sigue existiendo, per0 su codigo ya no
se puede ejecutar. Para evitar otro error del sistema, solo se debe enviar el mensa-
je si el formulario principal no se esta cerrando. Para poder determinar si se esta
cerrando se puede aiiadir un indicador a la clase TMainForm y modificar su
valor cuando se cierra el formulario principal, para asi poder probar el indicador
desde el codigo de la ventana secundaria. Esta es una buena solucion, tan buena
que la VCL ya proporciona una funcionalidad similar con la propiedad
Componentstate y su indicador csDestroying,como ya se ha menciona-
do anteriormente. Por tanto, podemos utilizar el siguiente codigo:
procedure T S e c o n d F o r m . F o r r n D e s t r o y (Sender: TObject);
begin
i f not (csDestroying i n MainForm.ComponentState) then
PostMessage (MainForm.Handle, -User, 0, 0) ;
end ;

Con este codigo, el cuadro de lista siempre muestra todos 10s formularios de la
aplicacion. ,

Existe otra alternativa, una solucion mas orientada a Delphi. El truco esta en
considcrar que cada vez que un componente es destruido, avisa a su propietario
acerca del evento llamando a1 metodo Notification definido en la clase
TComponent.Dado que 10s forn~ulariossecundarios son propiedad del formu-
lario principal, como se ha mostrado en el codigo del metodo NewButtonClick,
puede sobrecargarse este metodo y simplificarse el codigo (vease el directorio
Screen2 de 10s ejemplos adjunto para ver el codigo de esta version):
procedure TMainForm.Notification (AComponent: TComponent;
Operation: Toperation) ;
begin
i n h e r i t e d Not if ication (AComponent, Operation) ;
i f (Operation = opRemove) and Showing and (AComponent i s
TForm) then
FillFormList ;
end ;

hacer que 10s formularios secundarios avisaran a1 principal cuando hiran


destruidos. FreeNot i f i c a t i o n tecibe como parametro el componente
del que tiene que notificar que ha sido destruido. Este metodo es utitizado
------l---L-
generalmeme --- I~- - u uesarrwauores
pur -1- ----- 11--1----
s -1-
ue ---- cunecmr curn-
curnpunenies para
ponentes de diferentes formularios o modulos de datos de forma segura.
El ultimo cambio aiiadido a ambas versiones del programa es sencillo: cuando
se hace clic en un elemento del cuadro de dialogo, el formulario correspondiente
se activa mediante el metodo B r i n g T o F r o n t . Pero esta version tiene un peque-
iio fallo, si se hace clic en el cuadro de dialogo cuando el formulario principal no
esta activo, primer0 se activa este y despues se reordena el cuadro de dialogo; de
este mod0 puede ocurrir que seleccionemos un formulario distinto del esperado.
Este error del programa es un ejemplo de 10s riesgos de actualizar informacion
dinamicamente y permitir que el usuario trabaje con ella a1 mismo tiempo.

De eventos a hilos
Para comprender como funcionan las aplicaciones de Windows internamente,
dedicaremos unos momentos a explicar como soporta este entorno la multitarea.
Es necesario comprender tambien, el papel de 10s temporizadores (y el componen-
te T i m e r ) y 10s calculos en segundo plano (o en espera), asi como el metodo
P r o c e s s M e s s a g e s del objeto global A p p l i c a t i o n .
Resumiendo, debemos sumergirnos mas en la estructura orientada a eventos de
Windows y su soporte a la multitarea. Dado que este libro esta dedicado a la
programacion con Delphi no entraremos en detalles sobre este tema, per0 dare-
mos una vision global para aquellos lectores que tengan poca experiencia en
programacion con la API de Windows.

Programacion guiada por eventos


La idea fundamental de la programacion guiada por eventos consiste en que
hay eventos concretos que establecen el flujo de control de la aplicacion. Un
programa emplea la mayor parte del tiempo en esperar a que ocurran dichos
eventos, para ofrecer el codigo que corresponde a cada uno de ellos. Por ejemplo,
cuando un usuario pulsa uno de 10s botones del raton, produce un evento. Enton-
ces, se envia un mensaje que describe el evento a la ventana que en ese momento
esta bajo el cursor del raton. El codigo del programa que responde a 10s eventos
de dicha ventana recibira el evento, lo procesara y respondera segun corresponda.
Cuando el programa ha terminado de responder a1 evento, vuelve a un estado de
espera u "ocioso".
Los eventos, por lo tanto, funcionan en serie: se controla cada evento solo
cuando se ha terminado con el anterior. Cuando una aplicacion esta ejecutando un
codigo de control de eventos (es decir, cuando no esta esperando un evento), 10s
demas eventos de la aplicacion han de esperar en una cola de mensajes reservada
para dicha aplicacion (a menos que la aplicacion utilice varios threads o hilos).
Cuando la aplicacion ha respondido a un mensaje y ha vuelto a su estado de
espera, pasa a1 ultimo lugar en la lista de programas en espera para controlar
mensajes adicionales. En cada version de Win32 ( 9 x , NT, Me y 2000), despues de
que haya pasado un tiempo establecido, el sistema interrumpe la aplicacion actual
e inmediatamente pasa el control a la siguiente en la lista. El primer programa se
reanudara solo despues de que haya pasado el turno de cada aplicacion. A esto se
le llama multitarea no cooperativa.
Asi, una aplicacion que realice una operacion para la que emplee mucho tiem-
po en un controlador de eventos no evita que el sistema funcione correctamente
(porque otros procesos tienen su porcion de tiempo de procesador), per0 normal-
mente no puede pintar ni siquiera de nuevo sus propias ventanas correctamente, lo
cual causa un efecto horroroso. Si no se ha esperimentado este problema, pode-
mos hacer la siguiente prueba: Escribimos un bucle consumidor de tiempo que se
ejecute al hacer clic en un boton; intentarnos mover el formulario o mover otra
ventana encima de el. El efecto es realmente molesto. Si aiiadimos la llamada
Application. ProcessMessages dentro del bucle veremos que la opcra-
cion se vuelve mucho mas lenta, pero el formulario se refresca inmediatamentc.
Como cjemplo del uso de Application. ProcessMessages dentro de
un bucle consumidor de tiempo, podeinos acudir a1 ejemplo BackTask. Este es el
codigo que utiliza esta aproximacion:
procedure T F o r m l . B u t t o n 2 C l i c k ( S e n d e r : TObject);
var
I, Tot: Integer;
begin
Tot : = 0;
f o r I : = 1 t o Max do
begin
i f IsPrime ( I ) then
Tot : = Tot + I;
ProgressBarl.Position : = I * 100 div Max;
App1ication.ProcessMessages;
end;
ShowMessage (IntToStr (Tot)) ;
end;

TRUCO: Existe otra alternativa a la llamada a ProcessMess ages: la


funcion HandleMessaae. Hav dos diferencias: HandleMessaqe pro-
>

cesa un solo mensaje cada vez que es llamada, mientras ProcessMlessa-


ges sigue procesando 10s mensajes de la cola; y HandleMessaqe t ambien
-1 - ------_
_ - ! - -.n-
acrlva el riernpo ocloso
_-A!_.- L!
oe proceso.

Si una aplicacion ha respondido a sus eventos y esta esperando su turno para


procesar mensajes, no tiene oportunidad de recuperar el control hasta que recibe
otro mensaje (a no ser que utilice multiples hilos). Esta es una razon para el uso
de un temporizador, un componente del sistema que envia un mensaje a la aplica-
cion siempre que se cumple un interval0 de tiempo dado. La utilizacion de un
temporizador es la unica manera de hacer que una aplicacion realice operaciones
automaticamente de forma regular, incluso cuando el usuario esta ausente o no
esta usando el programa (y, por lo tanto, no esta procesando ningun evento).
Cuando hablamos de eventos, hay que recordar que 10s eventos de entrada
(generados mediante el raton o el teclado) suponen solo un pequefio porcentaje del
total del flujo de mensajes de una aplicacion Windows. La mayoria de mensajes
son 10s internos del sistema o 10s intercambiados entre diferentes controles y
ventanas. Incluso, una operacion de entrada tan familiar como un clic de raton
puede derivar en un gran numero de mensajes, la mayoria de 10s cuales son men-
sajes internos de Windows. Esto puede comprobarse utilizando la utilidad
W inS ight incluida en Delphi. En Wins ight,elegiremos ver las trazas de 10s
mensajes ( M e s s a g e Trace) y seleccionaremos 10s mensajes de todas las venta-
nas. Haremos clic sobre el boton S t a r t y, despues, realizaremos algunas opera-
ciones con el raton. Se mostraran cientos de mensajes en pocos segundos.

Entrega de mensajes Windows


Antes de ver 10s ejemplos, debemos tener en cuenta otro elemento clave del
control de mensajes. En Windows, hay dos modos de enviar un mensaje a una
ventana:
L a funcion A P I PostMessage: Se usa para colocar un mensaje en la cola
de mensajes de la aplicacion. El mensaje sera controlado solo cuando la
aplicacion tenga oportunidad de acceder a su cola de mensajes (es decir,
cuando reciba el control desde el sistema) y solo despues de que se hayan
procesado 10s mensajes anteriores. Esta llamada es asincrona, dado que no
sabemos cuando se recibe en realidad el mensaje.
L a funci6n API SendMessage: Se usa para ejecutar inmediatamente codi-
go de control de mensajes. SendMessage pasa por alto la cola de men-
sajes de la aplicacion y envia el mensaje directamente a una ventana o
control de destino. Esta llamada es sincrona. Esta funcion tiene incluso un
valor de retorno, que vuelve a pasar mediante el codigo de control de men-
sajes. Llamar a SendMessage no es diferente de llamar directamente
otro metodo o funcion del programa.
La diferencia entre estas dos formas de enviar mensajes es similar a la que
existe entre enviar una carta por correo, que tarde o temprano llega a su destino?
y enviar un fax, que llega inmediatamente a su destinatario. A pesar de que estas
funciones de bajo nivel se utilizaran poco en Delphi, esta descripcion nos ayudara
a determinar cual usar en caso de tener que escribir este tipo de codigo.

Proceso secundario y multitarea


Supongamos que es necesario implementar un algoritmo que emplee un tiempo
considerable. Si escribimos el algoritmo como respuesta a un evento? la aplica-
cion se detendra por completo durante el tiempo que emplee para procesar dicho
algoritmo. Para que el usuario sepa que se esta procesando algo, podemos usar el
cursor en forma de reloj de arena o una barra de progreso, per0 esta solucion no
sera la mejor para el usuario. Win32 permite que otros programas sigan ejecutan-
dose, per0 el programa en cuestion se congelara, ni siquiera actualizara su propia
interfaz de usuario si se solicita que se vuelva a pintar. De hecho, mientras el
algoritmo se esta ejecutando, la aplicacion no podra recibir ni procesar ningun
otro mensaje, como 10s mensajes de representacion.
La solucion mas sencilla a este problema consiste en llamar a 10s metodos
ProcessMessages y HandleMessage descritos anteriormente. El proble-
ma de hacerlo asi esta en que el usuario podria pulsar de nuevo el boton o las
teclas que iniciaron dicho algoritmo. Para solucionarlo, se pueden desactivar 10s
botones y ordenes que no queramos que el usuario seleccione y mostrar el cursor
en forma de reloj de arena (que tecnicamente no evita que tenga lugar un evento de
pulsado del raton, pero si sugiere a1 usuario que deberia esperar antes de realizar
otra operacion).
Para realizar algunos procesos secundarios de baja prioridad, tambien pode-
mos dividir el algoritmo en trozos mas pequeiios para ejecutar cada trozo de uno
en uno, dejando a la aplicacion que responda a todos 10s mensajes pendientes
mientras 10s procesa. Podemos usar un temporizador para hacer que el sistema
nos notifique cuando se ha consumido un interval0 de tiempo. Aunque podemos
usar temporizadores para implementar alguna forma de procesamiento secunda-
rio, esta no es una buena solucion. Seria mejor ejecutar cada paso del programa
cuando el objeto Application recibe el evento OnIdle.La diferencia entre
llamar a ProcessMes sages y usar el evento OnIdle esta en que a1 llamar a
ProcessMessages daremos a1 codigo mas tiempo de procesado que con la
tecnica OnIdle.Llamar a ProcessMessages es un buen mod0 de dejar que
el sistema realice otras operaciones mientras el programa esta procesando. Utili-
zar el evento OnIdle es una forma de dejar que la aplicacion realice las tareas
secundarias cuando no hay solicitudes del usuario pendientes.

Multihilo en Delphi
Cuando es necesario realizar operaciones en segundo plano, o cualquier proce-
so no estrictamente relacionado con la interfaz de usuario, se puede utilizar la
aproximacion mas correcta desde el punto de vista tecnico: crear un hilo de ejecu-
cion separado dentro del propio proceso. La programacion multihilo puede pare-
cer un tema complejo, pero, realmente, no es tan complicado, aunque tenga que
ser considerado cuidadosamente. Es conveniente conocer a1 menos 10s fundamen-
tos de la programacion multihilo porque, en el mundo de 10s sockets y la progra-
macion para Internet, hay pocas cosas que se puedan hacer sin hilos.
La biblioteca RTL de Delphi contiene una clase TThread que permite crear y
controlar hilos. La clase TThread no se utiliza nunca directamente dado que es
una clase abstracta (una clase con un metodo abstracto virtual). Para usar hilos,
sc hereda de T T h r e a d y se utilizan las caracteristicas de esta clase base.
La clase T T h r e a d tiene un constructor con un unico parametro
( C r e a t e s u s p e n d e d ) que permite elegir entre arrancar el hilo inmediatamente
o dejarlo en espera hasta mas tarde. Cuando el objeto hilo arranca automaticamente,
o cuando se reanuda su ejecucion, mantiene el metodo E x e c u t e en funciona-
miento hasta el final. La clase proporciona una interfaz protegida que incluye dos
mctodos basicos para 10s hilos:
procedure Execute:virtual; abstract;
p r o c e d u r e Synchronize(Method: TThreadMethod);

El metodo E x e c u t e declarado como un procedimiento abstracto virtual, debe


ser redefinido en cada hilo. ~ s t contiene
e el c6digo principal del hilo. es decir. el
codigo que habitualmente se utiliza en una funcion de hilo a1 usar las funciones
del sistema.
El metodo S y n c h r o n i z e se utiliza para evitar el acceso concurrente a com-
ponentes VCL. El codigo VCL sc cjecuta dentro del hilo principal del programa,
por ello. es necesario sincronizar el acceso a 10s componentes VCL para cvitar 10s
problemas de reentrada (errores por la reentrada de una funcion antes de comple-
tar una ejecucion previa) y el acceso concurrente a recursos compartidos. El
unico parametro de S y n c h r o n i z e es un metodo sin parametros, normalmente
un mctodo de la misma clase hilo. Dado que no se le pueden pasar parametros a
este metodo, habitualmente se guardan algunos valores entre 10s datos del objeto
hilo en el metodo E x e c u t e y se usan esos valores en 10s metodos sincronizados.

NOTA: Delphi 7 incluye dos nuevas versiones de S y n c h r o n i z e que


permiten sincronizar un metodo con el hi10 principal sin necesidad de lla-
marlo desde el objeto hilo. Am'bos metodos sobrecargados, s y n c h r o n i z e
y S t a t i c s y n c h r o n i z e son metodos de 1 a c l a s e T T h r e a d y requieren
un hilo como parametro.

Otro mod0 de evitar conflictos es utilizar las tecnicas de sincronizacion que


ofrccc cl sistema operativo. La unidad S y n c O b j s define unas pocas clases VCL
para algunos de cstos objetos de sincronizacioi~de bajo nivel, tales como eventos
(con las clases T E v e n t y T S i n g l e E v e n t ) y secciones criticas (con la clase
T C r i t i c a l s e c t i o n ) . (Los eventos de sincronizacion no deben de confundir-
se con 10s cucntos dc Dclphi. dado que ambos conceptos no estan relacionados.)

Un ejemplo con hilos


Para ver un ejemplo de un hilo, puede estudiarse nuevamente el ejemplo
Backstack. Este ejemplo produce un hilo secundario para calcular la suma de 10s
numeros primos. La clase hilo tiene el tipico metodo E x e c u t e , un valor inicial
pasado mediante una propiedad publica (Max), y dos valores internos (FTo t a l
y FPo s i t i o n ) usados para sincronizar la salida de 10s metodos S ho wTo t a 1y
U p d a t e P r o g r e s s . Esta es la declaracion completa para el objeto hilo:

tYPe
TPrimeAdder = c l a s s (TThread)
private
FMax, FTotal, FPosition: Integer;
protected
procedure Execute; override;
procedure ShowTotal;
procedure UpdateProgress;
public
property Max: Integer read FMax write FMax;
end ;

El metodo E x e c u t e es muy similar a1 codigo usado para 10s botones en el


ejemplo Backstack presentado anteriormente. La unica diferencia esta en la lla-
mada final a S y n c h r o n i z e , como puede comprobarse en el siguiente frag-
mento:
procedure TPrimeAdder.Execute;
var
I, Tot: Integer;
begin
Tot : = 0;
f o r I : = 1 t o FMax do
begin
i f IsPrime (I) then
Tot : = Tot + I;
i f I mod (£Max d i v 100) = 0 then
begin
FPosition : = I * 100 d i v £Max;
Synchronize (UpdateProgress) ;
end ;
FTotal : = Tot;
Synchronize (ShowTotal);
end ;

procedure TPrimeAdder.ShowTota1;
begin
ShowMessage ( ' Thread: ' + IntToStr ( FTotal) ) ;
end :

procedure TPrimeAdder-Updateprogress;
begin
Forml.ProgressBar1.Position : = £Position;
end :

El objeto hilo se crea a1 hacer clic sobre un boton y es automaticamente des-


truido cuando termina el metodo E x e c u t e :
procedure TForml.Button3Click(Sender: TObject);
var
AdderThread: TPrimeAdder;
begin
AdderThread : = TPrimeAdder .Create (True);
AdderThread.Max : = Max;
AdderThread.Free0nTerminate : = True;
AdderThread.Resume;
end;

En lugar de fijar el numero maximo utilizando una propiedad, hubiera sido


mejor pasar este valor como un parametro adicional de un constructor hecho a
medida; esto se ha evitado a fin de centrar el ejemplo en el uso del hilo. Se
estudiaran mas ejemplos de hilos en capitulos posteriores.

Verificando si existe una instancia previa


de una aplicacion
Una forma de multitarea consiste en ejecutar dos o mas instancias de la misma
aplicacion. Por lo general, cualquier aplicacion puede ser ejecutada por un usua-
rio en mas de una instancia y es necesario que pueda verificar si ya se ha ejecuta-
do una instancia anterior, para desactivar el comportamiento predefinido y permitir
a lo sumo una instancia. Veremos diversos modos de implementar dicha verifica-
cion.

Buscando una copia de la ventana principal


Para encontrar una copia de la ventana principal de una instancia previa, hay
que usar la funcion API FindWindow y pasarle el nombre de la clase de ventana
(el nombre utilizado para registrar el tipo de ventana del formulario, o WNDCLASS,
en el sistema) y el titulo de la ventana que buscamos. En una aplicacion Delphi, el
nombre de la clase de ventana WNDCLASS es el mismo que el nombre en Pascal
orientado a objetos para la clase del formulario (por ejemplo, TForml). El resul-
tad0 de la funcion FindWindow es o bien un manejador de la ventana o cero (si
no se encuentra ninguna correspondencia).
El codigo principal de la aplicacion Delphi deberia escribirse de tal mod0 que
se ejecute solo si el resultado de FindWindow es cero:
var
Hwnd : THandle ;
begin
Hwnd : = FindWindow ( ' TForml ', nil ) ;
if Hwnd = 0 t h e n
begin
Application.Initialize;
Application.CreateForm(TForm1, Forml);
Application.Run;
end
else
SetForegroundWindow (Hwnd)
end.

Para activar la ventana de la instancia anterior de la aplicacion, se puede usar


la funcion Set ForegroundWindow,que funciona en el caso de ventanas que
poseen otros procesos.
Esta llamada produce su efecto solo si se ha minimizado la ventana pasada
como parametro. De hecho, cuando se minimiza el formulario principal de una
aplicacion Delphi, se oculta y, por esa razon, el codigo de activacion no tiene
efecto.
Desafortunadamente, si se ejecuta un programa que utilice la llamada
Findwindow que acaba de aparecer en el IDE de Delphi, puede que ya exista
una ventana con ese titulo y clase: el formulario en tiempo de diseiio. Por lo tanto,
el programa no arrancara ni siquiera una vez.
Sin embargo, se ejecutara si cerramos el formulario y su archivo de codigo
fuente correspondiente (en realidad, cerrar el formulario oculta sencillamente la
ventana) o si cerramos el proyecto y ejecutamos el programa con el Explorador de
Windows. Tambien debemos considerar que tener un formulario llamado Form 1
puede, probablemente, hacer que no funcione como se espera ya que muchas
aplicaciones Delphi pueden tener un formulario con el mismo nombre. Esto se
corregira en las proximas versiones del codigo.

Uso de un mutex
Un mutex, u objeto de exclusion mutua, es una tecnica totalmente distinta. Es
una tecnica comun de Win32, utilizada normalmente para sincronizar hilos. Aqui,
utilizamos un mutex para sincronizar dos aplicaciones diferentes o, para ser mas
precisos, dos instancias de la misma aplicacion.
Cuando una aplicacion ha creado un mutex con un nombre determinado, puede
probar si el objeto ya lo posee otra aplicacion, llamando a la funcion de la API de
Windows Wait ForSingleOb jec t.De no ser asi, la aplicacion que llama a
esta funcion se transforma en la aplicacion propietaria. Pero si el mutex ya tiene
un propietario, la aplicacion espera hasta que se consume el tiempo (el segundo
parametro de la funcion). Entonces devuelve un codigo de error.
Para implementar esta tecnica, podemos usar el siguiente codigo fuente del
proyecto:
var
hMutex: THandle;
begin
hMutex : = CreateMutex (nil, False, 'OneCopyMutex ' ) ;
if WaitForSingleObject (hMutex, 0 ) <> wait-Timeout then
begin
Application.Initialize;
Application.CreateForm(TForm1, Forml);
Application.Run;
end;
end.

Si ejecutamos dos veces este ejemplo, veremos que se crea una nueva copia
temporal de la aplicacion (aparece el icono en la barra de tareas) y despues se
destruye cuando se ha consumido el tiempo Esta tecnica es realmente mas robusta
que la anterior, per0 tiene un pequeiio problema que esta en activar la instancia
existente de la aplicacion y encontrar su formulario, para lo que podemos emplear
una tecnica mejor.

Buscar en una lista de ventanas


Para buscar una ventana principal concreta del sistema, podemos usar las
funciones Enurnwindows de la API. Las funciones de enumeracion son bastante
peculiares en Windows, porque suelen requerir otra funcion como parametro.
Estas funciones de enumeracion necesitan un punter0 a una funcion (descrita
normalmente como funcion de retrollamada o callback). La idea consiste en que
esta funcion se aplica a cada elemento de la lista (en este caso, la lista de ventanas
principales), hasta que la lista termina o la funcion devuelve F a l s e .
Veamos la funcion de enumeracion del ejemplo Onecopy:
function EnumWndProc (hwnd: THandle; Param: Cardinal): Bool; .
stdcall;
var
ClassName, WinModuleName: string;
WinInstance: THandle;
begin
Result : = True;
SetLength (ClassName, 100) ;
GetClassName (hwnd, PChar (ClassName), Length (ClassName)) ;
ClassName : = PChar (ClassName);
if ClassName = TForml .ClassName then
begin
// o b t i e n e el n o m b r e d e m o d u l o d e la ventana d e d e s t i n o
SetLength (WinModuleName, 200) ;
WinInstance : = GetwindowLong (hwnd, GWL-HINSTANCE) ;
GetModuleFileName (WinInstance,
PChar (WinModuleName), Length (WinModuleName)) ;
WinModuleName := PChar (WinModuleName); / / a j us ta la
1 o n g i t ud
// c o m p a r a 10s nornbres d e m o d u l o
if WinModuleName = ModuleName then
begin
FoundWnd : = Hwnd;
Result : = False; / / d e t i e n e la enumeracion
end;
end;
end;

Esta funcion, a la que se llama para cada ventana no hijo del sistema, verifica
el nombre de cada clase de ventana, buscando el nombre de la clase TForml.
Cuando encuentra una ventana con esta cadena en su nombre de clase, usa
GetModule Fi lename para extraer el nombre del archivo ejecutable de la
aplicacion que pertenece a1 formulario correspondiente. Si el nombre del modulo
corresponde a1 del programa en uso (que se extrajo anteriormente con un codigo
similar), podemos estar casi seguros de que encontraremos una instancia anterior
del mismo programa. Veamos el mod0 en que podemos llamar a la funcion enu-
merada:
var
FoundWnd: THandle;
ModuleName: string;
begin
i f Wait ForSingleObj ect (hMutex, 0) <> wait-Timeout then
- ..
else
begin
/ / o b t i e n e e l n o m b r e d e l m o d u l o en u s o
SetLength (ModuleName, 200) ;
GetModuleFileName (HInstance, PChar (ModuleName), Length
(ModuleName)) ;
ModuleName : = PChar (ModuleName); // a j u s t a l a l o n g i t u d
// b u s c a una v e n t a n a d e una i n s t a n c i a p r e v i a
EnumWindows (@EnumWndProc, 0) ;

Controlar mensajes de ventana definidos


por el usuario
Antes hemos mencionado que la llamada a Set Fo regroundwindow no
funciona si se ha minimizado el formulario principal del programa. Para resolver
este problema, podemos pedir a1 formulario de otra aplicacion, la instancia previa
del mismo programa en este caso, que restaure su formulario principal enviandole
un mensaje de ventana definido por el usuario. A continuacion, se puede compro-
bar si el formulario esta minimizado y enviar un mensaje definido por el usuario a
la ventana anterior.
Veamos el codigo en el programa OneCopy. Esta es una continuacion del
parrafo del apartado anterior:
i f FoundWnd <> 0 then
begin
// rnuestra f i n a l r n e n t e l a v e n t a n a
i f n o t IsWindowVisible (FoundWnd) then
PostMessage (FoundWnd, %User, 0, 0) ;
SetForegroundWindow (FoundWnd);
end:

La funcion P o s tMessage de la API envia un mensaje a la cola de mensajes


dc la aplicacion que posee la ventana de destino. En el codigo del formulario, se
puede aiiadir una funcion especial para controlar dicho mensaje:
public
p r o c e d u r e W M A p p ( v a r msg : TMessage) ;
message C p p ;
p r o c e d u r e TForml .WMApp ( v a r msg : TMessage) ;
begin
Application.Restore;
end;

NOTA: El programa hace uso del mensaje wrn App en lugar de wm U s e r ;


algunas ventanas del sistema usan wm ~ s e r , p o lo
r que no hay gar&ia de
que otras aplicaciones o el sistema no enviarhn este mensaje. Esta es la
razon por la que Microsofi introdujo wm ~ p para p 10s mensajes que estim
limitados a la interpretacibn de la ap1icaci6n.

Creacion de aplicaciones MDI


Una tecnica comun en la estructura de una aplicacion es MDI (Interfaz de
Documento Multiple). Una aplicacion MDI esta compuesta por diversos formula-
rios que aparecen dentro de un unico formulario. Si usamos el bloc de notas de
Windows, solo podemos abrir un documento de texto, porque el bloc de notas no
es una aplicacion MDI. Sin embargo, con el procesador de textos, probablemente
se puedan abrir distintos documentos, cada uno con su propia ventana hijo. por-
que son aplicaciones MDI. Todas estas ventanas de documento se guardan nor-
malmente en una ventana de marco o aplicacion.

NOTA: Microsofi se aleja cada vez mas del modelo MDI utilizado en 10s
dias de Windows 3. Incluso las versiones recientes de Office tienden a
utilizar ventanas principales especificas para cada documento, la tCcnica
clasica SDI (Interfaz de Documento ~ n i c o ) En
. cualquier caso, MDI no
esta muerto y, a veces, puede ser una estructura util.

MDI en Windows: resumen tecnico


La estructura MDI ofrece a 10s programadores varias ventajas automaticas.
Por ejemplo, Windows controla una lista de ventanas hijo en uno de 10s menus de
lista desplegables de una aplicacion MDI y existen metodos especificos de Delphi
que activan la funcionalidad MDI correspondiente, para organizar las ventanas
hijo en forma de mosaic0 o cascada. A continuacion presentamos la estructura
tecnica de una aplicacion MDI en Windows:
La ventana principal de la aplicacion actua como marco o contenedor
Una ventana especial, conocida como el "cliente MDI", cubre toda la zona
de la ventana marco, Este cliente MDI es uno de 10s controles predefinidos
Windows, a1 igual que un cuadro de edicion o un cuadro de lista. La venta-
na cliente MDI carece de cualquier elemento especifico de interfaz de usua-
rio, per0 esta visible. De hecho, se puede cambiar el color de sistema
estandar de la zona de trabajo MDI (denominadaApplication Background)
en la ficha Apariencia del cuadro de dialogo Propiedades de pantalla
de Windows.
Existen diversas ventanas hijo, del mismo tipo o de distintos tipos. Estas
ventanas hijo no se colocan en la ventana marco directamente, sin0 que
cada una se define como un hijo de la ventana cliente MDI, que a su vez es
hijo de la ventana marco.

Ventanas marco y ventanas hijo en Delphi


Delphi facilita el desarrollo de aplicaciones MDI, aunque no se use la plantilla
de aplicacion MDI disponible en Delphi (vease la ficha Applications del
cuadro de dialogo File>New>Other). Solo es necesario crear como minimo dos
formularies, uno con la propiedad Formstyle definida como fsMDIForm y el
otro con la misma propiedad definida como fsMDIC h i1d.Hay que definir estas
dos propiedades en un programa sencillo y ejecutarlo y veremos 10s dos formula-
rios anidados en el tipico estilo MDI.
Sin embargo, por lo general, el formulario hijo no se crea a1 arrancar y sera
necesario ofrecer un mod0 de crear una o mas ventanas hijo. Esto se puede hacer
aiiadiendo un menu con un elemento New y escribiendo el siguiente codigo:
var
ChildForm: TChildForm;
begin
ChildForm : = TChildForm. Create (Application);
ChildForm. Show:

Otra caracteristica importante consiste en aiiadir un menu desplegable Window


y utilizarlo como el valor de la propiedad WindowMenu del formulario. Este
menu desplegable lista automaticamente todas las ventanas hijas disponibles. Po-
demos escoger, por supuesto, cualquier otro nombre para el menu desplegable,
per0 Window es el estandar.
Para que el programa funcione correctamente, podemos aiiadir un numero a1
titulo de cualquier ventana hijo cuando se crea:
procedure TMainForm.NewlClick(Sender: TObject);
var
ChildForm: TChildForm;
begin
WindowMenu := Window1 ;
Inc (Counter);
ChildForm := TChildForm.Create (Self);
ChildForm-Caption : = ChildForm.Caption + ' ' + IntToStr
(Counter);
ChildForm-Show;
end;

Tambien podemos abrir ventanas hijo, minimizar o maximizar cada una de las
mismas, cerrarlas y usar el menu desplegable Window para movernos de unas a
otras. Supongamos ahora que queremos cerrar alguna de estas ventanas hijo, para
despejar el area cliente del programa. Si hacemos clic en las cajas Close de
alguna de estas ventanas, esta se minimiza, en lugar de ocultarse como ocurria
hasta ahora. Los formularios cerrados en Delphi siguen existiendo aunque no
Sean visibles.
En el caso de las ventanas hijo, ocultarlas no funcionara porque el menu MDI
Window y la lista de ventanas continuara mostrando las ventanas hijo existentes,
aunque esten ocultas. Por esta razon, Delphi minimiza las ventanas MDI hijo
cuando intentamos cerrarlas. Para resolverlo, debemos borrarlas cuando son ce-
rradas dando el valor ca Free a1 p a r h e t r o de referencia Act ion del evento
OnClose.

Crear un menu Window completo


Nuestra primera tarea consiste en definir una estructura de menu mejor para el
ejemplo. Normalmente, el menu desplegable Window posee a1 menos tres elemen-
tos, Cascade, Tile y Arrange Icons. Para controlar las ordenes de menu, podemos
usar algunos de 10s metodos predefinidos de T Form que pueden ser usados solo
para marcos MDI:
El metodo Cascade: Organiza en forma de cascada las ventanas hijo MDI
abiertas. Las ventanas se solapan entre si. Tambien se organizan las venta-
nas hijo representadas por iconos (vease Arrange I cons).
El metodo Tile: Organiza en forma de mosaic0 las ventanas hijo MDI
abiertas. Los formularios hijo se organizan de tal mod0 que no se solapen.
El comportamiento predefinido es una organizacion horizontal, aunque si
tenemos muchas ventanas hijo, se organizaran en varias columnas. Esta
configuracion predefinida se puede modificar utilizando la propiedad
TileMode (dandole un valor tbHorizonta1 o tbvertical).
El procedimiento ArrangeIcons: Organiza todas las ventanas hijo reprc-
sentadas por iconos. Los formularios que estan abiertos no se mueven.
Como una alternativa mejor a la llamada a estos metodos, sc puede colocar un
A c t i o n L i s t en el formulario y aiiadirlo a una serie de acciones MDI
predefinidas. Las clases relacionadas son: TWi ndowArrange, TWi ndowCas -
cade, TWindowClose,TWindowTileHorizonta1,TWindowTile-
Vertical y TWindowMinimizeAll. Los elementos del menu conectados
realizaran las acciones correspondientes y se desactivaran si no existe ninguna
ventana hijo disponible. El ejemplo MdiDemo, que veremos a continuacion, de-
muestra el uso de acciones MDI, entre otras cosas.
Tambien existen otros metodos y propiedades interesantes relacionadas estric-
tamente con MDI en Delphi:
ActiveMDIChild: Es una propiedad solo de lectura en tiempo dc ejecu-
cion del formulario marco MDI y aloja a la ventana hijo activa. El usuario
puede cambiar este valor seleccionando una nueva ventana hijo o el pro-
grama puede cambiarlo usando 10s procedimientos Next y Previous, que
activan la ventana hijo siguiente o anterior a la activa en ese momento.
La propiedad CIientHandle: Aloja el controlador Windows de la ventana
cliente MDI, que cubre la zona de cliente del formulario principal.
La propiedad MDIChildren: Es una matriz de ventanas hijo. Podemos
usar esta y la propiedad MDIChildCount para movernos por todas las
ventanas hijo. Esto puede resultar util para encontrar una ventana hijo
concreta o trabajar en una dc ellas.
Fijese en que el orden interno de las ventanas hijo es el inverso a1 ordcn de
activacion. Esto significa que la ultima ventana seleccionada es la ventana activa
(la primera en la lista interna), la penultima ventana hijo seleccionada es la segun-
da y la primera ventana hijo seleccionada es la ultima. Este orden establece el
mod0 en que se organizan las ventanas en pantalla. La primera de la lista esta
encima del resto, mientras la ultima esta deba-jo de todas y, probablemente, ocul-
ta. Podemos imaginarlo como un eje (el eje z ) saliendo de la pantalla hacia noso-
tros.
La ventana activa tiene un valor mayor para la coordenada z y, por tanto,
cubrc cl resto de ventanas. Por ello, a1 esquema de ordenacion de Windows se le
conoce como z-order.
- -- ---
NOTA: El menu Window puede manejarse con ActionManager y con el
control de menu ActionMainMenuBar, a partir de Delphi 7. Este control
tiene un propiedad especifica, ~ i n d o w ~ e n que
u , debemos usar para es-
pecificar el menG que listara las ventanas hijo MDI. ,:
El ejemplo MdiDemo
Hemos creado un primer ejemplo para demostrar la mayoria de las funciones
de una aplicacion sencilla MDI. MdiDemo es en realidad un editor de testos MDI
completo, porque cada ventana hijo aloja un componente Memo y puede abrir y
guardar archivos de testo. El formulario hijo tiene una propiedad Modified
utilizada para indicar si el texto del memo ha cambiado (esta definido como True
en el controlador del evento OnChange del memo). Modified esta definida
como False en 10s metodos personalizados Save y Load y se verifica cuando
se cierra el formulario (sugiriendo que se guarde el archivo).
Como ya hemos dicho, el formulario principal del ejemplo esta basado en un
componente ActionList. Las acciones del ejemplo estan disponibles mediante al-
gunos elementos de menu y en una barra de herramientas, como muestra la figura
8 . 3 . Para conocer 10s detalles del ActionList podemos estudiar el codigo fuente
del ejemplo; aqui nos centraremos en el codigo de las acciones.

o lnleresanle
lgo Inlelesante
lgo Intelesante
lgo lntelesanle
lyo ~nteleranle
h o ~nlererante

Figura 8.3. El prograrna MdiDemo hace uso de una serie de acciones Delphi
predefinidas conectadas a un menu y una barra de herrarnientas.

Una de las acciones mas sencillas es el objeto ActionFont, que tiene un


controlador OnExecute, que usa un componente FontDialog, y un controla-
dor Onupdate, que desactiva la accion (y; por lo tanto, el elemento de menu
asociado y boton de la barra de herramientas) cuando no hay formularios hijo:
procedure TMainForm.ActionFontExecute(Sender: TObject);
begin
if FontDialog1.Execute then
(ActiveMDIChild as TChildForm) .Memol. Font :=
FontDialogl.Font;
end;
procedure TMainForm.ActionFontUpdate(Sender: TObject);
begin
ActionFont.Enabled : = MDIChildCount > 0;
end;

La accion denominada New crea el formulario hijo y define un nombre de


archivo predefinido. La accion Open llama a1 metodo Act ionNewExcecute
antes de cargar el archivo:
procedure TMainForm.ActionNewExecute(Sender: TObject);
var
ChildForm: TChildForm;
begin
Inc (Counter);
ChildForm : = TChildForm.Create (Self);
ChildForm.Caption : =
Lowercase (ExtractFilePath (App1ication.Exename)) +
'texto' +
IntToStr (Counter) + ' . t x t ';
ChildForm.Show;
end;

procedure TMainForm.ActionOpenExecute(Sender: TObject);


begin
if 0penDialogl.Execute then
begin
ActionNewExecute (Self);
(ActiveMDIChild as TChildForm).Load (OpenDialogl-FileName);
end;
end ;

En realidad, el archivo se carga mediante el metodo Load del formulario. De


igual modo, el metodo save del formulario hijo lo usan las acciones save y
Save As.Fijese en que el controlador OnUpdate de la accion Save activa la
accion solo si el usuario ha cambiado el texto del memo:
procedure TMainForm.ActionSaveAsExecute(Sender: TObject);
begin
// s u g i e r e e l nombre d e l a r c h i v o a c t u a l
SaveDialogl.FileName : = ActiveMDIChild.Caption;
i f SaveDialogl.Execute then
begin
// m o d i f i c a e l n o m b r e d e l a r c h i v o y g u a r d a
ActiveMD1Child.Caption : = SaveDialogl.Fi1eName;
(ActiveMDIChild as TChildForm) .Save;
end;
end;

procedure TMainForm.ActionSaveUpdate(Sender: TObject);


begin
Actionsave. Enabled := (MDIChildCount > 0) and
(ActiveMDIChild as TChildForm) .Modified;
end;
procedure TMainForm.ActionSaveExecute(Sender: TObject);
begin
(ActiveMDIChild as TChildForm) .Save;
end;

Aplicaciones MDI con distintas ventanas hijo


En las aplicaciones MDI complejas es frecuente incluir ventanas hijo de dife-
rentes tipos (es decir, basadas en diferentes formularios hijo). Hemos creado un
nuevo ejemplo, llamado MdiMulti, para resaltar algunos problemas de dicha tec-
nica. En el cjemplo, hay dos tipos de formularios, el primer0 aloja un circulo
dibujado en la posicion dcl ultimo clic del raton, mientras que el segundo conten-
dra un gran cuadrado. TambiCn hemos aiiadido a1 formulario principal un fondo
pcrsonalizado, obtenido a1 pintar una imagen en forma de mosaic0 sobre el.

Formularios hijo y mezcla de menus


El primer tipo de formulario hijo muestra un circulo en la posicion en la que el
usuario ha hecho clic en uno de 10s botones el raton. La figura 8.4 muestra un
cjemplo dcl rcsultado del programa MdiMulti. El programa incluye un menu Circle,
que permite a1 usuario cambiar el color de la superficie del circulo y el color y
tamaiio de su borde. En este caso, resulta interesante que para programar el for-
mulario hijo, no sea necesario considerar la existencia de otros formularios ni de
la ventana marco. Simplemente, escribimos el codigo del formulario y ya esta.
Solo se nccesita tener especial cuidado con 10s menus de 10s dos formularios.

Figura 8.4. El resultado del ejemplo MdiMulti, con una ventana hijo que rnuestra
10s circulos.
Si preparamos un menu principal para el formulario hijo, sustituira a1 menu
principal de la ventana marco cuando se active el formulario hijo. De hecho, una
ventana MDI hijo no puede tener un menu propio. Pero el hecho de que una
ventana hijo no pueda tener menus no deberia preocuparnos porque este es el
comportamiento estandar de las aplicaciones MDI. Podemos usar la barra de
menu de la ventana marco para mostrar 10s menus de la ventana hijo. Una tecnica
mejor es mezclar la barra de menu de la ventana marco y la del formulario hijo.
Por ejemplo, en este programa, el menu del formulario hijo puede colocarse entre
el marco de 10s menus desplegables File y Window de la ventana. Para ello se
usan 10s siguientes valores GroupIndex:
Menu desplegable File, formulario principal: 1.
Menu desplegable Circle, formulario hijo: 2.
Menu desplegable Window, formulario principal: 3 .
A1 usar estas definiciones para 10s indices de grupo del menu, la barra de menu
de la ventana marco tendra dos o tres menus desplegables. A1 arrancar, la barra
de menu tiene dos menus. Desde el momento en que creamos una ventana hijo,
existen tres menus y cuando se cierra la ultima ventana hijo, el menu desplegable
Circle desaparece.
El segundo tip0 de formulario hijo muestra una imagen en movimiento. El
cuadrado, un componente Shape,se mueve por la zona de cliente del formulario
a intervalos fijos de tiempo, utilizando un componente Timer y rebota a1 chocar
contra 10s extremos del formulario, cambiando su direccion. Este proceso de cam-
bio utiliza un complejo algoritmo que no examinaremos aqui, ya que el objetivo
principal del ejemplo es mostrar como se comporta la combinacion de menus
cuando tenemos un marco MDI con formularios hijo de diferentes tipos. (Queda
en manos del lector examinar el codigo fuente para ver como funciona).

El formulario principal
Tenemos que integrar ahora 10s dos formularios hijo en una aplicacion MDI.
El menu desplegable File tiene dos elementos de menu New aparte, que se usan
para crear una ventana hijo de cualquier tipo. El codigo usa un solo contador de
ventanas hijo. Como alternativa, podriamos usar dos tipos diferentes de contado-
res para 10s dos tipos de ventanas hijo. El menu Window usa las acciones MDI
predefinidas. Desde el momento en que un formulario de este tip0 aparece en
pantalla, su barra de menu se mezcla automaticamente con la barra de menu
principal. Cuando seleccionamos un formulario hijo de uno de 10s dos tipos, la
barra de menu cambia de acuerdo con ello. Cuando se hayan cerrado todas las
ventanas hijo, se configura de nuevo la barra de menu original del formulario
principal. A1 usar indices de menu adecuados, permitimos que Delphi lo haga
todo de forma automatica, como muestra la figura 8.5.
Figura 8.5. La barra de menu de la aplicacion MdiMulti cambia de forrna autornatica
para reflejar la ventana hijo seleccionada, corno puede verse cornparando la barra de
menu con la de la figura 8.4.

Se han aiiadido unos pocos elementos a1 menu del formulario principal para
cerrar todas las ventanas hijo y mostrar algunas estadisticas sobre ellas. El meto-
do relacionado con el comando C o u n t analiza la propiedad MDIChildren
para contar el numero de ventanas hijo de cada tipo (usando el operador RTTI
is):
for I : = 0 t o MDIChildCount - 1 do
if MDIChildren is TBounceChildForm then
Inc (NBounce);
else
Inc (NCircle) ;

Subclasificacion de la ventana MdiClient


El programa de ejemplo incluye tambi6n soporte para una imagen de fondo en
forma de mosaico. El mapa de bits se toma de un componente Image y deberia de
pintarse en el formulario con el controlador de mensajes de Windows
wm E r a s e B k g n d . El problema es que no podemos conectar sencillamente el
c6digo a1 formulario principal, porque una ventana aparte, MdiClient, cubre su
superficie.
No tenemos un formulario Delphi correspondiente a esta ventana, por lo que
para controlar sus mensajes, debemos recurrir a una tecnica de programacion de
ba.jo nivel conocida corno subclasificacion (aparte de por el nombre, no tiene nada
que ver con la herencia de la programacion orientada a objetos). La idea basica es
que podemos sustituir el procedimiento de ventana, que recibe todos 10s mensajes
de la ventana, por otro. Esto se realiza llamando a la funcion SetwindowLong
de la API y ofreciendo la direccion de memoria del procedimiento, el punter0 de
funcion.
NOTA: Un procedimiento de ventana es una funcion que recibe todos 10s
mensajes de una ventana. Cada mensaje habrh de tener un procedimiento de
ventana, solo uno. hcluso 10s formularios Delphi tienen un procedimiento
de ventanas, aunque estA oculto en el sistema. ~ s t llama
e a la funcion vir-
tual WndProc, que podemos usar. Pero la VCL tiene un mod0 de control
.-la ,
:
,
,, ,,A,C,:.-l, ,.., ,
, .:.,, . I,, ,&+,A, A, ....-...+-,l A, ,.,.
UG I l l G l l D t l J G 3 ~ l G U G l L l l l U U ,r j U G 36 I G G I I V l 4 4 I V 3 IILGLUUUD U G b V U L I U I U G l l L G l l D 4 '

jes de un formulario despuks de cierto procesado previo. Con todo este


soporte, es necesario controlar 10s procedimientos de ventana explicitamen-
te solo cuando se trabaje con ventanas que no s e a de Delphi, como en este
caso.

A mcnos que tengamos una razon para cambiar el comportamiento que tiene
por defecto esta ventana de sistema, podemos guardar cl procedimiento original y
llamarlo para obtener el procesamiento por defecto. Los dos procedimientos (vie-
jo y nuevo) a 10s que se refieren 10s dos punteros de funcion se guardan en dos
campos locales del formulario:
private
OldWinProc, NewWinProc: Pointer;
procedure NewWinProcedure (var Msg: TMessage) ;

El formulario tiene tambien un metodo que usaremos como un nuevo procedi-


miento de ventana, con el codigo real usado para pintar en el fondo de la ventana.
Dado que este es un metodo y no un procedimiento de ventana normal, el progra-
ma ha de llamar a1 metodo MakeOb j ectInstance para aiiadir un prefijo al
metodo y dejar que el sistema lo use como si fuera una funcion. Toda esta descrip-
cion se resume en dos sentencias complejas:
procedure TMainForm. Formcreate (Sender: TObject) ;
begin
NewWinProc : = MakeObjectInstance (NewWinProcedure);
OldWinProc : = Pointer (SetwindowLong (ClientHandle,
gwl-WndProc, Cardinal
(NewWinProc)) ) ;
Outcanvas := TCanvas .Create;
end:

El procedimiento de ventana que instalamos llama al procedimiento predefinido.


A continuacion, si el mensaje es wm-EraseBkgnd y la imagen no esta en blan-
co, la dibujamos en pantalla varias veces usando el metodo D r a w de un lienzo
temporal. Dicho objeto lienzo se crea cuando el programa arranca (vease el codi-
go anterior) y se conecta al controlador que el mensaje pasa como parametro
wParam. Con esta tecnica, no tenemos que crear un nuevo objeto T C a n v a s
para cada operacion de pintado del fondo solicitada, asi se ahorra un cierto tiem-
po en esta frecuente operacion. Veamos el codigo, que produce la salida que
aparece en la figura 8.5:
p r o c e d u r e TMainForm.NewWinProcedure (var Msg: TMessage);
var
BmpWidth, BmpHeight: Integer;
I, J: Integer;
begin
/ / p r o c e s a d o predefinido p r i m e r 0
Msg.Result : = CallWindowProc (OldWinProc, ClientHandle,
Msg.Msg, Msg.wParam,
M s g - l P a r a m );

/ / controla el pintado d e fondo


i f Msg.Msg = ~ E r a s e B k g n dt h e n
begin
BmpWidth : = MainForm.1magel.Width;
BmpHeight : = MainForm.Image1.Height;
i f (BmpWidth <> 0 ) a n d (BmpHeiyht <> 0 ) then
begin
0utCanvas.Handle : = Msg.wParam;
f o r I : = 0 t o MainForm.ClientWidth d i v BmpWidth d o
f o r J : = 0 t o MainForm.ClientHeight d i v BmpHeight d o
0utCanvas.Draw (I * BmpWidth, J * BmpHeight,
MainForm.Imagel.Pictu~e.Graphic);
end;
end;
end;

Herencia de formularios visuales


Cuando sea necesario crear dos o mas formularios similares, tal vez con dife-
rentes controladores de eventos, se pueden usar tecnicas dinamicas, ocultar o
crear componentes nuevos en tiempo de ejecucion, cambiar controladores de eventos
y utilizar sentencias i f o c a s e . Tambien se pueden aplicar tecnicas orientadas a
objetos, gracias a la herencia de formularios visuales. Asi, en vez de crear un
formulario basado en T F o r m , podemos heredar un formulario de uno existente,
aiiadiendole nuevo componentes o cambiando las propiedades de 10s existentes.
La ventaja real de esta tecnica de herencia de formularios visuales depende en
gran medida del tip0 de aplicacion que se Cree. Si tiene diversos formularios,
algunos de ellos muy similares'o solo con elementos comunes, se pueden colocar
componentes comunes y controladores de eventos comunes en el formulario base
y aiiadir el comportamiento especifico y 10s componentes a las subclases. Por
ejemplo, si preparamos un formulario padre estandar con una barra de herramien-
tas, un logotipo, codigo predefinido de ajuste del tamaiio y de cierre y 10s
controladores de algunos mensajes Windows, podemos usarlo como clase padre
de cada uno de 10s formularios de la aplicacion.
Tambien se puede usar herencia de formularios visuales para personalizar una
aplicacion para clientes diferentes, sin duplicar el codigo fuente ni el codigo de
definicion del formulario (se hereda la version especifica para un cliente de 10s
formularios estandar). La principal ventaja de la herencia visual es que mas ade-
lante puede modificarse el formulario original y actualizar automaticamente to-
dos 10s formularios derivados. Esta es una de las ventajas de la herencia en 10s
lenguajes de programacion orientados a objctos. Pero existe un efecto colateral
muy beneficioso: el polimorfismo. Podemos aiiadir un metodo virtual en un for-
mulario base y sobrecargarlo en un formulario heredado para despucs referirnos a
ambos formularios y poder llamar a ese metodo en cada uno de cllos.
- i

NOTA: Delphi incluye otra funcion, 10s marcos, que imita la herencia de
formularios visuales. En arnbos casos, se puede trabajar en tiempo de dise-
iio en las dos versiones de un formulario/marco. Sin embargo, en la heren-
cia de formularios visuales, definimos dos clases distintas (padre y derivada),
mientras que con 10s marcos, trabajamos en una clase y en una instancia.
Los marcos se trataran mas adelante en este capitulo.

Herencia de un formulario base


Las normas sobre herencia de formularios visuales son bastante sencillas, si se
tiene claro lo que es la herencia. Fundamentalmente, un formulario subclase tiene
10s mismos componentes quc el formulario padre asi como otros componentes
nuevos. No podemos eliminar un componente de la clasc basica, aunque podemos
haccrlo invisible (si es un control visual). Lo importante es que podemos cambiar
facilmcntc las propiedades de 10s componentes h,eredados.
Fijese cn quc si cambiamos una propiedad de un componcnte en un formulario
heredado, cualquier modificacion dc la misma propiedad cn cl formulario padre
no tendra cfccto alguno. Si cambiamos otl-as propiedades del componente afecta-
ran tambicn a las versiones heredadas. Podemos sincronizar de nuevo 10s dos
valores de la propiedad utilizando la orden Revert t o I n h e r i t e d del mcnu
local del Object Inspector. Al definir las dos propicdades con el mismo valor y
compilar de nuevo el codigo se consigue lo mismo. Tras modificar diversas pro-
picdades, podemos sincronizarlas de nuevo con la version basica aplicando la
orden R e v e r t t o I n h e r i t e d del menu local del componente.
Ademas de heredar componentes, el formulario nuevo hereda todos 10s meto-
dos del formulario base, incluyendo 10s controladores de eventos. Podemos aiiadir
controladores nuevos en el formulario heredados y sobrescribir tambicn 10s cxis-
tentes.
Para describir el mod0 en que funciona esta tecnica, hemos crcado un cjemplo
llamado VFI. Para crcarlo, primero, hay quc iniciar un nuevo proyecto y aiiadir
cuatro botones a su formulario principal. A continuacion, seleccionar
File>New>Other y escoger la pagina con el nombre del proyccto en el cuadro de
dialog0 New Items (vease la figura 8.6).
I I I
Data Modules InIraweb WtbSs~v~ces8-ss IWebsnap ) Web Docurnenlo (
I I I I I 1
New

a a Acb&

Form2
Mukltler Vfl F m Dialogs Rolccts

r 6 !nhrit r

Figura 8.6. El cuadro de dialogo New Items permite crear un formulario heredado

En cl dialogo New Items, se puede escoger el formulario del que se quiere


heredar. El nucvo formulario tiene 10s mismos cuatro botones. Veamos la des-
cripcion textual inicial del nuevo formulario:
inherited Form2: TForm2
Caption = 'Form2 '
end

Esta es la declaration dc clase inicial, en la que vemos que la clase basica no


es la habitual T F o r m sino el formulario de clase basica actual:
type
TForm2 = class (TForml)
private
{ Declaraciones privadas }
public
{ D e c l a r a c i o n e s pLiblicas )
end;

Fijese en la presencia de la palabra clave inherited en la descripcion tex-


tual y tambien en que el formulario tiene algunos componentes, aunque estan
definidos en el formulario de clase basico. Si movemos el formulario y aAadimos
el titulo de uno de 10s botones, la descripcion textual cambia de acuerdo con
dichos cambios:
inherited Form2 : TForm2
Left = 313
Top = 202
Caption = 'Form2'
inherited Button2: TButton
Caption = ' B e e p . . . '
end
end
Solo se listan las propiedades con un valor diferente (por lo que si quitamos
estas propiedades de la descripcion textual del formulario heredado podemos de-
jarlas con 10s valores del formulario base). Como muestra la figura 8.7, hemos
cambiado el titulo de la mayoria de 10s botones.

Figura 8.7. Los dos formularios del ejemplo VFI en tiempo d e ejecucion

Cada uno de 10s botones del primer formulario tiene un controlador OnClic k .
El primer boton muestra el formulario heredado llamando a su metodo Show,el
segundo y tercer boton llaman a1 procedimiento Beep y el ultimo boton muestra
un mensaje sencillo.
En el formulario heredado, primer0 deberiamos eliminar el boton Show, por-
que el formulario secundario ya esta visible. Sin embargo; no podemos borrar un
componente de un formulario heredado. Podemos dejar el componente, per0 defi-
nir su propiedad Visible como False (el boton seguira estando ahi per0 no
sera visible). Los otros tres botones estaran visibles per0 con distintos
controladores. Esto es sencillo de conseguir. Si seleccionamos el evento OnClic k
de un boton en el formulario heredado (haciendo doble clic en el), obtendremos un
metodo ligeramente diferente a1 predefinido; porque incluye la palabra clave
inherited.Esta palabra clave representa m a llamada a1 controlador de even-
tos correspondiente. Esta palabra clave siempre la aiiade Delphi, incluso si el
controlador no se define en la clase padre o si el componente no esta presente en la
clase. Es sencillo ejecutar el codigo del formulario base y realizar algunas opera-
ciones mas:
procedure TForm2.Button2Click(Sender: TObject);
begin
inherited;
ShowMessage ( 'Hi' ) ;
end;

Esta no es la unica posibilidad. Tambien podemos crear un nuevo controlador


de eventos y no ejecutar el codigo de la clase base, como hemos hecho con el
tercer boton del ejemplo VFI: para ello, solo debemos quitar la palabra clave
inherited.
Otra posibilidad consiste en llamar a un metodo de una clase base despues de
ejecutar algun codigo a medida, llamandolo cuando se cumple una condicion o
llamando a1 controlador de otro evento de la clase base, como hemos hecho con el
cuarto boton:
procedure TForm2.ButtonrlClick(Sender: TObject);
begin
inherited Button3Click ( S e n d e r ) ;
inherited;
end;

No es habitual heredar de un controlador diferente, per0 debemos tener en


cucnta quc cs posible. Podemos. por supuesto, considerar cada metodo del formu-
lario basc como un metodo de nuestro formulario, para asi llamarlo libremente.
Este ejcmplo nos permite explorar algunas caracteristicas de la herencia de for-
mularios visuales, pero para comprobar su valia debemos pensar en ejemplos del
mundo real, que son mas complejos de lo que este libro puede tratar. Ahora;
cstudiaremos cl polimorfismo de formularios visuales.
- -

NOTA: La herencia de formularios visuales no funciona muy bien con


colecciones. n n nnrlemnc extender iinn nrnnierlnrl rle cnlecciirn rle Iin cnm-
ponente en I
uso de serie!
les de lista con detalles. Estos componentes pueden usarse tanto en el tor-
mulario padre como en el heredado, por supuesto, per0 no podemos extender
10s elementos que contienen, ya que estan almacenados en una coleccion.
TT-,.,
A
:
,
.I
, ,
c, ,
I
L,
- ,,&A ,, ,.:,., ,A,:
I,,,,:,, A, ,
.c
, ,:,,,I,..
I I ~ cu C V I F ~ Ila a a q p a w u u uc csrira GU~CGGIU-
ulla SUIUGIUIIa CSLC ~ I U U I C I CSW
nes en tiempo de diseiio para, en su lugar, hacerlo en tiempo de ejecucion.
Seguiriamos usando la herencia de formularios per0 perderiamos la parte
visual de esta. Si intentamos usar el componente ActionManager, des-
cubriremos que ni siquiera podemos heredar de un formulario que lo con-
tenga. Borland deshabilito esta caracteristica porque podria causar
demasiados problemas.

Formularios polimorficos
Si quercmos afiadir un controlador de eventos a1 formulario y despues conver-
tirlo en formulario heredado, no hay forma de referirse a 10s dos metodos que
usan una variable comun de la clase basica; porque 10s controladores de eventos
usan por defect0 enlace estatico.
Veamos un ejemplo: queremos crear un formulario visor de mapas de bits y un
visor de texto en el mismo programa. Los dos formularios tienen elementos simi-
larcs, una barra de herramientas similar, un menii similar, un componente
OpenDialog y componentes diferentes para ver 10s datos. Por lo tanto, decidimos
crear un formulario de clase basica que contenga 10s elementos comunes y herede
10s dos formularios de el. En la figura 8.8, podemos ver 10s tres formularios en
tiempo de diseiio.
Figura 8.8. El forrnulario d e clase basica y 10s dos forrnularios heredados del
ejernplo PoliForrn.

El formulario principal contiene un panel de barra de herramicntas con algu-


nos botones (las barras de herramientas reales tienen problemas con la hcrcncia
de formularios visuales); un menu y un componente dc dialog0 abierto. Los dos
formularios heredados tienen solo diferencias mcnores, per0 presentan un nuevo
componente, o un visor de imageries ( T I m a g e ) o un visor de testo (TMemo).
Tambien modifican la configuration del componente O p e n D i a l o g , para refe-
rirse a diferentes tipos dc archivos.
El formulario principal incluye algo de codigo comun. El boton Close y la
orden File,>Close llaman a1 mdtodo c l o s e del formulario. La orden Help>About
mucstra un cuadro de mensa.je sencillo. El boton Load del formulario base tiene
unicamente una llamada a S h o w M e s s a g e para mostrar un mensa.je de error. La
ordcn File>Load en cambio llama a otro mttodo:
procedure TViewerForm.LoadlClick(Sender: T O b j e c t ) ;
begin
LoadFile;
end;

Este metodo se define en la clase T V i e w e r Form como un metodo abstracto


(de forma que la clase del formulario base es en realidad una clase abstracts).
Dado que este es un metodo abstracto, sera necesario redefinirlo (y sobrescribirlo)
en 10s formularios heredados.
El codigo de este metodo L o a d F i l e utiliza sencillamente el componentc
O p e n D i a l o g l para pedir a1 usuario que seleccione un archivo de entrada y lo
cargue en el componente de imagen:
procedure TImageViewerForm.LoadFi1e;
begin
if 0penDialogl.Execute then
1magel.Picture.LoadFromFile (0penDialogl.Filename);
end;

La otra clase heredada tiene un codigo similar, que carga el texto en el compo-
nente memo. El proyecto tiene un formulario mas, un formulario principal con
dos botones, utilizados para cargar de nuevo 10s archivos en cada uno de 10s
formularios de visor. El formulario principal es el unico formulario creado por el
proyecto a1 iniciar. El formulario de visor generic0 nunca se crea: solo es una
clase basica generica, que contiene codigo comun y componentes de dos subclases.
Los formularios de las dos subclases se crean en el controlador de eventos
oncre at e del formulario principal:
p r o c e d u r e TMainForm.FormCreate(Sender: T O b j e c t ) ;
var
I: Integer;
begin
FormList [ l ] : = TTextViewerForm.Create (Application);
FormList [2] : = T1mageViewerForm.Create (Application);
for I := 1 to 2 do
FormList [I] .Show;
end ;

Fo rmL i s t es una matriz polimorfica de objetos TVi ewe r Fo rm genericos,


declarada en la clase T M a i n Fo rm.Para hacer esta declaracion en la clase debe-
mos aiiadir la unidad Viewer (pero no 10s formularios especificos) en la clausu-
la uses de la parte de interfaz del formulario principal. La matriz de formularios
se usa para cargar un archivo nuevo en cada formulario de visor cuando hacemos
clic en alguno de 10s dos botones. Los controladores del evento oncl ic k de
cada uno de 10s botones funcionan de diferente manera:
//ReloadButtonlClick
for I := 1 to 2 do
FormList [I] .ButtonLoadClick (Self);

//ReloadButtonZClick
for I := 1 to 2 do
FormList [I] .LoadFile;

El boton secundario llama a un metodo virtual, funcionando sin problemas. El


boton primario llama a un controlador de eventos y siempre llega a la clase gene-
rica TFormView (mostrando el mensaje de error de su metodo ButtonLoad-
Click). Esto ocurre porque el metodo es estatico en lugar de virtual.
Para hacer que esto funcione, podemos declarar el metodo But tonLoadClic k
de la clase T FormVie w como virtual y sobrecargarlo en cada una de las clases
formulario heredadas, como hacemos con cualquier otro metodo virtual:
type
TViewerForm = class (TForm)
p r o c e d u r e ButtonLoadClick (Sender: TObject); virtual;
public
procedure LoadFile; virtual; abstract;
end;

type
TImageViewerForm = class (TViewerForm)
procedure ButtonLoadClick (Sender: TOb ject) ; override;
public
procedure LoadFile; override;
end;

Este truco funciona aunque no se mencione en la documentacion de Delphi.


Esta capacidad para usar controladores de eventos virtuales es lo que llamamos
polimorfismo de formularios visuales. En otras palabras, podemos asignar un
metodo virtual a una propiedad de evento, que capturara la direccion del metodo
en funcion de la instancia disponible en tiempo de ejecucion.

Entender 10s marcos


Como hemos visto en capitulos previos, con Delphi podemos crear un nuevo
marco, situar componentes en el, desarrollar controladores de eventos para el
componente y, despues, aiiadir el marco a un formulario. 0, dicho de otra manera,
un marco es similar a un formulario, per0 define solo una parte de una ventana,
no la ventana completa. El elemento totalmente nuevo de 10s marcos es que se
pueden crear diversas instancias de un marco en tiempo de diseiio y se pueden
modificar la clase y la instancia a1 mismo tiempo. Por ello, 10s marcos son una
herramienta muy practica para crear controles compuestos moldeables en tiempo
de diseiio, algo parecido a una herramienta de desarrollo de componentes visua-
les.
En la herencia de formularios visuales, podemos trabajar en tiempo de diseiio
tanto con un formulario base como con un formulario derivado, asi, cualquier
cambio que hagamos en el formulario base se extiende a1 heredado (a no ser que
sobrecarguemos una propiedad o un evento). En el caso de 10s marcos, trabaja-
mos en una clase, per0 tambien se puede personalizar una o mas instancias de la
clase en tiempo de diseiio. Cuando trabajamos con un formulario, no podemos
cambiar una propiedad de la clase T F o r m l para el objeto F o r m 1 en tiempo de
diseiio per0 con 10s marcos si.
Una vez que asumamos que estamos trabajando con una clase y una o mas de
sus instancias en tiempo de diseiio no tendremos que comprender nada mas acerca
de 10s marcos. En la practica, 10s marcos son utiles cuando queremos usar el
mismo grupo de componentes en varios formularios en una aplicacion. De hecho,
en este caso, podemos personalizar las instancias en tiempo de diseiio. Esto ya se
podia hacer con plantillas de componentes, per0 estaban basadas en el concept0
de copiar y pegar componentes y su codigo. No podiamos cambiar la definicion
original de la plantilla y ver el efecto en todos 10s sitios donde la usabamos. Con
marcos (y, de otra manera, con la herencia de formularios visuales), 10s cambios
de la version original (la clase) se reflejan en la copia (las instancias).
Podemos ver algunos elementos mas sobre 10s marcos con el ejemplo Frames2.
Este programa tiene un marco con un cuadro de lista, un cuadro de edicion y tres
botones con codigo sencillo que opera en 10s componentes. El marco tiene tam-
bien un boton Bevel alineado con su zona de cliente, porque 10s marcos no tienen
borde. El marco tiene tambien una clase correspondiente que parece una clase de
formulario:
type
TFrameList = class (TFrame)
ListBox: TListBox;
Edit: TEdit;
btnAdd: TButton;
btnRemove: TButton;
btnclear: TButton;
Bevel: TBevel;
procedure btnAddClick (Sender: TObj ect) ;
procedure btnRemoveClick(Sender: TObject);
procedure btnClearClick (Sender: TObject) ;
private
{ Declaraciones privadas )
public
{ Declaraciones publicas
end ;

L a diferencia con respecto a u11 formulario esta en que podemos aiiadir el


marco a un formulario. Hemos usado dos instancias del marco en el ejemplo
(como muestra la figura 8.9) y modificado el comportamiento ligeramente. La
primera instancia del marco tiene 10s eleme~itosdel cuadro de lista organizados.
A1 cambiar una propiedad de un componente de un marco, el archivo DFM del
formulario anfitrion listara las diferencias, como hace con la herencia de formula-
rios visuales:
object FormFrames : TFormFrames
Caption = ' F r a m e s P '
inline FrameListl: TFrameList
Left = 8
Top = 8
inherited ListBox: TListBox
Sorted = True
end
end
inline FrameList2: TFrameList
Left = 232
Top = 8
inherited btnclear: TButton
OnClick = FrameList2btnClearClick
end
end
end

......
&d(~I-l Du I , .: .; .: .; .; .:
meted , ... ... ... ... ... ...
I
Some !ex(

Figura 8.9. Un rnarco y dos instancias del rnisrno en tiempo de disetio, en el ejemplo
Frarnes2.

Como se ve en el listado, el archivo DFM de un formulario que tiene marcos


usa una nueva palabra clave DFM, inline.Las referencias a 10s componentes
modificados del marco, en cambio, utilizan la palabra clave inherited,aun-
que este termino se usa con un significado ampliado: inherited aqui no se
refiere a la clase basica de la que se hereda, sin0 a la clase de la que estamos
sacando una instancia de un objeto (o heredando). Hemos utilizado una caracte-
ristica existente en la herencia de formularios visuales para aplicarla a este nuevo
contexto. Asi, en realidad, se puede usar la orden Revert to Inherited del
Object Inspector o del formulario para cancelar 10s cambios y volver a1 valor
predefinido de las propiedades.
Los componentes de la clase marco que no han sido modificados tampoco son
mostrados en el archivo DFM del formulario que utiliza el marco, y aunque 10s
componentes de ambos formularios tengan el mismo nombre 10s dos marcos tie-
nen nombres diferentes. Estos componentes no son propiedad del formulario sino
del marco. Esto implica que el formulario debe referencia 10s componentes a
traves del marco. como puede verse en el codigo de 10s botones que copian ele-
mentos de un cuadro de lista a otro:
procedure TFormFrames.btnLeftClick (Sender: TObject);
begin
FrameListl.ListBox.Items.AddStrings
(FrameList2.ListBox.Items);
end ;

Ademas de modificar las propiedades de cualquier instancia de un marco,


tambien podemos cambiar el codigo de cualquiera de sus controladores de even-
tos. Si hacemos doble clic en uno de 10s botones del marco mientras trabajamos en
cl formulario (no en el marco independiente), Delphi generara este codigo por
nosotros:
procedure TFormFrames.FrameList2btnClearClick (Sender:
TObject) ;
begin
FrameList2.btnClearClick (Sender);
end;

La linea de codigo aiiadida automaticamente por Delphi corresponde a una


llamada a1 controlador de eventos hcrcdado dc la clase base mediante herencia de
forrnularios visuales. En este caso, para que se de el comportamiento original dcl
marco, debemos llamar a un controlador de eventos y aplicarlo a una instancia
especifica, el propio objeto marco. El marco actual no incluye este controlador ni
sabe nada de dl. Tanto si decidimos dcjar esta llamada como si la quitamos depen-
dera del efecto que busquemos.
- ... ~ -

TRUCO:Debemos tener en cuenta que, dado que el controlador de eventos


tiene codigo, dejarlo como lo ha generado Delphi y guardar el formulario
no lo eliminara como es habitual, ya que no esta vacio. Si queremos omitir
el codigo por defect0 para un evento, tenemos que aiiadirle, a1 menos, un
comentario para evitar que el sistema lo borre automaticamente.

Marcos y fichas
Cuando tenemos un cuadro de dialogo con muchas fichas llenas de controlesj
el codigo subyacente a1 formulario se vuelve muy complejo, porque todos 10s
controlcs y metodos se declaran en un ilnico formulario. Ademas, a1 crear todos
estos componentes (e iniciarlos) podriamos originar un retraso en la aparicion del
cuadro de dialogo. En realidad, 10s marcos no reducen el tiempo de construccion
e inicializacion dc 10s formularios cargados de forina equivalente. A1 contrario, es
mas complicado cargar 10s marcos para el sistema de slrenrning que cargar com-
ponentes simples. Sin embargo, a1 utilizar marcos se puedan cargar solo las fi-
chas visibles de un cuadro de dialogo, reduciendo el tiempo de carga inicial, que
es el que percibe cl usuario.
Los marcos pueden resolver estos dos temas. En primer lugar, se puede dividir
facilmente el codigo de un formulario unico complejo en un marco por ficha. El
formulario albergara sencillamente todos 10s marcos en un PageControl. Esto
ayuda realmente a tener unidades mas sencillas y mas centradas, y hace que
resulte mas sencillo reutilizar una ficha concreta en un cuadro de dialogo diferen-
te o una aplicacion. Reutilizar una unica ficha de un PageControl sin usar un
marco o un formulario incrustado es muy complicado (para conocer una tecnica
alternativa vease el apartado "Formularies en fichas").
Para ilustrar esto, hemos creado el e.jemplo FramePag,que tiene algunos mar-
cos colocados en el interior de las tres fichas de un Pagecontrol, como muestra la
figura 8.10. Todos 10s marcos estan alineados con la zona del cliente, utilizando
toda la superficie de la hoja de solapa (la ficha) en la que se encuentran. En reali-
dad, dos de las fichas tienen el mismo marco, per0 dos de las instancias del inarco
tienen algunas diferencias en tiempo de diseiio. El marco, llamado Frame3 en el
ejemplo, tiene un cuadro de lista que contiene un archivo de texto al arrancar y tiene
botones para modificar 10s elementos de la lista y guardarlos en un archivo. El
nombre de archivo se coloca en una etiqueta, para poder seleccionar con facilidad
un archivo en tiempo de diseiio cambiando el titulo de la etiqueta.

Figura 8.10. Cada ficha del ejemplo FramePag contiene un marco, separando el codigo
de este formulario complejo en trozos mas rnanejables.

Poder utilizar diversas instancias de un marco es una de las razones para la


introduccion de esta tecnica y personalization del marco en tiempo de diseiio
resulta incluso mas importante. Dcbido a que aiiadir propiedades a un marco y
hacer que estCn disponibles en tiempo de diseiio requiere cierto codigo complejo y
personalizado, esta bien poder usar un componente que contenga estos valores
personalizados.
Tambien esiste la opcion de ocultar estos componentes (como la ctiqueta dcl
ejcmplo) si no pertenecen a la interfaz de usuario.
En el ejemplo, es neccsario cargar el archivo cuando se crea la instancia del
marco. Como 10s marcos no tienen un evento Oncreate. la mejor opcion es
probablemente sobrescribir el metodo CreateWnd.Crear un constructor a me-
dida no funciona, porque es ejecutado demasiado pronto, antcs de que la etiqueta
de testo especifica este disponible. En el metodo CreateWnd, sencillamente
cargamos el contenido del cuadro de lista desde un archivo.
NOTA: La creacion de la ventana marco (como ocurre con la mayoria de
controles) se retrasa por razones de eficiencia. Causa mas problemas la
utilization de herencia entre formularios que contienen marcos, por lo que,
para evitar este problema, se desactivo el controlador de eventos oncreate
para marcos (asi lo programadores pueden escribir el codigo que conside-
ren razonable).

Formularios en fichas
A pesar de que podemos utilizar marcos para definir las fichas de un
PageControl en tiempo de diseiio, tambien podemos usar otros formula-
rios en tiempo de ejecucion. Esta tecnica nos da la flexibilidad de tener las
fichas definidas en unidades y ficheros DFM separados y, al mismo tiempo,
permite utilizar esos formularios como ventanas independientes.
Cuando tenemos un formulario principal con un control de fichas y uno o
mas formularios secundarios para mostrar en el, todo lo que tenemos que
hacer es escribir este codigo para crear 10s formularios secundarios y si-
tuarlos en las fichas:
var
Form: TForm;
Sheet: TTabSheet;
begin
// c r e a r una h o j a d e t a b u l a c i o n e n e l c o n t r o l f i c h a
Sheet : = TTabSheet.Create(PageContr011);
Sheet.PageContro1 : = PageControll;
// c r e a r e l f o r m u l a r i o y s i t u a r l o e n l a h o j a d e t a b u l a c i o n
Form := TForm2 .Create (Application);
Form.BorderStyle := bsNone;
Form.Align : = alclient;
Form. Parent := Sheet;
Form. Visible := True;
// a c t i v a r l o y p o n e r l e t i t u l o
PageContro1l.ActivePage : = Sheet;
Sheet.Caption : = Form.Caption;
end;

Se puede encontrar este texto en el ejemplo Formpage, per0 el programa no


hace nada mas. Para ver una aplicacion, puede consultarse el programa de
demostracion RWBlocks, en el capitulo 14.

Varios marcos sin fichas


Otra tecnica consiste en no crear todas las fichas con el formulario que las
contiene. Esto se puede realizar dejando el PageControl en blanco y creando 10s
marcos solo cuando aparece una ficha. Si tenemos marcos en varias fichas de un
Pagecontrol, las ventanas para 10s marcos se crean solo cuando se muestran por
primera vez, como podemos comprobar si colocamos un punto de parada en cl
codigo de creacion de ejemplo anterior.
Como tecnica mas drastica; podemos eliminar 10s controles de ficha y utilizar
TabControl. De este modo, la solapa no esta conectada a ninguna hoja (o ficha)
sino que simplemente muestra un conjunto de informacion cada vez. Por dicha
razon, sera necesario crear el marco actual y destruir el anterior o sencillamente
ocultarlo definiendo su propiedad V i s i b l e como F a l s e o llamando a
BringTo Front del nuevo marco. A pesar de que esto puede parecer muy tra-
bajoso, en una aplicacion grandc esta tecnica puede merecer la pena por el redu-
cido uso de recursos y memoria que se obtiene.
Para demostrar esta tecnica, hemos creado el ejemplo FrameTab, similar a1
antcrior, basado csta vez en un TabControl y hemos creado marcos de forma
dinamica. El formulario principal, visible en tiempo de ejecucion en la figura
8.1 1; solo tiene un TabControl con una ficha para cada marco:
o b j e c t Forml: TForml
Caption = ' F i c h a s d e M a r c o '
OnCreate = Formcreate
o b j e c t Buttonl: TButton...
o b j e c t Button2: TButton...
o b j e c t Tab: TTabControl
Anchors = [akLeft, akTop, akRight , akBottom]
Tabs .Strings = ( ' M a r c o 2 ' ' M a r c o 3 ' )
OnChange = Tabchange
end
end

Figura 8.11. La primera ficha del ejemplo FrameTab en tiernpo de ejecucion. El


marco dentro de la solapa es creado en tiempo de ejecucion.
Hemos dado un titulo para cada solapa que corresponde a1 nombre del marco,
porque vamos a usar esta informacion para crear nuevas fichas. Cuando creamos
el formulario y siempre que el usuario cambia la solapa activa, el programa
obtiene el titulo actual de la solapa y lo pasa a1 metodo Show Frame.El codigo
de este metodo, que aparece a continuacion, verifica si el marco solicitado ya
existe (10s nombres de 10s marcos de este ejemplo siguen el estandar de Delphi de
llevar un numero agregado a1 nombre de la clase) y, despues, hace que aparezca
en la parte de delante. Si el marco no existe, usa el nombre del marco para encon-
trar la clase de marco relacionada, crea un objeto de dicha clase y le asigna
algunas propiedades. El codigo utiliza de forma ampliada las referencias de clase
y las tecnicas de creacion dinamica:
type
TFrameClass = class of TFrame;

p r o c e d u r e TForml.ShowFrarne(FrameNarne: string);
var
Frame: TFrame;
FrameClass: TFrameClass;
begin
Frame : = Findcomponent (FrameName + '1 ' ) a s TFrame;
i f n o t Assigned (Frame) t h e n
begin
FrameClass : = TFrameClass ( Findclass ( 'T' + FrameName) ) ;
Frame : = FrameClass .Create (Self);
Frame.Parent : = Tab;
Frame.Visible : = True;
Frame.Name : = FrameName + '1';
end;
Frame.BringToFront;
end ;

Para que el codigo funcione, debemos recordar afiadir una llamada a


Reg i s t e rC 1a s s en la parte de inicializacion de cada una de las unidades que
definen el marco.

Formularios base e interfaces


Hemos visto que cuando necesitamos dos formularios similares en una aplica-
cion, podemos usar la herencia de formularios visuales para heredar uno de otro o
ambos de un ascendiente comun. La ventaja de la herencia de formularios visua-
les esta en que podemos usarla para heredar la definicion visual, la DFM. Sin
embargo, esto no siempre se solicita.
Para que varios formularios muestren un comportamiento comun o respondan
a las mismas ordenes, sin tener ningun componente compartido ni elementos de la
interfaz de usuario, en lugar de utilizar la herencia de formularios visuales con un
formulario base que no tiene componentes adicionales podemos utilizar otra tec-
nica. Es preferible utilizar una clase de formulario personalizada, heredada de
T F o r m y, a continuacion, editar manualmente las declaraciones de clase del for-
mulario para heredar de esa clase de formulario base personalizada en lugar de
heredar de la estandar. Si todo lo que hay que hacer es definir algunos metodos
compartidos o sobrescribir 10s metodos virtuales T F o r m de forma continuada,
puede ser buena idea definir clases de formulario personalizadas.

Uso de una clase de formulario base


En el ejemplo FormIntf, podemos ver el uso de dicha tecnica, en la que se
muestra tambien el uso de interfaces para formularios. En una nueva unidad,
llamada SaveStatusForm, hemos creado la siguiente clase de formulario (sin nin-
gun archivo relacionado DFM). En lugar de usar la orden N e w F o r m , creamos
una nueva unidad y escribimos el codigo en la misma:
type
T S a v e S t a t u s F o r m = c l a s s (TForm)
protected
p r o c e d u r e Docreate; override;
p r o c e d u r e DoDestroy; override;
end ;

Los dos metodos sobrecargados son llamados a la vez que el controlador de


eventos por lo que podemos agregarle codigo extra (que nos permita definir el
controlador de eventos como hacemos usualmente). Dentro de 10s dos metodos
cargamos y guardamos la posicion del formulario en un archivo IN1 de la aplica-
cion, en una seccion marcada con el nombre del formulario. Este es el codigo de
ambos metodos:
p r o c e d u r e TSaveStatusForm.DoCreate;
var
Ini: TIniFile;
begin
inherited;
Ini : = TIniFile.Create (ExtractFileName
( A p p 1 i c a t i o n . E x e N a m e )) ;
Left : = Ini .ReadInteger (Caption, 'Izquierda ', Left) ;
T o p : = Ini .ReadInteger (Caption, 'Arriba ', T o p ) ;
W i d t h := Ini. ReadInteger (Caption, 'Anchura ', W i d t h ) ;
Height : = Ini .ReadInteger (Caption, 'Altura ', H e i g h t ) ;
Ini. Free;
end ;

p r o c e d u r e TSaveStatusForm.DoDestroy;
var
Ini: TIniFile;
begin
Ini : = T I n i F i l e - C r e a t e (ExtractFileName (Application.ExeName));
Ini .WriteInteger (Caption, ' I z q u l e r d a ', Left) ;
Ini .WriteInteger (Caption, ' A r r i b a ', Top) ;
Ini .WriteInteger (Caption, ' A n c h u r a ', Width) ;
1ni.WriteInteger (Caption, ' A l t u r a ', Height) ;
Ini. Free;
inherited;
end ;

Estc cs un ejemplo muy sencillo per0 podemos definir una clase compleja en su
interior. Para utilizar esta como clase base para 10s formularios que construya-
mos, debemos dejar que Delphi c:ee 10s formularios como siempre (sin herencia)
y, despues, actualizaremos la declaracion a algo parecido a este:
type
TFormBitmap = class (TSaveStatusForm)
Imagel: TImage;
OpenPictureDialogl: TOpenPictureDialog;

Aunque es tan sencilla como parece, esta tecnica es muy potente, porque lo
unico que tenemos que hacer es cambiar la definicibn de 10s formularios de nues-
tra aplicacion para referirnos a esta clase base. Incluso si este paso nos resulta
muy tedioso porque, quiza, queramos en algun momento cambiar esta clase en
nuestro programa, podemos usar un truco extra: las clases de interposicion
- - - - -- - - -- - -

Archivos IN1y el Registro en Delphi


Si queremos guardar cierta infarmblcibn sobre el estadq de una aplicacion
para restaurarla la prbxima vez que se ejecute el pragwm, podemos usar
explicitamente el soporte que ofrece windows para almacenar este tip de
informacion. Los archivos MI son una vez nuis la forma preferible de guar-
dar datos de la aplicacibn. La alternativa es el Registro. Delphi ofiece
cIases Iistas para usar para trabajar con ambas t h i c a s .
La clase TIniFile: En el caso de 10s archivos INI, Delphi tiene una
clase TIniFile. Cuando hemos c r e d o un objeto de dicha clase y lo
hemos conectado a un archivo, podemos leer y escribir informacibn en
61. Para crear el objeto, es necesario llamar a1 constructor, pasando un
nombre de archivo, como en el siguiente c6digo:
var
IniFile: TIniFile;
begin
IniFile := TIniFile .Create ( 'myprogrsm. ini ') ;
Existen dos posibilidades para colocar un archivo MI. El &%go que
acabamos de listar guarda el archivo en el directorio Windows o en una
carpeta de usuario con las configuraciones en Windows 2000. Para
guardar'los datos de forma locd en la itpii~&i&n(en oposicibn a bacer-
lo de forma local para el usuario actual), deberiamos ofiecer una ruta
completa a1 constructor.
Los archivos IN1 se dividen en secciones, cada una de ellas indicada
mediante un nombre entre corchetes. Cada apartado puede contener
rlivarcnc alam~ntncAP tree tinnc nncihlac- rarlenac antarnc n hnnlaannc
. .

La clase T I n i F i l e tiene tres mCtodos Read, uno para cada tipo de


datos: R e a d B o o l , R e a d I n t e g e r y R e a d s t r i n g . TambiCn hay
. . .. .. . .

c s p c c ~ ~ ~UIIc a
valor
~ p~cu~lmu
para
u uiuuar SI lir c o ~ ~ c s p o u u r c rcn-
~u
trada no existe en un archivo INI.
Debemos tener en cuenta que Delphi utiliza 10s ficheros IN1 muy a
menudo, per0 con nombres diferentes. Por ejemplo, 10s archivos de es-
critorio (.dsk) y de opciones (.dof) e s t h estructurados como archivos
INI.
Las clases TRegistry y TRegIniFile: El Registro es una base de datos
jerarquica de inforrnacion sobre el ordenador, la configuraci6n de soft-
ware y las preferencias del usuario. Windows tiene un conjunto de fun-
ciones API para interactuar con el Registro. Basicamente abrimos una
clave (o carpeta) y, a continuacion, trabajamos con subclaves (o
subcarpetas) y con valores (o elementos), pero debemos ser conscientes
de la estructura y 10s datos del Registro.
Delphi ofrece basicamente dos tkcnicas a1 uso del Registro: la clase
T R e g i s t r y un encapsulado del Registro API, mientras que Ia clase
T R e g I n i F i l e la interfaz de la clase T I n i F i l e per0 guardando 10s
datos en el Registro. Esta clase es la opcion natural para conseguir el
intercambio entre la informacibn basada en IN1 y las veniones basadas
en Registro de un mismo programa. Cuando creamos un objeto
TReg I n i F i l e , nuestros datos termina.n en la infonaacb5n de usuario
actual, por lo que nomlmente usamos un ~wstructorcmm:
IniFile := TRegIniFile.Create
( 'Software\MyCompany \MyProgramp)
;

A1 usar las clases TJniFile y TRegistryhiFile que nos &ece la VCL,


podemos desplazamos de un modelo de almacenamiento local y por
usuario a otro. A pesar de todo esto, no deberiaanos usar el registro
demasiado porque tener un repositorio centralizado para la configma-
cion de cada aplicaci6n fue un error de diseilo de la arquitectura. Inclu-
so Microsoft reconoce este becho (sin admitir realmente el error) a1
I sugerir, en 10s requisites de compatibilidad con windows 2000, que
deberia evitarse usar el Registro para almacenar la configuracih de las
I
aplicaciones y, en su lugar, volver a utilizar 10s archivos IN1 dentro de
la carpeta de documentos del usuario actual (algo de lo que muchos
programadores no han oido hablar).

Un truco adicional: clases de interposicion


En oposicion a 10s componentes VCL de Delphi, que deben tener nombres
unicos, las clases de Delphi han de ser generalmente unicas solo dentro de su
unidad. Asi, podemos tener dos unidades diferentes que definan una clase con el
mismo nombre. Esto parece extraiio a primera vista, per0 puede ser util. Por
ejemplo, Borland esta usando esta tecnica para ofrecer compatibilidad entre las
clases VCL y VisualCLX. Ambas tienen una clase TForm,una definida en la
unidad Forms y otra en la unidad QForms.

NOTA: Esta tecnica es mucho mas antigua que CLXNCL. Por ejemplo,
las unidades de servicio y panel de control definen su propio objetaI
TApplication. que no tiene nada que ver con el TApplication usa-
.. . . . -.-- . - . . .. . -
do por las apllcaciones Vlsuales V L L y dekmldo en la unidad k-oms.

Existe una tecnica que hemos visto mencionada con el nombre de "clases de
interposicion", que sugeria que se sustituyesen 10s nombres de clase estandar de
Delphi por versiones propias, que tuviesen el mismo nombre de clase. Asi, pode-
mos usar el diseiiador Delphi que se refiere a componentes estandar de Delphi en
tiempo de diseiio, per0 usando nuestras propias clases en tiempo de ejecucion.
La idea es sencilla. En la unidad SaveStatusForm, podriamos definir la nueva
clase de formulario asi:
type
TForm = class ( F o r m s . TForm)
protected
procedure D o c r e a t e ; override;
procedure D o D e s t r o y ; override;
end;

Esta clase se llama TForm y hereda de TForm de la unidad Forms (esta


ultima referencia es obligatoria para evitar una especie de definicion recursiva).
En el resto del programa, no hay que cambiar la definicion de clase del formula-
rio, sino sencillamente aiiadir la unidad que define la clase de interposicion (la
unidad SaveStatusForm en este caso) en la sentencia u s e s despues de la unidad
que define la clase Delphi. El orden de las unidades en la sentencia u s e s es
importante, y es la razon por la que algunas personas critican esta tecnica, ya que
es dificil saber lo que ocurre. Y tienen razon: las clases de interposicion son
practicas a veces (mas para componentes que para formularios), per0 su uso hace
10s programas menos legibles y? en algunas ocasiones, mas dificil de depurar.

Uso de interfaces
Otra tecnica, que es ligeramente mas compleja per0 mas potente que la defini-
cion de una clase de formulario comun consiste en tener formularios que
implementes interfaces especificas. Asi, podemos tener formularios que
implementen una o mas interfaces, consulten cada formulario para saber que
interfaz implementa y llamen a 10s metodos soportados.
Como ejemplo, hemos definido una interfaz simple para cargar y almacenar:
type
IFormOperations = i n t e r f a c e
[ ' {DAC,FDB76-0703-4A40-A951-1OD1 40B4AZAO) '1
p r o c e d u r e Load;
p r o c e d u r e Save;
end;

Cada formulario puede implementar opcionalmente esta interfaz, como la si-


guiente clase T FormBitmap:
type
TFormBitmap = c l a s s (TForm, IFormOperations)
Imagel: T I m a g e ;
OpenPictureDialogl: TOpenPictureDialog;
SavePictureDialogl: TSavePictureDialog;
public
p r o c e d u r e Load;
p r o c e d u r e Save;
end;

El codigo de ejemplo incluye 10s metodos Load y Save, que utilizan 10s
cuadros de dialog0 estandar para cargar o guardar la imagen (en el ejemplo, el
formulario hereda tambien de la clase TSaveStatus Form). Cuando una apli-
cacion tiene uno o mas formularios que implementan interfaces, podemos aplicar
un metodo de interfaz concreto a todos 10s formularios que lo soportan, con codi-
go como este (extraido del formulario principal del ejemplo FormIntf):
p r o c e d u r e TFormMain.btnLoadClick (Sender: T O b j e c t ) ;
var
i: Integer;
iFormOp: IFormOperations;
begin
f o r i : = 0 t o Screen.FormCount - 1 d o
i f Supports (Screen.Forms [i],' IFormOperations, iFormOp) t h e n
iFormOp-Load;
end ;
Tengamos en cuenta que en una aplicacion empresarial podemos sincronizar
todos 10s formularios con 10s datos de una empresa especifica o un evento empre-
sarial especifico. Ademas, a diferencia de la herencia, podemos tener varios for-
mularios que implementen cada uno varias interfaces, con combinaciones
ilimitadas. Esta es la razon por lo que utilizar una arquitectura como esta puede
mejorar mucho una compleja aplicacion Delphi y hacerla mucho mas flexible y
mas sencilla de adaptar para la implernentacion de cambios.

El gestor de memoria de Delphi


Terminaremos este capitulo dedicado a la estructura de aplicaciones Delphi
con una seccion sobre la gestion de memoria. Este tema es muy complejo y,
posiblemente, merece dedicarle un capitulo entero, aunque aqui solamente le da-
remos un breve repaso y daremos algunas indicaciones para pruebas posteriores.
Para un analisis mas detallado de la memoria podemos acudir a las multiples
herramientas dedicadas a la verificacion y el control de la memoria con Delphi,
tales comoMemCheck, MemProof, MemorySleuth, Code Watch y AQTime.
Delphi tiene un gestor de memoria, accesible usando las funciones
GetMemoryManager y SetMemoryManager de la unidad System. Estas
funciones permiten acceder a1 registro del gestor de memoria original o modifi-
carlo con nuestro propio gestor personalizado. Un registro de gestor de memoria
es un conjunto de tres funciones usadas para asignar, liberar y reasignar memo-
ria:
type
TMemoryManager = record
GetMem: function (Size: Integer) : Pointer;
FreeMem: function (P: Pointer) : Integer;
ReallocMem: function (P: Pointer; Size: Integer) : Pointer;
end;

Es importante conocer como estas funciones son llamadas a1 crear un objeto


ya que podemos insertarlas en dos pasos. Como llamamos a un constructor, Delphi
invoca la funcion de clase virtual NewInstance, definida en TObject.Esta
funcion es virtual por lo que podemos modificar el gestor de memoria para una
clase especifica sobrecargandola. Para llevar a cab0 la asignacion de memoria
NewIns tance, normalmente, termina llamando a la funcion GetMem del ges-
tor de memoria activa, lo que nos da una segunda oportunidad para modificar el
comportamiento esthdar .
A no ser que tengamos necesidades especiales, generalmente no necesitaremos
intervenir en el gestor de memoria para modificar el funcionamiento de la asigna-
cion. De todas maneras, resulta practico hacerlo para determinar si este funciona-
miento es correcto, es decir, para asegurarnos de que el programa no tiene agujeros
de memoria. Por ejemplo, podemos sobrecargar 10s metodos New I ns t ance y
F r e e I n s t a n c e de una clase para llevar la cuenta del numero de objetos que se
crean y destruyen para verificar si el total es cero.
Una tecnica mas simple es hacer el mismo analisis sobre el total de objetos
asignados por el gestor de memoria. En las primeras versiones de Delphi, hacer
esto requeria codigo extra, per0 el gestor de memoria proporciona dos variables
(A1 1ocMemCount y A 1 1ocMemSize) que pueden ayudarnos a saber que esta
ocurriendo en el sistema.
El mod0 mas simple de determinar si nuestro programa esta tratando la memo-
ria adecuadamente es comprobar si A 1 1ocMemCount vuelve a cero. El proble-
ma es decidir cuando hacer esa comprobacion. Un programa empieza ejecutando
la seccion de inicializacion de sus unidades, que normalmente asigna memoria
liberada por las respectivas secciones de finalizacion. Para garantizar que nuestro
codigo se ejecuta a1 final, debemos escribirlo en la seccion de finalizacion de una
unidad y colocarlo a1 principio de la lista de unidades en el archivo de codigo
fuente del proyecto. Podemos ver una unidad como esta en el listado 8.1. Esta es
la unidad SimpleMemTest del ejemplo ObjsLeft, que tiene un formulario con un
boton para mostrar el contador de asignaciones actual y un boton para crear un
agujero de memoria (que es capturado a1 terminar el programa).

Listado 8.1. Una unidad para cornprobar agujeros de rnernoria, del ejemplo ObjsLeft.

unit SimpleMemTest;

interface

implementation

uses
Windows ;

var
msg: string;

initialization
finalization
if AllocMemCount > 0 then
begin
Str (AllocMemCount, msg) ;
Msg : = msg + ' b l o q u e s r e s t a n t e s e n l a p i l a ' ;
MessageBox ( 0 , PChar (msg), ' a g u j e r o d e memoria ' , MB-OK) ;
end ;
end.

Este programa es practico, per0 realmente no ayuda a comprender que ha ido


mal. Para conocer esto, existen herramientas muy potentes (algunas de las que
tienen versiones de prueba gratuitas), o podemos utilizar el gestor de memoria
descrito es este libro, que analiza las asignaciones de memoria.
Creacion
de componentes
Delphi

La mayoria de 10s programadores en Delphi estaran, probablemente, acostum-


brados a usarlos pero, a veces, puede resultar practico crear nuestros propios
componentes o personalizar 10s ya existentes. Uno de 10s aspectos mas interesan-
tes de Delphi es que crear componentes no es mucho mas dificil que crear progra-
mas. Por esta razon, aunque este libro este dirigido a programadores de aplicaciones
Delphi en lugar de a creadores de herramientas, este capitulo tratara sobre la
creacion de componentes y presentara 10s afiadidos de Delphi, tales como 10s
componentes y editores de propiedades.
Este capitulo ofrece una introduccion a la creacion de componentes Delphi y
presenta algunos ejemplos. No hay espacio suficiente para presentar componentes
muy complejos per0 las ideas que hemos incluido cubren todos 10s fundamentos y
nos ofreceran un punto de partida.
Este capitulo trata estos temas:
Ampliacion de la biblioteca de Delphi.
Creacion de paquetes.
Componentes compuestos.
Uso de propiedades de interfaz.
Definicion de eventos personalizados.
paquete solo de disefio, suele estar enlazado estaticamente al archivo eje-
cutable, utilizando el codigo de 10s archivos DCU (Delphi Compiled Unit)
correspondientes. Sin embargo, hay que tener en cuenta que tambiln es
tecnicamente posible utilizar un paquete solo de diseiio como paquete de
tiempo de ejecucion.
Las aplicaciones de Delphi utilizan 10s paquetes de componentes solo de
cjccucion en tiempo de ejecucion. No sc pueden instalar en cl entorno Delphi,
per0 se afiaden autonxiticamente a la lista de paquetcs en tiempo de ejecu-
cion cuando 10s necesita un paquetc solo de diseiio quc instalamos. Los
paquetes solo de ejecucion contienen normalmente el codigo de las clases
de componentes, per0 no poseen soporte en tiempo de diseiio (asi se mini-
miza el tamaiio de las bibliotecas de componentes incluidas con el archivo
ejecutablc). Los paquetes solo de ejecucion son importantes porque se pue-
den distribuir libremente junto con las aplicaciones, per0 no se pueden
instalar en cl entorno para crear programas nuevos.
Los paquetes normales de componentes (10s que no tienen ni la opcion de
solo disefio ni la dc solo ejecucion) no se pueden instalar y no se afiadiran
a la lista de paquctes en tiempo de ejecucion de forma automatica. Pueden
verse en paquetes de utilidades usados por otros paquetes, per0 no suelen
ser habituales.
Los paquetes que tengan ambos indicadores pueden instalarse y se afiaden
automaticamente a la lista de paquctcs en tiempo de ejecucion. Normal-
mente, dichos paquctes contienen componentes que necesitan poco o nin-
gun soporte en tiempo de disefio (aparte del reducido codigo de registro del
componente)
-- --
I - . --- - -

r
~-
1
. - - - .-
TRUCO: Lon nombres de archivide 10s oaouetes s61o de diseiio de Delobi
comienzan con las letras DCL (por ejemplo, DCLSTDGO BPI,). Los nom- .
? -

r
-

bres de archivo de 10s paquetes solo de ejecuci6n comienzan con las letras
VCL (por ejemplo, VCL60 .BPL). Podemos, si queremos, utilizar la mis-
/ . .
ma teenlea en nuestros paquetes.

Anteriormente, hemos tratado el efecto de 10s paquetes en el tamaiio del archi-


vo ejecutable del programa. Ahora nos centraremos en la creacion de 10s paque-
tes, porque este es un paso necesario para la creacion o instalacion de componentes
en Delphi.
A1 compilar un paquetc de tiempo de ejecucion, se produce una biblioteca de
enlace dinamico con el codigo compilado (el archivo BPL) y un archivo solo con
una informacion de simbolo (un archivo DCP), que incluye el codigo maquina no
compilado. El ultimo archivo lo usa el compilador Delphi para reunir informacion
de simbolo sobre las unidades quc forman park del paquete sin tener acceso a 10s
archivos de la unidad (DCU), que contienen tanto la informacion de simbolo
como el codigo maquina compilado. Esto reduce el tiempo de compilacion y per-
mite distribuir solo 10s paquetes sin 10s archivos de unidad previamente compila-
dos. Las unidades precompiladas son necesarias para enlazar de forma estatica
10s componentes en una aplicacion. La distribucion de archivos DCU precompilados
(o codigo fuente) puede ser util dependiendo del tipo de componentes que desarro-
llemos.
Veremos como crear un paquete despues de presentar algunas recomendacio-
nes generales y crear un componente.
...

I NOTA: Las DLL son archivos ejecutables que contienen colecciones de


hnciones y clases, que pueden ser usadas por una aplicacion u otra DLL en
tiempo de ejecucion. La ventaja comun es que si muchas aplicaciones usan
la misma DLL, por lo que solo es necesario que haya una copia en el disco
- . .. ..
o cargada en memona y el tamaflo de cada archlvo ejecutable sera mucho
menor. Eso es lo que ocurre tambien en 10s paquetes Delphi.

Normas para escribir componentes


Esisten ciertas normas generales sobre la escritura de componentes. A conti-
nuacion, esponemos un resumen de las mismas:
Estudiar el lenguaje Delphi con detenimiento. La herencia, la sobrescritura
y la sobrecarga de metodos, la diferencia entre partes publicas y publica-
das de una clase y la definition de eventos y propiedades son conceptos
especialmente importantes. Para estudiar estos conceptos basicos puede
consultar la primera parte del libro.
Estudiar la estructura de la jerarquia de clases de la VCL y tener a mano
un esquema de dichas clases (corno el que se incluye con Delphi).
Seguir las convenciones estandar de denominacion de Delphi. Hay algunas
normas para componentes y si se siguen, sera mas facil para otros progra-
madores interactuar con sus componentes y seguir ampliandolos.
Crear componentes sencillos, imitar a otros componentes y evitar depen-
dencias. Estas tres normas, significan basicamente que un programador
que utiliza 10s componentes deberia poder usarlos de un mod0 tan sencillo
como 10s componentes previamente instalados de Delphi. Usar, siempre
que sea posible, nombres de propiedades, metodos y eventos similares. Si
10s usuarios no necesitan aprender normas complejas sobre el uso de nues-
tros componentes (es decir, si las dependencias entre metodos o propieda-
des son reducidas) y pueden acceder sencillamente a propiedades con
nombres sin significado, 10s mantendremos satisfechos.
Usar excepciones. Cuando algo falla, el componente deberia crear una
excepcion. Cuando se asignan recursos de algun tipo, debemos protegerlos
con bloques t r y / f i n a l l y y llamadas a1 destructor.
Para completar un componente, hay que aiiadirle un mapa de bits, para que
lo utilice la Component Palette de Delphi. Si queremos que nuestro
componente lo use mucha gente, debemos considerar tambien la idea de
aiiadirlc un archivo de ayuda.
Prepararnos para cscribir codigo rcal y olvidar 10s aspectos visuales de
Dclphi. Por lo gcncral. cscribir componcntes significa escribir codigo
sin soporte visual (aunque la f u n c i o n c l a s s C o m p l e t i o n puede acele-
rar bastantc la codificacion de clases normales). La excepcion a esta
norma es que podemos m a r marcos para escribir componentes de forma
visual.

NOTA: Tambien podemos usar herramientas de escritura de componentes


de terceros para crear nuestros componentes o acelerar su desarrollo. La
herramienta mas potente de terceros para crear componentes Delphi que
conocemos es Component Development Kit (CDK) de Eagle Software
(www .eagle-software.com), per0 hay muchas mas.

Las clases basicas de componentes


Para crcar un nuevo componente, normalmente partimos de uno ya existente o
dc una de las clases basicas de la VCL. En ambos casos, el componente esta en
una de las tres grandes categorias de componentes, definidas por las tres clases
basicas de la jerarquia de componentes:
T w i n c o n t r o l : Es la clase padre de cualquier componente basado en una
ventana. Los componentes que descienden de esta clase pueden recibir el
foco de entrada y obtener mensajes Windows del sistema. Tambien pode-
mos usar su manejador de ventana a1 llamar a funciones API. Cuando
creamos un control de ventana nuevo, por lo general se hereda de la clase
derivada T C u s t o m C o n t r o l , que posee una serie de utiles caracteristi-
cas adicionales (sobre todo soporte para pintar el control).
TGraphicControl: Es la clase padre de 10s componentes visibles que no
tienen manejador de Windows (que guarda algunos recursos Windows).
Estos componentes no pueden recibir el foco de entrada ni responder a
mensajes Windows directamente. A1 crear un control grafico, se hereda
directamente de esta clase (que posee un conjunto de funciones muy simila-
res a T C u s t o m C o n t r o l ) .
TComponent: Es la clase padre de todos 10s componentes (incluidos 10s
controles) y se pueden usar como clase padre directa de componentes no
visuales.
En el resto del capitulo, crearemos algunos componentes usando varias clases
padre y analizaremos todas las diferencias entre ellas. Empezaremos con compo-
nentes que heredan de componentes o clases existentes a un bajo nivel de la jerar-
quia. Despues trataremos ejemplos de clases que heredan directamente de las
clases precedentes que acabamos de mencionar.

Creacion de nuestro primer componente


Crear componentes es una actividad importante para 10s programadores en
Delphi. La idea bisica es que cada vez que necesitemos el mismo comportamiento
en dos lugares distintos de una aplicacion o en dos aplicaciones diferentes, poda-
mos colocar el codigo compartido en una clase (0, mejor, en un componente).
En esta seccion, presentaremos un par de componentes para hacernos una idea
de 10s pasos que hay que seguir para construir uno. Tambikn mostraremos las
diferentes cosas que podemos hacer para personalizar un componente existente
con una cantidad limitada de codigo.

El cuadro combinado Fonts


Muchas aplicaciones tienen una barra de herramientas con un cuadro combi-
nado que podemos usar para seleccionar una fuente. Si utilizamos con frecuencia
un cuadro combinado personalizado como ese, podriamos convertirlo en un com-
ponente, lo que nos Ilevaria, probablemente, menos de un minuto.
Para empezar, hay que cerrar todos 10s proyectos activos en el entorno Delphi
y arrancar el asistente para componentes, seleccionando Component>New
Component o File>New para abrir el Object Repository y, a continuacion,
escoger el componente en la ficha New. Como vemos, el asistente para compo-
nentes necesita la siguiente informacion:

&kUePapa hid A
~dk c+.nm 1ud~0n1~0mbo
pas
J
El nombre del tip0 ascendente: la clase de componente de la que se quiere
heredar. En este caso podemos usar TComboBox.
El nombre de la clase del nuevo componente que vamos a crear. Podemos
usar TMdFontCombo.
La ficha de la Component Palette en la que queremos que aparezca el
componente, que puede ser una ficha nueva o una que ya exista. Podemos
crear una nueva ficha, llamada Md.
El nombre del archivo de la unidad en la que queremos que Delphi coloque
el codigo fuente del nuevo componente. Podemos escribir MdFont Box.
La ruta de busqueda actual (que deberia aparecer de forma automatica).
Hacemos clic sobre el boton OK y el asistente para componentes generara el
archivo fuente mostrado en el listado 9.1 con la estructura de nuestro componen-
te. El boton Install se puede usar para instalar el componente en un paquete de
forma inmediata. Veamos el codigo primer0 y despues trataremos la instalacion.

Listado 9.1. Codigo del TMdFontCombo, producido por el asistente para componentes.

u n i t MdFontBox;

interface

uses
windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
TMdFontCombo = c l a s s (TComboBox)
private
{ Private declarations 1
protected
{ Protected declarations 1
public
{ Public declarations 1
published
{ Published declarations ]
end;

p r o c e d u r e Register;

implementation

p r o c e d u r e Register;
begin
Registercomponents ( ' M d ' , [TMdFontCombo] ) ;
end;

end.
Uno de 10s elementos clave de este listado es la definicion de clase, que co-
mienza indicando la clase padre. La otra unica parte importante es el procedi-
miento R e g i s t e r . Como podemos ver, el asistente para componentes hace muy
poco trabajo.

ADVERTENC1A:El procedimiento Register debe escribirse con R ma-


yliscula. Este requisito se impuso por razones de compatibilidad con C++
Builder (10s identificadores en C++ hacen distincion entre maylisculas y
minusculas).

r
r
r~ornvresue clase u~sr~rl~os.
ror esa razo11,la mayorla ue los uesarrollauores
de componentes en Delphi han escogido aiiadir un prefijo de dos o tres
letras a 10s nombres de nuestros componentes. En este libro, hemos escogi-
d o Md para identificar 10s componentes escritos en este. La ventaja de esta
tecnica esta en que podemos instalar un componente TMd F o n t C o m b o ,
aunque ya hayamos instalado un componente denominado T F o n t Combo.
Observe que 10s nombres de unidad han de ser unicos para todos 10s com-
ponentes instalados en el sistema, por lo que hemos aplicado el mismo
prefijo a los nombres de unidad.

Esto es todo lo que hay que hacer para crear un componente. Este codigo, por
supuesto, no incluye demasiado codigo. Ahora, solo hay que copiar todas las
fuentes del sistema en la propiedad I t e m s del cuadro combinado a1 arrancar.
Para ello, podemos intentar sobrescribir el metodo c r e a t e en la declaracion de
clase, afiadiendo la sentencia I t e m s : = S c r e e n . F o n t s . Sin embargo, esta
no es la tecnica adecuada. El problema esta en que no podemos acceder a la
propiedad I t e m s del cuadro combinado, antes de que el manejador de ventana
del componente este disponible. El componente no puede tener un manejador de
ventana hasta que se defina su propiedad P a r e n t y esa propiedad no se define en
el constructor, sin0 mas adelante.
Por esa razon, en lugar de asignar las nuevas cadenas en el constructor Create.
debemos realizar esta operacion en el procedimiento C r e a t e W n d , a1 que se
llama para crear el control ventana despues de que se construya el componente, se
defina su propiedad P a r e n t y su manejador de ventana este disponible. De
nuevo, ejecutamos el comportamiento predefinido y, a continuacion, podemos
escribir nuestro codigo personalizado. Podiamos habernos saltado el constructor
C r e a t e escribiendo todo el codigo en C r e a t e W n d , per0 hemos usado ambos
metodos iniciales para mostrar las diferencias entre ellos. Veamos la declaracion
de la clase del componente:
type
TMdFontCombo = class (TComboBox)
private
FChangeFormFont: Boolean;
procedure SetChangeFormFont(c0nst Value: Boolean);
public
constructor Create (AOwner: TComponent); override;
procedure CreateWnd; override;
procedure Change; override;
published
property Style default csDropDownList;
property Items stored False ;
property ChangeFormFont: Boolean
read FChangeFormFont write SetChangeFormFont default True;
end;

Este es el codigo fuente de 10s dos mktodos ejecutados a1 arrancar:


constructor TMdFontCombo.Create (AOwner: TComponent);
begin
inherited Create (AOwner);
Style := csDropDownList;
FChangeFormFont : = True;
end;

procedure TMdFontCombo.CreateWnd;
begin
inherited CreateWnd;
Items .Assign (Screen.Fonts) ;
/ / obtiene la fuente predefinida del forrnulario propietario
if FChangeFormFont and Assigned (Owner) and (Owner is TForm)
then
ItemIndex := Items. IndexOf ( (Owner as TForm) .Font .Name) ;
end:

Fijese en que ademas de dar un nuevo valor a la propiedad S t y l e del compo-


nente, en el metodo c r e a t e , hemos definido de nuevo dicha propiedad definien-
do un valor con la palabra clave default. Tenemos que realizar ambas
operaciones, porque aiiadir la palabra clave default a una declaracion de pro-
piedad no tiene un efecto direct0 en el valor inicial de dicha propiedad. Debemos
definir un valor predefinido porque las propiedades que tienen un valor igual a1
predefinido no se agrupan en el mismo stream que la definicion del formulario (y
no aparecen en la descripcion textual del formulario, el archivo DFM). La pala-
bra clave d e f a u l t , informa a1 codigo de streaming, que el codigo de inicio del
componente definira el valor de dicha propiedad.

TRUCO: Es importante especificar un valor predefinido de la propiedad


publicada, para reducir el t a m d o de los archivos DFM y, en ultimo tCrmi-
no, el tarnaiio de 10s archivos ejecutables (que incluyen 10s archivos DFM).
La otra propiedad que hemos vuelto a definir, I t e m s , se define como una
propiedad que no deberia guardarse en el archivo DFM, sea cual sea el valor real.
Esto se consigue con la directiva s t o r e d seguida del valor F a l s e . El compo-
nente y su ventana se van a crear de nuevo cuando el programa arranca, por lo que
no tiene sentido guardar
- en el archivo DFM informacion que mas tarde se va a
dcscartar (para ser sustituida por la nueva lista de fuentes).

NOTA: Podriamos haber escrito el codigo del metodo CreateWnd para


rnniar lac
VY'JIU.I U Y
fiientpc
L U W Y C W "
en
V..
I.VU
n c elpmentnc
V.W..LVY.V"
rlel VUUU."
UW.
riigrlrn Yrnmh;narln
V..L"I..UUV
en
V..
timnnn
*."..y,V AP
UV

ejecucion. Esto se puede hacer utilizando sentencias como i f not


(csDesigning in ComponentState). Pero en el caso de este
..-;...a- n~...nn..a..** -..a P"+~-A" ,...a..-rlfi a1 ma..- af ,.;a..*a -a-n YX" ma..,.;
~ 1 1 1 1 1 ~
~U U~U G G ~ L ~ I I ~
1 IILVUIIGIIL I U ~ ~ U U kU1 I I I G I I U ~ ~ L I ~ I G I I C~G
I G I 1 1V
1aa S G I I ~ I -

110 metodo que hemos d e s c30


~ ofrece un enfoque mas claro del procedi-
miento basico.

La tercera propiedad, C h a n g e F o r m F o n t , no se hcreda sino que es introdu-


cida por el componente. Se usa para establecer si la seleccion de la fuente actual
del cuadro combinado, deberia especificar la fuente del formulario en el que se
incluye el componente. De nuevo, esta propiedad se declara con un valor
predefinido, declarado en el constructor. Se usa la propiedad ChangeFormFont
en el codigo del mdtodo C r e a t e W n d , que aparecia anteriormente, para estable-
cer la seleccion inicial del cuadro combinado que depende de la fuente dcl formu-
lario que ale-ja a1 componente. Estc es normalmente el propietario del componente,
aunque podiamos habernos desplazado por el arb01 P a r e n t en busca de un
componente formulario. Este codigo no es perfecto, per0 las verificaciones
A s s i g n e d e i s ofrecen una seguridad adicional.
La propiedad C h a n g e F o r m F o n t y la misma prueba i f tienen una funcion
clave en el metodo C h a n g e d , que en la clase basica desencadena el evento
O n C h a n g e . A1 sobrescribir este mCtodo, ofrecemos un comportamiento
predefinido, que sc puede desactivar cambiando el valor de la propiedad, pero
tambien permite la ejecucion del evento OnChange, para que 10s usuarios de
dicha clase puedan personalizar por completo su comportamiento. El ultimo me-
todo; S e t C h a n g e F o r m F o n t , se ha modificado para refrescar la fuente del
formulario en caso de quc se estd activando la propiedad. Este es el codigo com-
pleto:
procedure TMdFontCombo.Change;
begin
// asigna la fuente a 1 formulario propietario
if FChangeFormFont and Assigned (Owner)
and (Owner is TForm) then
TForm (Owner).Font.Name : = Text;
inherited;
end ;
procedure TMdFontCombo.SetChangeFormFont(const Value: Boolean);
begin
FChangeFormFont : = Value;
// refresca l a fuente
i f FChangeFormFont then
Change;
end ;

Creacion de un paquete
Ahora, tenemos que instalar el componente en el entorno, usando un paquctc.
Para este ejemplo; podemos crcar un nucvo paquctc o utilizar uno cxistcntc, como
el paquete predefinido del usuario.
En cada caso, hay que selcccionar la orden dcl menu Component>lnstall
Component. El cuadro dc dialog0 rcsultantc ticnc una ficha para instalar cl
componentc en un paquetc cxistentc y una ficha para crcar un nucvo paquctc. En
este ultimo caso? simplemente tecleainos un nombre dc archivo y una dcscripcion
dcl paquctc. A1 haccr clic sobrc cl boton OK sc abrc el Package Editor (veasc
la figura 9. l ) , que tiene dos partcs:
La lista Contains: lndica 10s componentes incluidos en el paquete (0, para
scr mas exactos, las unidades que definen esos componentes).
La lista Requires: Indica 10s paquctcs necesarios para dicho paqucte.
Norinalinentc, nucstro paquete necesitara 10s paquetes rtl y vcl (el paquetc
de la bibliotcca cn ticmpo dc cjccucion y el paquete principal VCL), pero
podria neccsitar tambicn cl paqucte vcldb (que contiene la mayoria de las
clases relacionadas con bases dc datos) si 10s componentes del nuevo pa-
quctc rcalizan alguna operacion relacionada con bases de datos.

22 2 s T
I
Compk Add Remove Opl~ons

Figura 9.1. El Package Editor.

I ..
NOTA: Los nombres de Daauetes desde Debhi 6 va no son es~ecificosde I
la version, aunque 10s paquetes compilados tengan todavia el numero de la
version en el nombre del archivo. Para conocer mas detalles acerca de como
- - . ..
I se logra. esro recnlcamenre
1 * . *
poaemos1.
acuair 3. 1 *
mas aaelanre,_ J 1 I
a la seccron que (
I trata 10s cambios en 10s nombres de proyecto y biblioteca. I
Si afiadimos el componente a1 nuevo paquete que acabamos de definir y, a
continuacion, sencillamente compilamos el paquete y lo instalamos (usando 10s
dos botones correspondientes de la barra de herramientas del Package Editor),
veremos aparecer inmediatamente el nuevo componente en la ficha Md de la
Component Palette. El procedimiento Register del archivo de la unidad del
componente inform6 a Delphi sobre donde instalar el nuevo componente. Por
defecto, el mapa de bits utilizado sera el mismo que el de la clase padre, porque no
hemos ofrecido un mapa de bits personalizado (haremos esto en ejemplos poste-
riores). Fijese en que si movemos el raton sobre el nuevo componente, Delphi
mostrara en forma de sugerencia el nombre de la clase sin la letra inicial T.

~ Q u hay
e detras de un paquete?
El Package Editor produce basicamente el codigo fuente del proyecto de
paquete: un tipo especial de DLL creada en Delphi. El proyecto de paquete se
guarda en un archivo con la extension DPK (de Delphi PacKage), mostrado si
pulsamos la tecla F12 en el editor de paquetes. Un proyecto de paquete normal es
como el siguiente:
package M d P a c k ;

...
{ $ D E S C R I P T I O N 'Mastering D e l p h i Package I }

{ $ IMPLICITBUILD ON)

requires
vcl ;

contains
MdFontBox in 'MdFontBox.pa s ';
end.

Como se puede ver, Delphi usa palabras clave del lenguaje especificas para
paquetes: la primera es la palabra clave package (similar a la palabra clave
library que se tratara mas adelante), que introduce un nuevo proyecto de
paquete.
A continuacion, hay una lista con todas las opciones del compilador, algunas
de las cuales han sido omitidas. Normalmente, las opciones de un proyecto Delphi
se guardan en un archivo a parte. Por el contrario, 10s paquetes incluyen todas las
opciones del compilador directamente en su codigo fuente. Entre las opciones de
compilador, esta la directiva de cornpilacion DESCRIPTION,utilizada para que
la descripcion del paquete este disponible en el entorno Delphi. De hecho, despues
de haber instalado un nuevo paquete, su descripcion aparecera en la ficha
Packages del cuadro de dialog0 Project Options, una ficha que se puede
activar tambidn seleccionando el elemento del menu Component>lnstall
Packages. Este cuadro de dialogo aparece en la figura 9.2.

..
Desciition I Campier I Cornplerbtessages 1 Luiker
DrectarmlConddimls Version Info Packages

, Design packages

'KI Eorlard BDE DE Components


14Barlard CLX Database Compmerls

Figura 9.2. Las opciones de proyecto de 10s paquetes

Ademas de dircctivas comunes como D E S C R I P T I O N ; hay otras directivas


especificas para paquetes. Se puede acceder facilmente a las opciones mas comu-
ncs mediante el boton Options del Package Editor. Despues de esta lista de
opciones, estan las palabras clave requires y contains,que listan 10s ele-
mentos que aparecen visualmente en las dos fichas del Package Editor. De
nuevo. la primera es la lista de paquetes que neccsita el actual y la segunda una
lista de las unidades que instala dicho paquete.
Veamos 10s efectos a nivel tecnico de la creacion de paquetes. Ademas del
archivo DPK con el codigo fuente, Delphi genera un archivo BPL con la version
de enlace dinamico del paquete y un archivo DCP con la informacion de simbolos.
En la practica, este archivo DCP es la suma de la informacion de simbolo de 10s
archivos DCU de las unidades contenidas en el paquete.
En tiempo de diseiio, Delphi necesita tanto 10s archivos BPL como DCP, por-
que el primero tiene el codigo real de 10s componentes creados en el formulario de
diseiio y la informacion de simbolos necesaria para la tecnologia Code Insight. Si
enlazamos el paquete de forma dinamica (usandolo como un paquete en tiempo de
ejecucion), el archivo DCP se utilizara tambien para el editor de enlaces y el
archivo BPL se enviara con el archivo ejecutable principal de la aplicacion. En
cambio, si enlazamos el paquete de forma estatica, el editor de enlaces hara refe-
rencia a 10s archivos DCU y solo sera necesario distribuir el archivo ejecutable
final.
Por ese motivo, como diseiiadores de componentes, deberiamos distribuir nor-
malmente a1 menos el archivo BPL, el archivo DCP y 10s archivos DCU de las
Uso del cuadro combinado Fonts
Ahora crearemos un nuevo programa en Delphi para probar el cuadro combi-
nado Font. Vamos a la Component Palette, seleccionamos el nuevo componen-
te y lo aiiadimos a un nuevo formulario. Aparecera un cuadro combinado de
apariencia tradicional. Sin embargoj si abrimos el editor de la propiedad 1terns,
veremos una lista de fuentes instaladas en nuestro ordenador. Para crear un ejem-
plo sencillo, hemos aiiadido un componente Memo a1 formulario con un texto en
el interior. A1 dejar activa la propiedad C h a n g e F o r m F o n t ; no es necesario
escribir ningun otro codigo en el programa, como veremos en el ejemplo. Como
alternativa, podriamos haber desactivado la propiedad y controlado el cvento
OnChange del componente. con un codigo como el siguiente:

El proposito de este programa es solo probar el comportamiento del compo-


nente que acabamos dc crear. Aun asi, el componente no resulta muy util (podia-
mos haber aiiadido unas pocas lineas de codigo a un formulario para conseguir el
mismo efccto), per0 ver algunos componentes sencillos deberia servir de ayuda
para que nos hagamos una idea de lo que implica la construccion de un compo-
nente.

Los mapas de bits de la Component Palette


Antes de instalar este segundo componente. podemos realizar un paso adicio-
nal: definir un mapa de bits para la Component Palette. Si no lo hacemos, la
paleta utiliza el mapa de bits de la clase padre o un mapa de bits de un objeto
predefinido si la clase padre no es un componente instalado. Definir un nuevo
mapa de bits para el componente es facil, una vez que conocemos las reglas.
Podemos crear un mapa de bits con el Image Editor, comenzando un proyecto
nue\ro y seleccionando el tipo de proyecto Delphi Component Resozrrce (DCR,
Recursos de componente de Delphi).
-

TRUCO:Los archivos DCR son sencillamente archivos RES esthdar con


una extension distinta. Si lo preferimos, se pueden crear con cualquier edi-
tor de recursos, como el Borland Resource Workshop, que es realmente
una herramienta mas potente que el ed itor Delphi Image. A1 finalizar la
creaci6n del archivo de recurs;, s61o hlay que dar otro nombre a1 archivo
. .-a
r . .. -n"
m3 para w a r una extension ULK.

Ahora podemos aiiadir un nuevo mapa de bits a1 recurso, seleccionando un


tamaiio de 24x24 piseles y podemos dibujar el mapa de bits. Las otras normas
importantes hacen referencia a la denominacion. En este caso, la norma de deno-
minacion no es solo una convencion, es un requisito para que el IDE pueda encon-
trar la imagen de una clase de componentes dada:
El nombre del recurso de mapa de bits habra de corresponder a1 nombre del
componente, incluida la letra inicial T. En este caso, el nombre del recurso
del mapa de bits deberia scr TMDFONTCOMBO. El nombre del recurso
de mapa dc bits habra de estar en mayuscula (esto es obligatorio).
Si queremos que el Package Editor reconozca e incluya el archivo de recur-
so, el nombre del archivo DCR habra de corresponder a1 nombre dc la
unidad compilada que define el componente. En este caso, el nombre de
archivo deberia ser M d C l o c k . DCR. Si incluimos el archivo de recurso
manualmente, mediante la directiva SR, podemos darle cl nombre que que-
ramos asi como utilizar una extension RES y afiadir multiples mapas de
bits en dl.
Cuando esta listo cl mapa de bits para el componentc, podemos instalar el
componcnte en Dclphi. utilizando el boton InstaII Package de la barra de herra-
mientas dcl Package Editor. Tras csta operacion, la scccion contains del
editor dcberia listar tanto el archivo PAS del componente como el correspondien-
tc archivo DCR. En la figura 9.3 podcmos ver todos 10s archivos (tambien 10s
archivos DCR) de la vcrsion final del paquete MdPack.Si la instalacion DCR no
funciona correctamentc, se puede aiiadir manualmente la sentencia { $ R
unitname. d c r } en el codigo fuente del paqucte.

I. _ .. ... .- - -- . ....-. _ -
. _ .-.
: J Contains
3
Md4ctiveBtn pas D:\md7code\OS\Mdpack
3
Md4rrow.dcr D:\md7code\OS\Mdpack
k
J Md4rrow.pas D:\md7code\OS\Mdpack
MdClock.dc1 D:\md7code\OS\Mdpack
a
MdClock.pas D:\rnd7code\OS\Mdpack
E 9
MdClockFfarns D:\rnd7code\OS\Mdpack
MdCollect.pas D:\md7code\OS\Mdpack
MdfontCombo.pas D:\md7code\OS\Mdpack
9
MQntfTest.pas D:\md7code\OS\M&ack
MdLifl4ct pas D.\rnd7de\OS\Md~zck
K 3
MdLiflDial D:\rnd7code\OS\Mdpack
9 MdListDddcr D:\rnd7code\OS\Mdpack
b D:\md7codeU!S\Mdpack
MdNumEd pas D:\md7code\OS\Mdpack
MdPersonalData... D:\md?code\OS\Mdpack
MdSounB dcr D:\rnd7code\OS\Mdpack
MdSounB.pas D:\rnd7code\OS\Mdpack
3 0 Requires
~N.dcp
vcl.dcp

Figura 9.3. La seccion Contains del Package Editor rnuestra tanto las unidades
incluidas en el paquete como 10s ficheros de recursos de componente.
Creacion de componentes compuestos
Los componentes no existen de un mod0 aislado. Los programadores usan a
menudo 10s componentes en conjuncion con otros. codificando la relacion en uno
o mas controladores de eventos. Una tecnica alternativa es crear componentes
compuestos, lo que puede encapsular esta relacion y facilitar su manejo. Hay dos
tipos diferentes de componentes compuestos:
Componentes Internos: Son creados y gestionados por el componentc
principal, que puede mostrar algunas de sus propiedades y eventos.
Componentes Externos: Se conectan usando propiedadcs. Automatizan
la interaccion entre componentes separados, que pueden estar en el mismo
formulario o diseiiador o en uno diferente.
En ambos casos, el desarrollo sigue algunas reglas estandar. Una tercera alter-
nativa, menos esplorada, implica el desarrollo de contenedores de componentes,
que pueden interactuar con 10s controles hijo. Este es un tema mas avanzado por
lo que no se tratara aqui.

Componentes internos
El componente en el que nos centraremos ahora es un reloj digital. Este ejem-
plo tiene algunas caracteristicas muy interesantes. Primero, tiene un componente
dentro de otro componente (un Temporizador). Segundo, muestra la tecnica de
datos en vivo: tendremos la posibilidad de ver un comportamiento dinamico (la
actualizacion del reloj) incluso en tiempo de diseiio, como ocurre, por ejemplo,
con componentes relacionados con datos.

NOTA: La primera caracteristica se ha convertido en algo mis relevante


desde Delphi 6, puesto que el Object Inspector de esta ultima version,
permite exponer propiedades de subcomponentes directamente.

Dado que el rcloj digital ofrecera una salida con un cierto texto, hemos consi-
derado el heredar de la clase TLabel.Sin embargo, esto permitiria que el usua-
rio cambiase el titulo de la etiqueta (es decir, el testo del reloj). Para evitar este
problema, sencillamente hemos utilizado el componente TCustomLabel como
clase padre.
Un objeto TCustomLabel tiene las mimas capacidades que un objeto
TLabel,per0 pocas propiedades publicadas. En otras palabras, una clase que
hereda de TCUS tomLabel puede decidir que propiedades deberian estar dispo-
nibles y cuales deberian permanecer ocultas.
-- _I

NOTA: L a mayoria de 10s componentes Delphi, sobrc todo 10s basados en


Windows, tienen una clase basica TCustomXxx, que implementa toda la
funcionalidad pero expone solo un conjunto limitado de propiedades. Here-
dar de estas clases basicas es la forma estandar de exponer solo algunas de
las propiedades de un componente en una version personalizada. De hecho,
no se pueden ocultar las propiedades publicas ni publicadas de una clase
b h i c a , a no ser que las ocultemos definiendo una nueva propiedad con el
mismo nombre en la clase heredada.

Con versiones previas de Dclphi, el componentc debia dcfinir una propiedad


nucva. A c t i v e , envolvicndo la propiedad E n a b l e d del Tcmporizador. Quc
una propicdad sca envolvcr~lesignifica que sus mctodos set y gel lccn y cscribcn
el valor de la propiedad er?vuelto, que pcrtcnece a un componente intcrno (gcnc-
ralmcnte, una propicdad envolvcnte no contiene datos locales). Veamos cl codigo
de cstc caso cspccifico:
f u n c t i o n T M d C l o c k - G e t A c t i v e : Boolean;
begin
Result : = FTimer.Enabled;
end;

p r o c e d u r e TMdClock.SetActive (Value: Boolean);


begin
FTlmer . Enabled : = Value;
end;

Publicacion de subcomponentes
Ya desdc Delphi 6. podemos exponer simplemcnte el componente completo (cl
tcmporizador) en una sola propiedad, quc ampliara norn~allnenteel Object Ins-
pector y pcrmitira a1 usuario definir cada una dc sus subpropiedades e incluso
controlar sus evcntos.
Vcamos la dcclaracion de tipo completa del componente T M d C l o c k , con cl
subconiponentc dcclarado en 10s datos privados y espucsto como una propiedad
publicada (en la ultima linea):
type
TMdClock = c l a s s (TCustomLabel)
private
FTimer : TTimer ;
protected
p r o c e d u r e Updateclock (Sender: TOblect);
public
constructor Create (AOwner: TComponent); override;
published
property Align;
property Alignment;
property Color;
property Font;
property Parentcolor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint ;
property Transparent;
property Visible;
property Timer: TTimer read FTimer;
end;

La propiedad T i m e r es solo de lectura, puesto que no queremos que 10s usua-


rios seleccionen otro valor para cste componente en el Object Inspector (ni que
desvinculen el componentc eliminando el valor de esta propiedad). Dcsarrollar
conjuntos de subcomponentes quc pueden ser usados alternativamente es posible,
per0 afiadir soporte de escritura para esta propiedad de un mod0 seguro no es
scncillo (considerando quc 10s usuarios de nuestros componentes puedcn no ser
programadores cspertos en Delphi). Por ello, es conveniente limitarse a propieda-
dcs de solo lectura para subcomponentes.
Para crcar cl tcmporizador. tenemos que sobrescribir el constructor del com-
p o n e n t ~rc10.j. El metodo c r e a t e llama a1 metodo correspondiente de la clase
basica y crea el objeto temporizador, instalando un controlador para su evento
OnTimer:
constructor T M d C l o c k - C r e a t e (AOwner: TComponent);
begin
i n h e r i t e d C r e a t e (AOwner);
// c r e a e l o b j e t o t e m p o r i z a d o r i n t e r n o
FTimer := TTimer .Create ( S e l f ) ;

FTimer .Name := ' C l o c k T i m e r ';


FTimer.OnTimer : = Updateclock;
FTimer. Enabled : = True;
FTimer . SetSubComponent ( T r u e );
end:

El codigo da un nombre a1 componente, para mostrarlo en el Object Inspec-


tor (vease la figura 9.4) y llama a1 metodo especifico S e t S u b C o m p o n e n t . No
necesitamos un destructor, sencillamente porque el objeto F T i m e r t i m e el
T M ~ ~ l o ccomo k propietario (como indica el parametro de su constructor
C r e a t e ) , por lo tanto se destruira automaticamente cuando se destruya el com-
ponente reloj.

NOTA: El efecto real de la llamada a1 m&odo SetSubCom~onenten el


codigo anterior es que define un i dicador interno, guardado en el conjunto
de propiedades Components t l e . El indicador (csSubComponent)
L.

afecta a! ; i i E Z Z s t r e a m i n g , que permite-que d's u b c e n t e ~ G u i


propiedades se guarden en el archivo DFM. De hecho, el sistema de streaming
ignora por defecto 10s componentes que no posee el formulario.

I
Pfoyr~ks Events 1- - -

I
.. -

1 ~elc 120 A
Name Mdnockl
ParelllCobr True

Figura 9.4. El Object Inspector puede ampliar automaticamente 10s subcomponentes,


mostrando sus propiedades, como en el caso de la propiedad Timer del componente
TMdClock.

La parte claw del codigo del componente cs el proccdimiento U p d a t e c l o c k ,


que consiste en una sola sentencia:
procedure TMdLabelClock.UpdateC1ock (Sender: TObject);
begin
/ / d e f i n e l a h o r a a c t u a l como t i t u l o
Caption : = TimeToStr ( T i m e ) ;
end;

Estc metodo usa C a p t i o n , que es una propiedad no publicada, para quc un


usuario del componentc no pueda modificarlo en el Object Inspector. El resul-
tad0 de la scntencia es mostrar la hora actual. Esto ocurre continuamente, porque
el mctodo esta conectado a1 evento O n T i m e r del temporizador.

NOTA: Pueden editarse 10s eventos de 10s subcomponentes en el Object


Inspector, de mod0 que un usuario pueda manejarlos. Si manejamos el
. .- . - --

evento lnternamente, como hemos hecho en T M d L a b e l C o c k, un usuarlo


podra sobrecargar el comportamiento controlando el evento, en este caso
O n T i m e r . Generalmente, la solucion esta en definir una clase derivada
para el componente interno, sobrecargando sus mktodos virtuales, como el
mCtodo T i m e r de la clase T T i m e r . Pero en este caso, esta tecnica no
funcionara porque Delphi activa el temporizador s61o si tiene un controla-
aor unlao. 31soorecargarnos el meroao vlrrual s. ~- proveer
J ! 0 I I *
n ~ el
I .
r .I ~ ~
conrrolaaor ae-I- . ~ - * - - l - J - - 3 -

eventos (lo correct0 en un subcomponente) el temporizador no funcionara.


Componentes externos
Cuando un componente referencia un componentes externo, no crea este com-
ponente por si mismo (que es la razon por la que se le llama externo). Es el
programador que usa 10s componentes quien crea ambos separadamente (arras-
trandolos desde la Paleta de Componentes a un formulario, por ejemplo) y
conecta 10s dos componentes usando una de sus propiedades. Por ello, podemos
decir que una propiedad de un componentes referencia a un componente enlazado
externamente. Esta propiedad debe ser de un tipo de clase que hereda de
TComponent.
Para mostrarlo, hemos creado un componente no visual que puede mostrar
informacion acerca de una persona en una etiqueta y actualizarla automaticamente.
El componente publica estas propiedades:
type
TMdPersonalData = class (TComponent)

published
property FirstName: s t r i n g r e a d F F i r s t N a m e w r i t e SetFirstName;
property LastName: s t r i n g r e a d FLastName w r i t e SetLastName;
property Age: Integer r e a d FAge w r i t e SetAge;
property Description: string r e a d GetDescription;
property OutLabel: TLabel r e a d FLabel w r i t e SetLabel;
end ;

Hay cierta informacion basica y una propiedad de solo lectura D e s c r i p t i o n


que devuelve toda la informacion de una vez. La propiedad O u t L a b e l esta
conectada con un campo local privado llamado F L a b e l . En el codigo del com-
ponente, hemos usado esta etiqueta externa por medio de la referencia interna
F L a b e l , como aqui:

p r o c e d u r e TMdPersonalData.UpdateLabe1;
begin
i f Assigned (FLabel) t h e n
FLabel.Caption : = Description;
end;

Este metodo U p d a t e L a b e l es ejecutado cada vez que una de las otras pro-
piedades cambia (como puede verse en tiempo de diseiio en la figura 9 . 5 ) , como
podemos ver aqui:
p r o c e d u r e TMdPersona1Data.SetFirstName (const Value: string);
begin
i f FFirstName <> Value t h e n
begin
FFirstName : = Value;
UpdateLabel;
end ;
end;
if FLabel <> Value then
begin
FLabel : = Value;
if FLabel < > n i l then
begin
UpdateLabel;
FLabel-FreeNotification ( S e l f ) ;
end;
end ;
end ;

s usar la notification opuesta (op I n s e r t)para


icamente a1 ailadirles a1 mismo formulario o
aisenaaor. A pesar ae que Csta es una tknica util en muchas situaciones
comunes, no se usa habitualmente. Esto puede deberse a que es mas 16gico
crear editores de propiedades y componentes especificos para dar soporte a
overaciones de tiempo de diseiio, que a empotrar codigo dentro de 10scorn-
ponentes.

Referencias a componentes mediante interfaces


A1 referirnos a componentes externos, hemos estado tradicionalmente limita-
dos en la jerarquia a un nivel por debajo del actual. Por ejemplo, el componente
que hemos creado en la seccion anterior, puede referirse so10 a objetos de la clase
TLabel o de las clases que heredan de ella, a pesar de que seria logico poder
publicar la inforrnacion a otros componentes. Delphi 6 aiiadio soporte para una
caracteristica interesante que tiene el potencial para revolucionar algunas areas
del VCL: referencias a componentes de tip0 interfaz.

NOTA: Esta caracteristica se usa raramente en Delphi 7. Dado que es


probablemente dernasiado tarde para actualizar la arquitectura de compo-
nentes relacionados con datos que utilizan interfaces, todo lo que podemos
esperar es que sera utilizada para expresar fbturas relaciones complejas
dentro de la biblioteca.

Si tenemos componentes que ofrecen una interfaz concreto (aunque no sean


parte de la misma subjerarquia), podemos declarar una propiedad de tipo interfaz
y asignarle cualquiera de esos componentes. Por ejemplo, supongamos que tene-
mos un componente no visual asociado a un control para mostrar su resultado,
algo parecido a lo que vimos en la seccion anterior. Habiamos usado una tecnica
tradicional, uniendo el componente a una etiqueta, per0 ahora podemos definir
una interfaz asi:
tYPe
IMdViewer = interface
['{97668600-8E4A-4254-9843-59B98FEE6C54}']
procedure View (const str: string);
end ;

Un componente puede usar su interfaz viewer para mostrar su resultado a otro


control (de cualquier tipo). El listado 9.2 muestra como declarar un componente
que usa esta interfaz para referirse a un componente externo.

Listado 9.2. Un cornponente que referencia un cornponente externo usando una


interfaz.

tYPe
TMdIntfTest = class (TComponent)
private
FViewer: IViewer;
FText: string;
procedure SetViewer (const Value: IViewer);
procedure SetText (const Value: string);
protected
procedure Notification (AComponent: TComponent;
Operation: TOperation) ; override;
pub1i shed
property Viewer: IViewer read FViewer write SetViewer;
property Text: string read FText write SetText;
end;

{ TMdIntfTest }

procedure TMdIntfTest.Notification (AComponent: TComponent;


Operation: TOperation) ;
var
int f : IMdViewer;
begin
inherited;
if (Operation = opRemove) and
(Supports (AComponent, IMdViewer, intf) ) and (intf =
FViewer ) then
begin
FViewer : = nil;
end;
end;

procedure TMdIntfTest.SetText(const Value: string);


begin
FText : = Value;
i f Assigned (FViewer) then
FViewer .View (FText);
end;

procedure TMdIntfTest.SetViewer(const Value: IMdViewer);


var
icomp: I I n t e r f a c e C o r n p o n e n t R e f e r e n c e ;
begin
if FViewer <> Value t h e n
begin
FViewer : = Value;
FViewer.View(FText);
i f Supports (FViewer, IInterfaceComponentReference,
iComp) t h e n
iComp.GetComponent.FreeNotification(Se1f);
end;
end;

El uso de una interfaz implica dos diferencias relevantesj comparado con el


traditional uso de un tip0 clase para referenciar un componente esterno. Primero,
en el metodo Notification. debemos estraer la interfaz del componente pa-
sado como parametro y compararlo con la interfaz que ya tenemos. Segundo, para
llamar a1 metodo FreeNotification,debemos ver si el objeto que pasamos
como parametro soporta la interfaz I Inter facecomponentReference.
Esto se declara en la clase TComponent y ofrece un mod0 de volvernos a referir
a1 componente (Getcomponent)y llamar a sus metodos. Sin esta a y d a hubie-
ramos tenido que aiiadir un metodo similar a nuestra interfaz personalizada, por-
que a1 extraer una interfaz de un ob-jeto no hay forma automatica de referirse de
nucvo a1 objeto.
Ahora que tenemos un componente con una propiedad interfaz podemos asig-
narlo a cualquier componente (de cualquier parte de la jerarquia VCL) aiiadiln-
dolc la interfaz Iviewer e implemcntando el mttodo View.Veamos un ejemplo:
type
TViewerLabel = c l a s s (TLabel, IViewer)
public
p r o c e d u r e View(str: S t r i n g ) ;
end:

p r o c e d u r e TViewerLabel.View(const str: String);


begin
Caption : = str;
end:

'I -
Creaci6n de componentes compuestos con marcos
En lugar de crear un componente compuesto utilizando esta tecnica, po-
r l r i n m n c h a h p r i i c a r l n iin m a r r n I .nc m a r m c h d~ mmnn-
a r ~ rn l ~rll ~ c a r r n l l n

aiiadlendolo a1 KepOsltory o creando una plantllla usando la orden A d d


t o Palette del menu de mCtodo abreviado del marco.
Como alternativa, podemos querer compartir el marco situandolo en un
paquete y registrandolo como un componente. Tecnicamente, esto no es
complicado: aiiadimos un procedimiento Register a la unidad del mar-
co, aiiadimos la unidad a un paquete y lo escribimos. El nuevo componente/
marco aparece en la Component Palette igual que cualquier otro compo-
nente. Cuando colocamos este componente/marco en un formulario vemos
sus subcomponentes. No podemos seleccionar estos subcomponentes con
un clic de raton en el Form Designer, per0 si en la Object Tree View.
A pesar de todo, cualquier cambio en estos componentes en tiempo de dise-
fio se perdera a1 arrancar el programa o a1 guardar y recargar el formulario,
porque 10s cambios en esos subcomponentes no se conservan (a diferencia
de lo que ocurre con 10s marcos estiindar colocados en forrnularios).
Veremos como aplicar una tecnica bastante sencilla para utilizar marcos en
paquetes, demostrada mediante el componente MdFr amedC l o c k. La idea
consiste en convertir 10s componentes que posee el formulario en
subcomponentes, llamando a1 metodo Set SubComponent . Tarnbien he-
mos expuesto 10s componentes internos con propiedades, aunque no sea
obligatorio (pueden seleccionarse la Object Tree View). Esta es la decla-
ration del componente y el codigo de sus mttodos:
type
TMdFramedClock = class (TFrame)
Lahell: TLabel;
Timerl: TTimer;
Bevell: TBevel;
procedure TimerlTimer(Sender: TObject);
publia
constructor Create (AOnwer: TComponent ) ; override;
pub1 i shed
property SubLabel: TLabel read Labell:
property SubTimer: TTimer read Timerl;
end;
constructor TMdFramedClock.Create(A0nwer: TComponent);
begin
inherited;
Timer1.SetSubComponent (true);
Label1.SetSubComponent (true);
end;
procedure TMdFramedClock.TimerlTimer(Sender: TObject):
begin
Labell. Caption := TimeToStr (Time);
end;

En este caso. en oposicibn a1 caso del reloj, no es necesario d e f i r las


propiedades del temporizador ni conectar el evento temporizador a su fun-
cion controlador manualmente, puesto que eso se realiza visualmente y se
guarda en el archivo DFM del marco. Fijese tambikn en que no hemos
Component en el), por 10 que podemos intentar editarlo en tiempo de
disefio y ver que todos 10s cambios se pierden, como hernos meecionado
antes.
Tras haber i~lstaladoeste marco/componente, pademos usarlo en d q u i e r
aplicacion. En este caso concreto, &sde el momento en quc tiqjm.105 el
marco en el formulario, el temporizador comamra a iwttdbm k etiqueta
con la hora actual. Sin embargo, todavia se puede controh el evento
OnTimer y el IDE de Delphi (que reconoce que el cornpone& BstB en el
marco) definlra un m h d o con este c6digo predefinido:
procedure TForml.MdFramedCLocklTimerlTimer(Sender: TObject);
bagi n
MdFramedClockl.TimerlTimer(Sender);
end;

Desde el momento en que se conecta el temponzador, &haq en tiempo de


diseilo, el reloj en vivo se detendrfi, porque se descollecta su contrdador de
eventos 0rigma.I. Sin embargo, despub de compib-g cjerc,.latarel pr~grama,
se restaurara el comportamiento ori& fa1d m , ari nahrraniodIa linea
anterior) y tambib se ejecutara c6digo persod* adic;ional, Este corn-
portmiento es cxa&mente lo qqe de la9 marcos. Podernos en-
contra una demostracih c o r n p l e t a z m marMlcomponente en el
ejemplo FrameCbck.
Como conclusi6e, podemos afinpar que @mica no cs'en absolutb Bt
neal. Es mucho mejor que en v a r h w s a&d~res & Delphi, .en la que 10s
marcos dentro de 10s paquetes w se p d i a n util izar. per0 pfobablernente no
merezca la pena el esfuerm. En el caso de pcquefias ~qanbacioneso gru-
pos de trabajo es mejor usar mmeos sinipl;es dnmcenados en el Repostbiy.
En orgapiswkmes nub pp'a9des spa disbljbuk los,marcos a una audien-
cia mayor, mkha g&te preferid Great mi eomponentes de rnodo tradicio-
d, 9jIE maqiw. ETT palabaras,esperamoq que Borland ofrezca un
sopork mis,(ieqpple<o para 4 desanollo vjsual de mrnponentes paquete
basado m a&tk

Un componente grafico complejo


Vamos a construir un componente grafico de flecha. Se puede usar dicho com-
ponente para representar un flujo de informacion o una accion. Este componente
es bastante complejo, por lo que presentaremos su creacion paso a paso en lugar
de ir directamente a1 codigo fuente completo. El componente que hemos aiiadido
a1 paquete MdPack es la version final de este proceso, que demuestra muchos
conceptos importantes:
La definicion de nuevas propiedades enumeradas, basadas en tipos de da-
tos enumerados.
La implementacion del metodo P a i n t del componente, que ofrece su
interfaz de usuario y deberia ser lo suficientemente generic0 como para
acomodar todos 10s valores posibles de las diversas propiedades, como su
W i d t h y H e i g h t . El metodo P a i n t tiene una funcion importante en
este componente grafico.
El uso de propiedades de clases derivadas de TPer s i s t e n t , como T Pen
y TBrush, y 10s temas relacionados con su creation, destruccion y con-
trol de sus eventos OnChange de forma interna en nuestro componente.
La definicion de un controlador de eventos personalizado para el compo-
nente, que responda a la entrada del usuario (en este caso, hacer doble clic
en la punta de la flecha). Para esto, sera necesario controlar directamente
10s mensajes de Windows y el uso de la API de Windows para partes
graficas.
El registro de propiedades en categorias del Object Inspector y la defini-
cion de una categoria personalizada.

Definicion de una propiedad enumerada


Tras haber creado el nuevo componente con el asistente de componentes y
seleccionar TGr a p h i c C o n t r o 1como la clase padre, podemos comenzar a per-
sonalizar el componente. La flecha puede apuntar en cualquiera de las cuatro
direcciones: arriba, abajo, izquierda o derecha. Un tip0 enumerado expresa estas
opciones:
'=me
TMdArrowDir = (adup, adRight, adDown, adleft);

Este tipo enumerado define un miembro de datos privado del componente, un


parametro del procedimiento utilizado para cambiarlo y el tip0 de la propiedad
correspondiente.
La propiedad A r r o w H e i g h t establece el tamaiio y F i l l e d si se rellena la
punta de la flecha:
'=me
TMdArrow = c l a s s (TGraphicControl)
private
FDirection: TMdArrowDir;
FArrowHeight: Integer;
FFilled: Boolean;
procedure SetDirection (Value: TMd4ArrowDir);
procedure SetArrowHeight (Value: Integer) ;
procedure SetFilled (Value: Boolean) ;
published
property W i d t h d e f a u l t 50;
property Height d e f a u l t 20;
property Direction: TMd4ArrowDir
read FDirection w r i t e SetDirection d e f a u l t adRight;
property ArrowHeight: Integer
read FArrowHeight w r i t e SetArrowHeight d e f a u l t 10;
property Filled: Boolean r e a d FFilled w r i t e SetFilled
d e f a u l t False;

cuando se coloca en un formulario, su t a m d o sera un h i c o pixel. Por esa


r d n , es importante aiiadir un valor predefinido para las propiedades Width
-. -.-I ---- J- ---- :-2-J--
y n e l g-nLrL; -.
7.-J d-C-:-
y aennlr I-- 2-
los ~iunposUG -I---
CMSG C u r n o v a 1 w E ; s ue PIVPIGU~SIGS
predefinidas en el constructor de la clase.

Las tres propiedades personalizadas se leen directamente del campo corres-


pondiente y se escriben usando tres metodos Set, que tienen todos la misma es-
tructura estandar:
procedure TMdArrow.SetDirection (Value: TMdArrowDir);
begin
i f FDirection <> Value then
begin
FDirection : = Value;
ComputePoints;
Invalidate;
end;
end;

Observe que pedimos a1 sistema que pinte de nuevo el componente (llamando a


Invalidate) solo si la propiedad cambia su valor y despues de llamar a1
metodo ComputePoints, que calcula el triangulo que delimita la punta de
flecha. De no ser asi, se pasa el codigo por alto y el metodo finaliza de forma
inmediata. Esta estructura de codigo resulta muy comun y la usaremos en el caso
de la mayoria de 10s procedimientos Set de propiedades.
Debemos recordar definir 10s valores predefinidos de las propiedades en el
constructor de componentes:
c o n s t r u c t o r T M d A r r o w - C r e a t e (AOwner: TComponent);
begin
/ / llama a 1 c o n s t r u c t o r padre
i n h e r i t e d Create ( A O w n e r );
// d e f i n e 10s valores predefinidos
FDirection : = adRight;
W i d t h : = 50;
Height : = 20;
FArrowHeight : = 10;
FFilled : = False;
Como hemos dicho anteriormente, el valor predefinido especificado en la de-
claracion de propiedad se usa solo para decidir si se guarda el valor de la propie-
dad en el disco.
El constructor create se define en la parte publica de la definicion de tip0
del nuevo componente y se indica mediante la palabra clave override,ya que
sustituye el constructor virtual Create de TComponent.Es fundamental re-
cordar esta palabra clave, puesto que de no ser asi, cuando Delphi crea un compo-
nente nuevo de esta clase, llamara a1 constructor de la clase basica; en lugar de a1
que hemos escrito para la clase derivada.

Convenciones de denominacl6n de propiedades


En la d&ici&n del componente Arrow, fijese en el uso de diversas con-
venciones de denomination de propiedades, m6todos de acceso y campos.
Veamos un resumen:
Los nombres de propiedades s e r h legibles y tendrh significado.
Cuando se use un campo de datos privado para contener un valor, el
campo deberia denominarse con una F (defield) maytiscula a1 princi-
pio, seguida del nombre de la propiedad correspondiente.
Cuando se use una hncion para cambiar el valor de la propiedad, dicha
funci6n deberia llevar a1 principio la paIabra Set, seguida del nombre
de la propiedad correspondiente.
Una funcidn correspondente utillzada para leer la propledad deberia
t principio, de nuevo seguida por el nombre de la
llevar la palabra ~ e a1
propiedad.
Estas directrices bque 10s programas resulten m h fhiles de leer. El
cornpilador no obliga a ello. Estas reglas son las seguidas por el mecanismo
que utiliza Delphi para completar 10s nombres de las clases.

Escritura del metodo Paint


Para dibujar la flecha en distintas direcciones y con diferentes formatos, sera
necesario usar algo mas de codigo. Para realizar el pintado personalizado,
sobrescribimos el metodo Paint y utilizamos la propiedad protegida canvas.
En lugar de calcular la posicion de 10s puntos que delimitan la punta de la
flecha en el codigo de dibujo que 10s ejecutara, hemos escrito una funcion a parte
para calcular la zona de la punta de flecha y guardarla en una matriz de puntos
definidos entre 10s campos privados del componente como:
FArrowPoints: array [ 0 . . 3 ] o f TPoint;
Estos puntos 10s establece el metodo privado Cornput e p o i n t s, llamado
cada vez que cambia alguna de las propiedades del componente. Veamos un ex-
tracto de su codigo:
procedure TMdArrow.ComputePoints;
var
XCenter, YCenter: Integer;
begin
// c a l c u l a 10s p u n t o s d e l a p u n t a d e f l e c h a
YCenter : = (Height - 1) div 2;
XCenter : = (Width - 1) div 2;
case FDirection of
adup: begin
FArrowPoints [0] := Point (0, FArrowHeight) ;
FArrowPoints [I] : = Point (XCenter, 0) ;
FArrowPoints [2] := Point (Width-1, FArrowHeight) ;
end;
// y a s i sucesivamente para o t r a s d i r e c c i o n e s

El codigo calcula el centro de la zona del componente (dividiendo sencillamen-


te las propiedades H e i g h t y W i d t h entre dos) y, a continuation, lo usa para
establecer la posicion de la punta de flecha. Ademas de cambiar la direccion u
otras propiedades, es necesario refrescar la posicion de la punta de flecha cuando
cambia el tamaiio del componente. Lo que podemos hacer es sobrescribir el meto-
do S e t B o u n d s del componente, a1 que llama la VCL cada vez que cambian las
propiedades L e f t , Top, W i d t h y H e i g h t del componente:
procedure TMdArrow.SetBounds(ALeft, ATop, AWidth, AHeight:
Integer) ;
begin
inherited SetBounds (ALeft, ATop, AWidth, AHeight);
ComputePoints;
end;

Cuando el componente sabe la posicion de la punta de flecha, su codigo de


pintado resultara mas sencillo. Veamos un extract0 del codigo del metodo P a i n t :
procedure TMdArrow-Paint;
var
XCenter, YCenter: Integer;
begin
// c a l c u l a e l c e n t r o
YCenter : = (Height - 1) div 2;
XCenter : = (Width - 1) div 2;
/ / d i b u j a l a lined d e l a f l e c h a
case FDirection of
adup: begin
Canvas .MoveTo (XCenter, Height-1) ;
Canvas-LineTo (XCenter, FArrowHeight);
end;
// y a s i s u c e s i v a m e n t e para l a s demds d i r e c c i o n e s
end ;

// d i b u j a l a punta d e flecha y, e n u l t i m o caso, l a p i n t a


if FFilled then
Canvas.Polygon (FArrowPoints)
else
Canvas.PolyLine (FArrowPoints);
end;

Podemos ver el resultado de este componente en la figura 9.6.

Figura 9.6. El aspect0 del componente Arrow.

Adicion de las propiedades TPersistent


Para que el resultado del componente sea mas flexible, le hemos aiiadido dos
propiedades nuevas, definidas con un tipo de clase (sobre todo, un tip0 de datos
T P e r s i s t e n t , que define objetos con 10s que Delphi pueda realizar streamrng
facilmente). Dichas propiedades son un poco mas complejas, porque el compo-
nente tiene que crear y destruir estos objetos internos. En esta ocasion, exportare-
mos tambien 10s objetos internos utilizando algunas propiedades, para que 10s
usuarios puedan cambiarlas directamente desde el Object Inspector. Para ac-
tualizar el componente cuando cambien estos subobjetos, tambien necesitaremos
su propiedad interna O n c h a n g e . Veamos la definicion de las dos propiedades
nuevas del tipo T P e r s i s t e n t y 10s otros cambios de la definicion del compo-
nente de clase:
type
TMdArrow = c l a s s (TGraphicControl)
private
FPen: TPen;
...
procedure SetPen (Value: TPen) ;
procedure RepaintRequest (Sender: TObject);
published
property Pen: TPen read FPen write SetPen;
end;
Lo primer0 es crear 10s objetos en el constructor y definir su controlador de
eventos OnChange:
constructor TMdArrow.Create (AOwner: TCornponent);
begin
...
/ / crea e l l d p i z y e l pincel
FPen : = TPen.Create;
/ / d e f i n e u n c o n t r o l a d o r p a r a e l e v e n t o OnChange
FPen.OnChange : = RepaintRequest;
end;

Estos eventos OnChange se producen cuando una de las propiedades del


lapiz cambia, todo lo que hay que hacer es pedir a1 sistema que pinte de nuevo
nuestro componente:
procedure TMdArrow.RepaintRequest (Sender: TObject);
begin
Invalidate;
end;

Tambien debemos aiiadir un destructor a1 componente, para eliminar el objeto


grafico de la memoria (y liberar sus recursos de sistema). Todo lo que tiene que
hacer el destructor es llamar a1 metodo F r e e del objeto P e n .
Las propiedades relacionadas con estos dos componentes requieren cierto con-
trol especial: en lugar de copiar el punter0 a 10s objetos, deberiamos copiar 10s
datos internos del objeto pasado como parametro. La operacion estandar := co-
pia 10s punteros, por lo que en este caso tenemos que usar en cambio el metodo
Assign.

procedure TMdArrow.SetPen (Value: TPen) ;


begin
FPen.Assign (Value);
Invalidate;
end:

Muchas clases TPe r s i s t e n t tienen un metodo A s s i g n que deberia utili-


zarse cuando hay que actualizar 10s datos de dichos objetos. Ahora, para utilizar
realmente el lapiz para dibujar, tenemos que modificar el metodo P a i n t , confi-
gurando las propiedades del componente c a n v a s como el valor de 10s objetos
internos antes de dibujar alguna linea (podemos ver un ejemplo del nuevo resulta-
do del componente en la figura 9.7):
procedure TMdArrow.Paint;
begin
// usa e l l d p i z a c t u a l
Canvas.Pen : = FPen;

Como C a n v a s usa una rutina de asignacion para el objeto lapiz, no solo


estamos almacenando una referencia a1 lapiz en un campo del C a n v a s , sin0 que
estamos copiando toda su informacion. Esto significa que podemos destruir el
objeto lapiz local (FPen) sin problemas y que modificar F P e n no afectara al
lienzo hasta que P a i n t sea llamado y el codigo anterior se ejecute otra vez.
.
I

-
Figura 9.7. El resultado del cornponente Arrow con un lapiz grueso y una trarna
especial.

Definicion de un nuevo evento personalizado


Para completar el desarrollo del componente Arrow, podemos aiiadir un even-
to personalizado. Generalmente, 10s componentes nuevos utilizan 10s eventos de
sus clases padre. Por ejemplo, en este componente, hemos creado algunos eventos
estandar, declarandolos de nuevo en la parte publicada de la clase:
type
TMdArrow = class (TGraphicControl)
pub1 i shed
property OnClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;

Gracias a esta declaration, 10s eventos anteriores (declarados originalmente en


una clase padre) estaran ahora en el Object Inspector cuando se instale el
componente. Sin embargo, a veces es necesario un evento personalizado. Para
definir un evento nuevo, primer0 hay que asegurarse de que existe un tip0 de
puntero de metodo valido para el evento; si no, debemos definir un tipo de evento
nuevo. Este tip0 es un tipo de puntero de metodo. En ambos casos, tenemos que
aiiadir a la clase un campo del tipo del evento. Veamos la definicion aiiadida en la
parte privada de la clase TMdArrow:

En este caso, hemos usado el tipo TNo t i fy E v e n t , que solo tiene un parametro
S e n d e r y Delphi lo utiliza con muchos eventos, comoOnClick y O n D b l C l i c k .
Usando este campo hemos definido una propiedad publicada muy simple, con
acceso direct0 al campo:
property OnArrowDblClick: TNotifyEvent
read FArrowDblClick write FArrowDblClick;

(Una vez mas utilizamos la convencion estandar al dar a1 evento un nombre


que empieza con On). El puntcro de metodo FArrowDblCl i c k se activa (eje-
cutando la funcion correspondiente) dentro del metodo dinamico especifico
ArrowDblClick. Esto ocurre solo si se ha especificado un controlador de
eventos en el programa que usa el componente:
procedure TMdArrow.ArrowDb1Click;
begin
if Assigned (FArrowDblClick) then
FArrowDblClick (Self);
end;

TRUCO: El uso de Self como ~a&nit& dkla inmcacibn dcl mttodo del :

controhdor de eventos gmm&a$uq;4& b a d 0 cl rnttodo, su parihctro


S e n d e r ae referira a1 objeto que 8cti~lSfdevento, @neralmcntc, un usua- ,.
rio d d agqmnentes.

Uso de llamadas de bajo nivel a la API de Windows


El metodo fArrowDblClic k se define en la parte protegida de la definition
de tipo para permitir que las subclases futuras lo llamen y lo modifiquen. Basica-
mente, el controlador del mensaje de Windows wm LButtonDblCl k llama a1
metodo ArrowDblClick, pero s61o si se hizo doble clic dentro de la punta de
flecha. Para probar csta caracteristica, podemos usar alguna de las funciones de
la zona Windows API.
. - . -
NOTA: Ona z- en eoestc contextoes una paflc &la pantalla mdeada por '
algGn tip0 de forma. Por ejdplo. pademos clear m a zona poligonal que
utilice 10s tres vtrtices del trislt$lo & la&qMil i(c flgcha. El rinico probb-
ma es que para rellenar la superficie correctamegte, ,debemos definir una
'

matriz de T P o i n t s en Ia dirtccih deli. w,asde ~ l q[ iv k e la descijp ;


cion d e C r e a t e P o l y g o n a l R g n enli+ayud.azJcla q ~ ~ & x ~ i n & w s ~ a r 3
conocer 10s detalles de esta tknica). E h o es b gne hiein-bs en el m d t d o
ComputePoints.

Una vez definida la zona, podemos probar si el punto en cl que se hizo doble
clic csta dentro de la zona, utilizando la llamada Pt InRegion de la API. Pode-
mos utilizar el codigo fuente completo de este procedimiento del siguiente listado:
procedure TMdArrow.WMLButtonDb1Clk (
var M s g : TWMLButtonDblClk) ; // mensa je wm-LBut tonDblClk;
var
HRegion: HRgn;
begin
// r e a l i z a e l c o n t r o l p r e d e f i n i d o
inherited;

// c a l c u l a l a zona d e l a p u n t a d e f l e c h a
HRegion : = CreatePolygonRgn (fArrowPoints, 3, WINDING);
try / / v e r i f i c a s i e l c l i c s e r e a l i z o e n l a z o n a
i f PtInRegion (HRegion, Msg.XPos, Msg.YPos) then
ArrowDblClick;
finally
DeleteOb ject (HRegion);
end ;
end :

La version CLX: Llamadas a funciones Qt nativas


El codigo previo no se podra portar a Linux y no tendria sentido en la version
CLXIQt del componente. Si queremos crear un componente similar para la biblio-
teca de clases CLX, podemos sustituir las llamadas a la API de Win32 con llama-
das directas (de bajo nivel) a la capa Qt, creando un objeto de la clase Q R e g i o n ,
como en el siguiente listado:
procedure TMdArrow.DblClick;
var
HRegion: QRegionH;
MousePoint: TPoint;
begin
// r e a l i z a e l c o n t r o l p r e d e f i n i d o
inherited;
// c a l c u l a l a z o n a d e l a p u n t a d e f l e c h a
HRegion := QRegion-create (PPointArray(FArrowPoints), True) ;
try
// o b t i e n e l a p o s i c i o n a c t u a l d e l r a t o n
GetCursorPos (MousePoint);
MousePoint : = ScreenToClient (MousePoint);
// v e r i f i c a s i e l c l i c se r e a l i z o e n l a zona
if QRegion-contains(HRegion, PPoint(@MousePoint)) then
ArrowDblClick;
finally
QRegion-destroy(HRegi0n);
end ;
end;

Registro de las categorias de propiedades


Hemos aiiadido a este componente algunas propiedades personalizadas y un
nuevo evento. Si organizamos las propiedades en el Object Inspector por cate-
gorias, todos 10s elementos apareceran en la categoria generica Miscellaneous.
S c s g o de S r tantas categorias como propie-
dades.

Nuestro codigo registra la propiedad Filled en dos categorias diferentes.


Esto no es un problema, porque la misma propiedad se puede mostrar varias
veces en el Object Inspector en diferentes grupos, como puede verse en la
figura 9.8. Para probar el componente flecha, hemos creado un programa ejemplo
muy sencillo, ArrowDemo, que permite modificar la mayoria de sus propiedades
en tiempo de ejecucion. Este tip0 de prueba, despues de haber escrito un compo-
nente o mientras lo creamos resulta muy importante.

Figura 9.8. El componente Arrow define una categoria de propiedades personalizada,


Arrow, como puede verse en el Object l n s p e c t o r . ~ a spropiedades pueden verse en
multiples secciones, como la propiedad Filled en este caso.

m - X " ~ acategoria dc propieda&s Loca 1izable posee una funcion


especial, relacionada con el uso del ITE (Entorno de Traduccion I n t e
do). Cuando una propiedad forma park de dicha categoria. su valor apz&
cera fstado en el ITE como una propiedad que se puede traducir a o k o
lenguaje.

Personalizacion de 10s controles de Windows


Una de las formas mas comunes de personalizar 10s componentes existentes
consiste en afiadir un comportamiento predefinido a sus controladores de eventos.
Cada vez que es necesario unir el mismo controlador de eventos a componentes de
formas distintas, deberiamos considerar aiiadir el codigo del evento directamente
a una subclase del componente. Un ejemplo claro es el de 10s cuadros de edicion
que aceptan solo una entrada numerica. En lugar de unir a cada uno de ellos un
controlador de eventos comun O n C h a r , podemos definir un nuevo componente
simple.
Sin embargo, dicho componente no controlara el evento, 10s eventos son solo
para 10s usuarios de 10s componentes. En cambio, el componente puede o bien
controlar el mensaje Windows directamente o sobrescribir un metodo, llamado
con frecuencia manejador de mensajes de segundo nivel. La tecnica anterior se
utilizaba habitualmente en el pasado pero hacia a 10s componentes ser especificos
para la plataforma Windows. Para crear un componente portable a CLX y Linux
(y, en el futuro, a la arquitectura .NET) deberiamos evitar 10s mensajes de segun-
do nivel Windows y, en su lugar, sobrescribir 10s metodos virtuales del compo-
nente basico y las clases de control.

NOTA: Cuando la mayoria de componentes VCL manejs. u.mensaje


Windows, llaman a un rnanejador de mensajes de s e g h nivel (normal-
mente, un mitodo dhhmico), en lugar de ejecutar c6digo directammte con
el metodo de respuesta a mensajes. Esta tecnica facilita personalizar el
componente en una clase derivada. Habitualmente, un rnanejador de segun-
do nivel hara su propio trabajo y llamara despds a cudqukr controlador
de eventos que el usuario del componente haya adtipado. Por tauto, siempre
deberiamos llamar a inherited para dejar a1compoaente actiw d wento
como se espera.

Ademas de la portabilidad, no hay razon por la que sobrescribir 10s manejadores


de segundo nivel esistentes, sea una tecnica mejor que manejar directamente 10s
mensajes Windows. Primero, esta tecnica es mas cercana a la perspectiva orienta-
da a objetos.
En lugar de duplicar el codigo de respuesta a mensajes de la clase b k i c a para
despues personalizarlo, estamos sobrescribiendo una llamada a un metodo vir-
tual, que 10s diseiiadores de VCL planearon para ser sobrescrita. Segundo, si
alguien necesita derivar otra clase de una de nuestras clases de componentes,
deberiamos facilitarle la posibilidad de personalizarla, y es muy facil que
sobrescribir 10s manejadores de segundo nivel induzca a errores. Por ejemplo,
podiamos haber creado este control de cuadro de edicion numeric0 manejando el
mensaje de sistema wm-C h a r :
type
TMdNumEdit = class (TCustomEdit)
public
procedure WmChar (var Msg: TWmChar); message wm-Char;

De todas maneras, el codigo es mas portable si sobrescribimos el metodo


Keypress, como hemos hecho en el codigo del siguiente componente. En un
ejemplo posterior tendremos que manejar mensajes Windows personalizados por-
que no hay un metodo correspondiente que sobrescribir.

El cuadro de edicion numeric0


Para personalizar un componente cuadro de edicion que restrinja la entrada
que se va a aceptar, todo lo que hay que hacer es sobrescribir su metodo
Ke yPre ss,que es llamado cuando el componente recibe el mensaje Windows
wm-Char.A continuation, aparece el codigo de la clase TMdNumEdit:
type
TMdNumEdit = class (TCustomEdit)
private
FInputError: TNotifyEvent;
protected
function GetValue: Integer;
procedure SetValue (Value: Integer) ;
procedure Keypress (var Key: Char) ; override;
public
constructor Create (Owner: TComponent) ; override;
published
property OnInputError: TNotifyEvent read FInputError
write FInputError;
property Value: Integer read GetValue write SetValue
default 0;
property Autoselect;
property Autosize;
.
// y a s i s u c e s i v a m e n t e . .

Este componente hereda de TCustomEdit en lugar de hacerlo de Tedit,


por lo que puede ocultar la propiedad Text y exteriorizar en cambio la propiedad
entera Value. Fijese en que no hemos creado un nuevo campo para alojar este
valor, porque podemos utilizar la propiedad Text existente (pero ahora no publi-
cada) .
Para ello, sencillamente convertiremos el valor numeric0 en una cadena de
texto y viceversa. La clase TCustomEdit (0, en realidad, el control de Windows
que envuelve) dibuja automaticamente la informacion de la propiedad Text en la
superficie del componente:
function TMdNumEdit-Getvalue: Integer;
begin
// d e f i n i d a como 0 en caso de e r r o r
Result : = StrToIntDef (Text, 0) ;
end ;

.
procedure TMdNumEdit SetValue (Value: Integer) ;
begin
Text : = IntToStr (Value);
end;
El metodo mas importante es el metodo redefinido Keypress, que filtra to-
dos 10s caracteres no numericos y crea un evento especifico en caso de error:
p r o c e d u r e TMdNumEdit .WmChar ( v a r Msg : TWmChar) ;
begin
i f n o t (Key i n [ ' O r . . ' 9 ' ] ) a n d n o t (Key = # 6 ) t h e n
begin
K e y : = #O; / / s i m u l a r q u e n o s e h a p u l s a d o n a d a
begin
i f Assigned ( F I n p u t E r r o r ) t h e n
FInputError ( S e l f );
end
else
inherited;
end;

Este metodo verifica cada caracter cuando el usuario lo introduce, compro-


bando 10s numeros y la tecla Retroceso (que tiene un valor ASCII 8). El usuario
dcberia poder utilizar la tecla Retroceso ademas de las teclas de sistema (las
tcclas del cursor y Supr), por lo que es necesario verificar dicho valor.
Ahora, si colocamos este componente en un formulario, podemos escribir algo
en el cuadro de edicion y observar su comportamiento. Tambien podemos asociar
un metodo a1 evento OnInputError para ofrecer respuesta a1 usuario cuando
se pulsa una tecla incorrecta.
Un editor numbrico con separador de millares
Como ampliacion del ejemplo, cuando el usuario escribe numeros largos (guar-
dados internamente como numeros de coma flotante que, comparados con 10s
enteros pueden ser mas grandes y tener digitos decimales) seria interesante que
aparecieran automaticamente separadores de millares y se actualizaran en fun-
cion de la entrada:

Podemos hacer esto sobrescribiendo el metodo interno Change y formateando


el numero correctamente. Solo hay un par de pequeiios problemas a tener en
cuenta. El primer0 es que para formatear el numero debemos tener una cadena
que contenga un numero, per0 el texto del cuadro de edicion no es una cadena
numerica que Delphi reconozca, ya que tiene separadores de millares, y no puede
ser convertido directamente a un numero. Hemos creado una version modificada
de la funcion StringToFloat llamada StringToFloatSkipping, para
realizar esta conversion.
El segundo pequeiio problema, es que si modificamos el texto del cuadro de
edicion, la posicion actual del cursor se perdera. Por eso, necesitamos salvar la
posicion original del cursor, reformatear el numero, y restaurar la posicion del
cursor (considerando que la posicion del cursor deberia cambiar en funcion de si
se ha aiiadido o quitado un separador).
Todas estas soluciones se contemplan en el siguiente codigo completo de la
clase TMdThousandEdit:
type
TMdThousandEdit = class (T~dNumEdit)
public
procedure Change; override;
end;

function StringToFloatSkipping (s: string) : Extended;


var
sl: string;
I: Integer;
begin
// q u i t a r c a r a c t e r e s no numericos
sl : = ' 1 ;
for i : = 1 to length (s) do
if s[i] i n [ ' 0 1 . . ' 9 ' ] then
sl : = sl + s [i];
Result : = StrToFloat (sl);
end;

procedure TMdThousandEdit.Change;
var
CursorPos, // p o s i c i o n o r i g i n a l d e l c u r s o r
LengthDiff: Integer; // n u m e r o d e n u e v o s s e p a r a d o r e s (+ o -)
begin
if Assigned (Parent) then
begin
CursorPos : = SelStart;
LengthDif f : = Length (Text);
Text : = FormatFloat ( I # , # # # ' ,
StringToFloatSkipping (Text)) ;
LengthDif f : = Length (Text) - LengthDif f ;
// mover e l c u r s o r a l a p o s i c i o n apropiada
SelStart : = CursorPos + LengthDiff;
end;
inherited;
end;

El boton Sound
Nuestro proximo componente, TMdSoundBut ton,emite un sonido cuando
pulsamos el boton y otro cuando lo soltamos. El usuario especifica cada sonido
modificando dos propiedades String que denominen 10s archivos WAV corres-
pondientes a 10s respectivos sonidos. Una vez mas, es necesario interceptar algu-
nos de 10s mensajes del sistema (wm LButtonDown y wm -LButtonUp), o
sobrescribir el controlador de segundonivel apropiado.
Veamos el codigo de la clase TMdSoundBut t o n , con 10s dos metodos prote-
gidos y las dos propiedades de cadena que identifican 10s archivos de sonido,
proyectados sobre campos privados porque no necesitamos hacer nada especial
cuando el usuario cambia esas propiedades:
type
TMdSoundButton = class (TButton)
private
FSoundUp, FSoundDown: string;
protected
procedure MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) ; override;
procedure MouseUp (Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) ; override;
published
property SoundUp: string read FSoundUp write FSoundUp;
property SoundDown : string read FSoundDown write FSoundDown;
end:

Veamos el codigo de dos controladores de segundo nivel:


uses
MMSystem;

procedure TMdSoundButton.MouseDown(Button: TMouseButton; Shift:


TShiftState;
X, Y: Integer) ;
begin
inherited MouseDown (Button, Shift, X, Y) ;
PlaySound (PChar (FSoundDown), 0, sndAsync) ;
end:

Hemos llamado a la version heredada de 10s metodos antes de hacer ninguna


otra cosa. En el caso de la mayor parte de 10s controladores de scgundo nivel, esta
es una buena costumbre, porque garantiza que ejecutamos el comportamiento
estandar antes que cualquier comportamiento personalizado. A continuacion, se
llama a la funcion de la API de Win32 P l a y S o u n d para que reproduzca el
sonido. Podemos usar dicha funcion, definida en la unidad MmS y s t e m , para
reproducir archivos WAV o sonidos del sistema, como demuestra el ejemplo
SoundB. Esta es la descripcion textual del formulario del programa de ejemplo
(del archivo DFM):
object MdSoundButtonl: TMdSoundButton
Caption = 'Press '
SoundUp = 'RestoreUp '
SoundDown = 'RestoreDown '
end
NOTA: Elegir un valor apropiado para estos sonidos no es nada simple.
Mas adelante en este capitulo, mostraremos cbmo aiIadir un editor de pro-
piedades a1 componente para simplificar la operacion.

Control de mensaje internos: El boton Active


La interfaz de Windows esta evolucionando hacia un nuevo estandar, que in-
cluye componentes que aparecen resaltados cuando el cursor se mueve sobre ellos.
Delphi ofrece un soporte similar en muchos de 10s componentes incorporados.
Imitar este comportarniento con un boton puede parecer una tarea compleja de
acometer, per0 no lo es. El desarrollo de un componente puede convertirse en algo
mucho mas sencillo una vez que sabemos que funcion virtual sobrescribir o a que
mensaje hay que cngancharlo.
El siguiente componentc, la clase TMdAct iveButton,demuestra esta tec-
nica, controlando algunos mensajes internos de Delphi para realizar esta tarea de
un mod0 muy sencillo. Para obtener mas informacion acerca de estos mensajcs
internos de Delphi podemos acudir a la seccion siguiente, "Mensajes de Compo-
nente y Notificaciones".
El componente ActiveButton controla 10s mcnsajes internos de Delphi
c m MouseEnter y cm MouseExi t,que se reciben cuando el cursor entra o
saledc la zona que corresionde al componente:

type
TMdActiveButton = class (TButton)
protected
procedure MouseEnter (var Msg: TMessage); message
cm-mouseEnter;
procedure MouseLeave (var Msg: TMessage); message

end;

El codigo que escribimos para estos dos metodos puede hacer lo que nosotros
queramos. Por ejemplo, hemos decidido que alternara el estilo negrita de la fuente
del propio boton. Podemos ver el efecto que se obtiene al mover el raton sobre uno
de esos componentes en la figura 9.9.
procedure TMdActiveButton.MouseEnter (var Msg: TMessage);
begin
Font.Style : = Font.Style + [fsBold];
end;

procedure TMdActiveButton.MouseLeave (var Msg: TMessage);


begin
Font .Style : = Font .Style - [fsBold];
end ;
Figura 9.9. Un ejemplo del uso de un componente ActiveButton.

Podemos aiiadir otros efectos, como agrandar el tip0 de letra, hacer el boton el
seleccionado por defect0 o cambiar el tamaiio del boton. Los meJores efectos
normalmente implican colores, pero debemos heredar de la clase T B i t B t n para
poder manipularlos (10s controles T B u t t on tienen un color predefinido).

Mensajes de componente y notificaciones


Para crear un componente A c t i v e B u t t on,hemos usado dos mensajes de
componentes Delphi, como lo indica su prefijo c m . Estos mensajes pueden ser
bastante interesantes, como subraya el ejemplo, pero no estan practicamente
documentados por Borland. Hay t a m b i h un segundo grupo de mensajes inter-
nos de Delphi, indicados como notificaciones de componentes y que se distin-
guen por su prefijo en. No tenemos espacio suficiente aqui para explicar cada
uno de ellos u ofrecer un analisis detallado; podemos estudiar el codigo fuente
VCL para saber mas.

ADVERTENCIA: Este es un tema bastante avanzado, por lo que aquellos


lectores que sean nuevos en la creaci6n de componentes Delphi pueden
saltarse esta seccion. Los mensajes de componente no estan documentados
en el archivo de ayuda de Delphi, por lo que se ha considerado importante
citarlos aqui.

Mensajes de componentes
Un componente de Delphi pasa mensajes de un componente a otros componen-
tes, para indicar cualquier cambio en su estado que podria afectar a dichos com-
ponentes. La mayoria de estos mensajes comienzan como mensajes Windows,
per0 algunos son mas complejos, traducciones de alto nivel y no simples
reproyecciones. Ademas, 10s componentes envian sus propios mensajes y reen-
vian aquellos recibidos de Windows. Por ejemplo, cambiar un valor de propiedad
o alguna otra caracteristica del componente puede requerir el informar a uno o
mas componentes sobre dicho cambio.
Podemos agrupar 10s mensajes en categorias:
Los mensajes de activacion y foco de entrada se envian a1 componente que
se activa o desactiva y que recibe o pierde el foco de entrada:
c m A c t i v a t e : Corresponde a1 evento OnActivate de formularios y
de la aplicaci6n.
c m-D e a c t i v a t e : Corresponde a OnDeactivate.
c m-E n t e r : Corresponde a OnEnter.
c m-E x i t : Corresponde a OnExit.
c m F o c u s C h a n g e d : Se envia siempre que cambia el foco entre 10s
componentes del mismo formulario (mas adelante, veremos un ejemplo
con este mensaje).
c m-~ oFO tc u s : Declarado per0 no se usa.
c m-L o s t F O C U S : Declarado per0 no se usa.

Los mensajes enviados a 10s componentes hijo cuando cambia una propie-
dad:
c m-BiDiModeChanged: c m-I c o n c h a n g e d
c m-B o r d e r C h a n g e d : c m-S h o w H i n t C h a n g e d
c m-C o l o r C h a n g e d : c m-S h o w i n g c h a n g e d
c m-C t l 3 D C h a n g e d : c m-S y s F o n t C h a n g e d
c m-C u r s o r C h a n g e d : c m-T a b s t o p c h a n g e d
c m-E n a b l e d C h a n g e d : cm-T e x t c h a n g e d
c m-F o n t C h a n g e d : c m-V i s i b l e C h a n g e d
Si se siguen estos mensajes, eso puede ayudarnos a mantener la pista de 10s
cambios de una propiedad. Podemos necesitar responder a estos mensajes
en un nuevo componente, per0 no es probable.
Los mensajes relacionados con las propiedades ParentXxx: c m -
P a r e n t F o n t C h a n g e d , c m P a r e n t C o l o r C h a n g e d , cm -
P a r e n t C t l 3 D C h a n g e d , c m ~ a r e n t B i D i M o d e C h a n g e dy
c m-P a r e n t ~ h o w ~ i n t ~ h a n g e d similares
Son a 10s mensajes del grupo
anterior.
Las notificaciones sobre 10s cambios en el sistema Windows: cm-
SysColorChange, cm-WinIniChange, cm-Timechange y
cm-Fontchange.Controlar estos mensajes resulta util en componentes
especiales que necesitan mantener un seguimiento de 10s colores o fuentes
del sistema.
Los mensajes del raton: cm-Drag se envia varias veces durante las opera-
ciones de arrastre. cm MouseEnter y cm MouseLeave se envian a1
control cuando el cursor entra o sale de su<uperficie, per0 10s envia el
objeto Ap p 1 i c a t i o n como mensajes de poca prioridad. c m
Mouse Whee 1 corresponde a las operaciones basadas en la rueda del ra:
ton.
Mensajes de la aplicacion:
cm AppKeyDown: Se envia a1 objeto Application para dejarlo que
decida si una tecla corresponde a un menu de metodo abreviado.
cm-AppSysComrnand:Corresponde a1 mensaje wm-SysCommand.
cm DialogHandle:Se envia en una DLL para recuperar el valor
de la propiedad DialogHandle (utilizada por algunos cuadros de dido-
go no creados en Delphi).
cm Invo keHelp: Lo envia el codigo en una DLL para llamar a1
m&do InvokeHelp .
cm WindowHook: S e e n v i a e n u n a D L L p a r a l l a m a r a l o s m e t o d o s
~ o i k ~ a i n ~ i n dy oUnhookMainWindow
w .
Es poco probable que necesitemos usar estos mensajes. Existe tambien un
mensaje cm-Hintshowpause, que nunca se maneja en VCL.
Mensajes internos de Delphi:
cm CancelMode:Termina operaciones especiales, como mostrar la
list; desplegable de un cuadro combinado.
cm Controlchange: Se envia a cada control antes de aiiadir o
elihinar un control hijo (controlado por controles comunes).
cm ControlLis tChange:Se envia a cada control antes de aiiadir
o eiminar un control hijo (controlado por el componente DBCtrlGrid).
cm DesignH itTest : Determina si una operacion de raton deberia
ir alcomponente o a1 diseiiador de formularios.
cm Hintshow:Se envia a un controljusto antes de mostrar su suge-
rencia (solo si la propiedad ShowHint est6 definida como rue).
cm Hit T est : Se envia a un control cuando un control padre intenta
loc&zar a un control hijo en una position de rat6n dada (si la hay).

Das könnte Ihnen auch gefallen