Agradecimientos ....................................................................................................................... Contactar con el autor ..............................................................................................................
6 7
Siete versiones y contando ..................................................................................................... La estructura del libro ............................................................................................................ Normas usadas en este libro ............................................................................................
29 31 32
......................................................................................................................... 33 1. Delphi 7 y su IDE .............................................................................................................. 35 .
Parte I Bases
Ediciones de Delphi ................................................................................................................ 36 Una vision global del IDE ..................................................................................................... 37 Un IDE para dos bibliotecas ............................................................................................ 38 . Configuration del escritorio ..................................................................................... 38 Environment Options ....................................................................................................... 40 40 Sobre 10s menus ................................................................................................................. El cuadro de dialog0 Environment Options .............................................................. 41 TO-DOList .......................................................................................................................... 41 Mensajes ampliados del compilador y resultados de busqueda en Delphi 7 .............. 43 44 El editor de Delphi ................................................................................................................. 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
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
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
El paquete RTL. VCL y CLX ..............................................................................................170 171 Partes tradicionales de la VCL ...................................................................................... 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 ............................................................................................................................ La matriz Components ......................................................................................... Cambio de propietario .............................................................................................. La propiedad Name ......................................................................................................... . Elimination de campos del formulario ......................................................................... Ocultar campos del formulario ...................................................................................... La propiedad personalizada Tag .................................................................................... Eventos ...................................................................................................... :............................ Eventos en Delphi ........................................................................................................... Punteros a metodo ....................................................................................................... Los eventos son propiedades ......................................................................................... Listas y clases contenedores ............................................................................................... Listas y listas de cadena ............................................................................................... Pares nombre-valor (y extensiones de Delphi 7) ................................................... Usar listas de objetos ................................................................................................. Colecciones ...................................................................................................................... Clases de contenedores ............................................................................................. . . . Listas asociativas de verification ............................................................................ Contenedores y listas con seguridad de tipos .......................................................... Streaming ............................................................................................................................... La clase TStream ............................................................................................................. Clases especificas de streams ......................................................................................... Uso de streams de archivo .............................................................................................. Las clases TReader y TWriter ........................................................................................ Streams y permanencia ................................................................................................... Compresion de streams con ZLib .................................................................................. Resumen sobre las unidades principales de la VCL y la unidad BaseCLX ................... La unidad Classes ........................................................................................................... Novedades en la unidad Classes .............................................................................. . . Otras unidades prlncipales .............................................................................................
VCL frente a VisualCLX ...................................................................................................... Soporte dual de bibliotecas en Delphi .......................................................................... Clases iguales, unidades diferentes ......................................................................... DFM y XFM ............................................................................................................... Sentencias uses .......................................................................................................... Inhabilitar el soporte de ayuda a la biblioteca dual ............................................... Eleccion de una biblioteca visual .................................................................................. Ejecucion en Linux ................................................................................................... Compilacion condicional de las bibliotecas ........................................................... Conversion de aplicaciones existentes .......................................................................... Las clases TControl y derivadas ......................................................................................... Parent y Controls ............................................................................................................ Propiedades relacionadas con el tamafio y la posicion del control ........................... Propiedades de activation y visibilidad ........................................................................ Fuentes .............................................................................................................................
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
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
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
.
.
...............................................401 8. La arquitectura de las aplicaciones Delphi ...............................................................403 .
Parte I1 Arquitecturas orientadas a objetos en Delphi
.
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 ............................................................................ El formulario principal ................................................................................................... Subclasificacion de la ventana MdiClient .................................................................... Herencia de formularios visuales ........................................................................................ Herencia de un formulario base .................................................................................... Formularios polimorficos ............................................................................................... Entender 10s marcos ............................................................................................................. Marcos y fichas ............................................................................................................... Varios marcos sin fichas ................................................................................................. Formularios base e interfaces .............................................................................................. Uso de una clase de formulario base ............................................................................. Un truco adicional: clases de interposition ............................................................ Uso de interfaces ............................................................................................................. El gestor de memoria de Delphi ..........................................................................................
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 ........................................................................... Uso del componente no visual ....................................................................................... Propiedades de coleccion ............................................................................................... Definicion de acciones personalizadas ........................................................................ Escritura de editores de propiedades .................................................................................. Un editor para las propiedades de sonido ................................................................ Instalacion del editor de propiedades ..................................................................... Creacion de un editor de componentes ............................................................................... Subclasificacion de la clase TComponentEditor ......................................................... Un editor de componentes para ListDialog .................................................................. Registro del editor de componentes ........................................................................
La funcion de las DLL en Windows ............................................................................ El enlace dinamico .......................................................................................................... Uso de las DLL ................................................................................................................ Normas de creacion de DLL en Delphi ....................................................................... Uso de las DLL existentes .................................................................................................... Usar una DLL de C++ .................................................................................................... Creacion de una DLL en Delphi ......................................................................................... La primera DLL en Delphi ...................................................................................... Funciones sobrecargadas en las DLL de Delphi ................................................... Exportar cadenas de una DLL ................................................................................. Llamada a la DLL de Delphi ...................................................................................... Caracteristicas avanzadas de las DLL en Delphi ............................................................ Cambiar nombres de proyecto y de biblioteca ............................................................. Llamada a una funcion DLL en tiempo de ejecucion .................................................. Un formulario de Delphi en una DLL .......................................................................... Bibliotecas en memoria: codigo y datos ............................................................................. Compartir datos con archivos proyectados en memoria ............................................. Uso de paquetes Delphi ........................................................................................................ Versiones de paquetes .................................................................................................... Formularios dentro de paquetes .......................................................................................... Carga de paquetes en tiempo de ejecucion ................................................................ Uso de interfaces en paquetes .................................................................................. Estructura de un paquete .....................................................................................................
.
504 508 508 512 516 517 520 521 522 522 524
11 Modelado y programacih orientada a objetos (con ModelMaker)
Comprension del modelo interno de ModelMaker ............................................................ Modelado y UML .................................................................................................................. Diagramas de clase ........................................................................................................ Diagramas de secuencia ............................................................................................. Casos de uso y otros diagramas ..................................................................................... Diagramas no W ........................................................................................................ Elementos comunes de 10s diagramas ........................................................................... Caracteristicas de codification de ModelMaker .......................................................... Integracion Delphi / ModelMaker .................................................................................
568 569 569 571 572 574 575 576 576
Gestion del modelo de codigo ........................................................................................ El editor Unit Code Editor ............................................................................................. El editor Method Implementation Code Editor ........................................................... La vista de diferencias .................................................................................................... La vista Event Types View ............................................................................................. Documentacion y macros ..................................................................................................... Documentacion frente a comentarios ............................................................................ Trabajo con macros ......................................................................................................... Reingenieria de codigo ......................................................................................................... .. Aplicacion de patrones de diseiio .................................................................................. Plantillas de codigo ......................................................................................................... Detallitos poco conocidos ...................................................................................................
Una breve historia de OLE y COM ..................................................................................... Implementacion de IUnknow .............................................................................................. Identificadores globalmente unicos ............................................................................... El papel de las fabricas de clases .................................................................................. Un primer sewidor COM ..................................................................................................... Interfaces y objetos COM ............................................................................................... Inicializacion del objeto COM ....................................................................................... Prueba del sewidor COM ............................................................................................... Uso de las propiedades de la interfaz ........................................................................... Llamada a metodos virtuales ......................................................................................... Automatization ..................................................................................................................... Envio de una llamada Automatizacion ......................................................................... Creacion de un sewidor de Automatizacion ...................................................................... El editor de bibliotecas de tipos .................................................................................... El codigo del sewidor ..................................................................................................... Registro del sewidor de autornatizacion ...................................................................... Creacion de un cliente para el sewidor ........................................................................ El alcance de 10s objetos de automatizacion ................................................................ El senidor en un componente ...................................................................................... Tipos de datos COM ....................................................................................................... Exponer listas de cadenas y fuentes ....................................................................... Us0 de programas Office ................................................................................................ Uso de documentos compuestos .......................................................................................... El componente Container ............................................................................................... Uso del objeto interno .................................................................................................... Controles ActiveX ................................................................................................................. Controles ActiveX frente a componentes Delphi ........................................................ Uso de controles ActiveX en Delphi ............................................................................. Uso del control WebBrowser .................................................................................... Creacion de controles ActiveX ............................................................................................ Creacion de una flecha ActiveX .................................................................................... Afiadir Nuevas Propiedades ........................................................................................... Adicibn de una ficha de propiedades ............................................................................
ActiveForms ..................................................................................................................... Interioridades de ActiveForm ................................................................................... El control ActiveX XClock ...................................................................................... ActiveX en paginas Web ................................................................................................ COM+ .................................................................................................................................... Creacion de un componente COM+ .............................................................................. Modulos de datos transaccionales ................................................................................. Eventos COM+ ................................................................................................................ COM y .NET en Delphi 7 ....................................................................................................
644 644 645 646 648 649 651 653 656
................................ 659 13. Arquitectura de bases de datos Delphi .....................................................................661 .
Parte I11 Arquitecturas orientadas a bases de datos en Delphi
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
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
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 ................................................................................................... Actualizaciones por lotes ............................................................................................... Bloqueo optimists ........................................................................................................... Resolution de conflictos de actualizacion .................................................................... Conjuntos de registros desconectados ................................................................................ Pooling de conexiones .......................................................................................................... Conjuntos de registros permanentes ............................................................................. El modelo de maletin ...................................................................................................... Unas palabras sobre ALIO.NET...........................................................................................
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
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 .......................................................................... Construir conjuntos de datos personalizados .................................................................... La definicion de las clases ............................................................................................. Apartado I: Inicio. apertura y cierre ............................................................................. Apartado 11: Movimiento y gestion de marcadores ..................................................... Apartado 111: Buffers de registro y gestion de campos ............................................... Apartado IV: De buffers a campos ................................................................................ Comprobacion el conjunto de datos basado en streams .............................................. Un directorio en un conjunto de datos ............................................................................... Una lista como conjunto de datos .................................................................................. Datos del directorio ......................................................................................................... Un conjunto de datos de objetos ..........................................................................................
Presentation de Rave ............................................................................................................ Rave: el entorno visual de creacion de informes ......................................................... El Page Designer y el Event Editor ..................................................................... El panel Property .................................................................................................. El panel Project Tree ................................................................................................. Barras de herramientas y la Toolbar Palette .......................................................... La barra de estado ..................................................................................................... Uso del componente RvProject ...................................................................................... Formatos de representacion ........................................................................................... Conexiones de datos ....................................................................................................... Componentes del Rave Designer ........................................................................................ Componentes basicos ...................................................................................................... Componentes Text y Memo ...................................................................................... El componente Section ............................................................................................. Componentes grhficos ............................................................................................... El componente FontMaster ...................................................................................... Numeros de pagina .................................................................................................... Componentes de dibujo ............................................................................................. Componentes de codigo de barras ........................................................................... Objetos de acceso a datos ............................................................................................... Regiones y bandas ........................................................................................................... El Band Style Editor ................................................................................................. Componentes data-aware ............................................................................................... El Data Text Editor ................................................................................................... De Text a Memo ........................................................................................................ Calculo de totales ...................................................................................................... Repeticion de datos en paginas ................................................................................ Rave avanzado ....................................................................................................................... Informes maestro-detalle ................................................................................................ Guiones de informes ....................................................................................................... Espejos .............................................................................................................................. Calculos a tope ................................................................................................................ CalcTotal ....................................................................................................................
..............................................................................................959 19. Programacidn para Internet: sockets e Indy .........................................................961
Parte IV Delphi e Internet
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
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 1021 WebSnap ............................................................................................................................ ., , . Gestion de varias paglnas ........................................................................................ 1025 Guiones de servidor ...................................................................................................... 1027 Adaptadores ................................................................................................................... 1030
Campos de adaptadores .......................................................................................... Componentes de adaptadores ................................................................................. Uso del Adapterpageproducer ........................................................................... Guiones en lugar de codigo .................................................................................... Encontrar archivos ........................................................................................................ WebSnap y bases de datos .................................................................................................. Un modulo de datos WebSnap ..................................................................................... El DataSetAdapter ........................................................................................................ Edicion de 10s datos en un formulario ........................................................................ Maestro/Detalle en WebSnap ................................................................................... Sesiones, usuarios y permisos ........................................................................................... Uso de sesiones .............................................................................................................. Peticion de entrada en el sistema ............................................................................ Derechos de acceso a una unica pagina ..............................................................
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
Presentacion de XML ......................................................................................................... Sintaxis XML basica .................................................................................................. XML bien formado ........................................................................................................ Trabajo con XML .......................................................................................................... Manejo de documentos XML en Delphi .............................................................. Programacion con DOM .................................................................................................... Un documento XML en una TreeView ................................................................... Creacion de documentos utilizando DOM ................................................................. Interfaces de enlace de datos XML ......................................................................... Validacion y esquemas ............................................................................................ Uso de la API de SAX .................................................................................................. Proyeccion de XML con transformaciones ................................................................. XML e Internet Express ..................................................................................................... El componente XMLBroker ......................................................................................... Soporte de JavaScript ................................................................................................... Creacion de un ejemplo ........................................................................................... Uso de XSLT .......................................................................................................................
Uso de XPath ................................................................................................................. XSLT en la practica ...................................................................................................... XSLT con WebSnap ...................................................................................................... Transformaciones XSL directas con DOM ................................................................. Procesamiento de grandes documentos XML ........................................................... De un ClientDataSet a un documento XML ............................................................ De un documento XML a un ClientDataSet ............................................................
Servicios Web ................................................................................................................... SOAP y WSDL .............................................................................................................. Traducciones BabelFish ........................................................................................ Creacion de un servicio Web ....................................................................................... Un servicio Web de conversion de divisas ............................................................... Publicacion del WSDL ............................................................................................ Creacion de un cliente personalizado ............................................................... Peticion de datos de una base de datos ................................................................... Acceso a 10s datos ................................................................................................... Paso de documentos XML ...................................................................................... El programa cliente (con proyeccion XML) ......................................................... Depuracion de las cabeceras SOAP ............................................................................ Exponer una clase ya existente como un servicio Web ............................................. DataSnap sobre SOAP ........................................................................................................ Creacion del semidor SOAP DataSnap ...................................................................... Creacion del cliente SOAP DataSnap ......................................................................... SOAP frente a otras conexion con DataSnap ............................................................. Manejo de adjuntos ............................................................................................................. Soporte de UDDI ................................................................................................................. ~ Q u Ces UDDI? .............................................................................................................. UDDI en Delphi 7 .........................................................................................................
............................................................................................................ ApCndice A. Herramientas Delphi del autor ...............................................................
1157
CanTools Wizards ............................................................................................................... Programa de conversion VclToClx ................................................................................... Object Debugger ................................................................................................................. Memory Snap ...................................................................................................................... Licencias y contribuciones .................................................................................................
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 programacion orientada a objetos y programacion visual no solo para este sistema operativo 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 software que lo coordina todo. iEsta buscando soluciones de bases de datos, clientel servidor, multicapa (multitier), Intranet o Internet? iBusca control y potencia? ~ B U Suna C ~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 extremadamente 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 componentes 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 lenguaje 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 componentes (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 lenguaje 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 aplicaciones Delphi, el desarrollo de componentes personalizados, el uso de bibliotecas 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 conjuntos 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 contenido 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 programacion, 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 instruccion 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 propiedades, 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 recomendables 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 caracteristicas 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 consejos y sugerencias dirigidas a1 usuario medio de Delphi. Si se trata de un programador 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 operaciones basicas del IDE; todos 10s capitulos despues de este se centraran en cuestiones 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, resaltaremos 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 profesionales. Posee todas las caracteristicas basicas, mas soporte para programacion 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 servicios Web avanzados, soporte de CORBA, internacionalizacion, arquitectura 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 personalizar 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 reflejadas algunas preferencias del autor como la instalacion de muchos aiiadidos, que
pueden reflejarse en el aspect0 de las pantallas. La version Professional y superiores 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 caracteristicas 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 programa, 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 despues escriben sus propias clases y componentes y, normalmente, acaban invirtiendo 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 Cornponente 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¶, qvfwnicqk bajc Fioux. Reipte~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 completamente diferente cuando se escriban componentes o codigo de bajo nivel mediante 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 recuperarla 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 configuracion 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 deberia 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 configuraciones 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 'escritorio (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 reorganizaron 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 verificacion 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 programador 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 comentarios 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 codigo 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 pendientes 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 seleccionar 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
I t e m del menu desplegable del editor (o la combinacion Control-MayusT ) se genero este comentario: To-Do
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.
---. _. I! 1 * A c m llcm
7 Check comp~lerfelhngs
1
.I ~ o a ~ e
--'
10-
-A
IWWY
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 elemento para indicar que se ha completado. El comentario del codigo fuente cambiara 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 pendientes 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. Todas 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 segundo 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 mediante 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 hacer 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 orientad0 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 soporte 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 cuadro 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 lenguajes 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 arquitectura .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 caracteristicas 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 disponibles 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 herramienta 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 depurador), 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 comenzamos 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 profundidad 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 Control-Mayus junto con las teclas de cursor arriba y abajo para saltar de la definicion 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 sugerencia sobre simbolos). A1 mover el raton sobre un simbolo del editor, una ventana de sugerencia nos mostrara el lugar en el que se declara el identificador. Esta caracteristica puede resultar especialmente importante para realizar el seguimiento de identificadores, clases y funciones de una aplicacion que estamos escribiendo y tambien para consultar el codigo fuente de la biblioteca de componentes visuales (VCL). -
ADVERTENCIA: Aunque pueda parecer buena idea en principio, no podemos usar la ventana de sugerencia sobre simbolos para averiguar que unidad declara un identificador que queremos emplear. En realidad. la ventana de sugerencia no aparece, si no se ha incluido todavia la unidad correspondiente. Sin embargo, la autentica ventaja de esta funcion, es que el usuario puede transformarla en un instrumento auxiliar para desplazarse. Si mantenemos pulsada 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 combinaciones Alt-Flecha izda. o Alt-Flecha dcha.). Tambien podemos hacer clic sobre 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, completando 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 controlador 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 definicion 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 funciones 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 configurar 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 adecuado 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 metodos de las interfaces implementadas, las propiedades de clase basica y, por ultimo, 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 descubrimiento 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.
b capaoidad de explorar la d e c h r w i h de elementos de la fista.de campletitud de codigo a1 mantener pul'sadd la teela Control y'hacer chc sohre cualquier identificador de la Ikta. TRgC(r:,l&fpG 7
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 palabra 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 metodos abreviados para 10s bloques de codigo que usemos normalmente. Por ejemplo, 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 descripcion 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, simplemente 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 desplegado 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 corresponden 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 permite 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 escribir el nombre de la funcion o metodo y abrir el parentesis, apareceran inmediatamente 10s nombres y tipos de parametro en una ventana de sugerencia contextual. Para que forzar a que aparezcan 10s parametros de codigo, podemos pulsar Control-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 marcadores 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. ControlMayus-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 Control-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 simplemente 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 seleccionar zonas rectangulares del editor, no solo lineas consecutivas y palabras.
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 componentes 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 flechas 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 etiqueta 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 direccion 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.
--
tih 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.
rM 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 similar), 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 comenzar abriendo el proyecto DiagramDemo. El formulario del programa , , , :+
.,
,..,
A, A: , , , , -,,,,,:,J,-. , ,I .I1, 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 formularios. 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 componcnte. 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, podemos 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 seleccionar 10s controles hijo (por ejemplo, 10s botones que se encuentran dentro de un panel), arrastraremos el raton dcntro del panel mientras que mantendremos pulsada 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 relativa 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 posicion 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 sugerencias 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 propiedades L e f t y Top). Por ultimo, se pueden guardar 10s archivos DFM (Delphi Form Module, Modulo 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 individual (usando la ficha Forms del cuadro de dialogo Project Options). Disponer de archivos DFM almacenados como texto permite trabajar de manera 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 ejecutable del programa. Los archivos DFM estan enlazados a su ejecutable en formato 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 version mas antigua - de Delphi puede no acevtar una nueva propiedad de un control en un archivo DFM ireado por u& veni6n posterior be Delphi, la version anterior si sera capaz de leer el resto del archivo de texto DFM. Sin a ,.t, A I, L :.,.. ,Ac~.-.:~,+, A, r\-1-L: -Z.-.Ar. .... , , :+ A, A,+,. GuIualgu, SI la YGIJIUU 111aaIGUGULG UG U G I ~ L U m a u ~ u u ....-.., IIUGVU c~yu 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.
1
Object lnspector Para visualizar y modificar las propiedades dc 10s componentes de un formulario (u otro disciiador) en tiempo de diseiio, se puede utilizar el Object Inspector. En comparacion con las primeras versiones de Delphi, el Object lnspector dispone 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 difercntc y pueden desplegarse seleccionado el simbolo + de la izquierda, como ocurre 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 subcomponentes, tal y como demuestra el nuevo control LabeledEdit.Una caracteristica 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 formulario y estamos echando un vistazo a las propiedades del formulario en el Object Inspector, podemos seleccionar el componente MainMenu moviendonos a la propiedad 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 Inspector. 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 comportamiento general de esta ventana. Desde Delphi 5, la lista desplegable de una propiedad puede incluir elementos 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 tiempo de diseKo utilimdo d meet ImpMor. Este n s a s l linodeloInterfaced por hterfaz) presentaComponent Reference ('Rdeienciasde G~m~pobetrte 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 hacemos 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-mulario actual (y formularios relacionados) que irnpleme& la interfaz.
1
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 nueva 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
ITFant)
Charsel
'DEFAULT-CHARSET
Cob
' W cWindowText
Existe una segunda forma, miis compteja, de personalizar el Object Ins. personalizada para todo el Object Inspector, para que pector: una. fuente su texto resulte mhs visible. Esta caracteristica resulta especialmente util en el caso de presentaciones publicas. P
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 organizaran por grupos, con la posibilidad de que cada propiedad aparezca cn diversos grupos. Las categorias tienen la ventaja de reducir la complejidad del Object Inspector. 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 apareceran 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 cambiamos 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/elemento, 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:
Ld h l -+ +
-
New (Newl) em, Open (Open11 bM,Save (Save1 ) hn
A veces, en la vista en arbol aparecen tambien nodos falsos, que no corresponden a un objeto real sino a uno predefinido. Como ejemplo de este comportamiento, 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 ejecucion), 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 componente para dejarlo ahi. Esto nos permite dejar un componente en el contenedor 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 ejemplo, 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 podemos 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 deSean aiiadir al diseiiador actual. Si movemos el raton sobre un componente, apareccra 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 completamente 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 senci110 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 componentes 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 especiales 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 caracteristica 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, mantiene 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 componente 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 posicion 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 formulario, 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 totalmente incorrect0 e intentamos pegarlo en un formulario, Delphi mostrara un mensaje 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 apropiadas 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, sencillamente 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 formulario, seleccionando el pseudocomponente desde la paleta, Delphi reproduce el codig0 fuente de 10s controladores de eventos en el nuevo formulario. Para crear una plantilla de componentes, seleccionamos uno o m b componentes 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, editarlo 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 plantillas 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 formularios 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 gracias 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 copiado en el. Si modificamos el marco original (en su propio diseiiador), las modificaciones 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 ejemplo, un grupo de proyectos puede incluir una DLL y un archivo ejecutable o varios archivos ejecutables. Todos 10s paquetes abiertos apareceran como proyectos 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 9 OtagrarnForrn ttl ToDoTesl exe n Fiamesl.ere ,-' Fum Fnm pas Form1 13 @ Frame @ Flame pas Fianel
a J
3
5
a
a
D \rn~code\~~\~~agram~erno D Lnd7caJe\Ol\D1agramDemo D \md7code\0l\ToDoTest D \rnd7caJe\Ol\Fiarnesl D hd7mde\Ol\Framesl D \md7wdeWl\Frametl D \md7codeWl\Fiamesl D \rnd7codeWl \Frames1 D \md7code\Ol \Frames1 D hd7mde\O1 \Flames1
Figura 1.9. El Project Manager de Delphi.
I
1 qudes abiaitos, hdusoaunque no selfiayaediadido sl grupo de proyedos. TRUCO: d i r d e Belphi 6; e f e e c t Manager muestra tambih 1bs pa-
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 compilar 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 proyectos 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 seleccionado 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 establecer 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 tecnica 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 archivos .DOF, sino que necesita el formato .CFG. Tambien se pueden guardar las opciones del compilador pulsando Control-00 (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-, ,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)
CONS TRUCTING-ABS TRACT ON) {$WARN COMPARISON-FALSE COMPARISON- TRUE ON) COMPARING- S IGNED- UNSIGNED ON) COMBINING- S I G N E D UNSIGNED ON) UNSUPPORTED-CONS TRUCT ON) FILE-OPEN ON) FILE-OPEN-UNITSRC ON) BAD-GLOBAL-SYWBOL ON) DUPLICATE-CTOR-DTOR ON) INVALID-DIRECTIVE ON) PACKAGE-NO- L I N K O N ) PACKAGED- THREADVAR ON) I M P L I C I T - IMPORT ON) HPPEMI T- IGNORED ON) NO-RETVAL ON) USE-BEFORE-DEF ON) FOR-LOOP-VAR-UNDEF ON) UNIT--MISMATCH ON) NO-CFG-FILE-FOUND ON) MESSAGE-DIRECTIVE ON) IMPLICIT-VARIANTS ON) UNICODE- TO-LOCALE ON) LOCALE- TO- UNICODE ON) IMAGEBASE-MULTIPLE ON) SUSPICIOUS-TYPECAST ON) PRIVATE-PROPACCESSOR ON) UNSAFE- T Y P E O F F ) UNSAFE-CODE OFF) UNSAFE-CAST O F F )
ON)
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 compilara primero. Cuando Delphi compila un proyecto, compila unicamente 10s archivos 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 normalmente 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 formularios. 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 ejecucion, lo que afecta a la distribucion del programa mas que a1 proceso de compilacion. 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 ejecutable por si solo. Por supuesto, si tenemos diversas aplicaciones en un unico sistema, 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 siempre. 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. Muestra informacion almacenada en un archivo local, que puede actualizarse descargando 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 herramienta Delphi for .NET. Estas advertencias son bastante exhaustivas, y se pueden inhabilitar como se muestra en la figura 1.10.
I
yarn-
-
- -
-
-
-
--.
3 ?. Unit idenlifier does m t match hle name % Na sonl~gu~at~an files laund -
,JJUser message
B lmpl~utuse of Varlants unit Errol conveltrng Unicode cha~to locale charsel
1
@ Er~orconverl~qlocals s l i i to ~ Unicode ,4 Imagebase e not a rmkiile d 64k
M
i
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 mediante 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 programs, 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 muchas veces (desde Object Browser a Project Explorer y ahora a Project Browser). En Delphi 7, se puede activar la ventana del Project Browser mediante 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 configuration 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 desplegable del Project Browser. Algunas de las categorias que se pueden ver en esta ventana son especificas del Project Browser; otras estan relacionadas con ambas 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 directamente 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 seguimiento 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 transformaciones XML aplicadas a1 formato producido por el componente ClientDataSet. External Translation Manager ( e t m 6 0 . e x e ) : Es la version independiente del Integrated Translation Manager. Esta herramienta externa puede 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 eliminar todas las entradas de Registro aiiadidas por Delphi 7 a un ordenador. TeamSource: Es un sistema de control de versiones avanzado proporcionado 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 disponible 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 herramienta 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 actualmente. OpenHelp (oh. e x e ) : Es la herramienta que podemos emplear para administrar la estructura de 10s propios archivos de ayuda de Delphi, integrando 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 ordenes, 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 podemos emplear para registrar bibliotecas ActiveX y servidores COM. El codig0 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 controlar 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 entonces. 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 explorar 10s recursos de 10s archivos ejecutables existentes.
Los archivos creados por el sistema Delphi produce diversos archivos para cada proyecto y seria conveniente saber 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 proyecto 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: iconos y cursores: archivos Image Editor CUR estandar de Windows usados para almacenar imagenes de mapas.
Normalmente no, per0 pueden ser necesarios en tiempo de ejecucion y para una posterior modificacion.
BPG
Desarrollo Borland Project Group (Grupo de proyectos Borland): archivos que usa el nuevo Project Manager. Es una especie de makefile.
Necesario para compilar de nuevo todos 10s proyectos del grupo a la vez.
BPL
Borland Package Library Compilacion: (Biblioteca de paquetes Enlace Borland): una DLL que contiene, entre otros, componentes VCL que usa el entorno Delphi en tiempo de disefio o las aplicaciones en tiempo de ejecucion. (Estos archivos usaban una extension .DPL en Delphi 3.)
Se distribuiran a otros desarrolladores Delphi y opcionalmente a usuarios finales.
CAB
Formato de archivo comprimido Microsoft Cabinet usado por Delphi para el despliegue Web. Un archivo CAB puede contener diversos archivos comprimidos.
Compilacion
Distribuido a usuarios
.CFG
Archivo de configuracion con las opciones de proyecto. Similar a 10s archivos DOF.
Desarrollo
Necesario han definido opciones especiales de compilacion.
.DCP
Delphi Component Packa- Compilacion ge (Paquete de componentes de Delphi): un archivo con informacion de simbo-
Necesario cuando usamos paquetes. Solo se distribuira a otros desarrolladores
lo para el codigo compilado en el paquete. No incluye codigo compilador, que se guarda en archivos DCU.
junto con 10s archivos BDPL. Se puede compilar una aplicacion con las unidades de un paquete simplemente con el archivo DCP y el BPL (sin archivos DCU).
.DCU
Delphi Compiled Unit (U ni- Com pilacion dad compilada Delphi): resultado de la compilacion de un archivo en Pascal.
Solo si el codigo fuente no esta disponible. Los archivos 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 Portfolio (Cartera de diagrama Delphi) usado por la vista en diagrarna del editor (era DTI en Delphi
No. Este archivo almacena informacion "solo en tiempo de disefio" no necesaria para el programa resultante per0 muy importante para el programador.
5).
.DFM
Delphi Form File (Archivo de formulario de Delphi): un archivo binario con la descripcion de las propiedades de un formulario (o un modulo de datos) y de 10s componentes que contiene.
Desarrc
Si. Todos 10s formularios se almacenan tanto en un archivo PAS como en un DFM.
.-DF
Copia de seguridad de Delphi Form File (DFM)
Desarrollo
No. Este archivo se 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) Integrated Translation Environment (hay un archivo DFN para cada formulario y cada lenguaje o bjetivo).
Si (para el ITE). Estos archivos contienen cadenas traducidas para cada formulario que editarnos en el Translation Manager
DLL
Dinamic Link Library (Biblioteca de enlace dinamico): otra version de un archivo ejecutable.
Vease .EXE
DOF
Delphi Option File: archivo Desarrollo de texto con la configuracion actual de las opciones actuales para las opciones del proyecto.
Necesario solo si se han instalado opciones especiales del compilador.
DPK y ~ h o r atamlien .DPKW r .DPKL
Delphi Package: el archivo de codigo fuente del proyecto de un paquete (o un archivo de proyecto especifico para Windows o Linux).
Copia de seguridad del archivo Delphi Project (.DPR).
Desarrollo
No. Este archivo se crea automaticarnente al guardar una nueva version de un archivo de proyecto.
DSK
Desktop file (Archivo de es- Desarrollo critorio): contiene informacion sobre la posicion de las ventanas Delphi, 10s archivos que se abren en el editor y otras configuraciones del escritorio.
No. En realidad deberian eliminarse si se copia el proyecto en un nuevo directorio.
Cornpilacion Enlace
DSM
Delphi Symbol Module (Modulo de simbolos Delphi): Alrnacena toda la informacion de sirnbolo del explorador.
EXE
Executable file (Archivo eje- Compilacion: cutable): la aplicacion Enlace Windows creada.
HTM
0 .HTML (Hypertext Markup Language, Lenguaje de rnarcas con hipertexto): el forrnato de archivo usado para paginas Web.
LIC
Asistente Los archivos de licencia relacionados con un archi- ActiveX y otras vo OCX. herrarnientas
No. Es necesario usar el control en otro entorno de desarrollo.
OBJ
Object file (Archivo objeto) (cornpilado), tipico del rnundo C/C++.
Podria ser necesario para rnezclar Delphi con codigo cornpilado C++ en un tinico proyecto.
. OCX
OLE Control Extension (Ex- Compilacion: 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.
Cornpilacion (pero solo si se ha activado la opcion Save Symbols)
No. El Object Browser usa este archivo, en lugar de 10s datos en memoria, cuando no puede volver a compilar un proyecto. No. Este archivo que se distribuira incluye todas las unidades compiladas, forrnularios y recursos.
Despliegue Web No. No participa en la de un cornpilacion del proActiveForrn yecto.
Paso intermedio de compilacion, generalmente no se usa en Delphi.
Vease .EXE.
-PA
Copia de seguridad de un archivo Pascal (.PAS).
Desarrollo
No. Este archivo lo crea Delphi autornaticamente al guardar una nueva version del codigo fuente.
RES, .RC
Resource file (Archivo de recurso): el archivo binario asociado con el proyecto de una aplicacion y que normalmente contiene su icono. Podernos afiadir otros archivos de este tip0 a un proyecto. Cuando creamos archivos de recurso personalizados podernos usar tambien el forrnato textual .RC.
Cuadro de dialogo Development Options. El ITE (Integrated Translation Environment) crea archivos de recurso con comentarios especiales.
Si. Delphi crea de nuevo el archivo RES principal de una aplicacion en funcion de la inforrnacion de la ficha Application del cuadro de dialogo Project Options.
.RPS
Translation Repository (parte de Integrated Translation Environment).
Desarrollo (ITE)
No. Necesario para adrninistracion de las traducciones.
.TLB
Type Library (Biblioteca de Desarrollo tipos): un archivo creado de forrna autornatica o por el Type Library Editor para aplicaciones del servidor OLE.
Este es un archivo que pueden necesitar otros prograrnas OLE.
TODO
Archivo de lista To-do en el que se guardan elernentos relacionados con el proyecto entero.
No. Este archivo contiene notas para 10s programadores.
.UDL
Microsoft Data Link (Enlace Desarrollo de datos Microsoft).
Desarrollo
Usado por ADO para referirse a un proveedor de datos. Similar a un alias en el entorno 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 mayoria 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) (Deberia 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 escritorio): 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 coloreado 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 fragmentos 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 neccsarioj 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 archivo de opciones (DOF). [Compiler] A= 1
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 componente, 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, componentes, 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 sobre todo importante cuando se echa un vistazo a los asistentes, proyectos o formularios que hemos aiiadido a1 Repository.
Cwone*
Cunlrol Panel Control Panel Dda Module Appbcalmn Module
TI
Package
Flame
1
-
Ploiect GI-
Console Applcat~on
DCC W ~ z a d
Fam
Resource DLL Wnald
Service
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 funcionamiento 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 desplazamos 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 titulo, 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 manualmente.
I
La plmWla de pmyecb en blanco 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-
I
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 directorio 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 plantilla. 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 baio la clave \Software\Borland\DelDhi\7.O\Ex~erts. Sirna . 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 nomL - _ J - ---l-_.3-1 m 0 . .2 . _ I - _ _..*---l-_ -..-_._ ore a e arctuvo ael asmenre nULL. 3 e pueaen ver- las enrraaas que ya estan presentes bajo la clave Experts para averiguar el mod0 en que se debe introducir la ruta.
.
__:_*_-A_
T
__LZ__
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 nuevo 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 correspondientes casillas de activacion.
I
--
Wc_hName ' V W Controls ' ' expected but end of lde found TRad~oButton Symbol was el~mtnatedby l~nker
rn rn
-
_ -_
.
..-
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 programacion 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 programacion. 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 ejemplo, Borland aiiadio el soporte para varias caracteristicas mas o menos relacionadas 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 versiones del compilador Delphi para Windows, per0 en Delphi 7 tiene un uso limitado.
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 operaciones (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 almacenamiento 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). , ,,'I, ., ,,--.,,, :*, I,,,, -1 ,,,:t,A,, 'Pa,,'I , ,.,, I1dC,L, 1 4 CiVUlV , , , , h~GE 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. ? , A ,
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 begin M o n t h : = m; Day : = d; Y e a r : = y; end;
(m, d, y: Integer) ;
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'cwtra 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 : finally // d e s t r u y e e l o b j e t o ADay. Free; end ; end :
' +
IntToStr ( A D a y - Y e a r ) ) ;
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 metodos 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 referencia 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 resulten 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 componentes 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 posicion. 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 metodos. 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 permite 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 directamente a 10s datos. Por supuesto, se supone que utilizamos 10s metodos para acceder 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 unidad.
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 disponibilidad 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 unidad 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 protegidos. 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 haremos, 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 privadaj 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 corresponde a1 dos de enero de 1900, a las 6:00 de la tarde (tres cuartos de un dia). n--_ -_L_ - -____--- restar el -1 --- ue 2---rara sumar o resrar r_ recnas, se pueue sumar o numero mas, que resulta d s sencillo que aiiadir &as con una representacih de dia/mes/aito.
-
---L--
J-
---A_-
.
--1_-
_I--
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 propiedades 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 metodos de acceso o directamente sobre 10s datos en ambas direcciones: property Month: property Month:
I n t e g e r read GetMonth write SetMonth; 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 protegidos) 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 proporcionara una definicion completa y el esqueleto del mttodo de escritura. Si se escribe G e t delante del nombre del identificador despues de la palabta 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 informacion 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 formulario 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 formulario. 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 resumen 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 propiedad 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 seleccionado. Las otras propiedades, normalmente denominadas propiedades solo de tiempo 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 propiedades 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.
I
incluso se pueden usar propiedades en expresiones, pero no siempre se puede pasar una propiedad como parimetro a un procedimiento o metodo. 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 formularios: 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 formularios, se deberia utilizar una propiedad. Simplemente hay que cambiar la declaration 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-MayusC 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. 4
NOTA:~ i ~ ceg s equo el a & d ~ p & i ~ d d a dpun f o n d ario, no 3. ~ del f a h~d ! del Qbject e B p e cs t o r .
Mia&
-~4'
a'la M a de ~
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 informacion (y con la propiedad s i m p l e p a n e l definida como T r u e ) y hay que modificar 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 componentes. 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 alternativa 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 propiedad 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 instancia, 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 obligatorio. 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 recordar 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 predeterminado, 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 mediante 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 deberia 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 crearse 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 necesarios y se liberen cuando ya no lo Sean, el modelo de referencia a objetos funcionara 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: var NewDay: TDate; begin NewDay : = TDate-Create; TheDay : = NewDay; LabelDate.Caption : = TheDay-GetText; end;
TObject);
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 considerarse esto como algo negativo: en muchos casos, ser capaz de definir una variable 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 diferentes 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 begin
(Button: TButton);
Button.Caption end;
/ / llamar.. CaptionPlus
: = Button.Caption
+
' + I ;
. (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: 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 ;
TObject);
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 aplicacion, 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 propietario, pasando el propietario a1 constructor del componente. El componente 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 destruccion 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 biblioteca cuando acaba el programa. Cuando la RTL de Delphi reserva memoria para las cadenas y matrices dinamicas, libera automaticamente la memoria cuando la referencia resulta inalcanzable. No es necesario liberar una cadena: cuando resulta inaccesible, 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 destructor 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 verifican 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, campos, propiedades y eventos de la clase T F o r m . Se puede llamar a cualquier metodo 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 nombres 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 configuracion 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 descendiente 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 jerarquia 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 conflictivos, por lo que deberiamos favorecer el encapsulado, sin sacrificar la flexibilidad. 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 rapida, 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 ~ 1de 3 ~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 escribimm 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 protegidos 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, respectivamente. (Son ejemplos bastante avanzados, asi que es mejor no enfientarse a ellos hasta tener un buen conocimientode Delphi.) Aunque el primer0 es un ^:--I--L11 - 1 ---- A - 13LA- -i -:---la ~jcruylo r ~ ~u cu~WQ~UG IU e z;onvwsrun oe upus wwX;sr, 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 bhicas). 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# ---a
----.---:IA:---
---A
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 momento 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 exlstente 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 identicos. 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; MyDog : = MyAnimal;
// E s t o es correcto // ; 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 posterior. 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 ejecucion. 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. - - .....---- -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
i-
[Y de 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.
GI
prugriu~lajiuuavla sc yucuc iulrpllar, aunquc sc n a y u
GSGIILU IIIIICS
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 estatico 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 estatico, que resulta algo bastante alocado). No hay ningun mod0 de cambiar este comportamiento, debido a la forma en que el compilador genera un codigo diferente 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; procedure Two; end; TMyDerivedClass = procedure One; procedure Two; end;
virtual; (metodo estdtico) class (TMyClass) override;
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 mismo 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 _ _ Iel parametro ae cauena, pero no la version sm parametros aeImaa en la 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 3
3
.
.
. . 4
1 L
!
I
A
.
,
1
pariimetros diferentes a 10s de la versibn de la clase bbica, se cunvierte cfedtivamente en un mktodo sobrecargado. Si no es asi, reemplaza a1 rnktodo 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 ejemplo 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, tambien 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 direccion 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 definido 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 programador, 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 veterano 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 llamada 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 definir solo en subclases de la clase actual. La directiva abstract define por completo 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 containing abstract methods" (Creando caso de +ombre 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 abstracto. 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 descendientes 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 subyacente), 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 problema, 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 comprobacion 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 directa 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 operador 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 intentando 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: begin if Sender is TButton then
TObject);
...
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 deberia limitar su uso para casos especiales. Cuando sea necesario resolver un problema 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 complementarlo. 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 rendimiento, 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 necesitan 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 tambien 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 independiente. m
7
z E d i a s e r IUnknown has@ ~ e l ~5,hper0 i ~el~hi 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 programacion 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 declaracion 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 (llamados 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 necesari0 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 comportamiento fundamental que necesita la interfaz II n t e r f ace. Para 10s objetos internos, 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
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 & 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 nuevos metodos supone tanto polimorfismo como usar metodos virtuales, pet0 requiere un codigo menor,
NQTA:
mi-
Ahora que hemos definido una implementacion de las interfaces, podemos escribir 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, mediante 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 efectivamente "compatible en tipo" con la clase dada. Con las interfaces, comprueba 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 metodo 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 diffciles de 1-ar: J3i la piktim, si memoria que sean extre-entc 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 vdrhbles, 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 excepciones hacen que 10s programas Sean mas robustos ya que proporcionan un mod0 estandar de notificar y gestionar errores y situaciones inesperadas. Las excepciones 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 mantenimiento no relacionados con el objetivo real de programacion. En tiempo de ejecucion, 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 generalmente para realizar operaciones de limpieza que siempre se deberian ejecutar, como cerrar archivos o tablas de bases de datos, liberar objetos y liberar memoria y otros recursos adquiridos en el mismo bloque de programa. 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 ejecucion. La palabra clave r a i s e tambien puede usarse dentro de un controlador 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*._ _ - _ _ n _I >.:_ rencias mcqmaa aei usuano y ouas posmres concuciopara co~nproopr nes de error. S61o d&erf& asarse ex.cepcianes para eituaciones a n o d e s o inesperadas.
.
C
d
3
. I
L-
,I 1-
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 realiza 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 incluido a proposito en el ejemplo TryFinally), el programa se detendra, per0 no volvera 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, simplemente 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 excepcion.
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 excepciones 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, captamos la excepcion EDivBy Zero, que define el RTL de Delphi. Otras excepciones 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 programadores 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 (probablemente 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 ligeramente, 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 begin
(A, B: Integer) : Integer;
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 controlador 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 funcionamiento 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 evento 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 demuestra 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 escepciones, 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, sabremos donde tuvo lugar la excepcion y podremo~ver la llamada del controlador 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 programa en la linea de c6digo fuente m& cercana a1 lvgar m ~l qw se cfeb la se excepcion. Asi, a1 moverse paso a paso por el cMgo, puede verse 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.
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 componente Appl icat ionEvent s y un controlador para su evento OnExcept ion:
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 asignar 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 predeterminada 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.
.............. ...................... ll:37:48:Divisiun bv zero ll:37:53: raise button pressed 11:37:56:Divislon b y zero ll:37:58:raise button pressed ll:37:59:raise button pressed 11:38:00:raise button pressed
I I
.
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 almacenar 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 cambio, 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 manipular 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, seleccionandolo 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 funcionan las referencias de clase, hemos creado un ejemplo llamado ClassRef. El formulario 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 etiquetas 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. Almacena 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: TMouseButton; Shift: TShiftState; X, Y: Integer) ;
TObject;
Button:
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
I
NOTA: Para que fkncione la construccion polimorfica. el tipo de la clase 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 constructax 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 trataremos 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 principalmente 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 funciones, 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 utilizado 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 mayoria 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 compiesta
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 ederoen 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 . - - .--. - . . . - . . 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 II
i.
7--o--
------ -- r-Y
ta menos de 10 KB. Esto es debido a rque se acabath eliminando las rutinas de v se -- scmarte - - r ---- deedenas ,tamhih el -- ,:stor de memoria, lo cud es hicamen-
te posible en programas que utilizan exclusivamente llamadas de bajo nivel. 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 operacibn, 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 aplicaciones 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 completo) 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 implicita 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 alguno 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 propio administrador de memoria, definido por el registro TMemoryManager y a1 que se accede mediante las funciones GetMemoryManager y SetMemoryManager. Para mas informacion, la funcion GetHeapStatus 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 informacion 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 procedimiento EnumModules. Una lista bastante larga de las variables globales, como el caso de aplicacion 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 esisten 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 rutinas 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, reemplazando 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 revisado, 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 diferenciar 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 predeterminado 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 programadores 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:
VER-PLATFORM-WIN32-NT: end:
ShowMessage ('Windows 9x'); ShowMessage ( 'Windows NT ' ) ;
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 (escritura 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 TMultiReadExclusiveWriteSync h r o n i z e r se ha actualizado en Delphi 7, pero mejoras similares estan disponibles 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 actualizaciones 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 devuelve -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 BoolToStr
La funcion inversa es S t r T o B o o l , que puede convertir una cadena que contenga uno de 10s valores de las dos matrices booleanas mencionadas anteriormente 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 TryStrToFloat 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 corresponde 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 information 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 corresponde 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 reahzar 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 sobrecargada 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 tipografia 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 ncludeTralingBackslash ahora es mas conocida como IncludingTrailingPathDelimiter). 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 variables 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 ejecutado, 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 opciones predeterminadas del ordenador en el que se ejecutar su programa mediante la invocation de la nueva funcion GetLocale Format Settings (solo disponible 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 funciones estadisticas y una doccna dc funciones economicas. Describir todas estas funcioncs seria bastante aburrido, aunquc algunos lectores probablementc se cncuentren muy intcresados en las capacidadcs matematicas dc Delphi. Es por esto, que hemos decidido centrarnos en las funciones matcmaticas 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 considcrable 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 sobrecargada 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 cadenas.) Puede usarse, por ejcmplo. para calcular el minimo dc dos valorcs: nMin : = IfThen (nA < nB, na, nB) ;
? : del lenguaje C/ C++,que es muy util porque permite reemplazar una sentencia completa
NOTA: La hncion IfThen es similar a1 operador
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 flotantc cs una aproximacion de un valor real teorico. Cuando realizamos operaciones matematicas con numeros de coma flotante, la inesactitud de 10s valores originales 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 aproximan 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 constantes 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, devolviendo 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 RoundTo
(123827, 3 ) ; (12.3827, -2);
// e l r e s u l t a d o e s 1 2 4 . 0 0 0 // 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 positivo 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 redondearlos 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 redondeo 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 unidad Math mediante la funcion SimpleRoundTo, que utiliza un redondeo aritmktico asimetrico. En este caso, todos 10s numeros ,5 se redondean a1 valor superior. Sin embargo, 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 continuacion: (SimpleRoundTo
(
d *10
,
0 ) / 10 )
Las unidades ConvUtils y StdConvs En la unidad ConvUtils se encuentra el nucleo del motor de conversion presentad0 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: -te parastones (la unidad britiinica de medida que es equivalente c . . C,.. U"I"..:,, , , " , ..:c:,,* ,., ,;,l m l G j a I a.:a,.*U, u I u a u G a UG,a, , , ..a..;I. a 14 lib&). LII I U ~ U L G Ibaa", 3 1 LIGUG ~ U IG LIIGUIUU 6 1 1
1
a ,
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 variable 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.jemplos.) Existen funciones para calcular el valor final o inicial de un intervalo de tiempo 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 intervalo 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 milesima 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 algunas 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 comparando 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 : _ _ ---__ _--_ 3 nomores a un coalgo ae cuatro caracteres Iormaao por una m r a y rres numeros. Puede encontrar mas information a1 respecto en www.nara.gov/ genealogylcoding .html . 1
3-
-..-L-_
L
L-
l-L-_
..-A
A_-_
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 mayoria de estas funciones de cadena se aiiadieron por comodidad para 10s programadores 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 (denominada 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 comparaciones de lista se realizan del siguiente modo: procedure begin
TForml.ButtonMatchesClick(Sender: TObject);
ShowMessage (BoolToStr (AnsiMatchText(EditMatch.Text, strArray) , True) ) ; end; procedure TForml.ButtonIndexClick(Sender: TObject); var m a t c h : Integer; begin m a t c h : = AnsiIndexText (EditMatch.Text, strArray) : ShowMessage ( IfThen ( m a t c h >= 0, ' C o r r e s p o n d e a 1 n u r n e r o d e c a d e n a ' + IntToStr ( m a t c h ), ' N o c o r r e s p o n d e ' ) ) ; end;
Fijese en el uso de la funcion I f T h e n en las ultimas lineas de codigo; tiene dos cadenas de salida alternativas, que dependen del resultado del test inicial ( n M a t c h >= 0). Tres botones adicionales realizan llamadas sencillas a otras tres funciones nuevas, con las siguientes lineas de codigo (una para cada una): // r e p i t e ( 3 v e c e s ) u n a c a d e n a ShowMessage (Dupestring (EditSample.Text, 3 ) ) ; // i n v i e r t e l a c a d e n a ShowMessage (Reversestring (EditSample.Text)); // e s c o g e u n a c a d e n a a l e a t o r i a ShowMessage (RandomFrom ( s t r A r r a y ) ) ;
De Pos a PosEx Delphi 7 aporta su granito de arena a la unidad StrUtils. La nueva funcion PO sE X resultara muy practica para muchos desarrolladores y merece que hable-
mos de ella. Cuando se buscan multiples apariciones de una cadena dentro de otra, una solucion clasica de Delphi era utilizar la funcion Pos y repetir la busqueda 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 sistemas operativos. En las anteriores versiones de Delphi, la unidad de Windows definia 10s mismos tipos; ahora se han desplazado a esta unidad comun, compartida 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 presentadas 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 ocupacion 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 escepcionj \,a quc no pucdc compararse un valor indcfinido: cste cra cl comportamicnto 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 comparacioncsi 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 mucho 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 interesante definicion para 10s numeros complejos en la unidad VarCmplx (disponibles en formato de codigo fuente en el directorio Rtl\Common). Se pueden crear variantes complejas utilizando una de las funciones sobrecargadas VarcomplexCreate 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 superficie 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 objetos y clases. De hecho, con una variante personalizada se tiene la ven. . ae usar la soorecarga ae operaaores en las esrrucruras . raja ae aaros, per0 se pierde la verification en tiempo de cornpilacion, se hace que el codigo sea I
I
.
I
I
I
.
l
r
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 altcrnativa para utilizarla a1 pasar cadenas de un ejecutable a una DLL (una bibliotcca 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 bibliotecas 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. Estas 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 Fahrenheit: 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 (guardados 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 programa 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 puede ver en la figura 3 3.Para que la operacion sea mas rapida, es posible seleccionar 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 segundo 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 ;
Inslruciimr hap types lrorn lM to dlboxes. enlm am*
Base lype: Cmlirnetus
&&:
Qerlinalica Type
Cmvwted Am&
1100
Cham
1
I
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, aparecera 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 personalizar 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 podria 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 legalmente 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 ofrece soporte para las conversiones a1 euro, utilizando un enfoque de redondeo ligeramente distintos, aunque no tan precis0 como el requerido por las reglas de conversion de divisas europeas. Aun asi, resulta aconsejable mantener 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 unidad de medida con el motor. Siguiendo la plantilla que proporciona la unidad S tdConvs creamos una nueva unidad (llamada EuroConvCons t). En la seccion de la interfaz, declaramos las variables para la familia y las unidades especificas: 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; cuESP: TConvType; cuFRF: TConvType; // y e l r e s t o .
. .
// A l e m a n i a // E s p a f i a // P r a n c i a
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 pasado 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 ' BelgianFrancs [BEF) Dutch Gulders [NLG) Auslnan Sch~nlngs(ATS] Porluwese Escudos lPTEl ~ m n &~ a r k o[FIM] ' Greek Drachmas(GRDI LuxembourgFrancs [LUF)
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 redondeo 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 escribir 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. Llamando simplemente esta funcion en lugar de la funcion Convert estandar conseguiremos 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 especiales, como la conversion de una divisa a si misma. Este es el codigo de la funcion EuroConv, que utiliza la funcion interna EuroRound para redondear a1 numero de digitos especificado en el parametro Decimals (que debe estar entre 3 y 6, de acuerdo con las reglas oficiales): 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.
I
D \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 faDirectory, s r ) = 0 then i f FindFirst (Folder + ' * . * I , try repeat i f ( sr.Attr and faDirectory) sList .Add (sr.Name) ; u n t i l FindNext (sr) <> 0; finally Findclose ( s r ); end ;
=
faDirectory then
end ;
Finalmente, el programa utiliza una interesante tecnica para solicitar a1 usuario 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 then . . .
('Seleccione una carpeta'
,
I ,
,
CurrentDir)
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), implicitamente (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 sistema. 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 definidos en la misma clase TObj e c t . Por ejemplo, el metodo ClassName devuelve 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 definido 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 Text
:= :=
Button1.ClassName; TButton.ClassName;
Hay ocasiones en las que es necesario usar el nombre de una clase, per0 tambien 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 cualquier 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 devuelve 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 podemos 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 : ' (Sender-Instancesize)); end;
+ IntToStr
El codigo verifica si la Classparent es nil, en caso de que se este utilizando 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 metodo 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 objeto dado, con este test: if Sender = Button1 then.. .
En lugar de verificar una clase o objeto concreto, sera necesario, por lo general, 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 referencia 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') w h i l e MyClass <> TObject do...
d o ...
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. -
7
N6%?& Comq extension adicional a este ejemplo. es posible crear un arb01 con todas las tlases basicas de diversos componentes en una jerarquia.
i
La biblioteca de clases principales
Ya vimos que Delphi incluye una gran cantidad de funciones y procedimientos, 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 detallada 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. Exploraremos 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 unidades 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 componentes, denominada CLX, pronunciado "clics", y que significa Biblioteca de Componentes 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 programadores 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 programas 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. Tecnicamente, 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 TWinControl)
Controles (oomponentes visuales)
Controles no de ventana (TComponent)
I
(subclases de TGraphicControl)
I
Componentes no visuales (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 directamente 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 confuso. Estas clases no componentes se utilizan normalmente para valores de propiedades 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 facie . 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 controles, 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 denominacion nuevas, 10s nombres tradicionales sobreviviran probablemente y sc mezclaran 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 llamados 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 datos 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, ahora 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 estrictamente 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 vuelven 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 AUinrlnrtre En U x r l i v tnrln Fnrmn.lnAm m m v u r 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 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 objeto. En la biblioteca, este metodo esta implementado por varias clases no componentes, 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 personalizar el sistema de streaming y aiiadir informacion adicional (pseudopropiedades); 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 efecto 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 propiedades. 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 propiedades pero retarda algunas asignaciones hasta una fase posterior de retoque, para evitar 10s problemas con las referencias a objetos que aun no se han .... . pero queaa tan . .. mlclanzaao. aste proceso es mas complejo, 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 simplifka 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,
.
.
T . .
.
~~~
,
<-
..
La palabra clave published Junto con las directivas de acceso public, protected y private se puede usar una cuarta. dcnominada published.Para cualquier campo. propiedad 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 componentc no contiene ni campos ni metodos sino propiedades y eventos. Cuando Delphi genera un formulario o modulo de datos, coloca las dcfiniciones 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 componente, 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 mismo mod0 que a 10s elementos publicos, es decir, empleando 10s identificadores correspondientes en el c6digo. Sin embargo, en algunos casos especiales, es posibIe acceder a elementos publicados en tiempo de ejecucibn por su nombre. La clase TOb ject tiene tres interesantes mdtodos 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 posicion 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 "nonnales", 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 dinamicos, 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 informacion 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 documentadas, 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 completamente 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 puntero. 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 generalmente resuelve el acceso normal a propiedades de una manera mas eficiente. La ventaja 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 formulario. 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 convertiran en cadenas mediante una conversion de variante. Los ob-ietos (como el valor de la propiedad Font) apareceran como direcciones de memoria
!?!~ow~Y Captron
I ~ a b e lCapl~on l = &Pmpe~ty
-
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 componentes, 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 definicion de posesion. Cuando se crea un componente, se le puede asignar un componente 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 propiedad de la matriz C o m p o n e n t s . El componente especifico tiene un o w n e r (propietario) 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 encargara 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 componente que se deje caer sobre el. Si siempre se crean componentes con un propietario (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 ejemplo, 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 componcntes 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 componente que tiene un propietario, digamos, por ejemplo, un formulario. Esta propiedad puede resultar muy util (comparada con el uso direct0 de un componente especifico) para escribir codigo generico, que actue en todos o en muchos componentes 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 componentes, 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 componente 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 FindComponent 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 propietario 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: begin RemoveComponent (Buttonl); Form2.InsertComponent (Buttonl); end;
TObject);
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 procedimiento 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: 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;
TObject);
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 realmente, 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. Normalmente, 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 clasifican 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 modificar su propiedad C a p t i o n o T e x t , el nuevo nombre se copia normalmente 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 diferente. Si mas tarde se cambia el nombre del componente, Delphi modificara 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 componente, 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, simplemente 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 aplicacion 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, eliminaremos 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.
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
I
,
1
utilizada en el formulario, de modo aue el'enlazador intelinente - Y el sktema 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-
,..-U , -,c,,,, SG G IGLIGIGU
,..I,,:, I I I U I ~ I I W~
,, , ---- ,-a, a w.mlpunGuiGs
ms - L - 1
-..---I,
,., slaicura , r ; i r ~ g u ~
, -1 , -&, : ,
I Ldual, wauuu GI
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 manualmente 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 formulario 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 rapidamente. 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 componentes 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: begin ListBoxl. Items .Add (Editl-Text); end;
Hemos listado estos metodos solo para mostrar que en el codigo de un formulario normalmente nos referimos a 10s componentes disponibles, definiendo sus interacciones. Por esa razon, parece imposible librarse de 10s campos que corresponden a1 componente. Sin embargo, lo que se puede hacer es ocultarlos y mover10s 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 cargara 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 queremos usar. Deberiamos hacerlo antes de crear el formulario, asi normalmente conviene 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 formulario 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 programacion 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 componente, en la que se pueden almacenar valores personalizados. El tip0 de informacion almacenada y la forma en que se usa dependen completamente de nosotros. Normalmente, es util disponer de una posicion de memoria para adjuntar information a un componente, sin la necesidad de tener que definirla en su clase. Tecnicamente, la propiedad Tag almacena un entero largo para poder, por ejemplo, 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 componente usando su identificador (tag).
Eventos En realidad, 10s componentes de Delphi, se programan usando PME: propiedades, metodos y eventos. Aunque a estas alturas, 10s metodos y propiedades deberian 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 operativo, 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 controlador 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 denomina 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 propietario 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 mCtodos 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 procedimiento, 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 relacionado 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 Buttonl: MyButton; end;
(Sender: TObject) ;
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 Inspector, 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 Inspector, 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 diferentes 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 cambiado. Para definir un evento, simplemente definimos una propiedad correspondiente 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 lanzamiento 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: begin TheDay.Increase; end;
TObject);
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 importante 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 mediante 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 nuevos elementos. La ventaja de las matrices dinamicas sobre una TLi st,en cambio, esta en que las matrices dinamicas permiten indicar un tip0 especifico para 10s objetos contenidos y realizar la verificacion de tipos apropiada 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 sencilla 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 comoda: 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 propiedad 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 sentencia 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 precisamente 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 (12), 1 + Random (30))); end;
+ Random
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 causar 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
)
1
hcraposible la exidencia de nada sin6 o m 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 actualiza 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 objetos, 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 especificar 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 ejemplo, 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 componentes, con total soporte para la notification de la destruccion (una importante caracteristica de seguridad cuando 10s componentes estan conectados mediante 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 objetos, a partir de 10s cuales se pueden extraer unicamente elementos comenzando 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 comunes 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 metodos 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 destruiqbjetos que queden en la estructura de datos cuando se destruyan. Simplernente se pueden &ar todos esrtos elementos, destruidos cuando se hayam terminado de usar y despues destruir el contenedor.
...,..'.""
r i n an1lpll-a 'UY
U
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 modificad0 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 identificar 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 elementos 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 verification, pero sustituirlo por uno propio implica sobrescribir la funcion virItual ~ u c k e For t y, en-ultimo caso, cambiar el ntimero de entradas dle la marnz, esrameclenao un valor alrerenre para la propleaaa ~ u c ~ e i x o u n t . -
-
-
I
~--A.~'
..*-S
l . - ? ~
3
-
1
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 clave 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 definiciones: type TBucketProc = procedure (AInfo, AItem, AData: Pointer; out AContinue: Boolean) ; function TCustomBucketList.ForEach(AProc: AInfo: Pointer) : Boolean;
TBucketProc;
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 defmida 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 verification 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 extraccion. Sin embargo, afiadir verificaciones en tiempo de ejecucion ralentiza un programa 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 tecnica 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 esthtica 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 descendientes, por lo que sus operaciones reales han de ser identicas. La 6nica J : f ----- :--- -1 A:2 - -- a _ --L&- J - - 2 ----- 3:--*--..airerencra es er upo- --*:a:-urruzaao 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 diferente 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: begin ListW.Add (TDate (TButton.Create (nil)) ) ; TList (Listl) .Add (TButton.Create (nil)) ; UpdateList; end;
TObject);
La llamada a Update List lanza una excepcion, que se muestra directamente 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 especifico hace que un programa resulte mucho mas robusto. Escribir una lista envoltorio 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 escribimos 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 importantes en el context0 de lectura y escritura de componentes dentro de un stream (por ejemplo, Readcomponent y Writecomponent), per0 algunas son utiles tambien en otros contextos. En el listado 4.2, se puede encontrar la declaracion 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 especificos 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 abstracts 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 archivo 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 representado 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 almacenados 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 fmOpenRead) ;
(OpenDialogl.FileName,
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 fmshareDenywrite, 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 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
mai'
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 . constructor to& L . & ~ oguu-hwro e s ~ e p:ci&a," e ' h $ s ~ t r e a m ~ r r o rSu ,..,, ,, , , ,, ,.,., , , , ,,ase estandariza y un nor..,.- ,, ,
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 despues 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 generalmente 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 permanencia. 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 ReadComponent 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 mostrarlo. Esto se puede conseguir transformando la representacion binaria del formulario 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 contienen el nombre Resource en lugar de Binary (como en Obj ect ResourceToText), convierten el formato del recurso obtenido por WriteComponentRes. Un ultimo metodo, TestStreamFormat, indica si un DFM contiene una representation binaria o textual. En el programa FormToText, se ha utilizado el metodo O b jectBinaryToText 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: var MemStr: TStream; begin MemStr : = TMemoryStream.Create;
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
- -
-
-
-
Hrn.306 &n
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 lenta, 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 operation 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 formulario (es decir, su archivo DFM) del corrcspondiente recurso inscrtado en el archive 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 formulario 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 .. . . - crear una nueva clase slernpre que la Ilamemos. m 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 . correcramente. La * con la VLL y runciona nueva clase sueam aeclara senci1
.
7
.
I
C
11
i
~
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 guardados 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&.
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 codificado en el segundo campo de memo, tras decodificar el archivo. En este ejemplo, 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 programa 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):
-bow&
-.
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 mucho tiempo ha estado disponible cn el CD dc Delphi una unidad que se comunicaba 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: TCompressS 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 cuadros 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 previamente. 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 resultado 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, podemos 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, intentara obtener el tamaiio del stream de entrada, que es un TDecompressionstream. Sin embargo, esta operacion causa una excepcion, ya que 10s streams de compresion 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 disponible 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 componentes 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 seguridad 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 propietario), 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 rectangulo 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 especial 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 componentes no visuales y; por lo general, se usan en bases de datos y en aplicaciones Web. nes anteriores de Delphi, la clase TDa taModule se Forms; desde Delphi 6 se ha desplazado a la unidad .,,,,,., J .e este cambio era eliminar el encabezamiento de cbdigo de las clases GUI de las aplicaciones no visuales (por ejemplo, 10s modulos 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 .
-.
VV
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 registrada 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 AncestorIsValid e funciones globales nuevas, 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 personalizado.
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 variantes. Esta unidad forma parte del soporte ampliado para la invocacion dinamica 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 desarrollo 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 componentes 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 especificas de programacion. Para empezar compararemos la biblioteca VCL y la VisualCLX y analizaremos 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 enfoque 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 (pronunciado "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 alternativa 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 programa 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 desplegar 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 distribuirla. Distribuir las bibliotecas Qt - con una aplicaciones profesional Ial contrario 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 aplicacion 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 tradicional 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 extremadamente 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, propiedades y eventos. En muchas ocasiones, se pueden volver a compilar 10s programas 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 directamente 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 clases, transformados en funciones esthdar (no en m h d o s de clase) que
k&
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 ---2 - -1 3nay que uarse cuenra ae que e s oollgaiorw el uso oe a1 menos una ae las
1T--.
---a
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 obliga a esta limitation a1 omitir de la interfaz Qt 10s constructores QFormH y QApplicationH. o m-~m a s en Debhi sblo v En la mavor e- - estos m , ~ r -a-r -. ~t de - - - usaremos ..-- . - - -obietos -r mttodos CLX. Es importante saber que si se necesita se pueden utilizar directamente algunas caracteristicas adicionales de Qt; o p&de ser necesario realizar llamadas de bajo nivel para solucionar fallos de CLX. -
-
-
~
-
-
-0
- -
-
~
d
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
I
~
~
Standard Addtond Wh32 S~tmDaa Access Data Corirds &€mess
a d,a~FcT~&
I
1
1
I DdaSnao I BDE I ADO I
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
~
InloBsrt I Weffiavices i
~
I
lnteld& ~
b
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 encontraran 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 poscc una clasc denominada T B u t t o n quc representa un boton pulsador con mctodos 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 tiencn una unidad correspondiente en la VCL porque se proyectan sobre caracteristicas 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 unidades 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 aplicaciones CLX usan la extension XFM (Cross-platform (X) jbrm modules, Modu10s dc formulario multiplataforma o plataforma X). Un modulo de formulario es el resultado del streaming del formulario y de sus componentes, y ambas bibliotecas 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 esperar 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 diferencias cn las propicdades. eventos y componcntes disponibles, de tal mod0 quc a1 abrir de nuevo la definicion de formulario para una biblioteca diferente, se ocasionarin probablemente algunas advertencias.
TRUCO:Aparentemente el IDE de Delphi escoge la biblioteca activa observando 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 aplicaciones, 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 Left = 192 Top = 107 Width = 350 Height = 210 Caption = ' Q L i b C o r n p l Color = clBackground VertScrollBar .Range = 161 HorzScrollBar.Range = 297
TextHeight = 13 Textwidth = 6 PixelsPerInch = 96 object Buttonl: TButton Left = 56 Top = 64 Width = 75 Height = 25 Caption = ' A d d ' TabOrder = 0 OnClick = ButtonlClick end object Editl: TEdit Left = 40 Top = 32 Width = 105 Height = 21 TabOrder = 1 Text = ' m y name' end object ListBoxl: TListBox Left = 176 Top = 32 Width = 121 Height = 129 Rows = 3 1tems.Strings = ( 'marc0 '
object Forml: TForml Left = 192 Top = 107 Width = 350 Height = 210 Caption = ' L i b C o n p ' Color = clBtnFace Font.Charset = DEFAULT-CHARSET Font-Color = clWindowText Font.Height = -11 Font.Name = 'MS S a n s S e r i f ' Font.Style = [ I TextHeight = 13 Oldcreateorder = False PixelsPerInch = 96 object Buttonl: TButton Left = 56 Top = 64 Width = 75 Height = 25 Caption = ' A d d ' TabOrder = 0 OnClick = ButtonlClick end object Editl: TEdit Left = 40 Top = 32 Width = 105 Height = 21 TabOrder = 1 Text = ' m y name' end object ListBoxl: TListBox Left = 176 Top = 32 Width = 121 Height = 129 ItemHeight = 13 1tems.Strings = ( ' m r c o'
'john' 'helen' ) TabOrder = 2 end end
'john' 'helen' ) TabOrder = 2 end end
Sentencias uses Las unicas diferencias entre ambos ejemplos estan relacionadas con las sentencias 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 alternativa 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 probablemente podremos conseguir que las cosas resulten mas sencillas y permitira mantener 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 ligeramente 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 adaptara (probablemente) a ella.Esto resulta muy ventajoso para el usuario, per0 podria 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 programa 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 especificos 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 unidades 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 problemas a1 e-iecutar la aplicacion.
-
-
- - --
-
TRUCO:Para evitar que una aplicacibn CLX compile si contiene referencias a unidades VCL, se pueden desplazar las unidades VCL a un directorio .r n&+sr ;nel..;r m n t n f i a m a t r wvl-wIUIL en la rub de busqueda. De este modo. ias referkcias a unidades VCIL W e s que queden causariin an error "Unitnot found"' (Unidad no encontrada) . rl;4Lran+a hain 1 l4 u h vw,v r u u w l w u r u
)I
M
L
~
U
~
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 referencias a la unidad Qt. Algunas estructuras de datos de Windows estan ahora disponibles 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 casualidad compilamos un proyecto que incluya un formulario CLX, per0 no actualizamos 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 inmediatamente. La raz6n es que no se creo ningrin formulario VCL, por lo que el programa finaliza directamente. En otros casos, intentar crear un formulario CLX dentro de una aplicacion VCL originaria errores en tiempo de ejecucion. Por ultimo, el IDE de Delphi podria aiiadir incorrectamente referencias 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 I
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 componentes 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 pueden 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 minimizar 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 limites 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 control. 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 establecer 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, enumerados 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 formulario, 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 componente 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 ) , normalmente 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 ejecucion, 10s componentes estan, por lo general, en gris. Para ver una tecnica mas drastica, se puede ocultar completamente un componente, 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 propiedad, 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 sucesivamente.
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 componente. 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 formulario 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 sistema. 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 ejemplo, 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 fuentes, 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 frecuencia 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 ventanas. 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 sistema, 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 desplegable). Tambien hay otras ventanas creadas por la aplicacion per0 que permanecen 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 ventana), 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 embargo, 10s mensajes interesantes de Windows pueden seguirse mejor mediante controladores de mensajes especificos. Aun mejor, la VCL convierte estos mensajes de bajo nivel en eventos. En resumen, Delphi nos permite trabajar a un alto nivel, simplificando el desarrollo 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 compleja). Por el contrario, se puede conservar un control existente en memoria (tal vez invisible) per0 destruir su manejador de ventana y ahorrar recursos del sistema.
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 mismo 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 TWidgetControl.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, exactamentc 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 tareas 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 entradas 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 controles 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 (Key i n [ ' 0 ' . . ' 9 ' , # 8 ] ) then
i f not 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 compuesto, 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 organizacion mas consistente para las etiquetas de todo un formulario o una aplicacion. 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 numero 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 parentesis 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:
TRUCO:Si hacemob did. sobre el&th se pueden eswger & c m
1
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 plataformas 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 encontrar 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 diferentes 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 (seleccionado, 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 seleccion 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 responsabilidad 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 construir un cuadro de grupo con botones de radio, sencillamente hay que colocar el componente GroupBox sobre un formulario y, a continuation, aiiadir 10s botones 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, aiiadir 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 seleccionado, 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 verificacion. 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 seleccionar 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 i f ListBoxl. Selected [nItem] then
SelItems : = SelItems
-
1 do
+ 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 opciones 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 explicitamente en cuenta). Se pueden solucionar ambos problemas utilizando un control ComboBox,que combina un cuadro de edicion y una lista desplegable. El comportamiento 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 habitual 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 usuaria 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 cadena 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 moderna. Este componente es ligeramente mas complejo de usar, como ya se vera. Otras alternativas para listar valores son el control comun TreeView, que muestra 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 programa como actualizada. TrecVicw y ListView son dos componentes clave del Esplorador de Windows y se puede suponer que muchos usuarios estaran familiarizados con ellos: incluso mas que con 10s controles tradicionales de Windows. CLX tambien aiiade un control IconView, que es similar en parte de las caracteristicas 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 TCustomDraws 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 generalmente 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 nombres 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 mantienen 10s nombres de las entradas basicas.
Rangos Por ultimo, existen unos cuantos componentes que podemos usar para seleccionar valores dentro de un rango. Los rangos se pueden usar para entradas numericas 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 componentes. 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 desplazamiento "falsas" se controlan normalmente en Delphi usando propiedades especificas 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 operacion 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 independiente, que muestre el valor actual en una etiqueta, o de cualquier otro modo. I
NOTA: En CLX no existe el control UpDown, sino un SpinEdit que as&ia. . ua;Edit con el ~ p ~ o en w un n "nico control. . .. . . . . , 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 desplazar independientemente del resto de la superficie. Por esta razon, el ScrollBox tiene dos barras de desplazamiento utilizadas para mover 10s componentes insertados. 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 formulario con muchos controles y una barra de herramientas o barra de estado, podriamos 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 anteriores, 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 independientes, 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 formularios o 10s menus desplegables locales que se activan mediante el boton derecho 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 propiedad A u t o c h e c k ) . Los botones generalmente se pintan en un estado presionado 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 establecer 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 dialogo 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 componente 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 comando 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 bastante flexiblc (pucdc asociarse una lista de imagenes con cualquier menu desplegable especifico, e incluso con un elemento de menu dado, mediante la propiedad SubMenuImages). A1 disponer de una lista de imagenes mas pequeiia especifica 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 conectar 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, introducido 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, aiiadiendo un elemento temporal que indica cuando se muestra el menu contextual. Este resultado no es particularmente util, per0 demuestra que si se necesita mostrar 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 procesamiento adicional. El parametro Handled se preinicia como False, de mod0 que si no hacemos nada en el controlador de eventos, el menu contextual se procesara con normalidad. 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: MousePos: TPoint; var Handled: Boolean) ; begin ColorDialogl.Color : = Label2.Color; if ColorDialogl.Execute then Label2.Color : = ColorDialogl.Color; Handled : = True; end :
TObject;
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 comenzar 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 cuadro de dialogo Edit Tab Order, como muestra la figura 5.6. Ademas de la configuration basica, es importante saber que cada vez que un componente recibe o pierde el foco de entrada, recibe el correspondiente evento
O n E n t e r u O n E x i t . Esto permite definir y personalizar el orden de las operaciones 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.
w o 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 correspondientes (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 consiste 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 aconsejable 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 disefiamos el formulario y tambib en tiempo de ejecuei6n.
I
mi&trT
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 disminuyen 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 controles 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 controles 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 componentes 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 alternativa es definir la nueva propiedad Autosnap del divisor como T r u e . Esta propiedad hara que el divisor oculte el control cuando su tamaiio vaya mas alla del
Wh -..-
Dog cat
zard 11
hr~~np ug
ee
Rhi Lzf Hare She,Sheep para cada control del formulario, incluso para aquellos no contiguos al divisor.
Es aconsejable probar el ejemplo Split 1, para que se comprenda completamente 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. Basicamente, podemos colocar un componente sobre un formulario, alinearlo con la parte superior del formulario y, a continuacion, colocar el divisor en el formulario. 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 memo: % d ' , [Memoup-Height, MemoDown.Height1); end;
-
Lower
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 ajustarlas sobre la marcha. Esto no significa que debamos dejar de aiiadir teclas aceleradoras personalizadas con el caracter &, porque el sistema automatico utiliza sencillamente la primera letra disponible y no sigue 10s esthdares predefinidos. Podriamos encontrar claves mnemotecnicas mejores que las elegidas por el sistema. Esta caracteristica es controlada por la propiedad Auto Hotkeys,disponible 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 desplegable 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, contamos 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 activada 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, sencillamente 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, podemos 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.
I
El tiempo que habra de mantenerse el cursor sobre un componente antes de que aparezcan las sugerencias. 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 siguiente 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 control 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 modificar 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 largos, asi que se podria desear mostrar el texto completo en una sugerencia mientras 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 sugerencia. 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 sugercncia 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 alternativa, cl sistema permite que el propietario de estos controles, un formulario por lo general, 10s dibuje. Esta tecnica, disponible para botones, cuadros de lista, cuadros combinados y elementos de menu, se denominaowner-draw (dibujo por parte del propietario). En la VCL, la situacion es ligeramente mas compleja. Los componentes pueden 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 correspondientes. El sistema envia la solicitud para dibujar al poseedor (normalmente el formulario) y el formulario reenvia el evento de nuevo al control adecuado, activando 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 comportamiento 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 contreks 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 comunes 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 sera - estamos creando una interfae de usuario ~ersonalizada. r nece*d o que la actualicemos nosbtros misrnos. Por I contraste. si sc usa la appcac se adaptwan a ona aparicmcia e s t h d a r de 10s contmlesr, lw appcaciones nueva1 v e r s i h de estos controlo9.
".
I . .
.
C
.. .
i\
.-
Elementos del menu dibujados por el usuario Gracias a la VCL, el desarrollo de elementos del menu graficos resultan bastante 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 posibilidades, 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 relacionados 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 rectangulo 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 IbOwnerDrawvariable. 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 personalizado 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 formulario, 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 podemos 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 var
(Colors: array of TColor);
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 correspondicnte al color, llamamos a la funcion Delphi C o l o r T o S t r i n g . ~ s t dea 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 especialmente 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: Index: Integer; Rect : TRect; State: TOwnerDrawState) ; begin w i t h Control as TListbox d o begin
Twincontrol;
// 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 cuadro 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 componente 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 propiedad 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 propiedades 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 elementos 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 definidos en la propiedad C o l u m n s :
Ft 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 contenidos 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, usando 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 verificacion a 10s elementos, como en un control CheckListBox. Se pueden ver las distintas combinaciones de estos estilos en la figura 5.12. Otra caracteristica importante, que es habitual en la vista detallada o de informe 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
I-
Borland Develo..
in Java
I
Heb
Delohi
Delohi
Delahi
ClienIfS ... Develo ... Informant
Masterinq The Delohi Delphi ...
I
fib ~ i mr ~ l p
Borland Develooers Conference ... .:-Delohi ClienVServer 00 Pelohi Develooer's Handbook n ~ $ + D e l o hInformant i Mastering D e b h i n & T h e D e b h i Maaanne q -Thinkina in Java OW [email protected] OQwww.borland.com O.dwww.marcocanlu.com
nQ
'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: Column: TListColumn); begin nSortCol : = Column.Index; ListViewl.AlphaSort; end;
TObject;
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
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 elemento seleccionado, este pasa a su mod0 edicion y el usuario puede cambiarlo (tengamos en cuenta que 10s cambios se guardan automaticamente cuando finaliza 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 "seguimiento 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 podemos verlas en su descripcion textual:
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, denominado Iconview. Como ya se comento, el soporte de clasificacion ya se encuentra 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 rkd
a
.
.
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 mienibro 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 compleja 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: begin
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 organigrama 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 soporte 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 desplazado 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 control 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 encuentra 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 aplicacion 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 necesario hacer es usar dos conjuntos distintos de sentencias uses,mediante la compilacion 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]
Una directiva condicional similar se usa en la seccion inicial de la implementacion, 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 especial 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 end ;
(filename);
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 programador 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 end:
* (Parent a s TMyNode) .Extracode;
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 (nValue)) as TMyNode; MyNode.ExtraCode : = nValue;
(nil, 'item' + IntToStr
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 + - ! + IntToStr (MyNode.Extracode) ; end;
'I
Este es el codigo empleado en el ejemplo CustomNodes para mostrar la descripcion del nodo seleccionado en una etiqueta. Fijese en que cuando seleccionamos 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 herramientas 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 completamente visual. Ahora tambien se puede dejar que 10s usuarios del programa personalicen esta interfaz facilmente, como sucede con muchos programas profesionales. 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 aplicaciones 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 varias fichas (parecidas a 10s paneles) que cubren el resto de su superficie. Como hay una ficha por solapa, podemos simplemente colocar componentes en cada ficha para obtener el efecto adecuado tanto en tiempo de diseiio, 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 paginas . 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 usando 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 introducidos 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 solapa 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: begin PaqeControll.SelectNextPaqe (True); end;
TObject);
I Paged ) @ Tebr Sue I
a,
.
A
Tabs T&
I Ckk on lha Mbcx to change papc
New_P q e
Mxt Pagl
Control
Add tu R_epo.;itory.. . frcw as Text
1
TextDFM
- .-
-
I
Figura 6.1. La prlmera h o p de Pagecontrol del ejemplo Pages con su menu local.
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 estamos 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 usuario 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 componentes UpDown), dos casillas de verificacion y dos botones de radio, como muestra 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 continuacion, 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 (recuerde 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 lateral 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 controlador 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 nueva 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
TRUCO:Siempre que escribimos un formulario basado en un PageControl, debemos recordar que la primera ficha que aparece en tiempo de ejecucion 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 continuacion compilamos y ejecutamos el programa, este arrancara mostrando dicha 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 senci110s). 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, alincado 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: begin Imagel.Picture.LoadFromFi1e (TabControll.Tabs [TabControll.TabIndex]); end;
TObject);
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: var TabNum: Integer;
TObject);
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: begin with Tabcontroll do begin if TabIndex >= 0 then Tabs.Delete (TabIndex); if Tabs.Count = 0 then Imagel-Visible : = False; end; end;
TObject);
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 constante 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
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 desplazarse. Este ejemplo no sera complejo, su funcion consiste unicamente en proporcionar unas cuantas directrices. El ejemplo se llama WizardUI. El punto de arranque es crear una serie de paginas en un PageControl y configurar 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 podemos 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 controles 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 recursos 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 repetirse 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: 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;
TObject);
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
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 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:
-
11)
;
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 direcciones de sitios Web, es muy sencillo. Lo bueno es que podemos reutilizar la estructura 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 formulario, 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 derecho 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 determina 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 cuadro combinado. La parte desplegable se puede implementar facilmente en Delphi conectando un control PopupMenu a la propiedad DropdownMenu 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 asignarlas 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 herramientas utilizando una ActionList o la reciente arquitectura Action Manaizer. En ese caso.,aDenas asinnaremos com~ortamientoalrmno a 10s botones de la barra de herramientas, puesto que sus propiedades y eventos serhn administrados por 10s componentes de accion. Aun mas, se acabad usando
.
w
w
w
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 aplicacion 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 utilizadas 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.
1
This document explains how do you create a simple editor based on the RichEdit control using Delphi 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?
I
l ~ i l eOperations The most complex part of this program is implemenhng the commands of the File pull-down menuNew. 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 , -
, , , ,
Figura 6.5. La barra de herramientas del ejemplo RichBar.
6,.
,i'
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 archivos es bastante compleja, per0 vale la pena explorarla, dado que muchas aplicaciones 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 componente puede controlarlas con ordenes sencillas como:
Es un poco mas avanzado conocer cuando deberian habilitarse estas operaciones (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 verificar 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 codigo no puede fijarse simplemente en ellos, sino que deberia preguntar a1 propio componente, usando una caracteristica de bajo nivel no exteriorizada por el control Delphi: p r o c e d u r e TForrnRichNote.ApplicationEventslIdle(Sender: 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;
TObject;
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: begin with RichEdit.SelAttributes do i f fsBold i n Style then Style : = Style - [fsBold] else Style : = Style + [fsBold]; end;
TObject);
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 desplegable 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 pulsar 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 predeterminada 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 contrario. 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 herramientas. Delphi incluye un componente StatusBar especifico, basado en el control comun de Windows correspondiente (en VisualCLX hay tambien un control similar). 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 informacion 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 mayusculas 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 sugerencias 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
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 ckbgo d s detallado permite personalizar este wmportami&ct,
I
Los paneles no son componentes independientes, por lo que no podemos acceder 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 continuacion, 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 sugerencia 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 mediante 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 of the RicllBar example. ~ l i s c ~ ~ sinr et h ~ al p t e ~G nf the book "Maste~itrgD e l p l ~7". i W l i n e n an11 copyriglrted I)y I r l a ~ c oL.11rti1.
1
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 ?
I~ile Operations The most complex p a t o f h s program 1s rnplemenhng the commands of the Fie pull-down menuNew, Open Save, and Save As In each case, we need to track whether the current file has changed, .LA CIA -1..
.c.* L A -
rCIA- Ihs - s c k t m to the dpboard
.Z,"
-L-..IA
*----.
.-
.L*
*-..*
.LA CIA
7
h--
*L-
A-a-*-.
7 -
,
s
Figura 6.6. La barra de estado del ejernplo RichBar muestra una descripcion mas detallada que la sugerencia contextual
I
,.
.
.- .
<
-
.
>
TRUCO:Cuando la sugerencia de un mntrol estP Eompuesta dc dos cadLnas, podemos usar 10s rnktodos GetSho~tHinty GetLongHint par# e x h e r la primera (corta) y la segunda Wga) subcada partir da cadePa qge pasamos como pmbnetxo, qu+ sronnaltneate es.gl valor de la brbpiedad Hint,
I
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 aplicacion 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 1I 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 superior 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 ejecutaban 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 principio 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 usuario 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.
I
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 apariencia 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 pantalla, 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 sigue estando disponible por cuestiones de compatibilidad, de manera que un programa 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 representacion 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 cantidad de codigo intcrno y a la biblioteca de administracion de temas desarrollada originalmente por Mike Lischke. Algunas de estas nuevas caracteristicas de represcntacion 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 enfoque 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 disponible 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 aparentemente 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 proporcionar la unidad o el archivo de recurso relacionado). Ademb, este componente no esth en absoluto documentado.
Listado 6.2. Un archivo de rnanifiesto de rnuestra (Pages.exe.rnanifest). Demo de la biblia de Delphi
/>
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 Tabs S~ze Tabs Text
Click on the listtaw 10 change page
[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 elemento de menu y cambiar el estado del boton para que aparezca como pulsado. Para superar este problema, Delphi incluye una estructura de gestion de eventos 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 estado ( 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 permiten 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 propiedades 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 varios 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 pueden crear un ActionLink, que se registra con el objeto de accion. No se deberian definir las propiedades de 10s controles de cliente que se conecten 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 elementos de menu y diversos tipos de botones (botones pulsador, casillas de verificacion, 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, empezando 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 componente ActionList tiene un editor especial que se puede utilizar para crear diversas 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, listadas 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, preparar 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 controles 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 minimizar 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 reemplazar. Acciones de 10s controles solapa y pagina: El desplazamiento pagina anterior y pagina siguiente. Acciones de dialogo: Activan color, fuente, abrir, guardar e imprimir dialogos. Acciones de lista: Borrar, copiar, mover, eliminar y seleccionar todo. Estas 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 electronic~. 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 edicion). 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 = end object Actioncut: TEditCut Category = ' E d i t ' Caption = ' C u & t t Shortcut = end object Actionpaste: TEditPaste Category = ' E d i t ' Caption = ' & P a s t e 1 Shortcut = end object ActionNew: TAction Category = ' P i l e ' Caption = ' & N e w 1 Shortcut = OnExecute = ActionNewExecute end object ActionExit: TAction Category = ' P i l e ' Caption = ' E & x i t l Shortcut = 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 = 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 asociados, 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: begin
Definir Enabled como True,producira el resultado durante un corto periodo 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 accion, como el elemento del menu Edit>Bold y su correspondiente boton de velocidad. 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 estado de la propiedad Checked en el codigo): procedure TForml.ActionBoldExecute(Sender: begin with Memo1 . Font do i f fsBold i n Style then
Style
:=
Style
:=
Style + [fsBold] ;
-
TObject);
[fsBold]
else
Style end ;
El objeto Actioncount tiene un codigo muy sencillo, per0 muestra el funcionamiento de un controlador Onupdate. Cuando el control de memo esta vacio, se desactiva automaticamente. Se podria haber conseguido el mismo resultad0 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: begin
Por i~ltimo,hemos aiiadido una accion especial que comprueba el objeto remitente 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 seleccionado, 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 compartido, y despues detendran la ejecucion (mediante el parametro H a n d l e d ) o dejaran 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 controles 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 destino, 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 aiiadido 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 (disponible 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: begin acSave.Enabled : = Modified; end;
TObject);
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, siendo 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 begin
TFormRichNote.ChangeAlignment(Sender:
RichEdit.Paragraph.Alignment
: = TAlignment
TObject);
((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 dependencias de bibliotecas externas. Ambos componentes pueden almacenar controles de barra de herramientas asi como algunos elementos adicionales, como cuadros combinados y otros controles. 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 Object Tree View. Se puede personalizar el componente CoolBar de . * .. mucnas rormas: se pueae esramecer un mapa ae bits para el ronao, anaair 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
.
C
.
.,. .
4 .
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
r
..
D ; D ~ ~ ] ~ .
rjr111
1lay que utilizar controlcs parcialmentt: transparentcs. El componentc ti1pic0 1~tilizadoen una CoolBar es el ToolBar per0 10s cuadros combinados. (:ua. , .,,t:..L , a,,, A ., -A:,:-, .. 1.,-, A, , , 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 ejemplo CoolDemo: , a : , , . ,
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 cuadro 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 Microsoft la utiliza en sus aplicaciones, pero alternativas como el componente 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 muchas versiones distintas e incompatibles, ya que Microsoft ha hecho publi-
--"
n m i r n t w mn .--"-----" -- -----------dp -- rnntrnlpr -------" r-----" --- diqtintac
r n c rlictintsrc v ~ r e i n n ~dp c 1% h i h l i n t p r n a " "
-"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 simplemente 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 verticales, 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. Basicamente, 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 usuario 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
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 controles 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 cuadro combinado tenga la misma altura que la barra de herramientas, hay que ajustar 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 generica (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: var I: Integer; mItem: TMenuItem; begin
TObject);
... // 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, utilizado 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 elemento 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 herramientas 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 libremente por la pantalla, sin anclarla. Sin embargo, configurar un prograrna adecuadamente 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 aparezcan 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 propiedad 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 muestra 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 componente, basado en el truco de la palabra clave p r o t e c t e d ya comentado con anterioridad.) 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;
Otra accion del ejemplo Bro kDemo . proporciona a 10s usuarios una lista de 10s parametros del sistema relacionados con la solicitud, algo que es bastante util para la depuracion. Tambien es instructivo aprender cuhnta informacion (y exactamente que informacion) transfiere el protocolo HTTP desde un navegador a un servidor Web y viceversa. Para generar esta lista, el programa busca el valor de cada propiedad de la clase TWebRequest, como muestra este fragment0 de codigo: procedure TWebModulel.StatusAction(Sender: TObject; Request: TWebRequest ; Response: TWebResponse; var Handled: Boolean) ; var I: Integer; begin
lnformes dinamicos de base de datos El ejemplo BrokDemo define otras dos acciones mas, indicadas mediante 10s nombres de ruta / t a b l e y / r e c o r d . Para estas dos ultimas acciones, el programa genera una lista de nombres y luego presenta 10s detalles de un registro, utilizando un componente DataSetTableProducer para dar formato a toda la tabla y un componente DataSetPageProducer para crear una vista del registro. Veamos a continuacion las propiedades de estos dos componentes: object DataSetTableProducerl: TDataSetTableProducer DataSet = dataEmployee OnFormatCell = DataSetTableProducerlFormatCell end object DataSetPage: TDataSetPageProducer HTMLDoc.Strings = ( '
Employee : <#Las tName>
' '
Employee ID: <#EmpNo> ' '
Name: <#FirstName> <#LastName>' < l i > Phone: <#PhoneExt>' '
Hired On: <#HireDate>' '
Salary: <#Salary>
' ) OnHTMLTag = PageTailHTMLTag DataSet = dataEmployee end
Para producir toda la tabla, simplemente conectamos el DataSetTableProducer a la propiedad P r o d u c e r de las acciones correspondientes sin proporcionar ningun controlador de evento especifico. La tabla se hace mas potente si aiiadimos vinculos internos a 10s registros especificos. El codigo siguiente se ejecuta para cada celda de la tabla, per0 solamente se crear un enlace para la primera columna a partir de la primera fila (no se incluye la celda del titulo): procedure TWebModulel.DataSetTableProducerlFormatCell(Sender: TObj ect; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs, CellData: String) ; begin if (CellColumn = 0) and (CellRow <> 0) then CellData : = ' ' + CellData + ' '; end;
La figura 20.3 muestra el resultado de esta funcion. Cuando el usuario selecciona uno de 10s vinculos, se llama de nuevo a1 programa y puede comprobar la lista de cadena Q u e r y F i e l d y estraer 10s parametros desde la URL. Es entonces cuando utiliza 10s valores correspondientes a 10s campos de la tabla utilizados para la busqueda del registro (basada en la llamada a F i n d N e a r e s t ) .
[I
Web Broker Demo Number
Last Name
First Name
Phone Ext.
L
Hire Date
Salary
2
Nelson
Robert
250
12/28/1988
105900
4
young
Bruce
233
12/28/1988
97500
5
Lambcrt
Kun
22
2/6/1989
102750
2-
Johnson
Leshe
410
41511989
64635
9
Forest
Phil
229
4/17/1989
j i 0 W D '
-
-
--
-
75060
w
=='
f Figura 20.3. La sahda correspondiente a la ruta table del ejemplo BrokDemo, que genera una tabla HTML con h~pervinculosinternos. procedure TWebModulel.RecordAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin dataEmployee.0pen; // va a 1 registro solicitado dataEmployee .Locate ( ' L A S T N A M E ; F I R S T N A M E r, VarArrayOf([Request.QueryFields.Values['LastName'], Request .QueryFields .Values [ 'FirstNdrnel] ] ) , [ ] ) ; / / obtiene l a salida Response.Content : = Response.Content + DataSetPage.Content; end;
Consultas y formularios El ejemplo anterior utilizaba algunos componentes productores de HTML presentados con anterioridad, per0 hay otro componente de este grupo que no hemos utilizado aun: el QueryTableProducer (para BDE) y su hermano SQLQueryTableProducer (para dbEspress). Como veremos, este componente hace que la creacion de programas complejos de bases de datos sea algo muy sencillo. Supongamos que queremos buscar algunos clientes en una base de datos. Para ello, podriamos
crear el siguiente formulario HTML (incrustado en una tabla HTML para que tenga un formato mejor): Customer QueryProducer Search Form
I i f
%> ( E n d U s e r . LoginPorrn. E n a b l e d )
( %>
< a href="<%=EndUser.LoginForm.As~R~FG>">Login
}
} %> %>
No hay mucho mas que decir sobre la aplicacion WSnapUsers, ya que casi no tiene codigo ni valores personalizados. Este guion para la plantilla esthdar muestra como se realiza el acceso a 10s datos de usuario.
Derechos de acceso a una unica pagina Ademas de hacer que las paginas necesiten entrar en el sistema para acceder a ellas, se puede dar a usuarios especificos el derecho de ver mas paginas que otros. Cualquier usuario tiene un conjunto de derechos separados por punto y coma, o comas. El usuario debe tener definidos todos 10s derechos para la pagina solicitada. Estos derechos, que en general se listan en las propiedades ViewAccess y Modif yAccess de 10s adaptadores, indican respectivamente si el usuario puede ver 10s elementos dados mientras navega o si puede editarlos. Estas configuraciones son muy granulares y pueden aplicarse a adaptadores completos o a campos especificos de adaptadores (fijese en que me estoy refiriendo a campos de adaptadores, no a 10s componentes de la interfaz de usuario del diseiiador). Por ejemplo, pueden esconderse algunas columnas de una tabla para unos usuarios escondiendo 10s campos correspondientes (y tambien en otros casos, tal y como especifique la propiedad HideOptions). El componente global PageDispatcher tambien tiene eventos oncanviewpage y OnPageAcce ssdenied, que puede utilizarse para controlar el acceso a varias paginas de un programa dentro del codigo del programa, proporcionando un control mucho mayor.
Programacion Web con
Desde 10s dias de Delphi 2, Chad Z . Hower se ha encargado de la creacion de una arquitectura Delphi que simplifique el desarrollo de aplicaciones Web, con la idea de hacer que la programacion Web tan simple y visual como la programacion de formularios Delphi estandar. Algunos programadores estan completamente acostumbrados a las tecnologias HTML, JavaScript, hojas de estilo en cascada y las mas recientes tecnologias de Internet. Otros programadores simplemente quieren crear aplicaciones Web en Delphi del mismo mod0 en que crean aplicaciones VCL 0 CLX. IntraWeb esta pensada para este segundo tipo de desarrolladores, aunque es tan potente que, incluso programadores expertos en Web pueden sacar partido de su utilizacion. Segun Chad, IntraWeb esta pensado desarrollar aplicaciones Web, no sitios Web. Aun mas, 10s componentes de IntraWeb pueden usarse en una aplicacion especifica o pueden utilizarse en un programa de WebBroker o WebSnap. En este capitulo no hablaremos en detalle de IntraWeb, ya que es una biblioteca muy grande, como 50 componentes instalados en la paleta de Delphi y varios diseiiadores de modulos. Lo que vamos a hacer es comentar su base, de manera que pueda sopesarse su uso para futuros proyectos o para partes de estos proyectos, lo que resulte mas adecuado.
' I I
\
-
I)
TRUCO: Si se desea conseguir documentation sobre IntraWeb, se pueden consultar 10s manuales PDF que se encuentran en el Companion CD de Delphi 7. Si no pueden encontrarse aqui, tambidn se pueden descargar desde el sitio Web de Atozed Software. Para soporte sobre IntraWeb, es conveniente acudir a 10s grupos de noticias de Borland.
NOTA: Este capitulo ha sido especialmente revisado por Chad Z. Hower (tambien conocido como "Kudzu", el autor y coordinador de proyecto original de Internet Direct (Indy) y autor de IntraWeb. Entre las especialidades de Chad se incluyen las redes y programacion TCPIIP, la comunicacion entre procesos, la programacion distribuida, 10s protocolos de Lnternet y la programacion orientada a objetos. Cuando no esta programando, tambiin le gusta montar en bicicleta, kayak, escalar, descender en ski, conducir y hacer casi cualquier cosa a1 aire libre. Chad tambien publica articulos, programas y herramientas gratuitas (y otras curiosidades) en Kudzu World Chad es un estadounidense exen el URL http:llwww.Hower.o~Kudrul. patriado que pasa sus veranos en San Petersburgo (Rusia) y sus inviernos en Limassol (Chipre). Se puede contactar con Chad a traves de [email protected]. En este capitulo trataremos 10s siguientes temas: IntraWeb, aplicaciones Web y sitios Web. Uso de componentes IntraWeb. lntegracion con WebBroker y WebSnap. Aplicaciones Web de bases de datos. Uso de componentes de cliente.
lntroduccion a IntraWeb lntraWeb es una biblioteca de componentes creada por Atozed Software (www.atozedsoftware.com).En las ediciones Professional y Enterprise de Delphi 7, sc puede encontrar la version correspondiente de IntraWeb. La version Professional solo puede usarse en mod0 de pagina, como veremos mas adelante. Aunque Delphi 7 es la primera version del IDE de Borland en incluir este con-junto de componentes, IntraWeb lleva existiendo ya varios aiios, ha sido muy bien recibida y apoyada, con la disponibilidad afiadida de varios componentes de terceras partes.
-
I
-
TRUC0 :Entre 10s componentes de terceras partes parar IntraWeb se inclu yen IW'Char de Steema (10s creaclores de Teechart), l7N Bold de CentillelY .. - - -. -- - -
(para la 1ntegraci6n con Bold), IWOpenSource, IWTranslator, IWD~alogs, IWDataModulePool d e Arcana, IW Component Pack d e T M S y IWGranPrimo de GranPrimo. Se puede encontrar una lista actualizada de este tip0 de componentes en www.atozedsoftware.com.
Aunquc no se dispone del codigo fuente para la biblioteca central (disponible bajo solicitud y previo pago), la arquitectura IntraWeb es bastante abierta, y el codigo fuente completo dc 10s componentes esta plenamente disponible. IntraWeb forma ahora parte de la instalacion estandar de Delphi, per0 tambien esta disponible para Kylis. Si se escriben con cuidado, las aplicaciones IntraWeb pueden ser completamente multiplataforma.
siones para C++ Builder y Java. Se esta trabajando en una version .NET y probablemente estara disponible junto con una futura version de Delphi
Como propietario de Delphi 7, se puede recibir la primera publicacion de una actualizacion significativa (la vcrsion 5. I) y se pucde actualizar la licencia a una version completa de la edicion Enterprise de IntraWeb que incluye actualizaciones y soporte de Atozed Software (como puede comprobarse en su sitio Web). Si se desea una documentacion mas completa (archives de ayuda y PDF), es recomendable acceder a esta actualizacion a la version 5.1.
De sitios Web a aplicaciones Web Como ya se ha comentado, la idea que esconde IntraWeb es crcar aplicaciones Web en lugar de sitios Web. Cuando se trabaja con WebBroker o WebSnap (de 10s que se hablaba en el capitulo anterior), se puede pensar en terminos de paginas Web y productores de paginas, y trabajar muy cerca del nivel de la generation de paginas HTML. Cuando se trabaja con IntraWeb hay que pensar en terminos de componentes, sus propiedades y sus controladores de eventos, a1 igual que en el desarrollo visual con Delphi. Por ejemplo, se crea una nueva aplicacion IntraWeb mediante el menu File>New>Other, accediendo a la pagina IntraWeb del cuadro de dialogo New Items y escogiendo la opcion Stand Alone Application. En el cuadro de dialogo siguiente (que forma parte de Delphi, no del asistente de IntraWeb) se puede escoger una carpeta ya esistente o escribir el nombre de una que se creara nueva (lo comentamos porque no quedara muy claro en el dialogo). El programa resultante tiene un archivo de proyecto y dos unidades distintas.
Por el momento, creemos un ejemplo (llamado IWSimpleApp en el codigo fuente del libro). Para construirlo, habra que seguir estos pasos: 1. Accedemos a1 formulario principal del programa y le aiiadimos un boton, un cuadro de edicion y un cuadro de lista desde la pagina IW Standard de la paleta de componentes, Es decir, no aiiadimos 10s componentes VCL de la pagina Standard de la paleta, sino que utilizaremos 10s correspondientes componentes IntraWeb: IWButton, IWEdit y IWListbox. 2. Modificaremos ligeramente sus propiedades de esta manera: object IWButtonl: TIWButton Caption = 'Add I t e m ' end object IWEditl: TIWEdit Text = ' f o u r ' end object IWListboxl: TIWListbox 1tems.Strings = ( 'one ' 'two' ' t h r e e ') end
3. Controlamos el evento O n c l i c k del boton haciendo doble clic sobre el componente en tiempo de diseiio (como siempre) y escribiendo este codigo tan familiar: procedure TforrnMain.IWButtonlClick(Sender: begin 1WListBoxl.Items.Add (1WEditl.Text); end;
TObject);
Esto es todo lo que se necesita para crear una aplicacion basada en Web capaz de aiiadir texto a un cuadro de lista, como muestra la figura 2 1.1 (que muestra la version final del programa, con un par mas de botones). Lo que es importante tener en cuenta es cuando se ejecuta este programa es que cada vez que se hace clic sobre el boton, el navegador envia una peticion nueva a la aplicacion, que ejecuta el controlador de evento de Delphi y produce una nueva pagina HTML basada en el nuevo estado de 10s componentes del formulario. Cuando se ejecute la aplicacion, no se vera la salida del programa en el navegador, sino en el formulario del controlador de IntraWeb que muestra la figura 21.2. Una aplicacion IntraWeb independiente es un servidor HTTP completo, como se vera a continuacion. El formulario que se puede ver esta controlado por la llamada IWRun en el archivo de proyecto creado de manera predefinida en cada aplicacion IntraWeb independiente. El formulario de depuracion permite escoger un navegador y ejecutar la aplicacion a traves de el o copiar el URL de la aplicacion en el Portapapeles, para que se pueda pegar despues dentro del navegador. Es importante saber que la aplicacion utilizara de manera predetermi-
nada un numero de puerto aleatorio, que es distinto para cada ejecucion. Por eso habra que utiliza un URL distinta cada vez. Se puede modificar este comportamiento si se selecciona el diseiiador del controlador del servidor (que es parecido a un modulo de datos) y se fija la propiedad port. En el ejemplo hemos usado 8080 (uno de 10s puertos HTTP habituales), per0 otros valores tambien pueden funcionar.
one hvo
lhree lour four
Figura 21.1. El programa IWSimpleApp en un navegador
Fie
Run I.ldp
3wmi- 1 -Ia lnlraweb Version. 5 0 43
HTTP Pmt.8080
A
Packaged Enterprise L~censeNumber 0
.-. .-. ...............
--
-
-
II ,,,
Figura 21.2. El formulario de controlador de una aplicacion IntraWeb independiente.
El codigo IntraWeb es bbicamente codigo de servidor, pero IntraWeb tambien puede generar JavaScript para controlar algunas de las caracteristicas de la aplicacion. Tambien puedc e.jecutarse codigo adicional en el lado del cliente. Para haccr esto se utilizan componente especificos de cliente de IntraWeb o se escribe un codigo JavaScript personalizado. Como comparacion, 10s dos botones de la parte inferior del formulario en el ejemplo IWSimpleApp muestran un cuadro de mensaje utilizando dos tecnicas distintas. El primer0 de 10s dos botones (IWButton2) muestra un mensa.je mediante un cvento de servidor, con este codigo Delphi: procedure T f o r m M a i n . 1 ~ B u t t o n 2 C l i c k ( S e n d e r : TObject); var nItem: Integer; begin nItem : = 1WListboxl.ItemIndex; if nItem >= 0 then WebApp1ication.ShowMessage (1WListBoxl.Items [nItem]) else WebApplication.ShowMessage ('No ltem s e l e c t e d ' ) ; end;
El segundo de estos dos botones (IWButton3) utiliza JavaScript, que se inserta cn el programa Delphi preparando el controlador de eventos JavaScript apropjado en cl cditor especial de propiedades para la propiedad S c r i p t E v e n t s :
Un primer vistazo interior Ya se ha visto que crear una aplicacion IntraWeb es tan sencillo como crear un programa Delphi basado en formularies: se colocan componentes en un formulario y se controlan sus eventos. El efecto es bastante distinto, por supuesto, ya que la aplicacion se ejecuta en un navegador. Para comprender mejor lo que sucede, vamos a analizar el comportamiento interno de este programa tan sencillo. Esto deberia ayudar a comprender el efecto de establecer las propiedades de componente y trabajar con ellas en general. Se trata de un programa basado en un navegador, por lo que no hay mod0 mejor de comprenderlo que fijarse en el codigo HTML que envia a dicho navegador. Si abrimos el codigo de la pagina del programa IWSimpleApp (que no se muestra
aqui, porque ocuparia un espacio excesivo), se podra ver que esta dividido en tres secciones principales. La primera es una lista de estilos (basados en la etiqueta HTTP style) con lineas como la siguiente:
IntraWeb utiliza estilos para determinar no solo la apariencia visual de cada componente, como su fuente y color, sin0 tambien la posicion del componente, mediante el posicionamiento absoluto predetern~inado.Cada estilo se ve afectado por un cierto ni~merode propiedades de 10s componentes IntraWeb, asi que se puede experimentar sin problemas si se sabe algo de ho-jas de estilo. Si no se esta familiarizado con las ho-ias de estilo, lo mas facil es utilizar simplemente las propiedades y confiar en que IntraWeb hara lo mejor que pucda para representar 10s componentes en la pagina Web. El segundo bloque consiste cn codigo de guiones JavaScript. El bloque de guiones principal contiene el codigo de inicializacion y el codigo de 10s controladores de evcntos de cliente para 10s componentes, como el estracto siguiente: function IWBUTTONl-OnClick(ASender) ( r e t u r n SubmitClickConf irm( 'IWBUTTONI
', ' ', true, ' ' )
;
)
Este controlador llama a1 correspondiente codigo de servidor. Si se ha proporcionado directamente el codigo JavaScript en la aplicacion IntraWeb, como ya hemos comentado, se vera este codigo: f u n c t i o n IWBUTTON3_onClick(ASender)
(
window. alert (ASender.value) : )
La seccion de guiones de la pagina tambien hacer referencia a otros archivos necesarios para el navegador y que IntraWeb pone a su disposicion. Algunos de estos archivos son genericos: otros estan enlazados con un navegador especifico: IntraWeb detecta el navegador que se esta utilizando y devuelve un codigo JavaScript y archivos basicos JavaScript distintos. . -
-
- .
- . --
--
..-
- ..
NOTA: Ya que el lenguaje JavaScript no es idkntico para todos 10s navegadores, IntraWeb soporta solo algunos de ellos, como el de las ultimas versiones de Microsoft Internet Explorer, Netscape Navigator y Mozilla (un proyecto de codigo abierto usado durante la elaboration de este capitulo). Opera ofiece un soporte mas limitado de JavaScript, por lo que, de manera predeterminada, IntraWeb emitira un error si lo reconoce (de acuerdo con la propiedad SupportedBrowsers del controlador). Opera puede
por la version 5.1 de IntraWeb. Hay que tener presente que un navegador puede simular su identidad: Por ejemplo, es habitual que Opera este configurado para identificarse como Internet Explorer, lo que irnpedira una identificacion correcta para posibilitar el uso de sitios restringidos a otros navegadores, pero posiblemente llevara a errores en tiempo de ejecucion o inconsistencias. La tercera parte del HTML generado es la definition de la estructura de pagina. Dentro de la etiqueta body se encuentra una etiqueta f o r m (en la misma linea) con la siguiente accion de ejecucion:
La etiqueta f o r m contiene componentes especificos de la interfaz de usuario, como botones y cuadros de edicion: < i n p u t v a l u e = " A d d I t e m " name=" I W B U T T O N l " t y p e = " b u t t o n " o n c l i c k = " r e t u r n IWBUTTONl-OnClick(this);" id="IWBUTTONl" class="IWBUTTONlCSS">
El formulario tiene tambien algunos componentes ocultos que IntraWeb utiliza para llevar informacion entre el cliente y el servidor, Sin embargo, el URL es el mod0 mas importante de pasar informacion en IntraWeb; en el programa tendra este aspecto:
La primera parte es la direccion IP y el puerto que suelen utilizarse para la aplicacion IntraWeb independiente (cambia cuando se usa una arquitectura distinta para desplegar el programa), seguida del comando EXEC, un numero de creciente peticion y un identificador de sesion. Ya hablaremos mas tarde de la sesion, pero por ahora bastara con saber que IntraWeb utiliza un elemento del URL en lugar de cookies para permitir el acceso a sus aplicaciones a pesar de las posibles configuraciones de 10s navegadores. Si se prefiere, se pueden utilizar cookies en lugar del URL, modificando la propiedad TrackMode en el controlador del servidor.
Arquitecturas IntraWeb Antes de escribir mas ejemplos para mostrar el uso de otros componentes IntraWeb disponibles en Delphi 7, vamos a analizar otro elemento clave de IntraWeb: las distintas arquitecturas que pueden usarse para crear y desplegar aplicaciones basadas en esta biblioteca. Se pueden crear proyectos IntraWeb en el mod0 Application (donde son aplicables todas las caracteristicas de IntraWeb o en el mod0 Page (que es una version simplificada que puede aiiadirse a aplicaciones WebBroker o WebSnap ya existentes). Las aplicaciones que utilizan el mod0 Application pueden desplegarse como bibliotecas ISAPI, modulos de Apache o utilizando el mod0 IntraWeb Standalone (una variante de la arquitectura del mod0 Application). Los programas en mod0 Page pueden desplegarse como cualquier otra aplicacion WebBroker (ISAPI, modulo de Apache, CGI, etc.. .). IntraWeb usa tres arquitecturas distintas que se solapan en parte:
Modo Standalone: Proporciona un servidor Web personalizado, como en el primer ejemplo creado. Resulta practico para depurar la aplicacion (ya que puede ejecutarse desde el IDE de Delphi y situar puntos de ruptura en cualquier parte del codigo). Tambien se puede usar este mod0 para desplegar aplicaciones en redes internas (intranets) y para permitir que 10s usuarios trabajen en mod0 desconectado en sus propios ordenadores, con una interfaz Web. Si se ejecuta un programa IntraWeb independiente con el indicador -inst a l l , se ejecutara como servicio y no aparecera el cuadro de dialogo. El mod0 Standalone ofrece un mod0 de desplegar un programs IntraWeb de mod0 Application mediante la propia IntraWeb como servidor Web. Modo Application: Permite desplegar una aplicacion IntraWeb en un servidor comercial, construido como modulo Apache o biblioteca ISS. El mod0 Application incluye gestion de sesiones y todas las caracteristicas de IntraWeb, y es el mod0 preferido de desplegar una aplicacion escalable para su uso en la Web. Para ser mas precisos, 10s programas IntraWeb en mod0 Application pueden desplegarse como programas independientes, bibliotecas ISAPI o modulos de Apache. Modo Page: Abre una via a la integracion de paginas IntraWeb en aplicaciones WebBroker y WebSnap. Se pueden aiiadir caracteristicas a programas ya existentes o confiar en otras tecnologias para partes de un sitio dinamico basado en paginas Web, mientras que se gestionan mediante IntraWeb las partes interactivas. El mod0 Page es la unica opcion para utilizar IntraWeb en aplicaciones CGI, per0 carece de las caracteristicas de gestion de sesiones. Los servidores IntraWeb independientes no soporta el mod0 Page.
En 10s ejemplos que apareceran en el resto del capitulo utilizaremos por simplicidad y un proceso de depuracion mas sencillo el mod0 Standalone, per0 tambien hablaremos del soporte del mod0 Page.
Creacion del aplicaciones IntraWeb Cuando se crea una aplicacion IntraWeb, se dispone de un gran numero de componentes. Por ejemplo, si nos fijamos en la pagina IW Standard de la Component Palette de Delphi, se vera una lista impresionante de componentes importantes, desde el boton, casilla de verificacion, boton de radio, cuadro de edicion, cuadro de lista, campo de memo, y demas hasta 10s interesantes componentes de vista en arbol, menu, temporizador, cuadricula y vinculo. No vamos a mostrar una lista de todos 10s componentes y describir su uso con un ejemplo (en lugar de eso usaremos algunos de ellos en unos cuantos programas de demostracion y remarcaremos la arquitectura de IntraWeb en lugar de 10s detalles especificos). Hemos creado un ejemplo (llamado IWTree) en el que se utilizan 10s componentes de menu y de vista en arbol de IntraWeb per0 tambien se crea un componente en tiempo de ejecucion. Este componente tan practico permite acceder en un menu dinamico a1 contenido de un menu estandar de Delphi, haciendo referencia a su propiedad At tachedMenu y a un componente TMenu. o b j e c t MainMenul : TMainMenu o b j e c t Treel: TMenuItem o b j e c t ExpandAlll: TMenuItem o b j e c t CollapseAlll: TMenuItem o b j e c t N1: TMenuItem o b j e c t EnlargeFontl: TMenuItem o b j e c t ReduceFontl: TMenuItem end o b j e c t Aboutl: TMenuItem o b j e c t Applicationl: TMenuItem o b j e c t TreeContentsl: TMenuItem end end o b j e c t IWMenul : TIWMenu AttachedMenu = MainMenul Orientation = iwOHorizonta1 end
Si 10s elementos del menu controlan el evento Onclick en el codigo, se convertiran en enlaces en tiempo de ejecucion. Se puede ver un ejemplo de un menu en la figura 2 1.3. El segundo componente del ejemplo es una vista en arbol con un conjunto de nodos predefinidos. Este componente utiliza mucho codigo JavaScript para permitir la expansion y colapso de 10s nodos directamente en el navegador (sin tener que volver a acceder a1 servidor). A1 mismo tiempo, 10s
elementos del menu permiten que el programa trabaje con el menu expandiendo o colapsando 10s nodos y modificando la fuente. Este es el codigo para un par de controladores de eventos: procedure TformTree.ExpandAlllClick(Sender: TObject); var i: Integer; begin for i : = 0 to 1WTreeViewl.Items.Count - 1 do 1WTreeViewl.Item.s [i].Expanded : = True; end; procedure TformTree.EnlargeFontlClick(Sender: TObject); begin 1WTreeViewl.Font.Size : = 1WTreeViewl.Font.Size + 2; end;
Gracias a1 parecido de 10s componentes de IntraWeb con 10s componentes estandar de la VCL de Delphi, es facil leer y comprender este codigo.
Figura 21.3. El ejemplo IWTree utiliza un menti, una vista en arbol y la creacion dinamica de un componente de memo.
El menu tiene dos submenus, que son bastante mas complejos. El primer0 muestra el identificador de la aplicacion, que es un identificador de la ejecucion y sesion de la aplicacion. Este identificador esta disponible mediante la propiedad
AppI D del objeto global WebApp 1i c a t i o n . El segundo submenu, Tree
Contents, muestra una lista de 10s tres primeros nodos del nivel principal junto con el numero de subnodos directos. Aim asi, lo que es interesante es que la informacion se muestra en un componente de memo creado en tiempo de ejecucion (como muestra la anterior figura 2 1.3.), esactamente del mismo mod0 que en una aplicacion VCL: procedure TformTree.TreeContentslClick(Sender: TObject); var i: Integer; begin w i t h T I W M e m o - C r e a t e (Self) d o begin Parent : = Self; A l i g n : = alBottom; f o r i : = 0 t o 1WTreeViewl.Items.Count - 1 d o Lines .Add ( IWTreeViewl. Items [i].Caption + ' ( ' + IntToStr ( IWTreeViewl. Items [i] .SubItems .Count) ')
+
'1;
end; end; --
TRUCO:Fijese en que el alineamiento en ~ n t r a ~ funciona eb de un m o d ~ parecido a su homologo VCL. Por ejemplo, el menu de este programa tiene un alineamiento a l T o p , la vista en h b o l se alinea como a l C 1 i e n t y el campo d i n h i c o de memo se crea con el alineamiento a l B o t tom. Como alternativa, se pueden usar anclajes (que b c i o n a n como en la VCL): Se pueden crear botones colocados en la esquina inferior derecha o componentes en el centro de la pagina, fijando 10s cuatro anclajes. En 10s siguientes programas de muestras se pueden ver ejemplos de esta tecnica.
Escritura de aplicaciones de varias paginas Todos 10s programas que se han creado hasta ahora tenian una sola pagina. Es hora ya de crear una aplicacion IntraWeb con una segunda pagina. Como se vera, incluso en este caso, el desarrollo mediante IntraWeb se parece a1 desarrollo estandar en Delphi o Kylis, y es diferente de la mayoria del resto de las bibliotecas de desarrollo para Internet. Este ejemplo t a m b i h servira como excusa para profundizar en parte del codigo fuente generado automaticamente mediante el asistente de aplicacion IntraWeb. Comencemos desde el principio. El formulario principal del ejemplo IWTwoForms utiliza una cuadricula de IntraWeb. Este potente componente permite situar en una cuadricula HTML tanto texto como otros componentes. En el ejemplo, el contenido de la cuadricula queda determinado durante el arranque (en el controlador del evento o n c r e a t e del formulario principal):
procedure TformMain.IWAppFormCreate(Sender: TObject); var i: Integer; link: TIWURL; begin // fija 10s titulos de la cuadricula IWGridl.Cell [0, 0] .Text := 'Row'; IWGridl .Cell [0, 11 .Text : = 'Owner'; IWGridl .Cell [0, 2 ] .Text := 'Web Site'; / / fija el contenido de las celdas for i : = 1 to 1WGridl.RowCount - 1 do begin IWGridl .Cell [i,01 .Text : = 'Row ' + IntToStr (i+l); 1WGridl.Cell [i,l] .Text : = 'IWTwoForms by Marc0 Cantu'; link := TIWURL.Create (Self); link.Text := 'Click here '; link .URL : = 'http://www.marcocantu. corn'; IWGridl Cell [i,2 ] .Control : = link; end ; end ;
.
El efecto de este codigo se muestra en la figura 2 1.4. Ademas de la salida, hay que fijarse en unos cuantos detalles interesantes. En primer lugar, el componente de cuadricula utiliza 10s anclajes de Delphi (a False) para generar el codigo que lo mantiene centrado en la pagina, incluso aunque un usuario ajuste el tamaiio de la ventana del navegador. En segundo lugar, hemos aiiadido un componente IWURL a la tercera columna, per0 podria aiiadirse cualquier otro componente (incluidos botones y cuadros de edicion) a la cuadricula. La tercera (y mas importante) cuestion es que un IWGrid se traduce en una cuadricula HTML, con o sin marco alrededor. Este es un fragment0 del codigo HTML generado para una de las filas de la cuadricula:
Figura 21.4. El ejemplo IWTwoForms usa un componente IWGrid, texto incrustado y componentes IWURL.
TRUCO:En el listado anterior, hay que fijarse en que el UIU. vinculado se activa mediante JavaScript, y no directamente. Se hace asi porque todas las acciones de IntraWeb permiten operaciones adicionales de cliente, como validaciones, comprobaciones y envios. Por ejemplo, si se establece la propiedad Required de un componente, si el campo estiz vacio no se enviaI--o--r-r - - - - - - - a:--1-1ran 10s aaws, -. y se Vera un mensaje ae error ae Javaacrlpr wersonallzaole mediante la propiedad descriptiva F r iendlyName). -1..
> - A _ -
1
1-
3-
3-
T
/
La caracteristica principal del programa es su capacidad de mostrar una segunda pagina. Para realizar esto, en primer lugar se necesita aiiadir una nueva pagina IntraWeb a la aplicacion, mediante la opcion ApplicationForm de la pagina IntraWeb del cuadro de dialogo New Items de Delphi (File>New>Other). Aiiadimos a esta pagina algunos componentes IntraWeb, como siempre, y despues la aiiadiremos un boton u otro control a1 formulario principal que podamos usar para mostrar el formulario secundario (con la referencia a n o t h e r f orm almacenada en un campo del formulario principal): procedure TformMain.btnShowGraphicClick(Sender: TObject); begin anotherform : = TAnotherForm.Create(WebApp1ication);
anotherform.Show; end;
Incluso aunque el programa llame a1 metodo Show, puede considerarse como una llamada a ShowModal, ya que IntraWeb considera las paginas visibles como una pila. La ultima pagina que se muestra esta en la parte superior de la pila y es la que muestra el navegador. A1 cerrar esta pagina (escondiendola o destruyendola), se vuelve a mostrar la pagina anterior. En el programa, las paginas secundarias se pueden cerrar cuando se llama a1 metodo R e l e a s e , que es (corno en la VCL) el mod0 correct0 de deshacerse de un formulario que se ejecuta en ese instante. Tambien se puede esconder el formulario secundario y volverlo a mostrar mas tarde, evitando volver a crearlo cada vez (en particular si hacer esto implica perder las operaciones de edicion del usuario). - -
-. - --
-
-
I
ADVERTENCIA: Hemos &dido en el pronrama un b o t h CIese en el - formulario principal. No deberia llamar a Release, she invocar - en SU lugar a1 mCtodo Terminate del objeto WebApplication, pashdlola el mensaje de salida, como en WebApplication .Terminat e , ... . . . . .-.. .. .. [ ' tiooaoye ! ' ) . La aemostracion urillza una llamaaa alternauva: TerminateAndRedirect. 1
.
.
I
.I
4
'
Ahora que se ha visto como crear una aplicacion IntraWeb con dos formularios, vamos a analizar brevemente el mod0 que se IntraWeb crea el formulario principal. El codigo relevante, generado por el asistente de IntraWeb cuando se crea un programa nuevo, esta en el archivo de proyecto: begin
IWRun(TFormMain,
TIWServerController);
Es algo distinto del archivo de proyecto estandar de Delphi, porque llama a una funcion global en lugar de aplicar un metodo a un objeto global que represente a la aplicacion. No obstante, el efecto es bastante parecido. Los dos parametros son las clases del formulario principal y del controlador IntraWeb, que maneja sesiones y otras caracteristicas, como veremos en breve. El formulario secundario del ejemplo IWTwoForms muestra otra caracteristica interesante de IntraWeb: su extenso soporte de graficos. El formulario tiene un componente grafico con la clasica imagen de Atenea de Delphi. Se consigue esto a1 cargar un mapa de bits en un componente IWImage: IntraWeb convierte el mapa de bits en un archivo JPEG, lo guarda en una carpeta cache creada dentro de la carpeta de la aplicacion y devuelve una referencia a ese archivo, con el siguiente codigo HTML:
La caracteristica adicional proporcionada por IntraWeb y aprovechada por el programa es que un usuario puede hacer clic sobre la imagen con el raton para modificar la imagen a1 ordenar la ejecucion de codigo de servidor. En este programa, el efecto es dibujar pequeiios circulos verdes.
Este efecto se consigue con el codigo siguiente: procedure Tanotherform.IWImagelMouseDown(ASender: const AX, AY: Integer); var aCanvas: TCanvas; begin aCanvas : = 1WImagel.Picture.Bitmap.Canvas; aCanvas.Pen.Width := 8; aCanvas.Pen.Color := clGreen; aCanvas. Ellipse (Ax - 10, A y - 10, Ax + 10, A y end;
TObject;
+
10) ;
rnapa de bits. No hay que intentar utilizar el lienzo &age (como se hacia con el companente TImage de la VCL) y no hay que tratar de war un JPEG en primer Ingar, o no se verh nin& efecto o aparecera un error en tiempo de e j m c i h .
,
Gestion de sesiones Si se ha realizado algo de programacion Web, ya se sabe que la gestion de sesiones es un asunto bastante complejo. IntraWeb proporciona un sistema de gestion de sesiones predefinido y simplifica el mod0 en que se trabaja con sesio-
nes. Si se necesita una sesion de datos para un formulario especifico, todo lo que hay que hacer es afiadir un campo a ese formulario. Los formularies IntraWeb y sus componentes tienen una instancia para cada sesion de usuario. Por ejemplo, en IWSession hemos aiiadido al formulario un campo llamado Formcount.Por contra, tambitn hemos declarado una variable de unidad global llamada Globalcount, que comparten todas las instancias (o sesiones) de la aplicacion. Para aumentar el control sobre 10s datos de sesion y permitir que varios formularios la compartan, se puede personalizar la clase TUserSession que coloca el IntraWeb Wizard en la unidad ServerController. En el ejemplo WSession, hemos particularizado la clase de esta forma: type TUserSession = class public Usercount : Integer; end ;
IntraWeb crea una instancia de este objeto para cada nueva sesion, como puede verse en el metodo IWServerControllerBaseNewSession de la clase TIWServerController en la unidad ServerController predefinida. procedure TIWServerController.IWServerControllerBaseNewSession( ASession: TIWApplication; v a r VMainForm: TIWAppForm); begin ASession.Data := TUserSession-Create; end;
En el codigo de una aplicacion, se puede hacer referencia al objeto de sesion accediendo al campo Data de la variable global RWebApplication,utilizada para acceder a la sesion de usuario actual.
la unidad IWInit. Ofrece acceso a 10s datos de sesibn dc un mod0 seguro con respecto a 10s hilos: hay que tener un cuidado especial para acceder a ella incluso en un entorno rnultihilo. Esta variable puede utilizarse fhera de un formulario o control (que se basan de manera nativa en sesiones), que es por lo que se usa sobre todo en modulos de datos, rutinas globales y clases que no Sean de LntraWeb. Una vez mas, la unidad ServerController predeterminada ofrece una funcion auxiliar que puede utilizarse: function UserSession: TUserSession; begin Result := TUserSession(RWebApp1ication.Data); end;
Ya que la mayor parte del codigo se genera automaticamente, despues de aiiadir datos a la clase TUser ses s ion simplemente hay que usarla mediante la funcion User ses s ion, como en el codigo siguiente, extraido del ejemplo IWSession. Cuando se hace clic sobre un boton, el programa incrementa varios contadores (uno global y dos especificos de sesion) y muestra sus valores en etiquetas: procedure TformMain.IWButtonlClick(Sender: TObject); begin InterlockedIncrement (GlobalCount); Inc (FormCount);
Fijese en que el programa utiliza la llamada Inter loc kedI ncrement de Windows para evitar el acceso concurrente a la variable global compartida por varios hilos. Entre las tecnicas alternativas se incluye el uso una seccion critica o de T i d T h r e a d S a f e I n t e g e r de Indy (que se encuentra en la unidad IdTrheadsafe). La figura 2 1.5 muestra el resultado del programa (con dos sesiones en ejecucion en dos navegadores distintos). El programa tambien tiene una casilla de verificacion que activa un temporizador. Aunque suene extraiio, en una aplicacion IntraWeb, 10s temporizadores funcionan casi del mismo mod0 que en Windows. Cuando expira el plazo del temporizador, se ejecuta un cierto codigo. En la Web esto significa refrescar la pagina lanzando una orden de refresco en el codigo JavaScript : ' ,5000); IWTIMERl=setTimeout ( ' S ~ b r n i t C l i c k ( ~ ~ ~ W T I M"", E R l ~false) ~,
Integracion con WebBroker (y WebSnap) Hasta ahora, hemos construido aplicaciones IntraWeb independientes. Cuando se crea una aplicacion IntraWeb en una biblioteca para desplegarla en ISS o Apache, nos encontramos basicamente en la misma situacion. Sin embargo, si se quiere usar el mod0 Page de IntraWeb, las cosas cambian de un mod0 importante. Se trata de integrar una pagina de IntraWeb en una aplicacion Delphi WebBroker (o WebSnap). El puente entre 10s dos mundos es el componente IWPageProducer.Este componente se conecta a una accion de WebBroker como cualquier otro componente productor de paginas y tiene un evento especial que puede usarse para crear y devolver un formulario IntraWeb:
procedure TWebModulel.IWPageProducerlGetForm(ASender: TIWPageProducer; AWebApplication: TIWApplication; var VForm: TIWPageForm); begin VForm : = TformMain.Create(AWebApplication); end;
Con esta sencilla linea de codigo (ademas de un componente IWModuleCon-troller en el modulo Web), la aplicacion WebBroker puede incrustarse en una pagina IntraWeb, como hace el programa CgiIntra. El componente IWModuleController proporciona servicios centrales para el soporte de IntraWeb. Debe existir un componente de este tip0 para que cada proyecto de IntraWeb funcione correctamente.
Global 24
Form 14 User. 14
Figura 21.5. La aplicacion IWSession tiene contadores globales y especlficos de sesion, como puede verse ejecutando dos sesiones en dos navegadores distintos (o incluso en el mismo navegador).
- -
.
-
ADVERTENCIA: La versibn que se incluye con Delphi 7 tiene un problema con el Web App Debugger de Delphi y el componente IWModuleController. Ya se ha solucionado este problema y existe una actualizacion
,
gratuita. Este es un resumen del archivo DFM del mbdulo Web del programa de ejemplo: object WebModulel: TWebModulel Actions = < item Default = True Name = ' W e b A c t i o n I t e m l r PathInfo = ' / s h o w ' OnAction = WebModulelWebActionItemlAction end item Name = ' W e b A c t i o n I t e m . 2 ' PathInfo = ' / i w d e m o r Producer = IWPageProducerl end> object IWModuleControllerl: TIWModuleController object IWPageProducerl: TIWPageProducer OnGetForm = IWPageProducerlGetForm end end
Ya que esta es una aplicacion CGI en mod0 Page, no hay ninguna gestion de sesiones. Aun mas, el estado de 10s componentes de una pagina no se actualiza automaticamente escribiendo controladores de eventos, como en un programa IntraWeb estandar. Para conseguir el mismo efecto se necesita escribir codigo especifico para manejar mas parametros de la peticion HTTP. Deberia quedar claro incluso mediante este ejemplo tan sencillo que el mod0 Page hace menos cosas de mod0 automatic0 que el mod0 Application, pero que es mas flexible. En particular, el mod0 Page de IntraWeb permite aiiadir prestaciones de diseiio RAD visual a las aplicaciones WebBroker y WebSnap.
Control de la estructura El programa CgiIntra utiliza otra interesante tecnologia disponible en IntraWeb: la definition de una estructura personalizada basada en HTML. (Este tema no tiene realmente relacion, ya que las estructuras HTML tambien funcionan en mod0 Application, pero, simplemente, se han usado estas dos tecnicas en un unico ejemplo.) En 10s programas creados h a s h este momento, la pagina resultante es la proyeccion de una serie de componentes colocados en tiempo de diseiio en un formulario, en el que se pueden usar propiedades para modificar el codigo HTML resultante. i Q u l es lo que sucederia si se deseara incrustar un formulario de
entrada de datos en una pagina HTML compleja? Es extrafio construir todo el contenido de una pagina mediante componentes IntraWeb, incluso aunque se pueda usar el componente IWText para incrustar una porcion de HTML personalizado en una pagina IntraWeb. La tecnica alternativa implica el uso de gestores de estructura de IntraWeb. En IntraWeb se usa de manera invariable un gestor de estructura; el predeterminado es el componente IWLayoutMgrForm. Las otras dos alternativas son componentes IWTemplateProcessorHTML para trabajar con un archivo de plantilla HTML externo e IWLayoutMgrHTML para trabajar con HTML interno. Este segundo componente incluye un potente editor HTML que puede usarse para preparar el HTML generic0 al igual que incrustar 10s componentes IntraWeb necesarios (algo que en ocasiones habra que hacer manualmente con un editor HTML esterno). Aun mas, cuando se selecciona un componente IntraWeb desde este editor (que se activa haciendo doble clic sobre un componente IWLayoutMgrHTML), se podra utilizar el Object Inspector de Delphi para personalizar las propiedades del componente. Como puede verse en la figura 2 1.6, el HTML Layout Editor disponible en IntraWeb es un potente editor HTML visual; el texto HTML que genera esta disponible en una pagina aparte. (El editor HTML se mejorara en una proxima actualization, y se arreglaran unos cuantos detalles.)
Html Example code Font test
More text goes here, and you can type it directly. j~nd finally we have some text and a combo box within a grid..
1-
Figura 21.6. El HTML Layout Editor de IntraWeb es un cornpleto editor HTML visual.
En el HTML generado, el HTML define la estructura de Ia pagina. Los componentes solo se marcan con una etiqueta especial basada en Ilaves, como en el ejemplo siguiente:
TRUCQ:Fijese mqw cuando se u w w , los c&p.ona@s nn otilizan el posicioI3ami~& m ~ u t os b que ae ;listrihuyende acukdo con el IfIhlL. Por eso, el form'dari~sc convierte hnipmente en up contenedor de componentes, porque se ignofa la posicib d mulario.
o de 10s companentes del for-
No hace falta decir que el HTML que se ve en el diseiiador visual del HTML Layout Editor se corresponde de manera casi perfecta con el HTML que se puede ver a1 ejecutar el programa en un navegador.
Aplicaciones Web de bases de datos Como en las bibliotecas de Delphi, una parte importante de 10s controles disponibles en IntraWeb tienen que ver con el desarrollo de aplicaciones de bases de datos. El Application Wizard de IntraWeb tiene una version que permite crear una aplicacion con un modulo de datos (un buen punto de partida para el desarro110 de una aplicacion de bases de datos). En este caso, el codigo predefinido de la aplicacion crea una instancia del modulo de datos para cada sesion, guardandolo en 10s datos de sesion. Esta es la clase TUserSession predefinida (y su constructor) para una aplicacion IntraWeb con un modulo de datos: type TUserSession = class(TComponent) public DataModulel: TDataModulel; constructor Create (AOwner: TComponent) ; override; end; constructor TUserSession.Create(AOwner: TComponent); begin inherited; Datamodulel : = TDatamodulel.Create(AOwner); end:
La unidad del modulo de datos no tiene asignada una variable global; si fuera asi, todos 10s datos se compartirian entre todas las sesiones, con una gran posibilidad de problemas en caso de peticiones concurrentes desde varios hilos. Sin embargo, el modulo de datos ya expone una funcion global que tiene el mismo nombre que la variable global que utilizaria Delphi, y que accede a1 modulo de datos de la sesion actual: function DataModulel: TDataModulel; begin Result : = TUserSession(RWebApp1ication.Data) .Datamodulel; end;
Esto significa que se puede escribir un codigo como el siguiente:
Pero en lugar de acceder a un modulo de datos global, se utiliza el modulo de datos de la sesion actual. En el primer programa de ejemplo en el que se incluyen datos de una base de datos, llamado IWScrollData, hemos afiadido a1 modulo de datos un componente S i m p l e D a t a S e t y a1 formulario principal un componente IWDBGrid con la siguiente configuracion: o b j e c t IWDBGridl: TIWDBGrid Anchors = [akLeft, akTop, akRight, akBottom] Bordersize = 1 Cellpadding = 0 CellSpacing = 0 Lines = t l R o w s UseFrame = False DataSource = DataSourcel FromStart = False Options = [dgShowTitles] RowAlternateColor = clSilver RowLimit = 10 RowCurrentColor = clTeal end
La configuracion mas importante es la eliminacion de un marco que albergue el control con sus propias barras de desplazamiento (la propiedad ~ s e ~ r a m e ) , el hecho de que 10s datos se muestren a partir de la posicion del conjunto de datos actual (la propiedad F r o m S t a r t ) y el numero de filas que se mostraran en el navegador (la propiedad RowLimi t ) . En la interfaz de usuario, hemos eliminado las lineas verticales y dado color a filas salteadas. Tambien hemos especificado un color para la fila actual (la propiedad R o w C u r r e n t C o l o r ) ; de no ser asi, 10s colores salteados no aparecerian correctamente, ya que la fila actual tiene el mismo color que las filas del fondo, sin importar su posicion (si se fija la propiedad R o w C u r r e n t C o l o r como c l N o n e se podra ver lo que queremos decir). Estos parametros producen el efecto que muestra la figura 2 1.7. Tambien puede verse si se ejecuta el ejemplo IWScrollData. El programa abre el conjunto de datos cuando se crea el formulario, utilizando el conjunto de datos enlazado con la fuente de datos actual. procedure TformMain.IWAppFormCreate(Sender: begin DataSourcel.DataSet.0pen; end;
TObject);
El codigo relevante del ejemplo esta en el codigo del boton, que puede usarse para recorrer 10s datos mostrando la pagina siguiente o volviendo a la anterior.
Este cs el codigo para uno de 10s dos metodos (el otro no se presenta porque es muy parecido): procedure TformMain.btnNextClick(Sender: TObject); var i: Integer; begin n P o s : = n P o s + 10; if n P o s > DataSourcel.DataSet.RecordCount - 10 then n P o s : = DataSourcel.DataSet.RecordCount - 10; DataSourcel.DataSet.First; for i : = 0 to nPos do DataSource1.DataSet.Next; end;
Figura 21.7. La cuadricula data-aware del ejemplo IWScrollData.
Enlaces con detalles La cuadricula del ejemplo IWScrollData muestra una unica pagina de una tabla de datos; 10s botones permiten desplazarse hacia arriba y abajo por las paginas. Un estilo de cuadricula alternativo en IntraWeb es el que ofrecen las cuadriculas con marcos, que pueden mover cantidades aun mas grandes de datos hacia el navegador Web dentro de un area de pantalla de un tamaiio fijo utilizando un marco y una barra de desplazamiento interna, como haria un control ScrollBox de Delphi. Esto se demuestra en el ejemplo IWGridDemo. El ejemplo personaliza la cuadricula de un segundo mod0 muy potente: establece la propiedad de conjunto c o l u m n s de la cuadricula. Este parametro permite ajustar con precision el aspecto y el comportamiento de columnas especificas, mostrando por ejemplo hipervinculo o controlando 10s clics sobre celdas de elementos o titulos. En el ejemplo IWGridDemo, una de las columnas (la del apelli-
do) se ha convertido en un hipervinculo; se pasa el numero de empleado como parametro a1 comando de continuar, como muestra la figura 2 1.8.
Figura 21.8. El formulario principal del ejemplo IWGridDemo utiliza una cuadricula con marco con hipervinculos hacia el formulario secundario.
El listado 2 1 . 1 , muestra un resumen de las propiedades clave de la cuadricula. Fijese en particular en la columna del apellido, que tiene un campo enlazado (lo que convierte a1 texto de la celda en un hipervinculo) y un controlador de evento que responde a su seleccion. En este metodo; el programa crea un formulario secundario mediante el cual el usuario puede editar 10s datos: p r o c e d u r e TGridForm.IWDBGridlColurnns1C~ick(ASender: TObject; c o n s t AValue: String) ; begin w i t h TRecordForm.Create (WebApplication) d o begin S t a r t I D : = AValue; Show; end; end:
Listado 21.1. Propiedades de IWDBGrid en el ejemplo IWGridDemo. o b j e c t IWDBGridl: TIWDBGrid A n c h o r s = [akLeft, akTop, akRight, akBottom]
UseFrame = True Usewidth = True Columns = < item Alignment = taLeftJustify BGColor = clNone DoSubmitValidation = True Font-Color = clNone Font-Enabled = True Font.Size = 10 Font-Style = [ I Header = False Height = '0' VAlign = vaMiddle Visible = True Width = ' 0 ' Wrap = False BlobCharLimit = 0 CompareHighlight = hcNone DataField = ' F I R S T - N A M E ' Title.Alignment = taCenter Title.BGColor = clNone Title.DoSubmitVa1idation = True Title.Font.Color = clNone Tit1e.Font.Enabled = True Title.Font.Size = 10 Title.Font.Style = [ I Title.Header = False Title-Height = '0' Title.Text = ' F I R S T - N A M E ' Title.VAlign = vaMiddle Title.Visible = True Title.Width = '0' Title.Wrap = False end item
A1 establecer la propiedad Start I D del segundo formulario, se puede encontrar el registro apropiado: procedure TRecordForm.SetStartID(const Value: string); begin FStartID : = Value; Value, [ I ) ; DataSourcel .Dataset .Locate ( 'EMP-NO', end;
otras operaciones sobre la columna. El formulario secundario esta enlazado con el mismo modulo de datos que el formulario principal. Por eso; despues de actualizar 10s datos de la base de datos, se pueden ver en la cuadricula (pero las actualizaciones se guardan solo en memoria. porque el programa no realiza una llamada a ApplyUpdates). El formulario secundario utiliza unos cuantos controles de edicion y un navegador, proporcionado por IntraWeb. La figura 2 1.9 muestra este formulario en tiempo de ejecucion.
maLast Name Hire
-
u
estan
1111 711 990
Figura 21.9. El formulario secundario del ejemplo IWGridDemo permite que un usuario edite 10s datos y explore 10s registros.
Transporte de datos al cliente Sin tener en cuenta como se utilice, el componente IWDBGrid produce HTML con 10s datos de la base de datos incrustados en las celdas, per0 no puede trabajar con 10s datos en el lado del cliente. Un componente distinto (o un conjunto de componentes de IntraWeb) permite utilizar un modelo distinto. Los datos se envian a1 navegador en un formato personalizado, y el codigo JavaScript del navegador rellena la cuadricula y trabaja con 10s datos, pasando de un registro a otro sin solicitar mas datos a1 servidor. Se pueden usar varios componentes IntraWeb para una aplicacion cliente, per0 estos son algunos de 10s mas importantes:
IWClientSideDataSet: Un conjunto de datos en memoria que se define fijando las propiedades ColumnName y Data en el codigo del programa. En futuras actualizaciones se podra editar datos en el cliente, ordenarlos, filtrarlos, definir estructuras maestro-detalle y muchas cosas mas. IWClientSideDataSetDBLink: Un proveedor de datos que puede conectarse a cualquier conjunto de datos de Delphi, conectandolo con la propiedad Datasource.
IWDynGrid: Un componente de cuadricula dinamica conectado con uno de 10s dos componentes anteriores mediante la propiedad D a t a . Este componente lleva todos 10s datos a1 navegador y puede trabajar con ellos en el cliente mediante JavaScript. Existen otros componentes de cliente en IntraWeb, como IWCSLabel, IWCSNavigator e IWDynamicChart (que solo funciona con Internet Explorer). Como un ejemplo del uso de esta tecnica, hemos construido el ejemplo IWClientGrid. El programa tiene poco codigo, per0 que hay mucho preparado para su uso en 10s componentes. Estos son 10s elementos centrales de su formulario principal: object formMain: TformMain SupportedBrowsers = [brIE, brNetscape61 OnCreate = IWAppFormCreate object IWDynGridl: TIWDynGrid Align = alClient Data = IWClientSideDatasetDBLinkl end object DataSourcel: TDataSource Left = 72 Top = 8 8 end object IWClientSideDatasetDBLinkl: TIWClientSideDatasetDBLink Datasource = DataSourcel end end
El conjunto de datos procedente del modulo de datos se conecta con el Datasource cuando se crea el formulario. La cuadricula resultante, que muestra la figura 2 1.10, permite ordenar 10s datos en cualquier celda (mediante la pequeiia flecha que se encuentra despues del titulo de cada columna) y filtrar 10s datos mostrados seglin uno de 10s valores posibles del campo. Por ejernplo. en la figura se pueden ordenar 10s datos de empleado de acuerdo con el apellido y filtrarlos por pais y categoria laboral.
lC'
LI
I
TJ
Green
Tcm Luke Carol Mary Leshe K J Randy Mchael
Lcc
Leung
Nordskom pas Phong Wcrton Wdhams
Yanowslu
4 4 4 4
4 4 4 4 4
USA USA USA USA USA USA USA USA USA
Figura 21.10. La cuadricula del ejernplo IWClientGrid soporta la ordenacion y filtrado personalizados sin tener que volver a traer 10s datos desde el servidor Web.
Esta caracteristica es posible porque 10s datos se llevan a1 navegador dentro del codigo JavaScript. Este es un fragmente de uno de 10s guiones incrustados en el codigo HTML de la pagina: < s c r i p t language="Javascriptl.Z"> var IWDYNGRIDl-Titlecaptions = [ "EMP-NO", "FIRST-NAME ","LAS T-NAME", "PHONE-EXT", "DEPT-NO " ,"JOB- CODE " ,"JOB-GRADE " ,"JOB- COUNTRY "1 ; var IWDYNGRIDl-Cellvalues = new Array(); IWDYNGRID1-CellValues[O] = [ Z , 'Robert', 'Nelson', '332', '600', 'VP',21 'USA'] ; IWDYNGRID1-CellValues[l] = [ 4 , 'Bruce', 'Young', '233', '621 ', ' E n g ' , 2 1' U S A ' ] ; IWDYNGRIDl-CellValues[Z] = [ 5 , 'Kim', 'Lambert ', '22', ' 1 3 O 1 ,' E n g 1 , 2 ,'USA']; IWDYNGRID1-Cellvalues [3] = [8, 'Leslie', 'Johnson', '410', ' 1 8 0 r ,'Mktg',3, 'USA'] ; IWDYNGRID1-CellValues[4] = [ 9 , 'Phil', 'Forest', '229', ' 6 2 Z 1 ,' M n g r ' , 3 1' U S A ' ] ;
El motivo para utilizar este enfoque basado en JavaScript en lugar de un enfoque basado en XML como el utilizado en otras tecnologias parecidas, es que solo Internet Explorer ofrecer soporte para islas de datos XML. Mozilla y Netscape carecen de esta caracteristica y tienen un soporte muy limitado de XML.
tecnolog~as XML
Crear aplicaciones para Internet significa usar protocolos y crear interfaces de usuario basadas en navegadores, como en 10s dos capitulos anteriores, per0 tambien abre una oportunidad para el intercambio de documentos de negocio electronicamente. Los estandares que surgen para este tip0 de actividad se centran en el formato de documento XML e incluyen el protocolo de transmision SOAP, 10s esquemas XML para la validacion de documentos y XSL para representar documentos como HTML. En este capitulo, comentaremos las principales tecnologias XML y el amplio soporte que Delphi les ofrece desde su version 6. Ya que el conocimiento sobre XML no esta muy extendido, vamos a ofrecer una pequeiia presentacion sobre cada tecnologia, per0 deberian consultarse libros dedicados especialmente a estas tecnologias para aprender mas. En el capitulo 23 nos centraremos de manera especifica en 10s servicios Web y SOAP. En este capitulo se tratan 10s siguientes temas: Presentacion de XML: Extensible Markup Language Trabajo con un DOM XML. Delphi y XML: interfaces y proyeccion. Procesamiento de XML con SAX.
Internet Express. Uso de XSLT. XSL en WebSnap.
Presentacion de XML El lenguaje extensible de marcas (extens~bleMarkup Language, XML) es una version simplificada de SGML y recibe mucha atencion en el mundo de las tecnologias de la informacion. XML es un lenguaje de marcas, que quiere decir que utiliza simbolos para describir su propio contenido (en este caso, etiquetas que consistente en un texto definido de manera especial, encerrado entre 10s caracteres < y >). Es extensible porque permite usar marcas libres (en contraste con, por ejemplo, HTML, que tiene marcas predefinidas). El lenguaje XML es un estandar promocionado por el World Wide Web Consortium (W3C). La recomendacion XML puede encontrarse en www.w3.org/TR/REC-xml. Se ha llamado a XML el ASCI del aiio 2000, para indicar que es una tecnologia simple y muy extendida y tambien que un documento XML es un archivo de texto plano (de manera opcional con caracteres Unicode en lugar de simple texto ASCII). La caracteristica mas importante de XML es que es descriptivo, ya que cada etiqueta tiene un nombre casi legible para un humano. Este es un ejemplo, en caso de que jamas se haya visto un documento XML: La biblia de Delphi 7CantuAnaya
XML presenta unas cuantas desventajas que estaria bien resaltar desde el principio. La mas importante es que sin una descripcion formal, un documento vale de poco. Si se quiere intercambiar documentos con otra empresa, hay que llegar a un acuerdo sobre lo que significa cada etiqueta y tambien sobre el significado semantic0 del contenido. (Por ejemplo, cuando se tiene una cantidad, hay que acordar el sistema de medida o incluirlo en el documento.) Otra desventaja es que 10s documentos XML son mucho mayores que otros formatos. Por ejemplo, usar cadenas para 10s numeros no es nada eficiente, y las etiquetas de apertura y cierre ocupan mucho espacio. Lo bueno es que XML se comprime muy bien, por el mismo motivo.
Sintaxis XML basica Merece la pena conocer algunos elementos tecnicos de XML antes de analizar su uso en Delphi. Este es un resumen de 10s elementos clave de la sintaxis XML:
Los espacios en blanco (corno el caracter de espacio, el retorno de carro, el salto de linea y 10s tabuladores) generalmente se ignoran (corno en un documento HTML). Es importante dar formato a un documento XML para que resulte legible, per0 a 10s programas no les importara demasiado. Se pueden aiiadir comentarios dentro de las marcas < ! -- y -->, que, en esencia, ignoran 10s procesadores de XML. Existente tambien directivas e instrucciones de proceso, encerradas entre las marcas < ? y ? > . Existen unos pocos caracteres especiales o reservados que no pueden usarse en el texto. Los dos unicos simbolos que no pueden usarse jamas son el caracter menor que (<, usado para delimitar una marca), que se sustituye por & 1t ; y el caracter ampersand (&), que se sustituye por & ; (y es la evolucion grafica del et latino). Otros caracteres especiales optativos son & g t ; para el simbolo mayor que (>), & apo s ; para la comilla simple ( ' ) y " para la comilla doble ("). Para aiiadir contenido que no sea XML (por ejemplo, informacion binaria o un guion), se puede usar una seccion CDATA, delimitada por < ! [CDATA[ y ] I > . Todas las etiquetas se encuentran entre 10s simbolos menor y mayor que, < y >. Las marcas son sensibles a las mayusculas (no como en HTML). Por cada marca de apertura, debe existir una marca de cierre correspondiente, indicada por un caracter inicial de barra inclinada:
Las marcas no pueden solaparse: deben anidarse correctamente, como en la primera linea que se muestra (la segunda linea no es correcta): xx yyxx yy
/ / correct0 / / erroneo
Si una marca no tiene contenido (pero su presencia resulta importante), pueden sustituirse las marcas de apertura y cierre por una marca unica que incluye una barra inclinada final: . Las marcas pueden tener atributos, usando varios nombres de atributos seguidos de un valor encerrado entre comillas:
Cualquier nodo XML puede tener varios atributos, varias etiquetas incrustadas y un unico bloque de texto que representa el valor del nodo. Es habitual que 10s nodos XML tengan un valor textual o etiquetas incrustadas, y no ambas variantes. Este es un ejemplo de la sintaxis completa de un nodo:
Un nodo puede tener varios nodos hijo con la misma etiqueta (las etiquetas no tiene por que ser unicas). Los nombres de atributos son unicos para cada nodo.
XML bien formado Los elementos comentados en la seccion anterior definen la sintaxis de un documento XML, per0 no bastan. Un documento XML se considera correcto sintacticamente, o bien formato, si sigue unas cuantas reglas adicionales. Fijese en que este tipo de comprobacion no garantiza que el contenido del documento sea significative, solo que las etiquetas esten bien dispuestas. Cada documento deberia tener un prologo que indica que es de hecho un documento XML, q u i version de XML cumple y posiblemente el tip0 de codificacion de 10s caracteres. Este es un ejemplo:
Entre las codificaciones posibles hay conjuntos de caracteres Unicode (como UTF-8, UTF- 16 y UTF-32) y algunas codificaciones I S 0 (como ISO- 10646-xxx o 1SO-8859-xss). El prologo tambien puede incluir declaraciones externas, el esquema usado para validar el documento, declaraciones de espacios de nombre, un archivo XSL asociado y algunas declaraciones de entidades internas. Consulte documentacion o libros sobre XML para conseguir mas informacion sobre estos temas. Un documento XML esta bien formado si tiene un prologo, tiene una sintaxis correcta (segun las reglas de la seccion anterior) y tiene un arb01 de nodos dentro de una raiz unica. La mayoria de las herramientas (como Internet Explorer) comprueban si un documento esta bien formado a1 cargarlo.
NOTA: XML es miis formal y precis0 que HTML.El W3C trabaja en un estandar XHTML que hara que los docurnentos HTML sean confonnes con XML, para que las herramientas XML 10s prowsen mejor. Esta implica muchos carnbios en un docurnento HTML tipico, d o evitar 10s atributos sin valores, a f d i r todas las marcas de cierie (wmo m y ), ahdir la barra invertida para marcas independientes (cam0 y €br/ >), un anidado correcto y muchas cosas mais. El sitio Web de W3C alberga u n cnnvercnr de HTMT, a XHTMT. Ilnmdn HTMI. Tidv en www w 3 mnl
Trabajo con XML Para acostumbrarse al formato de XML, se puede usar uno de 10s editores XML disponibles en el mercado (incluidos Delphi y Context, un editor para programadores escrito en Delphi). Cuando se carga un documento XML en Internet Explorer, se puede ver si es correct0 y, en ese caso, visualizarlo en el navegador con una estructura en arbol. (En el momento de escribir esto, otros navegadores tienen un soporte XML mas limitado.) Para acelerar este tipo de operacion, hemos creado el editor XML mas simple posible, basicamente un campo de memo con comprobacion de sintaxis XML y un navegador conectado. El ejemplo XmlEditOne tiene un Pagecontrol con tres paginas. La primera pagina, Settings, contiene un par de componentes en 10s que se puede escribir la ruta y el nombre del archivo con el que se quiere trabajar. (El motivo de no utilizar un dialog0 estandar quedara claro cuando mostremos una ampliacion del programa.) El cuadro de edicion que contiene el nombre completo del archivo se actualiza automaticamente con la ruta y el nombre de archivo, si esta seleccionado el cuadro de verificacion Autoupdate. La segunda pagina contiene un control de memo; se carga y se guarda el texto del archivo XML haciendo clic sobre 10s dos botones de la barra de herramientas. En cuanto se carga el archivo, o cada vez que se modifica su texto, su contenido se carga en un DOM para que un analizador sintactico o parser compruebe su correccion (algo que seria complejo hacer con codigo propio). Para procesar el codigo, hemos usado el componente XMLDocument disponible en Delphi, que es basicamente un envoltorio de un DOM disponible en el ordenador e indicado mediante su propiedad ~ b ~ v e n d oComentaremos r. el uso de este componente con mayor detalle en la siguiente seccion. Por el momento, baste con decir que se puede asignar una lista de cadena a esta propiedad XML y activarla para permitir que procese el texto XML e informe tal vez de un error mediante una excepcion. Para este ejemplo, este comportamiento dista de ser bueno, ya que mientras se escribe el codigo XML se tendra codigo XML temporalmente incorrecto. Aun asi, hemos preferido no pedir a1 usuario que haga clic sobre un boton para realizar la validacion, sino ejecutarla de manera continua. Ya que no es posible inhabilitar la excepcion de proceso lanzada por el componente XMLDocument, hemos tenido que trabajar a un nivel mas bajo, extrayendo la propiedad DOMPersist (que hace referencia a la interfaz de permanencia de DOM) despues de extraer la interfaz IXMLDo cumentAccess del componente XMLDocument, llamado XmlDoc en este codigo. Tambien puede extraerse la interfaz I DOMPars eError del componente del documento, para mostrar cualquier mensaje de error en la barra de estado: procedure TFormXmlEdit.MemoXmlChange(Sender: var eParse: IDOMParseError; begin
TObject);
XmlDoc.Active : = True; xmlBar. Panels [l] .Text := 'OK'; xmlBar Panels [2] .Text : = ' '; (XmlDoc as IXMLDocumentAccess) .DOMPersist loadxml (MemoXml .Text) ; eParse := (XmlDoc.DOMDocument as IDOMParseError) ; i f eParse. errorcode <> 0 then with eParse do begin xmlBar. Panels [1] .Text : = 'Error in: ' + IntToStr (Line) + '. ' + IntToStr (LinePos); xmlBar. Panels [2] .Text : = SrcText + ': ' + Reason; end; end;
.
.
La figura 22.1 muestra un ejemplo de la salida del programa, junto con la vista en arb01 XML que ofrece la tercera pagina (para un documento correcto). La tercera pagina del programa se construyo mediante el componente WebBrowser, que incluye un control ActiveX de Internet Explorer. Lamentablemente, no esiste un mod0 direct0 de asignar una cadena con testo XML a este control, por lo quc habra que guardar el archivo en primer lugar para luego pasar a esta pagina para iniciar la carga del XML en el navegador (despues de hacer clic a mano sobre el boton Refresh a1 menos una vez).
Figura 22.1. El ejemplo XmlEditOne permite escribir texto XML en un componente de memo, indicando 10s errores durante la escritura y mostrando el resultado en el navegador incluido. -
. -
--
.-
-
-
NOTA: Hemos utilizado este codigo como punto de partida para crear un editor XML cornpleto llamado XrnlTypist. Incluye resaltado de sintaxis, soporte XSLT y unas cuantas caracteristicas adicionales. En el apCndice A se puede consultar la disponibilidad de este editor XML gratuito.
Manejo de documentos XML en Delphi Ahora que ya se conocen 10s elementos principales de XML, podemos comenzar a analizar como se manejan 10s documentos XML en programas Delphi (o en
programas en general, ya que algunas de las tecnicas que vamos a ver van mas alla del lenguaje que se utilice). Hay dos tecnicas basicas para manipular documentos XML: utilizar una interfaz de modelo de objeto de documento (Document Object Model, DOM) o utilizar una API para XML sencilla (Simple API for XML, SAX). Los dos enfoques son bastante distintos:
DOM: Carga un documento completo en un arbol jerarquico de nodos, lo que nos permite leerlos y manipularlos para modificar el documento. Por ello, el DOM es aconsejable para navegar por la estructura XML en memoria, editarla e incluso para crear documentos completamente nuevos. SAX: Analiza sintacticamente el documento, lanzando un evento para cada elemento del documento sin crear ninguna estructura en memoria. Despues de que SAX haya analizado el documento, este se pierde, per0 este mod0 de funcionamiento suele ser mucho mas rapido que crear el arbol DOM. Usar SAX esta bien si el documento se va a leer de una vez, por ejemplo, si se busca una parte de sus datos. Existe una tercera posibilidad para manipular (y en concreto para crear) documentos XML: el manejo de cadenas. Crear un documento aiiadiendo cadenas es, sin duda alguna, la operacion mas rapida si podemos dar una sola pasada (y no necesitamos modificar 10s nodos ya generados). Incluso la lectura de documentos por medio de funciones de cadenas es muy rapida, per0 puede complicarse para estructuras complejas. Ademas de estos enfoques clasicos del procesamiento de XML, que tambien estan disponibles para otros lenguajes de programacion, Delphi 6 proporciona dos tecnicas mas que deberiamos tener en cuenta. La primera es la definicion de interfaces que proyectan la estructura del documento y que se utilizan para acceder a1 mismo en lugar de hacerlo a traves de la interfaz generica de DOM. Como veremos, este metodo contribuye a una codification mas rapida y aplicaciones mas solidas. La segunda tecnica es el desarrollo de transformaciones que nos permitan leer un documento XML generic0 dentro de un componente ClientDataSet o guardar el conjunto de datos en un archivo XML con una estructura dada (no en la estructura XML especifica que soporta nativamente el ClientDataSet o MyBase). No vamos a tratar de decidir que opcion es la que mejor se adapta a cada tip0 de documento y manipulacion, per0 resaltaremos algunas de las ventajas e inconvenientes mientras analizamos ejemplos de cada enfoque en las secciones siguientes. A1 final del capitulo, analizaremos la velocidad relativa de las tecnicas para el procesamiento de grandes archivos.
Programacion con DOM Ya que un documento XML tiene una estructura parecida a un arbol, cargar un documento XML en un arbol en memoria es una operacion bastante natural. Esto
es lo que hace DOM. DOM es una interfaz estandar, por lo que cuando se ha escrito codigo que utiliza un arb01 DOM, podemos cambiar de implementacion de DOM sin alterar el codigo fuente (a1 menos si no hemos utilizado extensiones personalizadas). En Delphi se pueden instalar varias implementaciones de DOM, disponibles como servidores COM, y utilizar sus interfaces. Uno de 10s motores DOM mas utilizados en Windows es el que proporciona Microsoft como parte del MSXML SDK, per0 que tambien instala Internet Explorer (y por ello todas las versiones recientcs de Windows) y muchas otras aplicaciones de Microsoft. (Con el MSXML SDK cornpleto tambien se incluye documentacion y ejemplos bastante detallados que no se conseguiran en otras instalaciones de la misma biblioteca incluidas con otras aplicaciones.) Otros motores DOM disponibles directamente en Delphi 7 son Xerces, de la fundacion Apache y OpenXML, de codigo abierto. TRUCO: OpenXML es un motor DOM nativo en Object Pascal disponible en www.philo.de/xml. Otro motor DOM nativo en Delphi lo ofrece Turbopower. Estas soluciones tienen dos ventajas. No necesitan una bi.. . . . a ouoteca externa para que se ejecure el programs, ya que el componente DOM se compila con la aplicacion; y son multiplataforma.
.
I
Delphi incluye las implementaciones DOM en un componente envoltorio Ilamado XMLDocument. Hemos usado este componente en el ejemplo anterior, per0 esaminaremos su papel en un aspect0 mas general. La idea de usar este componente en lugar de la interfaz DOM es permanecer independientes de las implementaciones y poder trabajar con metodos simplificados, o auxiliares. El uso de la interfaz DOM es bastante complejo. Un documento es un conjunto de nodos, cada uno con un nombre, un elemento de texto, un conjunto de atributos y un conjunto de nodos hijo. Cada conjunto de nodos permite el acceso a 10s elementos a traves de su posicion o buscandolos por nombre. Observese que el texto que se encuentra dentro de las etiquetas de un nodo, si hay alguno, se representa como un hijo como del nodo y se listara en el conjunto de nodos hijo. El nodo raiz tiene algunos metodos adicionales para crear nuevos nodos, valores o atributos. Con el XMLDocument de Delphi podemos trabajar a dos niveles: A un nivel inferior, podemos utilizar la propiedad DOMDocument (del tip0 de interfaz ~DOMDocument)para acceder a la interfaz estandar W3C Document Object Model. La interfaz DOM oficial se define en la unidad xmldom e incluye interfaces como IDOMNode, IDOMNodeList, IDOMAttr, IDOMElement e IDOMText. Con las interfaces DOM oficiales, Delphi soporta un modelo de programacion estandar per0 de bajo nivel. La implementacion de DOM la indica el componente XMLDocument en la propiedad DOMVendor.
A un nivel superior, el componente XMLDocument implementa tambien la interfaz IXMLDocument. Se trata de una API personalizada del tip0 de DOM definida por Borland en la unidad XMLIntf y que incluye interfaces como IXMLNode, IXMLNodeList e IXMLNodeCollection. Esta interfaz de Borland simplifica algunas de las operaciones de DOM sustituyendo varias llamadas a metodos, que suelen repetirse a mod0 de secuencia, por una sola propiedad o metodo. En 10s siguientes ejemplos (sobre todo en el ejemplo DomCreate), utilizaremos ambos enfoques para dar una mejor idea de las diferencias practicas entre ambos.
Un documento XML en una TreeView Normalmente, el punto de partida consiste en cargar un documento desde un archivo o crearlo a partir de una cadena, per0 tambien podemos empezar con un documento completamente nuevo. Como primer ejemplo de la utilizacion de DOM, hemos creado un programa que carga un documento XML en un DOM y muestra su estructura en un control TreeView. Tambien hemos aiiadido a1 programa XmlDomTree unos botones con codigo de muestra usados para acceder a 10s elementos de un archivo de prueba, como un ejemplo del acceso a 10s datos DOM. Cargar el documento es sencillo, per0 mostrarlo en un arb01 requiere una funcion recursiva que recorra 10s nodos y subnodos. Este es el codigo para 10s dos metodos : p r o c e d u r e TFormXmlTree.btnLoadClick(Sender: TObject); begin 0penDialogl.InitialDir : = ExtractFilePath ( A p p l i c a t i o n .ExeName) ; i f 0penDialogl.Execute t h e n begin XMLDocumentl.LoadFromFile(OpenDialogl.Fi1eName); Treeviewl.1tems.Clear; DomToTree (XMLDocumentl.DocumentElement, nil); TreeViewl-FullExpand; end; end: p r o c e d u r e TFormXmlTree.DomToTree (XmlNode: IXMLNode; TreeNode: TTreeNode) ; var I: Integer; NewTreeNode: TTreeNode; NodeText: string; AttrNode: IXMLNode; begin // omite nodos d e texto y otros casos especiales i f (XmlNode.NodeType <> ntElement) t h e n Exit;
// afiade e l p r o p i o nodo NodeText : = XmlNode.NodeName; i f XmlNode.1sTextElement then NodeText : = NodeText + ' = ' + XmlNode.NodeValue; NewTreeNode : = TreeViewl.Items.AddChild(TreeNode, NodeText); // a t i a d e s u s a t r i b u t o s f o r I := 0 t o xmlNode.AttributeNodes.Count - 1 d o begin AttrNode : = xmlNode.AttributeNodes.Nodes[I]; TreeViewl.Items.AddChild(NewTreeNode, ' I ' + AttrNode.NodeName + ' = " ' + AttrNode.Text + " ' 1 ' ) ; end; // afiade cada nodo h i j o i f XmlNode.HasChildNodes then f o r I : = 0 t o xmlNode.ChildNodes.Count - 1 d o DomToTree (xmlNode.Chi1dNodes.Nodes [I], NewTreeNode); end;
Este codigo es bastante interesante ya que resalta algunas de las operaciones que podemos realizar con un DOM. En primer lugar, cada nodo tiene una propiedad NodeType que podemos usar para determinar si el nodo es un elemento, un atributo, un nodo de testo o una entidad especial (como CDATA y otras). Ademas, no podemos acceder a la representacion textual del nodo, su Nodevalue, a menos que tenga un elemento de testo (el nodo de texto se omitira, como comprobacion inicial). Despues de mostrar el nombre del elemento y el valor del testo, si esta disponible, el programa muestra directamente el contenido de cada atributo y de cada subnodo llamando de manera recursiva a1 metodo DomToTree (vease figura 22.2).
a u h r = Canlu
i 3 book title = Delphi Devdoper'sHandbook aulhol = Canlu aulhor = Gaoch
r3boak litle = MarletingDelph~6 aulho~= Canlu
@i:book E book El ebwk l i b = EssenlidPascd utl = hllp:Nwww.marwcantucorn wthm = Canlu 8 ebmk title = Thinking in Java url = hllp:llwww,mindview.com aulhu = Eckel
Figura 22.2. El ejernplo XmlDomTree puede abrir un documento XML generic0 y mostrarlo dentro de un control TreeView cornun.
Una vez que hayamos cargado el documento de muestra que acompaiia a1 programa XmlDomTree (mostrado en el listado 22.1) en el componente XMLDocument, podemos utilizar diversos metodos para acceder a nodos genericos, como en el anterior codigo de construccion del arbol, o buscar elementos especificos. Por ejemplo podemos obtener el valor del atributo t e x t del nodo raiz si escribimos: XMLDocumentl.DocumentElement.Attributes
['text']
Hay que tener en cuenta que si no hay ningun atributo llamado t e x t , la llamada fallara con un mensaje de error generico: "Invalid variant type conversion" (conversion de tip0 variante invalida). Si necesitamos acceder a1 primer atributo de la raiz y no conocemos su nombre, podemos utilizar el siguiente codigo:
Para acceder a 10s nodos, utilizamos una tecnica similar, aprovechandonos posiblemente de la matriz ChildValues.Se trata de una extension de Delphi a DOM, que nos permite pasar como parametro el nombre del elemento o su posicion numerica:
Este codigo consigue el (primer) autor del segundo libro. No podemos utilizar la expresion Chi 1 dVa lues [ ' book ' ] , ya que hay varios nodos con el mismo nombre bajo el nodo raiz. Listado 22.1. El docurnento XML de muestra utilizado en 10s ejernplos de este capitulo. Cantu Delphi Developer's HandbookCantuGoochDelphi COM ProgrammingHarmonThinking i n C++Eckel
Creacion de documentos utilizando DOM Aunque hemos mencionado anteriormente que se puede crear un documento XML agrupando cadenas, Csta tecnica no es la mas robusta. Usar DOM para crear un documento garantiza que el XML estara bien formado. Ademas, si a1 DOM se el adjunta una definition de esquema, podemos validar la estructura del documento mientras le aiiadimos datos. Para resaltar 10s diferentes casos de creacion de un documento, hemos construido el ejemplo DomCreate. Este programa puede crear documentos XML dentro del DOM, mostrando su texto en un campo de memo y, opcionalmente, en una TreeView
para mejorar la salida al memo del texto XML, fo&tehdolo m$or. Podemos escoger el sangrado estqbleciendo la propie* ..$ Node- tip0- de .--. nco, tamb16 podemos .blecidos dos espacios ,no hay forma alguna d
*
El primer boton del formulario, Simple, crea un texto XML sencillo utilizando las interfaces oficiales de bajo nivel de DOM. El programa llama a1 metodo creat eElement del documento para cada nodo, aiiadiendolos como hijos de otros nodos: procedure TForml.btnSimpleClick(Sender: TObject); var iXml: IDOMDocument; iRoot, iNode, iNode2, iChild, iAttribute: IDOMNode; begin / / v a c i a e l docurnento XMLDoc.Active : = False; XMLDoc. XML-Text := "; XMLDoc.Active : = True;
/ / muestra XML en el memo Memol.Lines.Text : = FormatXMLData
(XMLDoc.XML.Text);
end;
Fijese en que 10s textos de 10s nodos se aiiaden explicitamente, que 10s atributos se crean con una llamada de creacion especifica y que el codigo utiliza cloneNode para hacer una replica de una rama entera del arbol. Globalmente, la escritura del codigo es un poco engorrosa, per0 se acostumbrara a1 estilo. El efecto del programa se muestra en la figura 22.3 (con formato en el memo y en el arbol).
9x d E test test2 test4 test3 = s~mplevalue
E test [color ="red"] test2 test4 lest3 = s~molevalue
Figura 22.3. El ejemplo DomCreate puede generar diferentes tipos de documentos XML utilizando un DOM.
El segundo ejemplo de creacion de DOM tiene que ver con un conjunto de datos. Hemos aiiadido a1 formulario un componente de conjunto de datos dbExpress (pero habria semido cualquier otro conjunto de datos) y agregado tambien la llamada a1 procedimiento personalizado DataSetToDOM a un boton, de la siguiente manera: DataSetToDOM
('customers',
' c u s t o m e r ' , XMLDoc,
SQLDataSetl);
El procedimiento Da t aSet ToDOM crea el nodo raiz con el texto del primer parametro, coge cada registro del conjunto de datos, define un nodo con el segundo parametro y agrega un subnodo para cada campo del registro utilizando un codigo extremadamente generico: p r o c e d u r e DataSetToDOM (RootName, RecordName: TXMLDocument; DataSet: TDataSet) ; var iNode, iChild: IXMLNode; i: Integer; begin DataSet.Open; Dataset-First;
string; XMLDoc:
// r a i z XMLDoc.DocumentElement
:=
XMLDoc.CreateNode
(RootName);
// afiade d a t o s d e t a b l a w h i l e n o t DataSet .EOF d o begin // a d a d e u n n o d o p a r a c a d a r e g i s t r o iNode : = XMLDoc.DocumentElement.AddChild (RecordName); f o r I : = 0 t o DataSet.FieldCount - 1 d o begin // a f i a d e u n e l e m e n t o p a r a c a d a c a m p o iChild : = iNode.AddChild (DataSet.Fields[i].FieldName); iChild.Text : = DataSet.Fields[i].AsString; end; DataSet.Next; end; DataSet.Close; end;
El codigo anterior utiliza las interfaces de acceso simplificado de DOM que proporciona Borland, que incluyen un nodo AddChi ld que crea el subnodo, y el acceso direct0 a la propiedad Text para definir un nodo hijo con contenido textual. Esta rutina extrae una representacion XML del conjunto de datos, ofreciendo muchas posibilidades para la publicacion Web, como veremos en la seccion sobre XSL. Otra interesante posibilidad es la generacion de documentos XML que describan objetos Delphi. El programa DomCreate tiene un boton que se utiliza para
describir algunas propiedades de un objeto usando, una vez mas, el DOM de bajo nivel : procedure AddAttr (iNode: IDOMNode; Name, Value : string) ; var iAttr: IDOMNode; begin iAttr : = iNode.ownerDocument.createAttribute (name); iAttr.nodeValue : = Value; iNode.attributes.setNamed1tem (iAttr); end; procedure TForml.btnObjectClick(Sender: var iXml: IDOMDocument ; iRoot: IDOMNode; begin // v a c i a e l documento XMLDoc.Active : = False; XMLDoc. XML.Text : = ' '; XMLDoc-Active : = True;
TObject);
// r a i z iXml := XmlDoc. DOMDocument ; iRoot : = iXml.appendChild (iXm1-createElement ( ' B u t t o n l ' ) ) ; / / a l g u n a s p r o p i e d a d e s como a t r i b u t o s ( t a m b i e n p o d r i a n s e r // n o d o s ) AddAttr (iRoot, ' N a m e ' , Buttonl-Name); AddAttr (iRoot, ' C a p t i o n ' , Buttonl .Caption); AddAttr (iRoot, ' F o n t .Name ', Buttonl.Font.Name) ; AddAttr (iRoot, ' L e f t ', IntToStr (Buttonl.Left)) ; AddAttr (iRoot, ' H i n t ', Buttonl .Hint); / / m u e s t r a XML e n u n memo Memol.Lines : = XmlDoc.XML; end;
Desde luego, seria mas interesante disponer de una tecnica generica capaz de guardar las propiedades de cada componente de Delphi (u objeto permanente, para ser mas precisos), recorriendo de manera recursiva 10s subobjetos permanentes e indicando 10s nombres de 10s componentes a 10s que se hace referencia. Esto es lo que hace el procedimiento C o m p o n e n t T o D O M , que utiliza la informacion RTTI bajo nivel proporcionada por la unidad TypInfo e incluye la extraccion de la lista de propiedades de componentes. Una vez mas, el programa utiliza las interfaces XML simplificadas de Delphi: procedure ComponentToDOM var nProps, i: Integer; PropList: PPropList;
(iNode: IXmlNode; Comp: TPersistent);
Value : Variant ; newNode: IXmlNode; begin // o b t i e n e l a lista d e p r o p i e d a d e s nProps : = GetTypeData ( C ~ m p . C l a s s I n f o ) ~ . P r o p C o u n t ; GetMem (PropList, nProps * SizeOf (Pointer)) ; try GetPropInfos (Comp.ClassInfo, PropList) ; for i : = 0 to nProps - 1 do begin Value : = GetPropValue (Comp, PropList [i] .Name) ; NewNode := iNode .Addchild (PropList [i] .Name) ; NewNode.Text : = Value; i f (PropList [i] . PropTypeA .Kind = tkclass) and (Value <> 0 ) then i f TObject (Integer (Value)) is TComponent then NewNode .Text : = TComponent (Integer (Value)) .Name else / / TPersistent p e r 0 n o TComponent: recursive ComponentToDOM (newNode, TOb ject (Integer(Value)) as TPersistent) ; end ; finally FreeMem (PropList); end; end;
Las siguientes dos lineas de codigo, disparan la creacion del documento XML (que se muestra en la figura 22.4): XMLDoc.DocumentE1ement : = XMLDoc.CreateNode(SelffC1assName); ComponentToDOM (XMLDoc.DocumentElement, Self) ;
Interfaces de enlace de datos XML Trabajar con DOM para acceder o generar un documento es bastante tedioso, ya que en lugar de utilizar un acceso logico a 10s datos se nos obliga a utilizar la informacion de posicion. Ademas, manipular series de nodos repetidos de distintos tipos posibles, no es nada sencillo (como en el ejemplo XML del listado 22.1, que describe libros). Ademas, utilizando el DOM podemos crear cualquier documento bien formado, per0 (a menos que utilicemos un DOM con validacion) podemos aiiadir subnodos a cualquier nodo, acabando con documentos casi inutiles, ya que el resto del mundo sera incapaz de manejarlos. Para solucionar estos problemas, Borland ha aiiadido a Delphi un XML Data Binding Wizard, que es capaz de examinar un documento XML o una definicion de un documento (un esquema, un DTD [definicion de tipo de documento] u otro tipo de definicion) y generar un conjunto de interfaces para manipular el documento. Estas interfaces son especificas a1 documento y su estructura y nos permiten disponer de un codigo mas legible, pero son bastante menos genericas en
cuanto a 10s tipos de documentos que podemos manipular con ellas (y esto es mas positivo de lo que podria parecer en primera instancia).
1 TFolml Name = Fmml Lell = 192 Top = 107 Wdh= He~gh!= 412 HorzScrollBar Range 97 VertScrollBar = 20260720 Act~veConlrol=btnRTTl B~DlMode= bdLellToR~ghl Capl~on= DomCrealc = 385 Cl~enlHerghl CfienlWdh = 563 Color = -16777201 Cnnslranls = 20255360
5n
H
a
-
-
FOIM
Charset = 1 Color = 16777208 He~ght= 11
., .... ,,"".-.*-A
'1
-
Figura 22.4. El XML generado para describir el formulario del programa DomCreate. Fijese en que las propiedades de 10s tipos de clase estan mas expandidas.
El asistente XML Data Binding Wizard se activa utilizando el icono correspondiente de la primera pagina del cuadro de dialogo New Items del IDE, o haciendo doble clic directamente sobre el componente XMLDocument. (Es extrafio que el comando correspondiente no este en el menu local del componente). Despues de una pagina en la que seleccionaremos un archivo de entrada, este asistente muestra graficamente la estructura del documento, como se puede ver en la figura 22.5 para el archivo XML de muestra del listado 22.1. En esta pagina es donde nombramos cada entidad de las interfaces generadas, en caso de que no nos gusten 10s que el asistente proporciona de manera predeterminada. Incluso podemos cambiar las reglas utilizadas por el asistente para generar 10s nombres (una flexibilidad especial que no estaria ma1 en otras partes del IDE de Delphi). La pagina final nos ofrece una vista previa de las interfaces generadas y ofrece opciones para generar 10s esquemas y otros archivos de definicion. Para el archivo XML de muestra con 10s nombres de autores, el XML Data Binding Wizard genera una interfaz para el nodo raiz, dos interfaces para las listas de elementos de 10s dos tipos distintos de nodos (libros y libros electronicos), y dos interfaces mas para 10s elementos de cada uno de estos tipos.
6~
booksType texi U Q book
O tale -.
P Generate B
.
--
i i
Figura 22.5. t l aslstente XML Data Blndlng Wlzard de Delphi puede anallzar la estructura de un documento o un esquema (u otra definicion de documento) para crear un conjunto de interfaces para un acceso mas simple y direct0 a 10s datos DOM.
Veamos a continuacion unos fragmentos del codigo generado, disponible en la unidad XmlIntfDefinition del ejemplo Xml I n t e r face: tYPe IXMLBooksType = interface (IXMLNode) [ ' {C9A9PB63-47ED-dP27-8ABA-E71P30BA7Pll) ' 1 )
( Property Accessors
function Get-Text: WideString; function Get-Book: IXMLBookTypeList; function Get-Ebook: IXMLEbookTypeList; procedure Set-Text(Va1ue: Widestring); ( Methods 6 Properties
f u n c t i o n Get-Author: IXMLString-List; p r o c e d u r e Set-Title (Value: WideString) ; { Methods & Properties } p r o p e r t y Title: WideString r e a d Get-Title w r i t e Set-Title; property Author: IXMLString-List r e a d G e t A u t h o r ; end:
Para cada interfaz, el XML Data Binding Wizard genera tambien una clase de implementacion que proporciona el codigo para 10s metodos de la interfaz, convirtiendo 10s consultas en llamadas DOM. La unidad incluye tres funciones de inicializacion, que pueden devolver la interfaz del nodo raiz desde un documento cargado en un componente XMLDocument (o un componente que proporcione una interfaz IXMLDocument generica), o devolverla desde un archivo, o crear un DOM completamente nuevo: f u n c t i o n Getbooks(Doc: IXMLDocument) : IXMLBooksType; f u n c t i o n Loadbooks(const FileName: WideString): IXMLBooksType; f u n c t i o n Newbooks: IXMLBooksType;
Despues de generar estas interfaces utilizando el asistente en el ejemplo Xml Interface,hemos repetido el codigo de acceso a1 documento XML que es similar a1 del ejemplo XmlDomTree per0 mas facil de escribir (y leer). Por ejemplo, podemos obtener el atributo del nodo raiz escribiendo simplemente: procedure TForml .btnAttrClick (Sender: TObject) ; var Books: IXMLBooksType; begin Books : = Getbooks (XmlDocumentl) ; ShowMessage (Books.Text) ; end;
Puede ser incluso mas sencillo si recuerda que mientras se escribe este codigo, la funcion Code Insight de Delphi puede ayudar listando las propiedades disponibles de cada nodo, gracias a que el analizador sintactico puede leer las definiciones de la interfaz (aunque no entienda el formato de un documento XML generico). Para acceder a un nodo de una de estas sublistas, escribiremos una de las siguientes sentencias (posiblemente la segunda, con la propiedad de matriz predeterminada) : Books.Book. Items [I] .Title Books .Book [l] .Title
// completo // mds simple
Podemos utilizar un codigo igualmente simplificado para generar nuevos documentos o aiiadir elementos nuevos, gracias a1 metodo personalizado ~ d dque , esta disponible en cada interfaz basada en una lista. Si no disponemos de una estructura predefinida para el documento XML, como en 10s ejemplos basados en un conjunto de datos y RTTI de la demostracion anterior, no podremos utilizar este enfoque.
Validation y esquemas El asistente XML Data Binding Wizard puede trabajar a partir de esquemas ya existentes o generar un esquema para un documento XML (e incluso guardarlo en un archivo con la extension .XDB). Un documento XML describe algunos datos, per0 para compartir estos datos entre empresas, tiene que adherirse a alguna estructura previamente acordada. Un esquema es una definicion de documento contra la que se puede comprobar la correccion de un documento, una operacion que suele llamarse validacion. El primer (y mas difundido) tipo de validacion disponible para XML usaba las definiciones de tipo de documento (Document Type Definitions, DTD). Estos documentos describen la estructura del XML per0 no pueden definir 10s posibles contenidos de cada nodo. Ademas, 10s DTD no son documentos XML ellos mismo, sino que usan una notacion diferente y algo extraiia. A finales del aiio 2000, el W3c aprobo el primer borrador oficial de 10s esquemas XML ya disponibles en una version incompatible llamada XML-Data dentro del DOM de Microsoft). Un esquema XML es un documento XML que puede validar tanto la estructura del arb01 XML como el contenido de 10s nodos. Un esquema se basa en el uso y la definicion de tipos de datos simples y complejos, de un mod0 parecido a un lenguaje orientado a objetos. Un esquema define tipos complejos, indicando cada uno de 10s nodos posibles, su secuencia opcional ( s e q u e n c e , a l l ) , el numero de ocurrencias de cada subnodo ( m i n o c c u r s , m a x 0 c c u r s ) y el tipo de datos de cada elemento especifico. Este es el esquema definido por el XML Data Binding Wizard para el archivo de libros de muestra:
Los motores DOM de Microsoft y Apache tienen un buen soporte para 10s esquemas. Otra herramienta que hemos usado para la validation es XML Schema Validator (XSV), un intento de codigo abierto de conseguir un procesador conforme con 10s esquemas, que puede usarse bien directamente a traves de la Web o despues de descargar un ejecutable en linea de comandos (en las paginas sobre XML Schema del W3C se encuentra el enlace a1 sitio Web actual de esta herra-
- - -.- - - -- NOTA: El editor de Delphi soporte la completitud de cbchgo para archives XML gracias a 10s DTD. Si se coloca un ar&vo DTD en el dir&to';io bin de Delphi y se hace referencia a 61 mediante una etiqaeta DOCTYPE, se habilitarh esta caracteristica, que brland no sa~ortadc h n a dfidd.
Uso de la API de SAX La Simple API for XML, o SAX, no crea un arbol para 10s nodos XML, sino que analiza sintacticamente el nodo, disparando eventos para cada nodo, atributo, valor, etc ... Puesto que el documento no se guarda en memoria, la utilizacion del SAX nos permite manejar documentos mucho mas grandes. Este enfoque tambien es muy util para examinar una sola vez un documento o para recuperar informacion especifica. Veamos una lista de 10s eventos activados por SAX: StartDocument y EndDocument para el documento complete. StarElement y EndElement para cada nodo. Charact er s para el testo contenido en 10s nodos
Es bastante comun utilizar una pila para manejar la ruta actual dentro del arbol de nodos, y meter y sacar elementos enly desde la misma para cada evento
Start Element y EndElement . Delphi no incluye soporte especifico para la
interfaz SAX per0 se puede conseguir facilmente importando el soporte XML de Microsoft (la biblioteca MSXML). En particular, para el ejemplo SasDemol, hemos utilizado la version 2 de MSXML, ya que se encuentra muy difundida. Hemos generado una unidad de importacion de biblioteca de tipos de Pascal para la biblioteca de tipos, y la unidad de importacion esta disponible dentro del codigo fuente del programa, per0 necesitamos que la biblioteca COM este registrada en nuestro ordenador para ejecutar el programa con exito.
I -
NOTA:Otro ejcmploVhaciael final delcapituli(Lpgc~rnl) muestra, entis otras cosas, el uso de la API de SAX, incluyendselmotor OpenXml.
I
Para utilizar SAX, tenemos que instalar un controlador de eventos de SAX dentro de un lector SAX, y despues cargar un archivo y analizarlo sinticticamente. Hemos utilizado la interfaz de lectura de SAX proporcionada por MSXML para programadores de VB. La interfaz oficial (C++) tenia unos cuantos errores en su biblioteca de tipos que impedia que Delphi la pudiera importar de forma correcta. En el formulario principal del ejemplo SaxDemo 1 se declara: sax:
IVBSAXXMLReader;
En el metodo Formcreate, la variable sax se inicializa con el objeto COM: sax : = CoSAXXMLReader.Create; sax.ErrorHandler := TMySaxErrorHand1er.Create;
El codigo tambien define un controlador de errores, que es una clase que implementa una interfaz especifica (IVBSAXErrorHandler)con tres metodos a 10s que se llama dependiendo de la gravedad del problema: error, fatalError e ignorablewarning. Simplificando un poco el codigo, el analizador sintactico SAX se activa al llamar a1 metodo parseURL despues de asignarle un controlador de contenido: s a x - C o n t e n t H a n d l e r := TMySaxHandler-Create; s a x .parseURL (filename)
A1 final el codigo se encuentra en la clase TMySaxHandler, que es la que contiene 10s eventos SAX. Ya que en el ejemplo tenemos varios controladores de contenido SAX, hemos escrito una clase basica con el codigo principal y unas cuantas versiones especializadas para el procesamiento especifico. A continuacion veremos el codigo de la clase basica, que implementa l a interfaz I V B S A X C o n t e n t H a n d l e r y la interfaz I D i s p a t c h en que se basa IVBSAXContentHandler: type TMySaxHandler =class (TInterfacedObject, IVBSAXContentHandler)
protected stack: TStringList; public constructor Create; destructor Destroy; override; / / IDispa tch function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; Namecount, LocaleID: Integer; DispIDs: Pointer) : HResult; stdcall; function Invoke(Disp1D: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer) : HResult; stdcall; / / IVBSAXContentHandler procedure S e t ~ d o c u m e n t L o c a t o r ( c o n s tParaml: IVBSAXLocator); virtual; safecall; procedure startDocument; virtual; safecall; procedure endDocument; virtual; safecall; procedure startPrefixMapping(var strprefix: WideString; var strURI : Widestring) ; virtual; safecall; procedure endPrefixMapping(var strprefix: Widestring); virtual; safecall; procedure startElement(var strNamespaceUR1: WideString; var strLocalName: WideString; var strQName: WideString; const ~Attributes: IVBSAXAttributes); virtual; safecall; procedure endElement(var strNamespaceUR1: WideString; var strLocalName: WideString; var strQName: WideString); virtual; safecall; procedure characters(var strchars: WideString); virtual; safecall ; procedure ignorableWhitespace(var strchars: WideString); virtual; safecall; procedure processingInstruction(var strTarget: WideString; var strData: WideString); virtual; safecall; procedure skippedEntity(var strName: WideString); virtual; safecall; end :
La parte mas interesante es la lista final de 10s eventos SAX. Todo lo que hace esta clase basica es enviar la informacion a un registro cuando el analizador sintactico empieza (startDocument) y finaliza (endDocument) y hace el seguimiento de 10s nodos actuales y 10s nodos padre con una pila: / / TMySaxHandler. s tartElement stack .Add (strLocalName); // TMySaxHandler. endEl ement stack .Delete (stack.Count - 1) ;
La clase TMyS impleSaxHand ler proporciona una implementation real que sobrescribe el evento st art E leme nt lanzado por cualquier nuevo nodo para enviar la posicion actual en el arbol, con la siguiente sentencia: Log.Add
(strLocalName +
' ( ' + stack.ComrnaText +
') ') ;
El segundo metodo de la clase es el evento characters, que se provoca cuando se encuentra un valor de nodo (o un nodo de texto) y envia su contenido (como muestra la figura 22.6): p r o c e d u r e TMySimpleSaxHandler.characters(var WideString) ; var str: WideString; begin inherited; s t r : = Removewhites (strchars); if (str <> " ) then L o g - A d d ('Text: ' + str); end;
List
( '
Paw TkJw
1
pam~w
'
strchars:
I-
s ~ a l ~ ~ o c l m--ent bwkslbooksl book(books.book] lkle(books.book.tille] Texl: La B~bhade Delphi 7 author~books.book.auIhar1 booklbooks,book] blle(books,book,liUe] Text DelphlDeveloper's Handbook author(books,bo&.author] Ten!: Canlu au(hor(books.book.au(hor1 Text: Gooch bwk[books.book] !~tle[books,book,l~Ile] Texl. La Bibl~ade Delphi 6 aulhor(books,book,aulhor] Text. Canlu bmk[books,bwk] Mle(bwks.book,lillej Ted: DelphiCOM Programrring [email protected],auIhor] Texl Herrnon bwk(books,book] title(books.book.lille) Text Thlnkmg in Ctt aulhor[bwks.bodLw(hal Text: Edtel
Figura 22.6. El registro generado por la lectura de un documento XML con SAX en el ejernplo SaxDemol.
Se trata de una operacion de analisis sintactico generica que afecta a todo el archivo XML. El segundo controlador de contenido SAX derivado se refiere a la estructura especifica del documento XML, extrayendo solamente nodos de un tip0 determinado. Concretamente, el programa busca 10s nodos de tipo title. Cuando
un nodo es de este tipo (en s t a r t E l e m e n t ) , la clase activa la variable booleana i s b o o k . El valor de texto del nodo se tiene en cuenta solo inmediatamente despues de encontrar un nodo de este tipo: procedure TMyBooksListSaxHandler.startElement(var strNamespaceUR1, strLocalName, strQName: Widestring; const ~ A t t r i b u t e s : IVBSAXAttributes) ; begin inherited; isbook := (strLocalName = 'title ') ; end : procedure T ~ y B o o k s L i s t S a x H a n d l e r . c h a r a c t e r s ( v a r strchars: Widestring) ; var str: string; begin inherited; if isbook then begin s t r : = Removewhites (strchars); if (str <> 'I) then Log.Add (stack.CommaText + ': ' + s t r ) ; end ; end ;
Proyeccion de XML con transformaciones Hay una tecnica mas en Delphi que podemos utilizar para manejar algunos documentos XML: podemos crear una transformacion, para traducir el XML de un documento generico a1 formato nativo que utiliza un componenteC l i e n t D a t a S e t cuando guarda datos en un archivo XML de MyBase. De la misma manera, y en sentido inverso, otra transformacion puede convertir un conjunto de datos disponible en un ClientDataSet (a traves del componente D a t a s e t p r o v i d e r ) , en un archivo XML con un formato (o esquema) determinado. Delphi incluye un asistente que genera estas transformaciones, llamado XML Mapping Tool o XML Mapper, a1 que se accede desde el menu Tools del IDE o que puede ejecutarse como una aplicacion independiente. El XML Mapper, que muestra la figura 22.7, es un asistente en tiempo de diseiio que nos ayuda con la definicion de las reglas de transformacion entre 10s nodos de un documento XML generico y 10s campos del paquete de datos del ClientDataSet. La ventana del XML Mapper tiene tres zonas: A la izquierda esta la seccion del documento XML: Muestra la informacion sobre la estructura del documento XML (y sus datos, si la casilla de activacion correspondiente esta activada) en la Document View o en un esquema XML en la Schema View, segun la solapa que seleccionemos.
Tmrh-mmon E w ~ ~ o ~ m d c v ~ u ~ l - ~ B d r ( , d a a lrri~d
J
Figura 22.7. El XML Mapper muestra 10s dos extremos de una transforrnac~onpara definir la proyeccion entre ellos (con las reglas indicadas en la parte central).
A la derecha se encuentra la seccion del paquete de datos: Muestra la informacion acerca de 10s metadatos en el paquete de datos, bien en la Field View (indicando la estructura del conjunto de datos) o en la Datapacket View (dandonos informacion sobre la estructura XML). Fi.jese en que el XML Mapper tambien puede abrir archivos en el formato original de ClientDataSet.
La parte central d e la ventana se utiliza para la proyeccion: Contiene a su vez dos paginas: Mapping, donde podemos ver las correspondencias entre 10s elementos seleccionados en ambos lados que formaran parte de la proyeccion; y Node Properties, donde podemos modificar 10s tipos de datos y otros detalles de cada posible proyeccion. La pagina Mapping del panel central alberga tambien el menu de metodo abreviado que se utiliza para generar la transformacion. El resto de 10s paneles y vistas tienen menus locales especificos, utilizados para realizar diversas acciones (ademas de unos cuantos comandos en el menu principal). Podemos utilizar XML Mapper para proyectar un esquema existente (o estraerlo a partir de un documento) sobre un paquete de datos nuevo, de un paquete de datos existente a un nuevo esquema o documento, o de un paquete de datos ekistente a un documento XML ya existente (si es razonable una cierta correspondencia). Ademas de convertir 10s datos de un archivo XML a un paquete de datos, tambien podemos convertirlos en un paquete delta del ClientDataSet. Esta tecnica es muy util para fusionar un documento con una tabla, como si un usuario hubiera insertado 10s registros modificados de la tabla. Concretamente, podemos transformar un documento XML en un paquete delta para modificar, borrar o insertar registros.
El resultado de usar el XML Mapper es uno o mas archivos de transformacion, cada uno de 10s cuales representa una conversion en sentido unico (necesitamos a1 menos dos archivos para hacer la conversion en ambos sentidos). Estos archivos de transformacion se utilizan en tiempo de diseiio y de ejecucion por 10s componentes XMLTransform, XMLTransformProvider y XMLTransformClient. A mod0 de ejemplo, hemos intentado abrir el documento XML de 10s libros, que tiene una estructura que no se corresponde facilmente con una tabla, ya que contiene dos listas de valores de distintos tipos. Despues de abrir el archivo sample. xml en la seccion XML Document, hemos utilizado su menu local para seleccionar todos sus elementos (Select All) y para crear el paquete de datos (Create Datapacket From XML). Esta operacion hace que el panel derecho se rellene automaticamente con el paquete de datos y la parte central con la transformacion propuesta. Tambien podemos ver inmediatamente su efecto en un programa de muestra haciendo clic sobre el boton Create and Test Transformation. Esto abre una aplicacion generica que permite cargar un documento en el conjunto de datos usando la transformacion creada. En este caso en concreto, podemos ver que el XML Mapper genera una tabla con dos campos de conjunto de datos: uno para cada una de las posibles listas de subelementos. Esta era la unica solucion estandar posible ya que las dos sublistas tienen estructuras diferentes, y es la unica solucion que permite editar 10s datos en la DBGrid conectada a1 ClientDataSet y guardarlos de nuevo en un archivo XML, tal y como se muestra en el ejemplo XmlMapping. Basicamente, este programa es un editor basado en Windows para un documento XML complejo. El ejemplo utiliza un componente TransformProvider con dos archivos de transformation aiiadidos para leer un documento XML y para que el ClientDataSet pueda disponer de el. Como sugiere su nombre, este componente es un proveedor de conjuntos de datos. Para construir la interfaz de usuario, no hemos conectado directamente el ClientDataSet a una cuadricula, ya que contiene un unico registro con un campo de texto y dos conjuntos de datos detallados. Por ello, hemos aiiadido a1 programa dos componentes ClientDataSet mas enlazados con 10s campos del conjunto de datos y conectados con 10s dos controles DBGrid. Probablemente sea mas facil entender esto si echamos un vistazo a la definicion de 10s componentes no visuales en el codigo fuente DFM en el siguiente fragmento, y a su salida en la figura 22.8. object XMLTransformProviderl: TXMLTransformProvider TransforrnRead.TransformationFile = ' B o o k s D e f a u l t . x t r f TransformWrite.TransformationFile = 'BooksDefau1tToXml.xtr' XMLDataFile = 'Sanple.xml ' end object ClientDataSetl: TClientDataSet ProviderName = 'XMLTransformProviderl ' object ClientDataSetltext: TStringField object ClientDataSetlbook: TDataSetField object ClientDataSetlebook: TDataSetField end
o b j e c t ClientDataSet2: TClientDataSet DataSetField = ClientDataSetlbook end o b j e c t ClientDataSet3: TClientDataSet DataSetField = ClientDataSetlebook end
Delplw COM Pcgfarrunmg
Harmon
Thinkinp m C t t La Bbl~ade Delph 7
Btuce
Can(u
hltg//ww.ma~cocantu.~m Cantu
II
Figura 22.8. El ejemplo XmlMapping utiliza un componente TransformProvider para permitir la edicion de un documento XML complejo dentro de varios componentes ClientDataSet.
Este programa no solo permite editar 10s datos de las diferentes sublistas de nodos dentro de las cuadriculas, sin0 tambien modificarlos, borrarlos o aiiadir nuevos registros. Cuando aplicamos 10s cambios al conjunto de datos (haciendo clic sobre el boton Save, que llama a ~pplyupdates),el proveedor de transformaciones guarda una version actualizada del archivo en el disco. Como metodo alternativo, tambien podemos crear transformaciones que proyecten solo determinadas partes del documento XML sobre un conjunto de datos. Como ejemplo, puede consultarse el archivo Booksonly .x t r que se encuentra en la carpeta del ejemplo XmlMapping. El documento XML modificado que generara tendra una estructura y contenido distintos del original, incluyendo solo la parte que se ha seleccionado. Por eso, puede ser util para ver 10s datos per0 no para editarlos.
Una transformacion puede utilizarse para coger una tabla de una base de datos o el resultado de una consulta y producir un archivo XML con un formato mas
legible que el que nos proporciona por defect0 el mecanismo de permanencia de ClientDataSet. Para construir el ejemplo MapTable, hemos colocado un componente SimpleDataSet de dbExpress en un formulario y le hemos conectado un DataSetProvider y un ClientDataSet a1 proveedor. Despues de abrir la tabla y el conjunto de datos de cliente, hemos guardado su contenido en un archivo XML. El siguiente paso ha sido abrir el XML Mapper, cargar el archivo del paquete de datos en el, seleccionar todos 10s nodos del paquete de datos (con el comando Select All de su menu local) y llamar a1 comando Create XML From Datapacket.
En el siguiente cuadro de dialogo, aceptamos las proyecciones predeterminadas de 10s nombres para 10s campos y solo cambiamos el nombre sugerido para 10s nodos de registro (ROW) por algo mas legible (Customer). Si probamos ahora la transformacion, el XML Mapper mostrara el contenido del documento XML resultante en una vista de arb01 personalizada Una vez que hemos guardado el archivo de transformacion, podemos reanudar el desarrollo del programa, eliminando el ClientDataSet y aiiadiendo un Datasource y una DBGrid (para que un usuario pueda editar 10s datos en una DBGrid antes de transformarlos), y un componente XMLTransformClient. Este componente tiene conectado el archivo de transformacion, per0 no un archivo XML. En lugar de eso, hace referencia a 10s datos a traves del proveedor. A1 hacer clic sobre el boton, veremos el documento XML dentro de un campo de memo (despues de darle formato) en lugar de guardarlo en un archivo, algo que podemos hacer llamando a1 metodo GetDataAsXml (aunque el archivo de ayuda no resulta muy claro sobre el uso de este metodo): procedure TForrnl.btnMapClick(Sender: TObject); begin Mernol.Lines.Text : = ForrnatXmlData(XMLTransf0rmC1ientl.GetDataAsXml(")); end;
Este es el unico codigo del programa que podemos ver en tiempo de ejecucion en la figura 22.9. El conjunto de datos original puede verse en la DBGrid, y el documento XML resultante en el control de memo que se encuentra bajo la cuadricula. La aplicacion dispone de un codigo mucho mas sencillo que el que hemos utilizado en el ejemplo DomCreate para generar un documento XML parecido, per0 requiere la definition de la transformacion en tiempo de diseiio. El ejemplo DomCreate podria trabajar en tiempo de ejecucion sobre cualquier conjunto de datos, sin necesidad de una conexion a una tabla especifica ya es un codigo bastante generico. En teoria, podemos producir proyecciones dinamicas similares utilizando 10s eventos del componente generico XMLTransform, per0 parece mas sencillo usar el enfoque basado en DOM ya comentado. Ademas, la llamada a FormatXmlData produce una salida mas agradable per0 ralentiza el programa, ya que implica la carga del XML en un DOM.
I
1231 Unirco 1351 S~ghlD i w 1354 Cayman Diverr World Llnhded 1356 Tom Swyer D i n g Cedre 1 3 0 Blw Jack Aqua Center 1384 VIP Divers Club
.............. .........
.
Suhe 310
.
..
.
PO BoxZ-547 1 Neptune Lane PO Box 541 632.1 T hid Frydenhq 23-73 PaddnglonL a m 32 Main St.
.-...
....... ........,...Map
...
-,
..........
.-
.
....
..
- -:
O x m l vefs~on="l0"b < Docwnent~ ~Custl~lo>t221 ;ICustNo) cAddrl>4.976 Sugarloaf Hwyc/Addrl> cAd&2>Smte 103
Figura 22.9. El ejemplo MapTable genera un documento XML a partir de una tabla de base de datos rnediante un archivo de transforrnacion personalizado.
XML e lnternet Express Una vez que hemos definido la estructura de un documento XML, podemos desear permitir que 10s usuarios vean y editen 10s datos en una aplicacion de Windows o a traves de la Web. Este segundo supuesto es bastante interesante ya que Delphi proporciona un soporte especifico para ello. Delphi 5 ya incluia una arquitectura llamada Internet Espress, que ahora forma parte de la plataforma WebSnap. WebSnap ofrece tambien soporte para XSL, del que hablaremos mas adelante. En el capitulo 16, ya hemos hablado del desarrollo de aplicaciones de DataSnap. Internet Espress proporciona un componente de cliente llamado XMLBroker para esta arquitectura, que puede utilizarse en lugar de un conjunto de datos para obtener 10s datos a partir de un programa DataSnap de capa intermedia y ponerlo a disposicion de un tip0 especifico de productor de pagina llamado InetXPageProducer. Podemos utilizar estos componentes en una aplicacion WebBroker estandar o en un programa WebSnap. La idea de Internet Express es escribir una estension de servidor Web que produzca paginas Web conectadas con nuestro servidor DataSnap. La aplicacion personalizada actua como un cliente DataSnap y produce paginas para un navegador cliente. Internet Espress ofrece 10s servicios necesarios para crear facilmente esta aplicacion personalizada. Puede que resulte confuso, pero Internet Express es una arquitectura de cuatro niveles. Estos son: servidor SQL, servidor de aplicacion (el servidor DataSnap),
servidor Web con una aplicacion personalizada y, finalmente, el navegador Web. Podemos colocar 10s componentes de acceso a la base de datos dentro de la misma aplicacion que maneja la peticion HTTP y que genera el resultado HTML, como en una solucion cliente/servidor. Incluso podemos acceder a una base de datos local o a un archivo XML, con una estructura de dos capas (el programa servidor y el navegador). Es decir, Internet Express es una tecnologia para crear clientes basados en un navegador, lo que nos permite enviar, junto con el HTML, todo el conjunto de datos a1 ordenador cliente. Tambien enviamos algo de codigo JavaScript para poder manipular el XML y mostrarlo dentro de la interfaz de usuario definida por el codigo HTML. El codigo JavaScript es lo que hace posible que el navegador pueda mostrar 10s datos e incluso manipularlos.
El componente XMLBroker Internet Express utiliza diversas tecnologias para conseguir este resultado. Convierte 10s paquetes de datos DataSnap a1 formato XML para que el programa pueda insertar estos datos en la pagina HTML para su manipulation Web en el cliente. Realmente, el paquete de datos delta tambien se representa como XML. El componente XMLBroker lleva a cabo estas operaciones, maneja el XML y proporciona 10s datos a 10s nuevos componentes JavaScript. A1 igual que el ClientDataSet, el XMLBroker tiene: Una propiedad MaxRecords: Sirve para indicar el numero de registros que se aiiaden a una sola pagina. Una propiedad Params: Se utiliza para albergar 10s parametros que 10s componentes reenviaran a la consulta remota a traves del proveedor. Una propiedad WebDispatch: Sirve para indicar la consulta actualizada a la que responde el broker.
El InetXPageProducer permite generar visualmente 10s formularios HTML a partir de 10s conjuntos de datos, de una forma similar a1 desarrollo de una interfaz de usuario AdapterPageProducer. En realidad, la arquitectura de Internet Express, las interfaces internas que utiliza y parte de su editor IDE pueden ser considerados como el progenitor de la arquitectura WebSnap. Con la notable diferencia de generar guiones que se ejecutan del lado del servidor y del cliente, ambos proporcionan un editor para colocar componentes visuales y generar estos guiones. Cabe destacar que el antiguo Internet Express se orienta mas a XML que el mas reciente WebSnap.
I
-
-
--
TRUCO: Otra caracteristica comun del InetXPageProducer y el AdapterPageProducer es el soporte para hojas de estilo en cascada (CSS).
-,
h
I
-.
~ s t o cornponent&~tienen s las propiedyad8- st yi&-e$ ~ i i para e definir el CSS y cada elemento visual time una propiedad 3 t y l e Rule que puede usarse para seleccioaar el nombre del &lo. -
'
I
Soporte de JavaScript Para producir potentes operaciones de edicion en el lado del cliente, el InetXPageProducer utiliza un codigo y unos componentes JavaScript especiales. Delphi incluye una biblioteca bastante extensa de JavaScript, que el navegador tiene que descargar. Es un proceso algo fastidioso, per0 es la unica manera de que la interfaz del navegador (que se basa en codigo HTML dinamico), sea lo suficientemente buena como para soportar restricciones de campos y otras reglas de negocio similares. Esto seria totalmente imposible con el HTML simple. Los archivos de JavaScript proporcionados por Borland, que deberian hacerse disponibles en la pagina Web que albergue la aplicacion, son 10s siguientes:
Analizador sintactico XML compatible con DOM (para navegadores que carezcan de soporte nativo DOM).
Xmldom. j s
I x m l d b . js
Clases JavaScript para 10s controles HTML.
Xm1disp.j~
I
.
I
Clases JavaScript para enlace de datos XML con controles HTML.
Xrnlerrdisp js
Clases para arreglar errores.
Xrn1Show.j~
Funciones JavaScript para mostrar datos y paquetes delta (cuya finalidad es la depuraci6n).
I
Normalmente, las paginas HTML generadas por Internet Express incluyen referencias a estos archivos JavaScript, como en:
Podemos personalizar el codigo JavaScript aiiadiendo directamente codigo a las paginas HTML, o creando nuevos componentes de Delphi escritos para encajar con la arquitectura de Internet Express que produce el codigo JavaScript (posiblemente junto con codigo HTML). Como ejemplo, la clase TPromptQueryButton de muestra de InetXCustom genera el siguiente codigo HTML y JavaScript:
<script language=javascript type="text/javascript"> function PromptSetField (input, msg) ( v a r v = prompt (msg); i f ( V == null I I v == " " ) return false; input. value = v return true ; 1 var QueryForm3 = document.forms['QueryForm3'];
..."
TRUCOt Los mriipooentjq dcionales '&muestia.de ENetXCustom sew de graa ayuda si tenaaos ktFncibn de u k Inkmet Express. E& c(mponentcn, estiin dirponibles en la Carpeta \ o dho s \ M i d a s \ ~ n t e r n e t ~ x ~ r e~ s st\~ e ttom. ~ ~ Siga u s 1% dew* instrucciones del ar'chivo readma. t x t para ins'talar estos ~ ~que Borlands proporciona sin n&im tipo de sopork pmo que penniten afUadir muchias [email protected] a Ias aplicaciones &bm&Eqress ccin hpxpdlo esfhrzo adicid. Para utilizar esta arquitectura no necesitamos nada especial en el cliente, ya que puede usarse cualquier navegador que entienda el estandar HTML 4, en cualquier sistema operativo. Sin embargo, el servidor Web debe de ser un servidor de Win32 (esta tecnologia no esta disponible en Kylis) y hay que utilizarlo con bibliotecas DataSnap.
Creacion de un ejemplo Para entender mejor de que hablamos, y como forma de comentar algunos detalles mas tecnicos, vamos a probar sencillo ejemplo llamado IeFirst. Para evitar problemas de configuracion, esta es una aplicacion CGI que accede directamente a un conjunto de datos (en este caso a una tabla local conseguida mediante un componente C l i e n t D a t a S e t ) . Mas tarde veremos como convertir un cliente DataSnap de Windows ya existente en una interfaz que basada en un navegador. Para crear IeFirst, hemos creado una nueva aplicacion CGI y afiadido a su modulo de datos un componente C l i e n t Dataset conectado con un archivo .CDS local y un componente D a t a S e t P r o v i d e r conectado con el conjunto de datos. El paso siguiente es aiiadir un componente XMLBroker y conectarlo con el proveedor: object ClientDataSetl: TClientDataSet FileName = 'C: \Archives d e programa \Archives comunes \Borland Shared\Data \employee.cds '
.
end o b j e c t DataSetProviderl: TDataSetProvider
DataSet = ClientDataSetl end o b j e c t XMLBrokerl: TXMLBroker
Se necesita la propiedad ReconcileProducer para mostrar un mensaje de error adecuado en caso de conflict0 de actualizacion. Uno de 10s programas de ejemplo de Delphi incluye un codigo personalizado, pero, para este caso, simplemente hemos conectado un componente Pageproducer tradicional con un mensaje de error HTML generico. Despues de preparar el XMLBroker, podemos aiiadir un InetXPageProducer al modulo de datos Web. Este componente tiene un esqueleto HTML estandar, que hemos personalizado para aiiadir un titulo, sin modificar las etiquetas especiales: IeFirstInternet Express First Demo (IeFirst . exe) <#INCLUDES><#STYLES><#WARNINGS><#FORMS><#SCRIPT>
Las etiquetas especiales se expanden automaticamente mediante 10s archivos JavaScript del directorio especificado en la propiedad Include Pat hURL. Es necesario establecer esta propiedad para que haga referencia al directorio del servidor Web donde residen estos archivos. Podemos encontrarlos en el directorio \ D e l p h i 7 \ S o u r c e \ W e b M i d a s . Las cinco etiquetas tiene el siguiente efecto:
<#INCLUDES>
Genera las instrucciones para incluir las bibliotecas JavaScript.
<#STYLES>
Aiiade la hoja de definicion de estilo incrustada.
<#WARNINGS>
Se utiliza en tiempo de diseiio para mostrar 10s errores en el editor InetXPageProducer.
<#FORMS>
Genera el codigo HTML producido por 10s componentes de la pagina Web. Aiiade un bloque de JavaScript utilizado para iniciar el guion del lado del cliente.
NOTA: El componente InetXPageProducer tambien maneja unas cuantas etiquetas internas mas. <#BODYELEMENTS> se corresponde con las cinco Name=WebCom~oetiauetas dantilla aredefinida. <#COMPONENT .. - .-x - - ---- de - - la r ----------nentName> forma parte del codigo HTML generado utilizado para declarar 10s componentes generados visualmente. < D A T A P A C K E T -.. ' m -w -m o .Ke r =-m o .Ke m.a m e > se sustltuye con el coaigo AML ael paquete de datos. ~
---
. . A
- - - -
-
-
--
.I
-
-
-
~
I.
--
-
. , 1 ' *
-
~
1
1
-
.-
Para personalizar el HTML resultante dcl InetXPageProducer, podemos utilizar su editor. que vuelve a scr parecido a1 editor de guiones de servidor de WebSnap. Haciendo dobIe clic sobre el componente InetXPageProducer,Delphi abre una x n t a n a como la que muestra la figura 22.10 (con la configuracion final del ejemplo). En este editor podemos crear estructuras complejas partiendo de un formulario de consulta. un formulario de datos o un grupo generic0 de estructura. En el formulario de datos de nuestro ejemplo, hemos aiiadido dos componentes DataGr id y DataNavigator sin personalizarlos (operacion que se puede llevar a cabo aiiadiendo botones hijo, columnas y otros objetos que sustituyan completamente a 10s predeterminados).
Figura 22.10. El editor de InetXPageProducer nos permite crear visualmente complejos formularios HTML de una forma parecida al Adapterpageproducer.
El codigo DFM para el InetXPageProducer y sus componentes internos en el ejemplo se muestra a continuacion. Se pueden ver las configuraciones principales, ademas de algunas limitadas personalizaciones graficas: InetXPageProducerl: TInetXPageProducer IncludePathURL = ' / j ssource/ ' HTMLDoc.Strings = ( . . . ) o b j e c t DataForml: TDataForm o b j e c t DataNavigatorl: TDataNavigator XMLComponent = DataGridl Custom = 'align="center" '
object
end o b j e c t DataGridl: TDataGrid
XMLBroker = XMLBrokerl DisplayRows = 5 TableAttributes.BgCo1or = 'Silver' TableAttributes.CellSpacing = 0 TableAttributes.Cel1Padding = 2 HeadingAttributes.BgCo1or = 'Aqua' o b j e c t EmpNo: TTextColumn... o b j e c t LastName: TTextColumn. . . o b j e c t FirstName: TTextColumn... o b j e c t PhoneExt : TTextColumn. . o b j e c t HireDate: TTextColumn. .. o b j e c t Salary: TTextColumn.. . o b j e c t StatusColumnl: TStatusColumn...
.
end end end
El valor de estos componentes esta en el codigo HTML (y JavaScript) que generan y que podemos ver previamente a1 seleccionar la pestaiia H T M L del editor de InetXPageProducer. Veamos a continuacion un parte de las definiciones en el HTML para 10s botones, el encabezado de la cuadricula de datos y una de sus celdas: / / botones
... / / titulo de cuadricula de datos
/ / u n a celda d e datos
...
Despues de preparar el generador de HTML, podemos volver a1 modulo de datos Web, aiiadirle una accion y conectarla con el InetXPageProducer mediante la propiedad P r o d u c e r . Esto deberia bastar para que el funcione a travts-de un navegador, como muestra la fig& 22.1 1 .
programs
1
Internet Express First Demo (IeFirst.exe)
lelson
IRoberio .
'oung
-
--
-
l~ruce
:uao
Figura 22.11. La aplicacion IeFirst envia al navegador algunos componentes HTML, un documento XML complete, y codigo JavaScript para mostrar 10s datos en 10s componentes visuales
Si miramos en el archivo HTML recibido por el navegador, encontraremos la tabla mencionada en la definition anterior, algo de codigo JavaScript y 10s datos de la base de datos en el formato XML del paquete de datos. Estos datos 10s controla el XMLBroker y se 10s pasa a1 componente productor para insertarlos en el archivo HTML. El numero de registros enviados al cliente depende del XMLBroker y no del numero de lineas de la cuadricula. Despues de que se envien 10s datos XML a1 navegador, podemos usar 10s botones del componente navegador para movernos por ellos sin necesidad de acceder a1 servidor para buscar mas. Esto difiere bastante del comportamiento de WebSnap. No queremos decir que un enfoque sea mejor que otro, sino que depende del tip0 de aplicacion que vayamos a construir.
A1 mismo tiempo, las clases JavaScript del sistema permiten que el usuario introduzca datos nuevos, siguiendo las reglas impuestas por el codigo JavaScript que conectado a 10s eventos HTML dinamicos. De manera predeterminada, la cuadricula tiene una columna adicional con un asterisco que indica que registros se han modificado. Los datos de actualizacion se agrupan en un paquete de datos XML en el navegador y se envian de vuelta al sewidor cuando el usuario hace clic sobre el boton Apply Updates. A partir de aqui, el navegador activa la accion especificada por la propiedad WebDispat h . Pat hInf o del XMLBroker. No hay necesidad de exportar esta accion desde el modulo de datos Web, ya que es una operacion automatica (aunque podemos desactivarla si establecemos WebDispath. Enable como False). El XMLBroker aplica 10s cambios al servidor, devolviendo el contenido a1 proveedor conectado a la propiedad Re conc i 1e Provider (o lanzando una excepcion si esta propiedad no esta definida). Si todo funciona bien, el XMLBroker redirige el control a la pagina principal que contiene 10s datos. Sin embargo, hemos experimentado algunos problemas con esta tecnica, por lo que el ejemplo IeFirst controla el evento OnGetResponse, indicando que se trata de una vista actualizada: procedure TWebModulel.XMLBrokerlGetResponse(Sender: TObject; Request: TWebRequest; Response: TWebResponse; v a r Handled: Boolean) ; begin Response .Content : = ' < h l > U p d a t e d < / h l > < p >' + 1netXPageProducerl.Content; Handled : = True; end;
Uso de XSLT Otra posibilidad para generar un codigo HTML partiendo de un documento XML es usar el lenguaje de hojas de estilo extensible (Extensible Stylesheet Language, XSL) o, para ser mas precisos, su subconjunto XSL Transformations (XSLT). El objetivo de de XSLT es transformar un documento XML en otro documento, generalmente un documento XML. Uno de 10s usos mas frecuentes de la tecnologia es convertir un documento XML en un documento XHTML para enviarlo a un navegador desde un servidor Web. Otra interesante tecnologia relacionada es XSL-FO (XSL Formatting Objects), que puede usarse para convertir un documento XML en un documento PDF u otro tipo de documento con formato. Un documento XSLT es un documento XML bien formado. La estructura de un archivo XSLT necesita un nodo raiz como el siguiente:
El contenido del archivo XSLT se basa en una o mas plantillas (o reglas o funciones) que procesara el motor. Su nodo es xsl : template,normalmente con un atributo match. En el caso mas sencillo, una plantilla funciona sobre nodos con un nombre determinado; se invoca la plantilla pasandole uno o mas nodos con una expresion XPath:
El punto de partida para esta operacion es una plantilla que procesa el nodo raiz, que puede ser la unica plantilla del archivo XSLT. En las plantillas, se puede encontrar cualquier otro comando, como la extraccion de un valor desde un documento XML (xsl :value-of select), sentencias de bucle (xsl : for-each), expresiones condicionales (xs1 : if,xs 1 :choose), peticiones de ordenacion (xs1 : sort) y peticiones de numeracion (xsl : number) por mencionar solo algunos comandos XSLT comunes.
Uso de XPath XSLT usa otras tecnologias XML, sobre todo XPath para identificar partes de documentos. XPath define un conjunto de reglas para encontrar uno o mas nodos dentro de un documento. Estas reglas se basan en una estructura de lineas de ruta del nodo dentro del arb01 XML. De esta manera, la /books/book identifica cualquier nodo book bajo la raiz de documento books.XPath utiliza unos simbo10s especiales para identificar a 10s nodos: Un asterisco (*) significa cualquier nodo; por ejemplo book/* indica cualquier subnodo bajo el nodo book. Un punto ( .) significa el nodo actual. El simbolo de barra
(
I
)
significa alternativas como en book I ebook.
Una doble barra inclinada ( / / ) significa cualquier ruta. / /title nos indica todos 10s nodos title, cualesquiera que sean sus nodos padre y books/ /author nos indica cualquier nodo author bajo un nodo books sin tener en cuenta 10s nodos intermedios. El signo de arroba o at ( @ ) indica un atributo en lugar de un nodo, como en el caso hipotetico de author/@lastname. Los corchetes cuadrados ( [ y ] ) pueden usarse para escoger solo 10s nodos o atributos que contengan un valor dado. Por ejemplo, author [ @name= "marco"] seleccionaria todos 10s autores con un atributo de nombre (name) dado (en este caso, marco). Hay muchos casos mas, per0 esta pequeiia introduccion a las reglas de XPath ayudaran con el comienzo y a comprender 10s ejemplos que siguen. Un documento XSLT es un documento XML que trabaja sobre la estructura de un documento
XML origen y que genera como salida otro documento XML, como un documento XHTML que podemos ver en un navegador Web.
NOTA: Entre 10s procesadores XSLT m h usados se incluyen MS-XML, Xalan del proyecto Apache XML (xml. apache. org) y Xt basado en Java de James Clarke. En Delphi tambih se puede usar el motor XSLT, incluido en XML Partner Pro de Turbopower (www.turbopower.com).
XSLT en la practica Vamos a analizar un par de ejemplos. Como punto de partida, deberiamos estudiar el propio estandar XSL y centrarnos en su activacion desde una aplicacion Delphi. Como prueba inicial, podemos conectar directamente un archivo XSL con un a r c h i ~ ~XML. o Cuando cargamos en archivo XML en lnternet Explorer veremos la transformacion en XHTML resultante. La conexion se indica en la cabecera del documento XML con un comando como el siguiente: Esto es lo que hemos hecho en el archivo samplelembedded . xml disponible en la carpeta XslEmbed.El XSL relacionado incluye varios fragmentos de XSL que no tenemos espacio para comentar en detalle. Por ejemplo, coge la lista completa de autores o filtra un grupo especifico de ellos con el siguiente codigo:
Se usa codigo mas complejo para extraer nodos solo cuando se encuentra presente un valor especifico en un subnodo o atributo, sin tener en cuenta 10s nodos de mayor nivel. El siguiente fragment0 de XSL tambien tiene una sentencia i f y produce un atributo en el nodo resultante, como un mod0 de crear un hipervinculo href en el codigo HTML: < h 3 > M a r c o 1 s works
(books
+ ebooks)
XSLT con WebSnap Dentro del codigo de un programa, existe la posibilidad de ejecutar el metodo Transf ormNode de un nodo DOM, pasandole otro DOM que contenga el documento XSL. Sin embargo, en lugar de utilizar este enfoque de bajo nivel, podemos crear un ejemplo basado en XSL con la ayuda del WebSnap. Podemos crear una nueva aplicacion de WebSnap (En este caso, hemos creado un programa CGI llamado XslCust) y escoger un componente XSLPageProducer para su pagina principal, para permitir que Delphi nos ayude a comenzar con el codigo de la aplicacion. Delphi incluye tambien un archivo XSL esqueleto para manipular un paquete de datos ClientDataSet y aiiade muchas nuevas vistas a1 editor. El texto XSL sustituye al archivo HTML; la pagina XML Tree muestra 10s datos, si hay alguno; la pagina XSL Tree muestra el XSL dentro de un componente ActiveX de Internet Explorer; la pagina HTML Result muestra el codigo producido por la transformacion y, finalmente, la pagina Preview muestra lo que el usuario vera en un navegador.
TRUCO: En Delphi 7, el editor proporciona la completitud de codigo para XSLT, que hace que la edicion de este tipo de c6digo en el editor sea tan potente como en algunos sofisticados editores especificos para XML. Para que funcione este ejemplo, debemos proporcionar algunos datos al componente XSLPageProducer a traves de su propiedad XMLData. Esta propiedad puede conectarse a un XMLDocument o directamente a un componente XMLBroker, como hemos hecho en este caso. El XMLBroker toma 10s datos de un proveedor conectado a una tabla local, enlazada con el clasico componente de tabla customer. c d s de DBDEMOS. El efecto es que, con el siguiente XSL generado con Delphi, se consigue (incluso en tiempo de diseiio), el resultado que muestra la figura 22.12:
NOTA: La ~lantillaXSL esthdar se ha &mPIiado desde D e l ~ h6. i va aue . las versiones originales no tenian en cuenta 10s campos nulos omitidos en el T
GXLGLISIULI a 1
c;uu~gu A
~
.
uLn g u GLI ~aLUUL Durlanu LUIUGIGIL~;~: y S
U
~
~
de sus sugerencias han sido incorporadas a la plantilla. Este codigo genera una tabla HTML consistente en la expansion de 10s metadatos de campo y datos de fila. Los campos se usan para generar el encabezado de la
S
tabla, con una celda < t h > para cada entrada de una sola fila. Los datos de fila se usan para rellenar el resto de las filas de la tabla. No basta con tomar el valor de cada atributo (select=" @ * "); ya que 10s atributos pueden no existir. Por este motivo, la lista de campos y la fila actual se guardan en dos variables: despues, para cada campo, el codigo XSL extrae el valor de un elemento de fila que tenga un nombre de atributo (@* [name( ) = . .) que se corresponda con el nombre del campo actual guardado en su atributo attrnnme (eattrname). Este codigo no resulta nada sencillo, per0 es un mod0 compacto y adaptable para analizar divcrsas partes de un documento XML a1 mismo tiempo.
.
j Procedures .JUser
Tom
--
I
39 1
Insert
~ H T M ~ ~ P ~ Tree,&~~ ~ ~ ~Tree/ ~ , $ M. L
'igura 22.12. El resultado de una transforrnacion XSL generada por el cornponente XSLPageProducer en el ejernplo XslCust.
Transformaciones XSL directas con DOM Usar el XSLPageProducer puede ser util, per0 generar varias paginas basadas en 10s mismos datos simplemente para poder manejar estilos XSL posiblemente distintos con WebSnap, no parece el mejor metodo. Hemos creado una aplicacion CGI simple, llamada CdsXstl que puede transformar un paquete de datos ClientDataSet en diferentes tipos de HTML, segun el nombre del archivo XSL que se haya pasado como parametro. La ventaja es que ahora se pueden modificar 10s archivos XSL existentes y aiiadir nuevos archivos XSL sin tener que volver a compilar el programa. Para conseguir la transformacion XSL, el programa carga 10s archivos XML y XSL en sendos componentes XMLDocument, llamados XmlDom y XslDom. Despues invoca el metodo transformNode del documento XML, pasando el documento XSL como parametro y rellenando un tercer componente XMLDocument, llamado HtmlDom:
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; var
xslfile, xslfolder: string; attr: IDOMAttr; begin // abre el conjunto de datos del cliente y carga su XML en un // DOM
ClientDataSetl.0pen; XmlDom.Xml.Text : = ClientDataSetl.XMLData; XmlDom.Active := True; // carga el archivo xsl solicitado xslfile : = Request.QueryFields.Va1ues ['style']; i f xslfile = " then xslfile : = 'customer.xsl '; xslfolder : = ExtractFilePath (ParamStr (0)) + 'xsl\'; i f FileExists (xslfolder + xslfile) then xslDom.LoadFromFile (xslfolder + xslfile) else r a i s e Exception.Create('Missing
file: '
+
xslfolder +
xslfile) ; XSLDom.Active : = True; i f xslfile = 'single.xsl ' then begin
El codigo usa el DOM para modificar el documento XSL para mostrar un unico registro, aiiadiendo la sentencia XPath para seleccionar el registro indicado por el campo de consulta i d . Este i d se aiiade a1 hipervinculo gracias a1 XSL con la lista de 10s registros, pero no vamos a listar mas archivos XSL, ya que podemos obtenerlos en la subcarpeta XSL de la carpeta de este ejemplo y analizarlos con mayor detenimiento.
ADVERTENCIA: Para ejecutar este programa, hay que colocar 10s archivos XSL en una carpeta llamada XSL dentro de aquella en que se encuentre la Carpeta de 10s guiones de este capitdo. Para desplegar e m s archivos en
~
t
i
n
t
a
,
-
~
w
k
i
g que'e&e l .
el dombn
de la carpeia XSL a partir del mmbbredeJpn$ramq dispuitible en el primer pariunetio ep b e a de comandos (yaque el orrjeto%pplication definido en la unidad Forms no es accesible por media & iiiia aplicacib CGI).
-
Procesamiento de grandes documentos XML Como ya se ha visto, suelen existir muchas tecnicas distintas para realizar la misma tarea con XML. En la mayoria de 10s casos se puede escoger cualquier solution, teniendo en mente el objetivo de escribir un codigo menor y mas facil de mantener. Pero cuando se necesita procesar un gran numero de documentos XML o documentos XML muy grandes, hay que tener en cuenta la eficiencia. Comentar la teoria por si misma no es muy util, asi que crearemos un ejemplo que pueda utilizarse (y modificarse) para probar distintas soluciones. El ejemplo se llama LargeXml, y trata un campo especifico: llevar datos desde una base de datos a un archivo XML y en sentido inverso. El ejemplo puede abrir un conjunto de datos (usando dbExpress) y replicar despues 10s datos varias veces en un ClientDataSet que resida en memoria. La estructura del ClientDataSet en memoria se duplica a partir del componente de acceso a 10s datos:
Despues de utilizar un grupo de botones de radio para definir la cantidad de datos que se desea procesar (algunas opciones pueden requerir minutos en un ordenador lento); 10s datos se duplican mediante este codigo: while ClientDataSet1.RecordCount < nCount do begin SimpleDataSet1.RecNo : = Random (SimpleDataSet1.RecordCount) + 1; ClientDataSetl-Insert; ClientDataSetl. Fields [0].AsInteger := Random (10000); for I : = 1 to SimpleDataSetl.Fie1dCount - 1 do ClientDataSetl.Fields [i].AsString : = SimpleDataSetl.Fields [i].AsString; ClientDataSetl-Post; end:
De un ClientDataSet a un documento XML Ahora que el programa tiene en memoria un gran conjunto de datos, proporciona tres modos distintos de guardar el conjunto de datos en un archivo. El primer0 es guardar directamente la propiedad XMLData del ClientDataSet en un
archivo, consiguiendo asi un documento basado en atributos. Probablemente no sea este el formato que se desee, por lo que la segunda solucion es aplicar una transformacion de XmlMapper mediante un componente XMLTransf ormClient . La tercera solucion implica procesar directamente el conjunto de datos y escribir cada registro en un archivo: procedure TForml.btnSaveCustomClick(Sender: TObject); var str: TFileStream; s: string; i: Integer; begin str : = TFileStream.Create ( 'data3.xmZ ', fmcreate) ; try ClientDataSet1.First; s : = ' '; str .Write (s[1] , Length (s)) ;
.
while not ClientDataSetl.EOF do begin :=
' I .
for i : = 0 to ClientDataSetl.Fie1dCount - 1 do s : = s + MakeXmlstr (ClientDataSetl.Fields [i] . FieldName, ClientDataSetl.Fields[i].AsString); s : = MakeXmlStr ( 'employeeData ', s) ; str-Write(s[1] , length (s)) ; ClientDataSet1.Next end; s : = ''; str.Write (s[1] , length (s)) ; finally str . Free; end; end;
Este codigo utiliza una funcion auxiliar simple per0 eficaz para crear 10s nodos XML: function MakeXmlstr (node, value: string) : string; begin Result : = ' < I + node + ' > I + value + I < / ' + node + end :
' > I ;
Si se ejecuta el programa, se podra ver el tiempo que se tarda en cada operacion, como lo muestra la figura 22.13. Guardar 10s datos del ClientDataSet es el enfoque mas rapido, per0 probablemente no se consiga el efecto deseado. El streaming personalizado es solo ligeramente mas lento, per0 deberia considerarse que este codigo no necesita que 10s datos se lleven en primer lugar a un ClientDataSet, porque se puede aplicar directamente, incluso en un conjunto de datos unidireccional de dbExpress. Deberiamos olvidarnos de utilizar el codigo
basado en el XmlMapper para un conjunto de datos grande, porque es varios cientos de veces mas lento, incluso para un conjunto de datos pequeiio (ni siquiera hemos podido probarlo con un conjunto de datos grande, porque, sencillamente, tarda demasiado). Por ejemplo, 10s 50 milisegundos que necesita un streaming personalizado para un conjunto de datos pequeiios se convierten en m b de 10 segundos cuando usamos la proyeccion, y el resultado es muy parecido.
313 Robert
E
--
4747 LesLe
332
G Ion
937
k
6716 161 7 Janet
'
Nelson
_
!-
5 -
Bddwn
2
s-'
,7 -
__ 1410 -
Johnson h",bM
.
-
-
'2-L.
1
+I
Figura 22.13. El ejemplo LargeXml en accion.
De un documento XML a un ClientDataSet Una vez que se tiene un documento XML grande, conseguido por un programa (como en este caso) o a partir de una fuente externa, es necesario procesarlo. Como ya se ha visto, el soporte de XmlMapper es demasiado lento, por lo que solo quedan tres alternativas: una transformacion XSL, un SAX o un DOM. Las transformaciones XSL probablemente Sean lo suficientemente rapidas, per0 en este ejemplo hemos abierto el documento con un SAX, ya que se trata del enfoque mas rapido y no necesita mucho codigo. El programa tambien puede cargar un documento en un DOM, per0 no hemos escrito el codigo necesario para recorrer el DOM y guardar 10s datos de nuevo en un ClientDataSet. En ambos casos, hemos probado el motor OpenXml contra el motor MSXML. Esto permite ver las dos soluciones SAX como comparacion, porque (lamentablemente) el codigo es ligeramente distinto. A mod0 de resumen de 10s resultados: usar la SAX de MSXML es ligeramente mas rapido que usar el SAX de OpenXML (la diferencia esta en torno a un 20 por ciento), mientras que la carga en el DOM significa una amplia ventaja a favor de MSXML. El codigo del SAX de MSXML utiliza la misma arquitectura ya comentada en el ejemplo SaxDemol, por lo que
aqui solo vamos a mostrar el codigo de 10s controladores que se utilizan. Como puede verse, a1 comienzo de un ejemplo employeeData se inserta un nuevo registro, que se envia cuando se cierra el mismo nodo. Los nodos de menor nivel se aiiaden como campos al registro actual. Este es el codigo: procedure TMyDataSaxHandler.startElement(var strNamespaceUR1, strLocalName, strQName: WideString; const ~Attributes: IVBSAXAttributes); begin inherited; i f strLocalName = 'employeeDatat then Forml.clientdataset2.Insert; strcurrent : = ' I ; end; procedure TMyDataSaxHandler.characters(var strchars: WideString) ; begin inherited; strcurrent : = strcurrent + Removewhites (strchars); end; procedure TMyDataSaxHandler.endElement(var strNamespaceUR1, strLocalName, strQName : WideString) ; begin i f strLocalName = employeeData ' then Forml.clientdataset2.Post; if stack.Count > 2 then Forml.ClientDataSet2.FieldByName (strLocalName).Asstring .= strcurrent; inherited; end;
El codigo para 10s controladores de eventos en la version OpenXml es parecido. Todo lo que cambia son las interfaces de 10s metodos y 10s nombres de 10s parametros: type TDataSaxHandler = class (TXmlStandardHandler) protected stack: TStringList; strcurrent: string; public constructor Create(aowner: TComponent); override; function endElement(const sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; namespaceUR1, tagName: widestring): TXmlParserError; override; function PCDATA(const sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; data: widestring): TXmlParserError; override;
f u n c t i o n s t a r t E l e m e n t ( c o n s t sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; namespaceURI, t a g N a m e : widestring; attributes: TdomNameValueList) : TXmlParserError; override; d e s t r u c t o r Destroy; override; end;
Tambien es mas dificil invocar el motor de SAX, como se muestra en el codigo siguiente (del que hemos eliminado el codigo de creacion del conjunto de datos, la medida del tiempo y el registro): p r o c e d u r e TForml.btnReadSaxOpenClick(Sender: TObject); var agent: TXmlStandardProcessorAgent; reader: TXmlStandardDocReader; filename: string; begin L o g : = memoLog. L i n e s ; filename := ExtractFilePath (Application.Exename) + ' d a t a 3 . xml '; agent : = TXmlStandardProcessorAgent.Create(ni1); reader:= TXmlStandardDocReader.Create ( n i l ) ; try reader.NextHandler : = TDataSaxHandler.Create (nil); / / our custom c l a s s agent.reader : = reader; agent.processFile(filename, filename); finally agent.free; reader.free; end; end;
Servicios Web y SOAP
De entre todas las caracteristicas mas recientes de Delphi, una sobresale por encima de todas: el soporte para servicios Web incluido en el producto. El hecho de que lo tratemos hacia el final del libro no tiene nada que ver con su importancia, sino solamente con el logico desarrollo del texto y con el hecho de que no es el mejor punto de partida para comprender la programacion en Delphi. El tema de 10s servicios Web es muy amplio e implica varias tecnologias y estindares de negocio. Como siempre, nos centraremos en la implementacion subyacente de Delphi y en la parte tecnica de 10s servicios Web, en lugar de analizar el panorama global y las implicaciones empresariales. Este capitulo tambien es importante porque Delphi 7 aiiade una gran cantidad de potencia a la implementacion de servicios Web que ofrecia Delphi 6, incluyendo soporte para adjuntos, cabeceras personalizadas y muchas cosas mas. Veremos como crear un cliente y un servidor de servicios Web, y tambien como transportar datos de una base de datos sobre SOAP empleando la tecnologia DataSnap. Este capitulo trata 10s siguientes temas: Servicios Web. SOAPyWSDL. DataSnap sobre SOAP
Manejo de adjuntos. UDDI.
Servicios Web Esta tecnologia en rapida expansion tiene el potencial de cambiar la forma en la que Internet funciona para las empresas. Explorar una pagina Web para hacer un pedido esta bien para un unico usuario (las conocidas aplicaciones B2C o aplicaciones de empresa a consumidor), pero no para una empresa (las aplicaciones B2B de empresa a empresa). Si queremos comprar unos cuantos libros, visitar el sitio Web de un vendedor de libros y escribir nuestros pedidos estara bien, pero si nuestro negocio es una libreria y queremos hacer cientos de pedidos a1 dia, este metodo esta lejos de ser el mas adecuado, en particular si tenemos un programa que nos ayude a hacer el seguimiento de ventas y determinar nuevos pedidos. Seria ridiculo tomar la salida de este programa y volver a escribirla en otra aplicacion. La idea de 10s s e ~ i c i o Web s es solventar este problema: el programa utilizado para el seguimiento de ventas, puede crear automaticamente una consulta y enviarla a un servicio Web, el cual, devuelve inmediatamente la informacion sobre el pedido. El siguiente paso podria ser una consulta sobre el numero de envio. A continuacion, el programa puede utilizar otro servicio Web para hacer el seguimiento del envio hasta que llegue a su destino y asi poder decirle a1 cliente cuanto tardara en llegar. Cuando llegue el envio, el programa puede enviar una notificacion SMS o mediante un busca a las personas que tengan solicitudes pendientes, emitir una orden de pago con el servicio Web de un banco, ... y podriamos continuar pero creemos que ya se ha captado la idea. Los servicios Web estan pensados para la interoperabilidad informatics a1 igual que la Web y el correo electronico, estan pensados para la comunicacion entre personas.
SOAP y WSDL Los servicios Web son posibles gracias a1 Simple Object Access Protocol (SOAP). Creado sobre el protocolo HTTP estandar para que un servidor Web pueda manejar las consultas SOAP y que 10s paquetes de datos puedan pasar a traves de cortafuegos. SOAP define una notacion basada en XML para solicitar la ejecucion de un metodo por parte de un objeto en el servidor, pasandole 10s parametros. Mediante otra notacion se define el formato de la respuesta.
I
ROTA: SOAP h e desarrollado originalmente por DevelopMentor (la cornpafiia de entrenamiento de Don Box, el experto en COM)y Microsofi, para superar la debilidad del uso de DCOM en s e ~ d o r e Web. s Sometido a1
r
W para f su es C n i m u c h &qxis&~ lo .Mgieron. co.aparticular empujc de IBM. Es muy pronto pQa aaber si se producid una eatandarizacih real para que 10s programas de software de Miqrosoft, IBM, Sun, Oracle y muchos otros interaden realmeate o si algunas de cstas marcas tratarh de promover una version privada del esthdar. En cualquier caso, SOAP es s61o una de Ias piedras angulares de la arquiWhm .NETde Microsoft, asi como de las platafonnas aduales de Sun y Omclc.
1
SOAP reemplazara, a1 menos entre ordenadores diferentes, el uso de COM. Del mismo modo, la definicion de un servicio SOAP en el formato Web Services Description Language (WSDL) sustituira a1 IDL y las bibliotecas de tipos utilizadas por COM y COM+. Los documentos WSDL son otro tipo de documentos XML que proporcionan la definicion de metadatos de una consulta SOAP. Cuando obtenemos un archivo en este formato (generalmente publicado para definir un servicio), podremos crear un programa para llamarlo. De manera especifica. Delphi proporciona una proyeccion bidireccional especifico entre WSDL y las interfaces. Esto significa que podemos coger un archivo WSDL y generar una interfaz para el. Podemos incluso crear un programa de cliente que incluya las consultas de SOAP mediante estas interfaces y utilizar un componente especial de Delphi que nos permita convertir las consultas de la interfaz local en llamadas SOAP (a menos que queramos generar manualmente el XML necesario para una consulta SOAP). En sentido inverso, podemos definir una interfaz (o utilizar una existente) y permitir que un componente Delphi genere una descripcion WSDL para ella. Otro componente nos proporciona una proyeccion de SOAP a Pascal para que a1 insertar este componente y un objeto que implemente la interfaz dentro del programa de servidor, consigamos en unos minutos poner en marcha un servicio Web.
Traducciones BabelFish Como primer ejemplo del uso de un servicio Web, vamos a crear un cliente para el servicio de traduccion BabelFish ofrecido por AltaVista. Se puede encontrar este y muchos otros servicios para su experimentacion en el sitio Web de XMethods (~vw.xmethods.com). Despues de descargar la descripcion WSDL de este servicio del sitio de XMethods (disponible tambien entre 10s archivos de codigo fuente para este capitulo), hemos invocado a1 Web Services Importer de Delphi en la pagina Web Services del cuadro de dialog0 New Items y hemos seleccionado el archivo. El asistente permite acceder a una vista previa de la estructura del servicio (vCase la figura 23.1) y generar las interfaces en lenguaje Delphi apropiadas en una unidad como la siguiente (con muchos de 10s comentarios eliminados):
klsDL Conocrmnlx E8 . Bab$F~shSewm I'(DZDB671Z-BBBO-lDA6-8DBC-8A445595ABOC)'l function BabelF~sh(const trmslatlonrodo: Wldel end; f m c c x o n CetBabelFlsMortType(UseWSDL- Boolean-Systs
- - .- - -
1
I
--
lil
--An-
--
I
Figura 23.1. El WSDL Import Wizard en accion. unit ~abelFishService; interface uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns; type BabelFishPortType = interface(IInvokab1e) [ ' (D2DB6712-EBEO-lDA6-8DEC-8A445595AEOC)' 1 function BabelFish(const translationmode: WideString; const sourcedata: WideString): WideString; stdcall; end; function GetBabelFishPortType(UseWSDL: Boolean=System.False; Addr: string="; HTTPRIO: THTTPRIO = nil): BabelFishPortType; implementation
Observe que la interfaz hereda de la interfaz Ilnvokable. Esta interfaz no aiiade nada con respecto a 10s metodos de la interfaz base llnterface de Delphi,
sino que se compila con el indicador utilizado para establecer la generacion RTTI, ( $M+}, como la clase T P e r s i s t e n t . En la seccion de inicializacion puede verse tambien que la interfaz se registra en el registro de invocacion global (o I nvReg i s t r y), pasando la referencia de informacion de tipo del tipo de interfaz.
NOTA: Dis-r de info@n pan$ mtmfaccs q m&mpte d avance tecd&gicq &I imparfslnte relacionado-conla i ~ c a c i l i t SOAP. l No es que proyea$h de ~ C l h .PGcal 6 no'sea M p b m e (es v i a paf'd simplificar el prowso) sino que' d$sponex d.e. i n t k m n a d b RTfl para ma interfaz es lo que realmente hace t p e la arquite&ra sea pdente .j;r~pusta. El tercer elemento de la unidad generada por el WDSL Import Wizard es una funcion global que toma su nombre del servicio, introducida en Delphi 7. Esta funcion ayuda a simplificar el codigo utilizado para llamar a1 servicio Web. La funcion G e t B a b e l F i s h P o r t T y p e devuelve una interfaz del tipo apropiado, que puede usarse para lanzar directamente una Ilamada. Por ejemplo, el siguiente codigo traduce una breve frase de ingles a italiano (corno indica el valor de su. primer parametro, e n -i t ) y la muestra en pantalla: ShowMessage w o r l d ! ' )) ;
(GetBabelFishPortType .BabelFish ( 'en-it'
,
'Hello,
Si se presta atencion a1 codigo de la funcion G e t B a b e l F i s h P o r t T y p e , se vera que crea una componente interno de invocacion de la clase THTTPRIO para procesar la Ilamada. Puede colocarse manualmente este componente en el formulario cliente (corno en el programa de ejemplo) para tener mas control sobre sus diversas variables (y controlar sus eventos). Este componente puede configurarse de dos maneras basicas: puede hacerse referencia a1 archivo o a1 URL WSDL, importarlo, y extraerlo a partir del URL de la llamada SOAP; o puede proporcionarse un URL direct0 para la Ilamada. El ejemplo tiene dos componentes que proporcionan 10s enfoques alternatives (que tienen exactamente el mismo efecto): o b j e c t HTTPRIOI: T H T T P R I O WSDLLocation = 'C:\md6code\23\BdbelFish\Bdbe1FishService.~ml' Service = ' B a b e l F i s h ' Port = ' B a b e l F i s h P o r t ' end o b j e c t HTTPRI02: T H T T P R I O U R L = 'http://services. xrnethods. net :80/perl/sodplite. c g i ' end
Llegados a este punto, poco mas hay que hacer ya. Tenemos informacion sobre el servicio que podemos usar para invocarlo y conocemos 10s tipos de 10s parametros requeridos por el unico metodo disponible tal y como se indican en la interfaz.
Los dos elementos se unen extrayendo la interfaz a que queremos llamar directamente desde el componente HTTPRIO, con una expresion como HTTPRIOl a s Babe 1Fi s hPor tTy p e . Puede parecer sorprendente, per0 es increiblemente simple. Esta es la llamada a1 servicio Web realizada por el ejemplo: EditTarget.Text : = (HTTPRIO1 as BabelFishPortType). BabelFish(ComboBoxType.Text, Editsource-Text);
La salida del programa, como muestra la figura 23.2, permite aprender idiomas (aunque, en este caso, el profesor tiene algunas limitaciones, claro). No hemos repetido este mismo ejemplo con opciones de compra, divisas, pronosticos del tiempo y muchos otros servicios disponibles, porque tendrian todos un aspect0 muy parecido.
ADVERTENCIA: Aunpe la interfaz de servicio Web pmporciona 10s tipos de 10s padmetros, en muclios casos se necesitarb ooakuhar.la documentacion red del servicio para saber q d significan reabmitk 103 valores de 10s parbeti.0~y c6mo 10s interpreti el servicio. El setvhio Web de BabeIFish es un ejempio de esto, ya que ha sido necesario &liar documentacion textual paraenmntrar la lista de las tipos de trst&ccion, disponibles en el programa he muestra en un cuadro combinado.
pzq
I~hs1s a sample message lci an aularnabc llanslal~on
len_de
Ideses !st e m Basp~ebnze~ge lu am automat~scheUbwsel-
i
.--.-.__I
Dud
1
Figura 23.2. Un ejemplo de la traducclon de ingles a alernan conseguida con BabelFish de AltaVista rnediante un serviclo W e b .
Creacion de un servicio Web Si llamar a un servicio Web en Delphi es sencillo, lo mismo podriamos decir del desarrollo de un servicio. Si vamos a la pagina Web Services del cuadro de dialog0 New Item, podemos ver la opcion SOAP Server Application. Seleccionandola, Delphi nos presentara una lista que es bastante parecida a la seleccion de una aplicacion WebBroker. De hecho, el servicio Web tipicamente se alberga en un servidor Web empleando una de las tecnologias de extension de servidores (CGI, ISAPI, modulos Apache, etc. ..) disponibles o el Web App Debugger para
las pruebas iniciales. Despues de completar este paso, Delphi aiiadira tres componentes a1 modulo Web resultante, que no es mas que un modulo Web basico sin adiciones especiales: El componente HTTPSoapDispatcher recibe la consulta Web como lo haria cualquier otro repartidor HTTP. El componente HTT PSoapPasca 1Invo ker realiza la operacion inversa a la del componente HTTPRIO: es capaz de traducir consultas SOAP en llamadas para interfaces Pascal (en lugar de convertir las llamadas a metodos de la interfaz en consultas SOAP). El componente WS DLHTMLPub1ish puede usarse para extraer la definicion WSDL del servicio a partir de las interfaces que soporta, y realiza el papel contrario a1 del Web Services Importer Wizard. Tecnicamente, se trata de otro repartidor HTTP.
Un servicio Web de conversion de divisas Una vez preparado este marco (que tambien podemos crear aiiadiendo 10s tres componentes anteriores a un modulo Web ya existente) podemos comenzar a escribir el servicio. Como ejemplo, vamos a transformar el ejemplo de conversion a1 euro del capitulo 3 en un servicio Web llamado Convertservice. Lo primer0 ha sido aiiadir a1 programa una unidad que defina la interfaz del servicio: tn?e IConvert = interface (IInvokable) [ ' {FFlEAA45-0B94-4630-9A18-E768A91A78E2) ' 1 function Convertcurrency (Source, Dest: string; Amount: Double) : Double; s tdcall ; function ToEuro (Source: string; Amount : Double) : Double; s tdcall ; function FromEuro (Dest: string; Amount: Double): Double; stdcall; function TypesList : string; stdcall; end;
Si definimos una interfaz directamente en el codigo, sin necesidad de utilizar una herramienta como el Type Library Editor, conseguimos una gran ventaja, ya que podemos crear facilmente una interfaz para una clase ya existente sin tener que aprender a utilizar una herramienta especifica para ello. Fijese en que le hemos dado un GUID a la interfaz, como es habitual, y que hemos utilizado la convencion de llamada stdcall,ya que el convertidor de SOAP no soporta la convencion de llamada predefinida, reg ister . En la misma unidad que define la interfaz del servicio, deberiamos ademas registrarlo. Esta operacion sera necesaria tanto en la parte del cliente como en la
del servidor, ya que podremos incluir la unidad de definicion de esta interfaz en ambos. uses
Ahora que disponemos de una interfaz que podemos mostrar a1 publico, tenemos que proporcionarle una implementacion. Para ello utilizaremos, una vez mas, el codigo estandar de Delphi (con la ayuda de la clase TInvo kableclass predefinida): type TConvert = c l a s s (TInvokableClass, IConvert) protected f u n c t i o n ConvertCurrency (Source, Dest: string; Amount: Double) : Double; stdcall; f u n c t i o n T o E u r o (Source : string; Amount: Double) : Double; s tdcall; f u n c t i o n F r o m E u r o (Dest: string; Amount: Double) : Double; stdcall; f u n c t i o n TypesList: string; stdcall; end;
La implementacion de estas funciones, que llaman a1 codigo del sistema de conversion a1 euro del capitulo 3, no se cornentan aqui porque tiene poco que ver con el desarrollo del servicio. No obstante, es importante tener en cuenta que esta unidad de implementacion tambien tiene una llamada de registro en su seccion de inicializacion: 1nvRegistry.RegisterInvokableClass
(TConvert);
Publicacion del WSDL A1 registrar la interfaz, se permite que el programa genere una descripcion WSDL. La aplicacion del servicio Web (a partir de la actualization Delphi 6.02) es capaz de mostrar una pagina inicial que describe sus interfaces y 10s detalles de cada interfaz, y devuelve el archivo WSDL. A1 conectar a1 servicio Web mediante un navegador, ser vera algo similar a lo que muestra la figura 23.3.
NOT*: Aunclu6 otr& arcpitccturas de servicios Web proporcionan auto^^^ uu modo de el seretieio%bWd'e el navegador, e m th&aqsuele ~ e £MA, r 9orque utilizar seryiciqs web'kene sentido en una arquitectQraen la qqs is.ieractbeadiSiqt8s aplicadbneg. Si todo lu t p e se necesita es mostrar datos ed pn aahgkdm. dskria creme un 'BitioWeb.
-
ConvertService Service Info Page ConvertService
-
PortTypes:
Iconvert [WSDL] 0
Convertcurrency
0
ToEuro
0
FromEuro
0
TypesLtst
IWSDLPublish [.=I LI-t; ,711 !h* P 0 r t P p ~ 5p ~ ~ b l ~ b ~ yhl eb dl Servlre ~
WSIL:
0
GetPortTypeLirt
0
GetWSDLForPortType
0
GetTypeSystemsList
0
GetXSDForTypeSystem
Lird t > : ' I S - l n s ~ e r t l n n dnwriwnt ot 5eralces
Figura 23.3. La descripcion del servicio Web ConvertService proporcionada por componentes Delphi.
Esta caracteristica autodescriptiva no estaba disponible en 10s servicios Web creados en Delphi 6 (que solo proporcionaba un listado WSDL a bajo nivel), per0 es bastante sencilla de afiadir (o personalizar). Si se analiza el modulo Web SOAP de Delphi 7, ser vera una accion predefinida con un controlador para el evento OnAct i o n que invoca el siguiente comportamiento predefinido: WSDLHTMLPublishl.ServiceInfo(Sender, Handled) ;
Request,
Response,
Esto es todo lo que hay que hacer para proporcionar esta caracteristica a un servicio Web de Delphi ya esistente que carezca de ella. Para conseguir de forma manual una prestacion similar, hay que llamar a1 registro de invocacion (el objeto global I n v R e g i s t r y ) , con llamadas comoGet I n t e r f a c e E x t e r n a l N a m e y G e t M e t h E x t e r n a l N a m e . Lo que es importante es la capacidad del servicio Web de autodocumentarse para cualquier otro programador o herramienta de programacion, presentando el WSDL.
Creacion de un cliente personalizado Veamos la aplicacion cliente que llama a1 servicio. No necesitamos partir del archivo WSDL, dado que ya tenemos la interfaz Delphi. Ni siquiera es necesario que el formulario tenga el componente HTTPPRIO, el cual se crea en el codigo: private Invoker: THTTPRio;
procedure TForml.FormCreate(Sender: TObject); begin Invoker : = THTTPRio.Create(ni1); 1nvoker.URL : = 'http://localhost/scripts/ConvertService.exe/ soap/iconvert ' ; ConvIntf : = Invoker a s IConvert; end;
Como una alternativa al uso del un archivo WSDL, el componente que invoca a SOAP puede asociarse con un URL. Una vez que se ha realizado esta asociacion y la interfaz necesaria se ha extraido del componente, podemos empezar a escribir el codigo Pascal para invocar a1 servicio, como hemos visto anteriormente. Un usuario puede rellenar 10s dos cuadros combinados, llamando a1 metodo TypesList,que devuelve una lista de las monedas disponibles dentro de una cadena (separada por puntos y coma). Extraeremos esta lista sustituyendo cada punto y coma por un caracter de nueva linea y asignando despues directamente la cadena multilinea a 10s elementos combinados: procedure TForml.Button2Click(Sender: TObject); var TypeNames: string; begin TypeNames : = ConvIntf.TypesList; ComboBoxFrom.1tems.Text : = StringReplace (TypeNames, sLineBreak, [rfReplaceAll]) ; ComboBoxTo. Items : = ComboBoxFrom. Items; end :
I ; ' ,
Despues de seleccionar dos divisas, podemos realizar la conversion con este codigo (la figura 23.4 muestra el resultado): procedure TForml.ButtonlClick(Sender: TObject); begin LabelResu1t.Caption : = Format ('tn', [(ConvIntf.ConvertCurrency( ComboBoxFrom.Text, ComboBoxTo-Text, StrToFloat(EditAmount.Text)))]); end;
-[
"z Fill List DEM
I =I
Figura 23.4. El cliente Convertcaller del servicio Web Convertservice rnuestra 10s rnarcos alemanes necesarios para conseguir muchisimas liras italianas, antes de que el euro lo cambiara todo.
POCOS
Peticion de datos de una base de datos Para este ejemplo, hemos creado un servicio Web (basado en el Web App Debugger) capaz de presentar datos sobre 10s empleados de una empresa. Estos datos de proyecto sobre la tabla EMPLOYEE de la base de datos InterBase de muestra que hemos usado a menudo en este libro. La interfaz Delphi del servicio Web se define en la unidad SoapEmployeeIntf de este modo: type ISoapEmployee = interface (IInvokable) ['{77DOD940-23EC-49A5-9630-ADE0751E3DB3)'] function GetEmployeeNames: string; stdcall; function GetEmployeeData (EmpID: string): string; stdcall; end:
El primer metodo devuelve una lista de 10s nombres de todos 10s empleados de la empresa, y el segundo devuelve 10s detalles de un empleado determinado. La implementacion de esta interfaz se proporciona en la unidad SoapEmployeeImpl con la clase siguiente: type TSoapEmployee = class(TInvokableClass, ISoapEmployee ) public function GetEmployeeNames: string; stdcall; function GetEmployeeData (EmpID: string) : string; s tdcall; end;
La implementacion del servicio Web recae en 10s dos metodos anteriores y algunas funciones auxiliares para gestionar 10s datos XML devueltos. Pero antes de llegar a la parte XML del ejemplo, vamos a analizar brevemente la seccion de acceso a la base de datos.
Acceso a los datos Toda la conectividad y el codigo SQL de este ejemplo se guarda en un modulo de datos independiente. Por supuesto, podriamos haber creado dinamicamente algunos componentes de conexion y de conjuntos de datos en 10s metodos, per0 hacerlo seria contrario a1 enfoque de una herramienta de desarrollo visual como Delphi. El modulo de datos tiene la siguiente estructura: object DataModule3: TDataModule3 object SQLConnection: TSQLConnection ConnectionName = 'IBConnection' DriverName = 'Interbase' Loginprompt = False Params .Strings = // omitido end object dsEmplList: TSQLDataSet ComrnandText = 'select EMP-NO, L A S T-NAME, FIRST-NAME EMPLOYEE '
from
SQLConnection = SQLConnection o b j e c t dsEmplListEMP-NO: TStringField o b j e c t dsEmplListLAST-NAME: TStringField o b j e c t dsEmplListFIRST-NAME: TStringField end o b j e c t dsEmpData: TSQLDataSet ComrnandText = 'select * f r o m E M P L O Y E E where E m p N o = : i d f Params = < item DataType = ftFixedChar Name = 'id' ParamType = ptInput end> SQLConnection = SQLConnection end end
Como puede verse, el modulo de datos tiene dos sentencias SQL en sendos componentes SQLDataSet. La primera se utiliza para obtener el nombre e identificador de cada empleado, y la segunda devuelve el conjunto de datos completo para un empleado dado.
Paso de documentos XML El problema esta en como devolver 10s datos a un programa cliente remoto. En este ejemplo, hemos usado un buen enfoque: devolver documentos XML, en lugar de trabajar con las complejas estructuras de datos de SOAP. (No es facil entender que XML pueda verse como un mecanismo de comunicacion para la invocacion mediante SOAP, junto con el mecanismo de transporte que proporciona HTTP, per0 luego no se utilice para 10s datos que se transmiten. Aun asi, muy pocos servicios Web devuelven documentos XML, por lo que puede que otros programadores no lo tengan asi de claro.) En este ejemplo, el metodo GetEmployeeNames crea un documento XML que contiene una lista de empleados, con sus nombres y apellidos como valores y el identificador en la base de datos como atributo, empleando las dos funciones auxiliares MakeXml S tr (descrita en el ultimo capitulo) y MakeXmlAt tr ibut e (que se muestra aqui): f u n c t i o n TSoapEmployee.GetEmployeeNames: string; var dm: TDataModule3; begin d m : = TDataModule3 .Create ( n i l ) ; try dm.dsEmplList.0pen; Result : = '' + sLineBreak; w h i l e n o t dm.dsEmplList.EOF do begin Result : = Result + ' ' + MakeXmlStr ('employee', dm.dsEmp1ListLASTNAME.AsString + ' ' +
dm.dsEmplListFIRSTNME.AsString, MakeXmlAttribute ( 'id', dm.dsEmplListEMPN0.AsString)) + sLineBreak; dm.dsEmplList.Next; end; Result : = Result + ' < / e m p l o y e e L i s t > '; finally dm. Free; end; end;
function MakeXmlAttribute (attrName, attrvalue: string) : string; begin Result : = attrName + ' = " ' + attrvalue + ""; end ;
En lugar de emplear la generacion manual de XML, podriamos haber empleado el XML Mapper o alguna otra tecnologia, per0 hemos preferido crear directamente XML en forma de cadenas. Usaremos el XML Mapper para procesar 10s datos recibidos en el cliente --
- --
-
- -
--
- -.- -
--
-
.
.
NOTA: Puede que se pregunte por qui crea el programa una nueva instancia del mMulo de datos cada vez. La parte negativa de este enfoaue es oue 6:1 programa establece cada vez un;a nueva conexion con I :una operation bastante lenta); perco la pmte positiva es qu -'----=- - - - --I--: - - - el -1 1 mln nenwn relmmnaan con ilnn ilna anlicacihn milltihun -a r -s e eiecil------ ne ---- -r------tan concurrentemente dos peticiones a1 servicio Web, se puede utilizar una conexiirn carnpartida a la base de datos, per0 hay que usar componentes de . . . . .- . conjunto ae aatos cusumos para a acceso a aatos. rwrmws aespmar ms conjuntos be dams en el d&go & la fuudb y mantener &lo la conexi6n en el modulo de datos, o tener un mbdulo de datos cornpartido global para la conexion (usado por varias hebras) y una instancia especifica de un segundo modulo de datos albergado por 10s Gaqjuntos be datos para cada llamada a m&odo. 2
--.d-
..--
---------a
.. .
. . - .,
-J---
. .
Prestemos ahora atencion a1 segundo metodo, Get EmployeeData.Utiliza una consulta parametrica y da formato a 10s campos resultantes en nodos XML independientes (mediante otra funcion auxiliar, FieldsToXml): function TSoapEmployee .GetEmployeeData (EmpID: string) : string; var dm: TDataModule3; begin dm : = TDataModule3 .Create (nil); try dm.dsEmpData.ParamByName('ID') .Asstring : = EmpId;
dm.dsEmpData.Open; Result : = FieldsToXml finally dm. Free; end; end;
(
'employee ', dm. dsEmpData) ;
f u n c t i o n FieldsToXml (rootName: string; data: TDataSet): string; var i: Integer; begin Result : = ' < ' + rootName + ' > ' + sLineBreak;; f o r i : = 0 to data.FieldCount - 1 d o Result : = Result + ' ' + MakeXmlStr ( Lowercase (data.Fields [i] FieldName) , data. Fields [i] .Asstring) + sLineBreak; Result : = Result + ' < / I + rootName + ' > ' + sLineBreak;; end;
.
El programa cliente (con proyeccion XML) El paso final para este ejemplo es escribir un programa cliente de prueba. Podemos hacerlo como siempre, importando el archivo WSDL que define el servicio Web. En este caso, tambien tendremos que convertir 10s datos XML que se reciben en algo mas manejable, en particular la lista de empleados que devuelve el metodo Get Emp 1 o ye eName s . Como ya comentamos, hemos usado el XML Mapper de Delphi para convertir la lista de empleados recibida del servicio Web enun conjunto de datos que pueda mostrarse mediante una DBGrid. Para realizar esto, en primer lugar hemos escrito el codigo para recibir el XML con la lista de empleados y copiarlo en un componente de memo y de ahi, a un archivo. Despues, hemos abierto el XML Mapper, cargado el archivo y generado a partir de el la estructura del paquete de datos y el archivo de transformacion. (Puede encontrarse el archivo de transformacion entre el resto de archivos de codigo fuente del ejemplo SoapEmployee.) Para mostrar 10s datos XML dentro de una DBGrid, el programa utiliza un componente XMLTransformProvider, que hace referencia a1 archivo de transformacion. object XMLTransformProviderl: TXMLTransformProvider TransforrnRead.TransformationFile = 'EmplListToDataPacket. xtr' end
El componente Client D a t aSet no esta conectado a1 proveedor, ya que trataria de abrir el archivo de datos XML especificado por la transformacion. En este caso, 10s datos XML no se encuentran en un archivo, si no que se pasan a1 componente tras llamar a1 servicio Web. Por este motivo, el programa lleva directamente en el codigo 10s datos a1 ClientDataSet. procedure TForml.btnGetListClick(Sender: TObject); var
Con este codigo, el programa puede mostrar la lista de empleados en una DBGrid, como muestra la figura 23.5. Cuando se consiguen 10s datos de un empleado especifico, el programa extrae el identificador del registro activo desde el ClientDataSet y muestra el XML resultante en un campo de memo: procedure TForml.btnGetDetailsClick(Sender: TObject); begin Memo2.Lines.Text : = GetISoapEmployee.GetEmployeeData( end ;
Nelson Robert Young B w e
Jdnson Leslie Forest Ph1 Weslon K. J. Lee Te~ri Hall Stewart Young Katherine Papadopoules Chris Fisher Pete De Sarra Ropr
Figura 23.5. El programa cliente del ejemplo de servicio Web SoapEmployee.
Depuracion de las cabeceras SOAP Una indicacion final sobre este ejemplo esta relacionada con el uso del Web App Debugger para probar aplicaciones SOAP. Por supuesto, puede ejecutarse el programa servidor desde el IDE de Delphi y depurarlo facilmente, per0 tambien pueden vigilarse las cabeceras SOAP que se pasan a traves de la conexion HTTP. Aunque prestar atencion a SOAP desde esta perspectiva de bajo nivel puede ser algo muy poco sencillo, es el mod0 definitive de comprobar si algo va ma1 con una aplicacion SOAP de cliente o servidor. Como ejemplo, en la figura 23.6 puede verse el registro HTTP de una solicitud SOAP del ultimo ejemplo.
El Web App Dcbugger podria no cstar siempre disponiblc, por eso otra tecnica habitual es controlar 10s eventos del componente HTTPRIO, como hace el ejemplo BabelFishDebug. El formulario del programa time dos componentes de memo en 10s que puede verse la peticion SOAP y la respuesta: p r o c e d u r e TForml.HTTPRIO1BeforeExecute(const String; v a r SOAPRequest: W i d e s t r i n g ) ; begin MemoRequest.Text : = SoapRequest; end:
MethodName:
p r o c e d u r e TForml.HTTPRIO1AfterExecute(const MethodName: SOAPResponse: T S t r e a m ) ; begin S0APResponse.Position : = 0; MemoResponse.Lines.LoadFromStream(S0APResponse); end ;
String;
Content Type fext/xrnl User Agent Baland SOAP 1 2 Host Iocalhosl 1024 Contenc Length 508 C o r m c t ~ o nKeep Alwe Cache Control n o a c h e
thd vewon="1.0"?) t SOAP-ENV:Envelopemlns:S~P~ENV="hll~//schemas.mlsoap.ug/w~en~ebpe~~ m l n s xsd="hltp~//www w3.org12001 M L S c h e m a " mlns HSI-'lafp //www w3 org/2001 /XMLSchemamstance" xmlns SO~.ENC="htfp.//schemas.xmlsoap.o~g/soap/enc~n'~cSOAP~ENV:Body SOAP-ENV encodiylStyle="http://a:hemas.Mnlmap org/soap/encod1ng/"~tNS1:GetErnployeeData xmhs NS 1="run SoapEmployeelnlf.ISoapEmployee"~ tEmplD wxtype-"xsd slrmg">ll c/EmplD>c/NSl GelEmployeeDala>t/SOAP.ENV Body)c/SOAP.ENV Envelope,
Figura 23.6. El registro HTTP del Web App Debugger incluye la peticion SOAP a bajo nivel.
Exponer una clase ya existente como un servicio Web Aunque podria desearse desarrollar un servicio Web desde la nada, en algunos casos puede existir codigo que se quiera hacer accesible. Este proceso no es demasiado complejo, dada la arquitectura abierta de Delphi en este campo. Para probarlo, habra que seguir estos pasos:
1. Crear una aplicacion de servicio Web o aiiadir 10s componentes relacionados a un proyecto WebBroker ya existente. 2. Definir una interfaz que herede de llnvokable y aiiadirle 10s metodos que se desean hacer accesibles en el servicio Web (mediante la convencion de llamada s tdcall). Los metodos seran parecidos a 10s de las clase que se quiera hacer accesible. 3. Definir una nueva clase que herede de la clase que se desea exponer e implementar la interfaz. Los metodos se implementaran llamando a 10s metodos correspondientes de la clase base. 4. Escribir una metodo de creacion de un objeto de la clase de implernentacion para cada vez que lo necesite una peticion SOAP.
Este ultimo paso es el mas complejo. Podria definirse una fabrica y registrarla de esta manera: procedure MyObjFactory begin
Sin embargo, este codigo crea un objeto nuevo para cada llamada. Utilizar un unico objeto global seria igual de malo: varios usuarios podrian tratar de usarlo, y si el objeto tiene un estado o sus metodos no son concurrentes, podrian darse problemas. Queda la necesidad de implementar algun tipo de control de sesion, que es una variante del prob!,cma que teniamos con el primer servicio Web que se conectaba a la base de datos.
DataSnap sobre SOAP Ahora que tenemos una idea razonablemente aceptable de como crear un servidor y un cliente SOAP, vamos a ver como utilizar esta tecnologia para crear una aplicacion DataSnap multicapa. Usaremos un Soap Server Data Module para crear el nuevo servicio Web y el componente Soapconnection para conectarle una aplicacion cliente.
Creacion del servidor SOAP DataSnap Vamos a ver en primer lugar el caso del servidor. Accederemos a la pagina Web Services del cuadro de dialogo New Items y usaremos el icono Soap Server Application para crear un nuevo servicio Web, y despues el icono Soap
Server Data Module para aiiadir un modulo de datos de servidor DataSnap a1 servidor SOAP. Hemos hecho esto en el ejemplo SoapDataServer7 (que usa la arquitectura Web App Debugger para poder hacer pruebas). A partir de aqui, todo lo que hay que hacer es escribir un servidor DataSnap normal (o una aplicacion DataSnap de capa intermedia), como se comento en el capitulo 16. En este caso, hemos aiiadido el acceso a InterBase a1 programa mediante dbExpress, con lo que tenemos la siguiente estructura: o b j e c t SoapTestDm: TSoapTestDm object SQLConnectionl: TSQLConnection ConnectionName = ' I B L o c d l ' end object SQLDataSetl: TSQLDataSet SQLConnection = SQLConnectionl CommandText = 'select * f r o m EMPLOYEE' end o b j e c t DataSetProviderl: TDataSetProvider DataSet = SQLDataSetl end end
El modulo de datos creado para un servidor DataSnap basado en SOAP define una interfaz personalizada (para que se le puedan aiiadir metodos) que hereda de IAppServerSOAP, que se define en una interfaz publicada (incluso aunque no herede de Ilnvokable).
para exponer'datos media& SOAP. ~ e l i h 7i ha py$t&do a ese ~ & r predefmido mediante la interfaz beredada lAppSe;iu&fJ~I?. ,que es fbncionalmente identica pero permite que el eistam pueda determinar el tipo de llamada atendiendo a1 nombre de la interfaz. En breve, veremos c6mo llamar a una aplicaci6n antigua desde un cliente creado con Delphi 7, ya que este proceso no es autondtticol . La clase de implementation, TSoapTes t Dm, es el modulo de datos, como en otros servidores DataSnap. Este es el codigo Delphi generado, con el aiiadido del metodo personalizado: type ISampleDataModule = interface(IAppServerS0AP) [ ' {D47A293F-4024-4690-9915-8A68CB273D39) '1 f u n c t i o n GetRecordCount: Integer; stdcall; end; TSampleDataModule = class(TSoapDataModule, ISampleDataModule, IAppServerSOAP, IAppServer)
DataSetProviderl: TDataSetProvider; SQLConnectionl: TSQLConnection; SQLDataSetl: TSQLDataSet; public f u n c t i o n GetRecordCount: Integer; stdcall; end;
El TSoapDataModule base no hereda de T I n v o k a b l e C l a s s . No se trata de un problema siempre que se proporcione un procedimiento de creacion adicional para crear el objeto (que es lo que hace automaticamente la T I n v o k a b l e C l a s s ) y se aiiada el codigo de registro (como ya se comento anteriormente): p r o c e d u r e TSampleDataModuleCreateInstance(out begin obj : = TSampleDataModule .Create (nil); end ;
La aplicacion servidor tambien publica las interfaces IAppServerSOAP e IAppServer, gracias al codigo (breve) de la unidad SOAPMidas. Como comparacion, puede encontrarse el servidor DataSnap de SOAP creado con Delphi 6 en la carpeta SoapDataServer. El ejemplo sigue pudiendose compilar en Delphi 7, y funciona bien, per0 deberian escribirse 10s nuevos programas, con la estructura del nuevo; en la carpeta SoapDataServer7 se encuentra un ejemplo. -.
- -
-
-
-
- - -- -
TRUCO: Las aplicaciones de servicios Web en Delphi 7 pueden incluir mas de un m6dulo de datos SOAP. Para identificar un m6dulo de datos SOAP especifico, utilizaremos la propiedad SOAPServerIID del componente SoapConnection o afiadiremos el nombre de la hterfaz del m6dulo de datos a1 final del URL. El servidor tiene un metodo personalizado (la version para Delphi 6 del programa tambien tenia uno, per0 jamas funciono) que utiliza una consulta con la sentencia SQL s e l e c t count ( * ) f r o m EMPLOYEE. f u n c t i o n TSampleDataModu1e.GetRecordCount: Integer; begin / / l e e e l r e c u e n t o d e r e g i s t r o s m e d i a n t e una consulta SQLDataSet2.0pen; Result : = SQLDataSetZ.Fields[O].AsInteger; SQLDataSet2.Close; end ;
Creacion del cliente SOAP DataSnap Para crear la aplicacion cliente, llamada SoapDataClient7, comenzamos a partir de un programa sencillo y le aiiadimos un componente Soapconnect ion (desde la pagina Web Services de la paleta), conectandolo a1 URL del servicio Web DataSnap y haciendo referencia a la interfaz especifica que queriamos usar: object SoapConnectionl: TSoapConnection URL = ' h t t p : / / l o c a l h o s t :1024/SoapDa t a S e r v e r 7 . s o a p d a t a s e r v e r / ' + ' s o a p / I s a m p l e d a tarnodule ' SOAPServerIID = ' I A p p S e r v e r S O A P - [ C 9 9 F 4 7 3 5 - 0 6 0 2 - 4 9 5 C - 8 C A 2 E53E5A439E61) ' UseSOAPAdapter = False end
Fijese en la ultima propiedad, Use SOAPAdap t er, que indica que trabajamos contra un servidor creado con Delphi 7. Como comparacion, el ejemplo SoapDataClient (de Delphi 6), que utiliza un servidor creado con Delphi 6 y se ha vuelto a compilar con Delphi 7, debe tener establecida esta propiedad como True. Este valor obliga a1 programa a usar la interfaz IAppServer simple en lugar de la nueva interfaz IAppServerSOAP. Desde aqui, todo es como siempre: aiiadir un componente Cl ient Dat aSe t, un Datasource y una DBGrid a1 programa, escoger el unico proveedor disponible para el conjunto de datos cliente y conectar el resto. No es sorprendente que para este ejemplo tan simple la aplicacion cliente tenga poco codigo personalizado: una unica llamada para abrir la conexion cuando se hace clic sobre un boton (para evitar errores de arranque) y una llamada Appl yUpdat e s para enviar 10s cambios de vuelta a la base de datos.
SOAP frente a otras conexion con DataSnap A pesar del aparente parecido de este programa con el resto de 10s programas de cliente y sewidor DataSnap creados en el capitulo 16, existe una diferencia muy importante que hay que resaltar: 10s programas SoapDataServer y SoapDataCliente no utilizan COM para exponer o llamar a la interfaz IApp Server SOAP.Mas bien a1 contrario, las conexiones basadas en sockets y HTTP de DataSnap siguen basandose en objetos COM locales y en el registro del sewidor en el Registro de Windows. Sin embargo, el soporte basado en SOAP nativo permite una solucion completamente personalizada que es independiente de COM y que ofrece muchas mas posibilidades de adaptarse a otros sistemas operativos. (Se puede volver a compilar este sewidor en Kylix, per0 no es asi para 10s programas del capitulo 16.) El programa cliente tambien puede llamar a1 metodo personalizado que hemos aiiadido a1 servidor para devolver el recuento de registros. Este metodo podria usarse en una aplicacion real para mostrar solo un numero limitado de registros
per0 informar a1 usuario del numero de registros que aun no se han descargado desde el servidor. El codigo del cliente para llamar a1 metodo se basa en un componente HTTPRIO adicional: p r o c e d u r e TFormSDC.Button3Click(Sender: TObject); var SoapData: ISampleDataModule; begin SoapData : = HttpRiol a s ISampleDataModule; ShowMessage (IntToStr (SoapData.GetRecordCount)); end;
Manejo de adjuntos Una de las caracteristicas mas importantes que Borland ha afiadido a Delphi 7 es el completo soporte de adjuntos SOAP. Los adjuntos en SOAP permiten enviar datos que no Sean texto XML, como archivos binarios o imagenes. En Delphi, 10s adjuntos se gestionan a traves de flujos. Se puede leer o indicar el tip0 de codification del adjunto, per0 la transformacion de un flujo bruto de bytes hacia y desde una codificacion dada depende del codigo. Aun asi, este proceso no es demasiado complejo, si se tiene en cuenta que Indy incluye unos cuantos componentes de codificacion. Como ejemplo del uso de adjuntos, hemos escrito un programa que reenvia el contenido binario de un ClientDataSet (que tambien alberga imagenes) o una sola de las imagenes. El servidor tiene esta interfaz: tn?e ISoapFish = i n t e r f a c e ( IInvokable) [ ' {4E4C57BF-4AC9-41C2-BB2A-64BCE4 7OD4SO} ' 1 f u n c t i o n GetCds: TSoapAttachment; stdcall; f u n c t i o n GetImage(fishName: s t r i n g ) : TSoapAttachment; s tdcall; end;
La implernentacion del metodo G e t C d s usa un ClientDataSet que hace referencia a la clasica tabla BIOLIFE, crea un flujo en memoria, copia en el 10s datos, y despues adjunta el flujo a1 resultado T S o a p A t t a c h m e n t : function TSoapFish.GetCds: TSoapAttachment; stdcall; var memStr: TMemoryStream; begin Result : = TSoapAttachment-Create; memStr : = TMemoryStream-Create; WebModule2.cdsFish.SaveToStream(MemStr); // binary Result.SetSourceStream (memStr, soReference); end;
En el cliente, hemos preparado un formulario con un componente Client Dataset conectado a una DBGrid y una DBImage. Todo lo que hay
que hacer es conseguir el adjunto SOAP, guardarlo en un flujo temporal en memoria y despues copiar 10s datos desde el flujo de memoria al ClientDataSet local. p r o c e d u r e TForml.btnGetCdsClick(Sender: TObject); var sAtt: TSoapAttachment; memStr: TMemoryStream; begin nRead : = 0; sAtt : = (HttpRiol a s ISoapFish) .GetCds; try memStr : = TMemoryStream.Create; tr Y sAtt SaveToStream(memStr) ; memStr.Position : = 0; ClientDataSetl.LoadFromStream(MemStr); finally memStr. Free; end; finally DeleteFile (sAtt .CacheFile) ; sAtt.Free; end; end ;
.
ADVERTENCIA: De manera predeterminada, 10s adjuntos de SOAP recibidos por un cliente se guardan en un archivo temporal, a1 que hace referencia la propiedad CacheFile del objeto TSOAPAttachment. Si no se borra este archivo, permanecera en una carpeta que albergue archims temporales. Este codigo produce el mismo efecto visual que una aplicacion cliente que cargue un archivo local en un ClientDataSet, como muestra la figura 23.7. En este cliente SOAP hemos usado un componente HTTPRIO de manera explicita para poder inspeccionar 10s datos entrantes (que posiblemente seran muy grandes y lentos). Por este motivo, hemos puesto a cero una variable nRead global antes de invocar a1 metodo remoto. En el evento OnReceivingData de la propiedad HTTPWebNode del objeto HTTPRIO, aiiadiremos 10s datos recibidos a la variable nRead. Los parametros Read y T o t a l que se pasan a1 evento se refieren a1 bloque de datos especifico que se envia a traves de un socket, por lo que resultan casi inutiles por si solos para inspeccionar el progreso: p r o c e d u r e TForml.HTTPRIO1HTTPWebNodelReceivingData( Read, Total: Integer); begin Inc (nRead, Read) ;
StatusBarl.SimpleText : = IntToStr ( n R e a d ) ; App1ication.ProcessMessages; end ;
Clown Triggerf'ish Red Emperor G~anlM m r ~Wrasse Blue Angellish Lunartail Rockead Fnefish
Figura 23.7. El ejemplo Fishclient recibe un ClientDataSet binario dentro de un adjunto de SOAP.
Soporte de UDDI La gran popularidad de XML y SOAP abre nuevas vias para que las aplicaciones de comunicacion B2B interacthen. XML y SOAP proporcionan una base, per0 no bastan (la estandarizacion en 10s formatos XML, en el proceso de comunicacion y en la disponibilidad de la informacion sobre un negocio son todos ellos elementos claves de una solucion real). Entre 10s estandares propuestos para superar esta situacion 10s mas notables son Universal Description, Discovery, and Integration (UDDI, www.uddi.org) y Electronic Business using extensible Markup Language (ebXML, www.ebxml.org). Estas dos soluciones se solapan y difieren en parte y ahora se trabaja con mas empeiio en ellas por parte del consorcio OASIS (Organizationjor the Advancement of Structured Information Standards, www.oasis-open.org). No vamos a entrar en 10s problemas de 10s procesos de negocio; en lugar de eso, solo vamos a comentar 10s elementos tecnicos de UDDI, ya que, de manera especial, Delphi 7 soporta este estandar.
~ Q u es e UDDI? La especificacion Universal Description, Discovery, and Integration (UDDI) es un esfuerzo para crear un catalog0 de servicios Web ofrecidos por empresas de
todo el mundo. El objetivo de esta iniciativa es crear un marco de trabajo abierto, global e independiente de plataformas para permitir que las entidades de negocio se encuentren entre si, definir como interactuan con la red Internet y compartir un registro de negocio global. Por supuesto, la idea es acelerar la adopcion del comercio electronico, en forma de aplicaciones B2B. Basicamente, UDDI es un registro de negocios global. Las empresas pueden registrarse en el sistema, describir su organizacion y 10s servicios Web que ofrecen (en UDDI, el termino "servicios Web" se usa en un sentido muy amplio, incluyendo direcciones de correo electronico y sitios Web). La informacion del registro de UDDI para cada empresa se divide en tres campos:
Paginas blancas: Incluyen informacion de contacto, direcciones y cosas similares. Paginas amarillas: Registran la compaiiia en una o mas taxonomias, incluyendo categorias industriales, productos vendidos por la empresa, information geografica y otras taxonomias (posiblemente personalizables). Paginas verdes: Proporcionan la lista de 10s sewicios Web ofrecidos por la empresa. Cada servicio se lista bajo un tip0 de servicio (llamado un tModel), que puede estar predefinido o un tip0 descrito especificamente por la empresa (por ejemplo en terminos de WSDL). Tecnicamente, el registro UDDI deberia verse como un DNS actual, y deberia tener una naturaleza distribuida similar: varios servidores, reflejados y con almacenamiento de datos para acelerar 10s procesos. Los clientes pueden guardar en cache datos siguiendo unas reglas determinadas. UDDI define modelos de datos especificos para una entidad de negocio, un sewicio de negocio y una plantilla de enlace. El tipo BusinessEntity incluye informacion esencial sobre el negocio, como su nombre, la categoria a la que pertenece informacion de contacto. Soporta las taxonomias de las paginas amarillas, con informacion industrial, tipos de producto y detalles geograficos. El tipo Businessservice incluye descripciones de 10s servicios Web (usados por las paginas verdes). El tip0 principal es solo un contenedor para 10s servicios relacionados. Los s e ~ i c i o pueden s estar ligados a una taxonomia (zona geografica, producto, etc.. .). Toda estructura BusinessService incluye una o mas BindingTemplates (la referencia a1 s e ~ i c i o )La . BindingTemplate tiene un tModel. El tModel incluye informacion sobre formatos, protocolos y seguridad, y referencias a especificaciones tecnicas (probablemente mediante el formato WSDL). Si varias empresas comparten un tModel, un programa puede interactuar con todas ellas con el mismo codigo. Un programa de negocio determinado, por ejemplo, puede ofrecer un tModel para que otros programas interactuen con el, sin importar la empresa que haya adoptado el software. La API de UDDI se basa en SOAP. Mediante SOAP, se pueden registrar datos y consultar un registro. Microsoft tambien ofrece un SDK basado en COM, e
IBM tiene un conjunto de herramientas Java Open Source para UDDI. Las API de UDDi incluyen consultas ( f i n d x x y g e t x x ) y publicaciones ( s a v e x x y d e l e t e x x ) para cada una d e las cuatro estructuras de datos principales ( b u s i n e s s ~ n t i tb~u ,s i n e s s s e r v i c e , b i n d i n g T e r n p l a t e y t M o d e l ) .
UDDI en Delphi 7 Delphi 7 incluye un navegador UDDI que puede usarse para encontrar un servicio Web cuando se importa un archivo WSDL. El navegador UDDI, que muestra la figura 23.8, lo activa el WSDL Import Wizard. Este navegador solo utiliza la version 1 de servidores UDDI (hay disponible una interfaz mas nueva, pero no esta soportada) y tiene unos cuantos registros UDDI predefinidos). Se pueden aiiadir configuraciones predefinidas en el archivo UDDIBrow.ini que se encuentra en la carpeta bin de Delphi. Este es un mod0 muy practico de acceder a informacion sobre servicios Web, pero no es todo lo que permite Delphi. Aunque el UDDI Browser no este disponible como una aplicacion independiente, las unidades de interfaz UDDI estan disponibles (y no es trivial importarlas). Por ello, se puede escribir un navegador UDDI propio.
Figura 23.8. El navegador UDDI incorporado al ID€ de Delphi.
Vamos a bosquejar una solucion sencilla, que es un buen punto de partida para un navegador UDDI mas complete. El ejemplo UddiInquiry, que se muestra en la figura 23.9, tiene un gran numero de caracteristicas, pero no todas ellas funcionara correctamente (en particular las caracteristicas de busqueda por categoria). El
motivo es que usar UDDI implica recorrer estructuras de datos muy complejas, que no siempre se proyectan del mod0 mas obvio mediante el importador WSDL. Esto complica bastante el codigo del ejemplo; por eso solo vamos a mostrar el codigo de una busqueda sencilla, y no todo (otro motivo es que algunos lectores pueden no tener particular interes por UDDI).
Search by Name
Search t o r lmicro
] Description Micro C, Inc. Micro Focus Micro Focus Micro lntormalica LLC MICRO MACHINES Micro Motion Inc. MicroApplications. Inc. rnrcrobizl
We provide systems inte... Welcome to the future of ... Welcome to the future of ... This is a UDDI Business ... Plant and Machinery for ... Micro Motion manufactur ... informalion syslems dev... desc
Figura 23.9. El ejemplo Uddilnquiry presenta un limitado navegador UDDI.
Cuando arranca el programa, enlaza el componente HTTPRIO que alberga con la interfaz Inquiresoap de UDDI, definida en la unidad inquiry-vl que proporciona Delphi 7: procedure TForml.FormCreate(Sender: TObject); begin httpriol-Url : = comboRegistry.Text; inftInquire := httpriol as Inquiresoap; end;
A1 hacer clic sobre el boton Search, el programa llama a la funcion find business de la API de UDDI. Ya que la mayoria de las funciones UDDI-necesitan muchos parhmetros, se ha importado empleando un 6nico parametro basado en un registro de tipo FindBusiness. Devuelve un objeto businessList2:
procedure TForml.btnSearchClick(Sender: var findBusinessData: Findbusiness; businessListData: businessList2; begin httpriol.Ur1 : = cornboRegistry.Text;
TObject);
businessListData : = inftInquire.find-business(findBusindBusinessData); BusinessListToListView (businessListData); findBusinessData.Free; end ;
El objeto bus ines sList 2 es una lista que se procesa en el metodo bus ines sListToListView del formulario principal del programa, mostrando 10s detalles mas importantes en un componente de vista de lista: procedure TForml.businessListToListView(businessList: businessList2); var i: Integer; begin ListViewl.Clear; for i : = 0 to businessList.businessInfos.Len do begin with ListViewl.Items.Add do begin Caption : = businesslist. businessInfos [i] .name; SubItems.Add (businessList.business1nfos [i].description); SubItems.Add (businessList.business1nfos [i].businessKey); end ; end ; end ;
A1 hacer doble clic sobre el elemento de vista de lista, se pueden explorar aun mas sus detalles, aunque el programa muestra la informacion XML resultante en un formato de texto plano (o en una vista XML basada en TWebBrowser) y no lo procesa mas. Como se ha mencionado, no queremos entrar en detalles tecnicos; si se siente interes, se puede analizar con mas detalle el codigo hente.
Parte V
Apendices
Herramientas Delphi del autor
Durante 10s ultimos aiios el autor de este libro ha desarrollado algunos pequeiios componentes y herramientas complementarias de Delphi. Algunas de estas herramientas fueron creadas para libros o como resultado de la ampliacion de ejemplos de libros. Otras fueron escritas como ayuda para tareas repetitivas. Todas estas herramientas estan disponibles gratuitamente y algunas incluyen el codigo fuente. Este aphdice proporciona una lista, incluyendo especialmente las mencionadas en este libro. En el for0 de discusion del autor se ofrece soporte para todas estas herramientas (vease www.marcocantu.com para obtener las direcciones).
CanTools Wizards Este es un conjunto de asistentes que podemos instalar en Delphi, en un menu desplegable extra o como un submenu del menu Tools. Los asistentes (disponibles gratuitamente en www.marcocantu.com/cantoolsw)no estan relacionados entre si y tienen caracteristicas diferentes:
List Template Wizard: Racionaliza el desarrollo de clases similares basadas en listas, cada una con su propio tip0 de objetos. Este asistente se
menciona en el capitulo 4. Como realiza una operacion de busqueda y reemplazo en un archivo fuente base puede usarse siempre que necesitemos codigo repetido y el nombre de la clase (u otra entidad) cambie. O O P Form Wizard: Mencionado en el capitulo 4. Permite ocultar 10s componentes publicados de un formulario, haciendo el formulario m b orientad0 a objetos y ofreciendo un mejor mecanismo de encapsulacion. Debemos ejecutarlo cuando un formulario este activo y rellenara el controlador de eventos O n c r e a t e . Despues, tendremos que mover manualmente parte del codigo a la seccion de inicializacion de la unidad. Object Inspector Font Wizard: Permite cambiar el tip0 de letra del Object lnspector (algo especialmente util para presentaciones, ya que el tipo de letra del Object lnspector es demasiado pequeiio para mostrarse con facilidad en una pantalla de proyeccion). Otra opcion permite modificar una caracteristica interna del Object lnspector y mostrar 10s nombres de 10s tipos de letra (en la lista combinada desplegable de esa propiedad) usando un tipo de letra especifico. Rebuild Wizard: Permite reconstruir todos 10s proyectos de una subcarpeta determinada desputs de cargar cada uno de ellos secuencialmente en el IDE. Podemos usar este asistente para coger una serie de proyectos (como 10s de este libro) y abrir el que nos interesa haciendo clic en la lista:
I
sbookshd7codeU4RunPropR~~1Prop dpr e bmkshd7codeU4\ICompess~Compress
I
Tambien podemos compilar automaticamente un proyecto concreto o comenzar una (lenta) creacion de multiples proyectos: en el cuadro de resultados del compilador, haremos clic sobre un boton para proceder solo si la opcion del entorno correspondiente esta fijada. Si esta opcion del entorno no esta fijada, no veremos 10s errores del compilador, porque 10s mensajes del compilador son reemplazados en cada cornpilacion. Clip History Viewer: Mantiene una lista de elementos de texto que hemos copiado a1 Portapapeles. Un campo de la ventana del visor muestra las ultimas 100 lineas copiadas. Editar ese campo (y hacer clic sobre S a v e ) modifica este historic0 del Portapapeles. Si mantenemos abierto Delphi, el Portapapeles recogera texto de otros programas (pero solo texto, por su-
puesto). En ocasiones ocurren errores relacionados con el Portapapeles provocados por este asistente.
VCL Hierarchy Wizard: Muestra la jerarquia (casi) completa de VCL, incluyendo componentes de terceros que hayamos instalado, y permite buscar una clase y ver multiples detalles (clases basicas y subclases, propiedades publicadas, etc.). Hacer clic en el boton regenera tanto la lista como el arb01 (secuencialmente, por lo que la barra de progreso se muestra dos veces):
La lista de clases se genera usando un nucleo de clases predefinido (faltan algunas por lo que se aceptan sugerencias) y aiiadiendo cada componente de 10s paquetes instalados (10s de Delphi, 10s nuestros y 10s de terceros) junto con las clases de todas las propiedades publicadas que son de tip0 clase. A pesar de todo, las clases usadas solo como propiedades publicadas no se incluyen. Extended Database Forms Wizard: Hace muchas mas cosas que el D a t a b a s e Forms Wizard disponible en el IDE de Delphi, permitiendonos elegir 10s campos que queremos colocar en un formulario y usar conjuntos de datos diferentes de 10s basados en BDE. Multiline Palette Manager: Permite convertir l a c o m p o n e n t Palette de Delphi en un control de fichas con multiples lineas de fichas:
Programa de conversion VclToClx Podemos usar esta herramienta independiente para convertir proyectos de Delphi de VCL a CLX (y viceversa, si lo configuramos para ello). Puede convertir simultaneamente todos 10s archivos de una carpeta y sus subcarpetas. Este es un ejemplo de su resultado:
E !bwks\rnd7code\08\VI1\VI1dpr E-\books\md7code\OB\Pol1Fo1m\PoliForrn dp E:\books\rnd7code\OBF1arnes2\F1arnes2 dp
Do RepbDsI ROld QForms
--
.
-1%
-
-
-_-_I
Fums
El codigo fuente del programa esta disponible en la c a r p e t a T o o l s del codigo del libro. El programa VclToClx convierte nombres de unidades (basandose en un fichero de configuracion) y manipula 10s DFM renombrando 10s archivos DFM a XFM y corrigiendo las referencias en el codigo fuente. El programa no es sofisticado, no analiza el codigo pero busca apariciones del nombre de la unidad seguidos de una coma o un punto y coma, como ocurre en una sentencia u s e s . Tambien requiere que el nombre de la unidad vaya precedido por un espacio, pero podemos modificar el programa para que busque una coma. No debemos saltarnos esta comprobacion, jo la unidad Forms se convertira en QForms per0 la unidad QForms se reconvertira en QQForms!
Object Debugger En tiempo de diseiio, podemos usar el Object Inspector para fijar las propiedades de 10s componentes de nuestros formularios y otros modulos. En Delphi 4,
Borland present6 un Debug Inspector de tiempo de ejecucion, que tiene una interfaz similar y muestra informacion parecida. Antes de que Borland aiiadiera esta caracteristica, el autor implement6 una copia del Object Inspector de tiempo de ejecucion pensado para depurar programas:
DlapMode Enabbd
M d TIW
EutsmkdSckcl
Trw
+Font
[ O W 01343DF8)
Charsc(
Cdn He* Nams Pilch S k
SM
-
1 &ridowTed
,;,.
.
,
l
T m t Nau Roman
IpDeld 0
n
-.
Este programa ofrece acceso de lectura y escritura a todas las propiedades publicadas de un componente, y tiene dos cuadros combinados que permiten seleccionar un formulario y un componente dentro del formulario. Algunos de 10s tipos de propiedad tienen editores de propiedades a medida (listas y similares). Podemos situar el componente Ob je c t D e b b u g e r en el formulario principal de un programa (o podemos crearlo dinamicamente en el codigo): aparecera en su propia ventana. Puede mejorarse, per0 incluso en su forma actual esta herramienta es practica y tienen muchos usuarios. El codigo fuente de este componente esta disponible en la carpeta ~ o o l del s codigo del libro.
Memory Snap Existen multiples herramientas para analizar el estado de la memoria de una aplicacion Delphi. Este es un gestor de memoria personalizado que se conecta con el gestor de memoria por defecto de Delphi, analizando todas las asignaciones y liberaciones de memoria. Ademas de informar del numero total (algo que ahora Delphi hace por defecto), puede guardar una descripcion detallada del estado de la memoria en un archivo. Memory Snap mantiene en memoria una lista de bloques asignados (hasta una cantidad maxima, facilmente modificable), de mod0 que puede volcar
el contenido de la pila a un fichero con una perspectiva de bajo nivel. Esta lista se genera examinando cada bloque de memoria y determinando su naturaleza con tecnicas empiricas que podemos ver en el codigo fuente (aunque no son faciles de comprender). La salida se guarda en un archivo, porque esta es la unica actividad que no requiere una asignacion de memoria que pueda afectar a 10s resultados. Este es un fragment0 de un fichero de ejemplo: 00C035CC: 00C035EO: 00C03730: 00C03744: 00C03968: OOCO3990: 00C039B4: 00C039F4: OOCO3B34: OOCO3B48: 00C03B58:
El programa puede ampliarse para que analice el uso de la memoria por tipos (cadenas, objetos, otros bloques), vigile 10s bloques no liberados y mantenga las asignaciones de memoria bajo control. El codigo fuente de este componente tambien esta disponible gratuitamente en la carpeta Tools del codigo del libro.
Licencias y contribuciones Como hemos dicho, algunas de estas herramientas estan disponibles con su codigo fuente completo. Estan protegidas bajo licencia LGPL (Lesser General Public License, www.gnu.org/copyleft/lesser.htm),lo que significa que pueden ser usadas gratuitamente y redistribuidas de cualquier modo, incluyendo modificaciones, mientras el autor retiene el copyright. La LGPL no permite cerrar el codigo fuente de nuestras extensiones, per0 podemos usar este codigo de biblioteca en programas comerciales, independientemente de la disponibilidad del codigo fuente. En caso de ampliar estas herramientas corrigiendo errores o aiiadiendo nuevas caracteristicas, el autor solicita que se le envien las actualizaciones de mod0 que pueda distribuirlas y evitar multiplicar el codigo en diferentes versiones, aunque la licencia no nos obliga a ello.
Contenido del CD-ROM
Este libro se basa en ejemplos. Tras la presentacion de cada concept0 o componente Delphi, encontrara un programa de ejemplo (a veces mas de uno) que demuestra como se puede usar dicha caracteristica. En total, en el libro se presentan mas de 300 ejemplos. La mayoria de 10s ejemplos son bastante sencillos y se centran en una unica caracteristica. Los ejemplos mas complejos se elaboran normalmente paso a paso, con pasos intermedios como soluciones parciales y mejoras.
le la base de datos de ejemplo; fonnamt parte da de Delphi. &Enotros casosi es &esa@'la WSLOIJ ~lt:t;jernpw I I I L G ~ ~ S B EMPLOYEE.[ y tambih el sewidor & DWG merflase, pba sqguaeao). -
<
Tambien hay una version HTML del codigo fuente, en la que la sintaxis aparece resaltada, junto con un indice completo de las palabras claves y 10s identificadores (clase, funcion, metodo y nombres de propiedades, entre otros). El archivo del indice es un archivo HTML, por lo que podra utilizar su explorador facilmente para encontrar todos 10s programas que usen la palabra clave o el
identificador Delphi que este buscando (no es un completo motor de busqueda per0 se le acerca mucho). La estructura del directorio del codigo de ejemplo es bastante simple. Basicamente, cada capitulo del libro posee su propia carpeta y una subcarpeta para cada ejemplo (ej: 0 3 \ F i l e L i s t).En el texto, se hace referencia a 10s ejemplos solo por su nombre (ej: FileList).
1
chivo Readrne.de 10s archlvos de c M g q r u , ~ que , , conuene Importanre informacion sobre el uso legal y efectivg sdware.
En la carpeta Delphi7 del CD-ROM encontrara la version de prueba de Delphi 7, la edicion superior limitada en el tiempo. Para poder instalar esta version de Delphi 7, que le permitira seguir sin problemas todos 10s ejemplos descritos en el libro, es necesario que efectue una operacion de registro para obtener el numero de serie y la clave de activacion. No tiene mas que seguir las instrucciones de la utilidad de instalacion para completar el proceso de registro y activar su Delphi 7. Para ello necesitara disponer de una conexion a Internet y una cuenta de correo electronico. En el proceso tendra que crear una cuenta de registro en la pagina Web de Borland, responder a algunas preguntas y, finalmente, obtendra por correo electronico el numero de serie y la clave de activacion. Tambien encontrara en la carpeta PDF, anexos en 10s que le explican entre otras cosas, algunas de las tecnologias que conforman la iniciativa .NET, 10s cambios especificos que se realizaron en el lenguaje Delphi para hacerlo compatible con el Common Language Runtime, etc.