DELPHI 5

302
Delphi 5 básico Lic. Jack Díaz Iglesias. Lic. Franklin Pérez González.

description

Curso Delphi 5

Transcript of DELPHI 5

Page 1: DELPHI 5

Delphi 5 básico Lic. Jack Díaz Iglesias. Lic. Franklin Pérez González.

Page 2: DELPHI 5

Introducción................................................... 9 1.1. Introducción ........................................... 10 1.2. Consideraciones previas .................................. 10 1.2.1. Versiones de Delphi 5 .............................. 10 1.2.2. Requerimientos de Delphi 5 ......................... 11

1.3. Instalación de Delphi 5 .................................. 11 2. El entorno................................................. 15 2.1. Introducción ............................................ 15 2.2. Elementos iniciales ...................................... 15 2.2.1. Ventana principal .................................. 15 2.2.2. La Paleta de componentes ........................... 17 2.2.3. El formulario ...................................... 19 2.2.4. El Inspector de objetos ............................ 19 2.2.5. El menú principal .................................. 20

2.3. El Depósito de objetos ................................... 20 2.4. Obtener ayuda ........................................... 21 2.5. Creación de la interfaz de un programa .................... 21 2.5.1. La ventana del programa ............................ 22 2.5.2. La rejilla de puntos ............................... 23 2.5.3. Inserción de componentes ........................... 24 2.5.4. Manipulación de los componentes .................... 25 2.5.5. Modificación de propiedades ........................ 26 2.5.6. Uso de los eventos ................................. 27 2.5.7. Edición de código .................................. 28 2.5.8. Ejecución de un programa ........................... 31

3. Proyectos.................................................. 32 3.1. Introducción ............................................ 32 3.2. Gestión del proyecto ..................................... 32 3.2.1. El proyecto por defecto ............................ 32 3.2.2. Uso del Gestor de proyectos ........................ 33 3.2.3. Proyectos predefinidos ............................. 35

3.3. El archivo de proyecto ................................... 36 3.4. Archivos de formulario ................................... 37 3.5. Módulos de código ........................................ 38 3.6. Grupos de proyectos ...................................... 38 3.7. Otros elementos de un proyecto ............................ 39

4. Fundamentos de Object Pascal............................... 40 4.1. Introducción ............................................ 40 4.2. Estructura general ....................................... 40 4.2.1. El punto y el punto y coma ......................... 41 4.2.2. Módulos y la cláusula Uses ......................... 41 4.3.2. Tipos .............................................. 42 4.3.3. Declaración de variables ........................... 44 4.3.4. Matrices ........................................... 44 4.3.5. Definir nuevos tipos ............................... 46 4.3.6. Constantes y literales ............................. 51

Page 3: DELPHI 5

4.4. Ámbito de los identificadores ............................. 53 4.4.1. Identificadores locales ............................ 53 4.4.2. Identificadores globales ........................... 54 4.4.3. Problemas de accesibilidad ......................... 55

4.5. Expresiones ............................................. 56 4.5.1. Operadores aritméticos ............................. 56 4.5.2. Operadores relaciónales ............................ 57

4.5.3. Operadores lógicos ................................... 58 4.5.4. Otros operadores ................................... 59 4.5.5. Orden de prioridad ................................. 63

4.6. Estructuras de control ................................... 64 4.6.1. Condicionales ...................................... 64 4.6.2. Bucles ............................................. 67

4.7. Procedimientos y funciones ............................... 69 4.7.1. Definición ......................................... 69 4.7.2. Parámetros de entrada .............................. 71 4.7.3. Parámetros de salida ............................... 73

5. Fundamentos de la Programación Orientada a Objeto 5.1 Introducción.................................................. 74 5.2 Tipos de datos abstractos. .............................. 74 5.3 Clases. ................................................. 76 5.4 Objetos ................................................. 76 5.5 Mensajes ................................................ 77 5.6 Relaciones .............................................. 77 5.7 Herencia ................................................ 78 5.8 Asignación dinámica de memoria. ......................... 80 5.8 Polimorfismo ............................................ 81

6. Manipulación de componentes................................ 84 6.1. Introducción ............................................ 84 6.3. Instalación de un componente .............................. 84 6.4. Propiedades ............................................. 85 6.4.1. Acceso a los miembros de un objeto ................. 86 6.4.2. Posición y dimensiones del componente .............. 87 6.4.3. Títulos, colores y tipos de letra .................. 88 6.4.4. Estado visual y de acceso .......................... 89 6.4.5. Orden de acceso a los controles .................... 90 6.4.6. Contenedores y contenidos acoplables ............... 91

6.5. Eventos ................................................. 91 6.5.1. El evento OnClick .................................. 92 6.5.2. Eventos de ratón ................................... 92 6.5.3. Eventos de teclado ................................. 93 6.5.4. Otros eventos ...................................... 93

6.6. Métodos ................................................. 94 7. Componentes más habituales................................. 95 7.1. Introducción ............................................ 95 7.2.Trabajar con el formulario .............................. 95 7.2.1. Aspectos visuales del formulario ................... 95

Page 4: DELPHI 5

7.2.2. Eventos de un formulario ........................... 99 7.2.3. Métodos de un formulario .......................... 101 7.2.4. En la práctica .................................... 101

7.3. Botones ................................................ 103 7.3.1. Tecla de acceso rápido ............................ 104 7.3.2. El evento de pulsación ............................ 104 7.3.3. Botón por defecto y de cancelación ................ 104 7.3.4. En la práctica .................................... 105

7.4. Etiquetas de texto ...................................... 107 7.4.1. Tamaño del control ................................ 107 7.4.2. Alineación del texto .............................. 108 7.4.3. Otras propiedades de TLabel ....................... 108 7.4.4. En la práctica .................................... 109

7.5. Petición de datos ....................................... 110 7.5.1. Longitud del texto ................................ 110 7.5.2. Selección de texto ................................ 110 7.5.3. Texto de sólo lectura y oculto .................... 111 7.5.4. Otras propiedades de TEdit ........................ 112 7.5.5. Control de la entrada ............................. 112 7.5.6. En la práctica .................................... 113

7.6. Entrada de texto ........................................ 114 7.6.1. Barras de desplazamiento .......................... 115 7.6.2. Trabajando con líneas de texto .................... 116 7.6.3. Otras propiedades de TMemo ........................ 116 7.6.4. En la práctica .................................... 117

7.7. Selección de opciones ................................... 119 7.7.1. En la práctica .................................... 120

7.8. Selección de opciones exclusivas ......................... 121 7.8.1. En la práctica .................................... 122

7.9. Grupos de opciones exclusivas ............................ 123 7.9.1. Opciones existentes y opción activa ............... 123 7.9.2. En la práctica .................................... 124

7.10. Listas de elementos .................................. 125 7.10.1. Contenido de la lista ............................ 125 7.10.2. Elementos seleccionados .......................... 126 7.10.3. Otras propiedades de TListBox .................... 127 7.10.4. En la práctica ................................... 127

7.11. Listas desplegables .................................. 130 7.11.1 En la práctica .................................... 130

7.12. Controles contenedores ............................... 132 7.12.1. Alineación del contenedor ........................ 133 7.12.2. Elementos de realce .............................. 133 7.12.3. En la práctica ................................... 134

7.13. Barras de desplazamiento ............................. 136 7.13.1. Límites y posición de la barra ................... 136 7.13.2. Incrementos grandes y pequeños ................... 136 7.13.3. En la práctica ................................... 137

Page 5: DELPHI 5

7.14. Elementos gráficos ................................... 139 7.14.1. Figura a dibujar ................................. 139 7.14.2. Brocha y lápiz ................................... 140 7.14.3. En la práctica ................................... 141

7.15. Imágenes de mapas de bits ............................ 142 7.15.1. Mostrar imágenes contenidas en archivos .......... 142 7.15.2. La superficie de dibujo .......................... 143 7.15.3. En la práctica ................................... 144

7.16. Listas de unidades, carpetas y archivos .............. 146 7.16.1. Una lista de unidades ............................ 146 7.16.2. Una lista de carpetas ............................ 147 7.16.3. Una lista de archivos ............................ 147 7.16.4. En la práctica ................................... 148

7.17. Eventos periódicos ................................... 149 7.17.1. En la práctica ................................... 149 7.18.1. Diseño de un menú principal ...................... 150 7.18.2. Construcción de un menú emergente ................ 151 7.18.3. En la práctica ................................... 152

8. Depuración y excepciones.................................. 153 8.1. Introducción .......................................... 153 8.2. Proceso de depuración ................................. 153 8.2.1. Estado de ejecución ............................... 154 8.2.2. Ejecución paso a paso ............................. 154 8.2.3. Puntos de parada .................................. 156 8.2.4. Inspección de valores y evaluación de expresiones . 158 8.2.5. Pila de llamadas .................................. 160

8.3. Control de excepciones .................................. 161 8.3.1. La construcción Try/Except ........................ 161 8.3.2. Clases de excepciones ............................. 162 8.3.3. En la práctica .................................... 162

9. Uso de múltiples ventanas................................. 165 9.1. Introducción .......................................... 165 9.2.Trabajo con múltiples formularios ...................... 165 9.2.1. El formulario principal del programa .............. 166 9.2.2. Formularios creados automáticamente y formularios disponibles .............................................. 166 9.2.3. Visualización de un formulario .................... 168 9.2.4. MDI ............................................... 169

9.3. Cuadros de diálogo de uso común ....................... 170 9.3.1. Abrir y guardar archivos .......................... 170 9.3.2. Tipos y atributos de letra ........................ 172 9.3.3. Selección de colores .............................. 174 9.3.4. Opciones de impresión y configuración de impresora 175 9.3.5. Búsquedas y sustituciones ......................... 178

9.4. En la práctica ......................................... 180 9.4.1. Diseño del formulario principal ................... 180 9.4.2. Diseño del formulario hijo ........................ 181

Page 6: DELPHI 5

9.4.3. El código del formulario principal ................ 182 9.4.4. El código del formulario hijo ..................... 186 9.4.5. Probando el programa .............................. 188

10. Trabajo con base de datos................................ 188 10.1. Introducción .......................................... 188 10.2. Gestión de alias ....................................... 189 10.2.1. Creación de un nuevo alias ....................... 191 10.2.2 Modificación y eliminación de alias ............... 191

10.3. Creación de una tabla .................................. 192 10.3.1. Definición de los campos de la tabla ............. 192 10.3.2. Propiedades de la tabla .......................... 193 10.3.3. Índices .......................................... 195 10.3.4. Guardar la tabla ................................. 196

10.4. Modificar la estructura de una tabla .................... 197 10.5. Editar el contenido de una tabla ........................ 198 10.5.1. Edición de datos ................................. 199 10.5.2. Columnas no accesibles ........................... 200

10.6. Consultas ............................................ 200 10.6.1. Construcción de una consulta QBE ................. 200 10.6.2. Construcción de una consulta SQL ................. 201

11. Componentes enlazados a datos............................ 203 11.1. Introducción ......................................... 203 11.2. Edición de una tabla ................................. 203 11.2.1. Establecer un enlace con la tabla ................ 203 11.2.2. El editor de campos de TTable .................... 204 11.2.3. Inserción de los controles de edición ............ 205 11.2.4. Navegación por los datos ......................... 205

11.3.Tablas y consultas .................................... 206 11.3.1. El componente TTabIe ............................. 206 11.3.2. El componente TQuery ............................. 207 11.3.3. El componente TDataSource ........................ 208

11.4. Controles enlazados a datos .......................... 208 11.4.1.Mostrar y editar datos ............................ 209 11.4.2. Datos lógicos y botones de radio ................. 209 11.4.3. Textos extensos e imágenes ....................... 210 11.4.4. Listas y listas combinadas ....................... 210 11.4.5. Rejillas de datos ................................ 211 11.4.6. Rejillas de controles ............................ 212

11.5. Acceso programático a los datos ...................... 214 11.5.1. El objeto TField ................................. 215 11.5.2. Métodos de TTable ................................ 217

12. Creación de Informes..................................... 221 12.1. Introducción ......................................... 221 12.2. Funcionamiento general de QuickReport ................ 221 12.3. El componente TQuickReport ........................... 222 12.3.1. Selección de los datos a imprimir ................ 223 12.3.2. Formato del informe .............................. 223

Page 7: DELPHI 5

12.3.3. Información durante la ejecución ................. 223 12.3.4. Visualización e impresión del informe ............ 224

12.4. El componente TQRBand ................................ 226 12.4.1. Aspecto de la sección en el informe .............. 227 12.4.2. El componente TQRExpr ............................ 228 12.4.3. El componente TQRSysData ......................... 228 12.4.4. El componente TQRShape ........................... 228 12.4.5. Imágenes en el informe ........................... 229 12.4.6. Un ejemplo ....................................... 229

13. Componentes avanzados.................................... 233 13.1. Introducción ......................................... 233 13.2. Valores discretos y rangos ........................... 233 13.2.1. Marcas de posición ............................... 234 13.2.2. Selección de rangos .............................. 235 13.2.3. En la práctica ................................... 235

13.3. Curso de un proceso .................................. 237 13.3.1. En la práctica ................................... 238

13.4. Incrementar y decrementar valores .................... 240 13.4.1. Otras propiedades de TUpDown ..................... 240 13.4.2. En la práctica ................................... 241

13.5. Cabeceras redimensionables ........................... 241 13.5.1. Propiedades de un objeto THeaderSection .......... 241 13.5.2. Edición de la propiedad Sections ................. 242 13.5.3. Eventos de THeaderControl ........................ 242 13.5.4. En la práctica ................................... 243

13.6. Barras de estado ..................................... 246 13.6.1. El objeto TStatusPanel ........................... 246 13.6.2. Otras propiedades de TStatusBar .................. 247 13.6.3. Eventos de TStatusBar ............................ 247 13.6.4. Edición de la propiedad Panels ................... 248 13.6.5. En la práctica ................................... 248

13.7. Ventanas multipágina ................................. 248 13.7.1. Gestión de las páginas en la etapa de diseño ..... 249 13.7.2. Propiedades de TPageControl ...................... 249 13.7.3. Propiedades de TTabSheet ......................... 250 13.7.4. Gestión de las páginas durante la ejecución ...... 250 13.7.5. En la práctica. .................................. 251

13.8. Almacenes de imágenes ................................ 251 13.8.1. Asignación de imágenes durante el diseño ......... 251 13.8.2. Asignación de imágenes mediante código ........... 252 13.8.3. Obtener imágenes de un TImageList ................ 253 13.8.4. En la práctica ................................... 253

13.9. Listas jerárquicas ................................... 253 13.9.1. Definir elementos en la fase de diseño ........... 254 13.9.2. Definir elementos durante la ejecución ........... 254 13.9.3. Propiedades de TTreeView ......................... 254 13.9.4. Propiedades de TTreeNode ......................... 255

Page 8: DELPHI 5

13.9.5. Métodos de TTreeView ............................. 256 13.9.6. Métodos de TTreeNode ............................. 257 13.9.7. En la práctica ................................... 258

13.10. El control TListView ................................ 265 13.10.1. Propiedades de TListView ........................ 265 13.10.2. Propiedades de TListItem ........................ 266 13.10.3. Métodos de TListView ............................ 267 13.10.4. Métodos de TListItem ............................ 267 13.10.5. Definición de columnas .......................... 267 13.10.6. En la práctica .................................. 268

13.11. Texto con formato ................................... 271 13.11.1. Atributos por defecto y de selección ............ 272 13.11.2. Atributos de párrafo ............................ 272 13.11.3. Métodos de TRichEdit ............................ 273 13.11.4. En la práctica .................................. 273

13.12. Barras de botones ................................... 275 13.12.1. Inserción de botones ............................ 275 13.12.2. Propiedades de TToolBar ......................... 276 13.12.3. Propiedades de TToolButton ...................... 277 13.12.4. Uso de una barra de botones ..................... 277

14.1. Introducción ......................................... 278 14.2. Listas de acciones ................................... 278 14.2.1. El componente TActionList ........................ 278 14.2.2. Propiedades del objeto TAction ................... 279 14.2.3. La propiedad Action .............................. 280 14.2.4. Uso de una lista de acciones ..................... 280

14.3. Componentes compuestos ............................... 282 14.3.1. Creación de un nuevo marco ....................... 283 14.3.2. Reutilizar el marco en el mismo proyecto ......... 284 14.3.3. Reutilizar el marco en otros proyectos ........... 285 14.3.4. Un ejemplo ....................................... 285

15. Creación de componentes.................................. 287 15.1 Introducción ........................................... 287 15.2. Niveles de acceso a los componentes ..................... 287 15.3 Creando un componente. ................................ 287 15.4 Creando un componente visual. ......................... 290 15.5 Creando un componente no visual. ...................... 291 15.6 Creando un bitmap para el componente. ................. 292 15.7 Instalando componentes creados. ....................... 293 15.8 Introducción a los editores de propiedades. ........... 293 15.9 Responsabilidades de un editor de propiedades. ........ 294 15.10 Pasos necesarios para escribir un editor de propiedades. ........................................................... 294 15.11 Un editor de propiedades para números binarios. ...... 295 15.12 Registro de un editor de propiedades. ................ 298 15.13 Ubicación de un editor de propiedades. ............... 300

Page 9: DELPHI 5

Introducción Todo comenzó con el oscuro y difícil mundo del lenguaje de máquina y luego el ensamblador. Por entonces la programación de las computadoras era privativa de especialistas y científicos. Era tan complicado programar y tan difícil acceder a una computadora que solo unos pocos podían hacerlo. Un programa que resolviese un problema medianamente complejo requería de la escritura de muchas líneas de código lo que hacía que fuese difícil no solo crearlo sino también el mantenerlo y mejorarlo. Como suele suceder, la evolución, no se detuvo y se fueron mejorando los lenguajes, aparecieron los llamados entornos de desarrollo integrado que incluyen editor, compilador o interprete y debugger. Ya hoy contamos con los llamados entornos visuales de programación que se clasifican dentro de la cuarta generación y que permiten al programador una mayor concentración en la resolución de los problemas que se le plantean y una menor perdida de tiempo en el diseño de las interfaces para el acceso de los usuarios y la escritura de cientos de líneas de código. Estos nuevos entornos de programación le permiten a los programadores crear aplicaciones utilizando técnicas como la de arrastrar y soltar, añadiendo iconos que representan objetos, modificando sus propiedades y escribiendo algo de código. La posibilidad de reusar componentes realizados previamente así como la de usar los fabricados por otros son una expresión de los niveles de reusabilidad que redundan en un acortamiento del ciclo de desarrollo. Delphi califica como uno de estos nuevos lenguajes visuales y sin dudas lo hace muy bien permitiendo el desarrollo rápido de aplicaciones de alta complejidad con una interface gráfica funcional y de un calidad gráfica importante. Disponible inicialmente para las plataformas Windows y recientemente, con el proyecto Kylix, también para las del tipo Linux. Basado en un compilador de indudable calidad es ahora posible desarrollar aplicaciones multiplataforma pues el código, siempre que no utilice funciones específicas de la API de windows, puede ser compilado y ejecutado bajo plataforma linux. La versión 5.0 que es en la que se basa este libro nos permite crear aplicaciones para Internet e Intranets, servicios locales y distribuidos, y acceso a una amplia gama de Bases de Datos personales o del tipo Cliente/Servidor. En fin si usted decide crear aplicaciones de calidad que no simplemente funcionen sino que saquen partido a su sistema operativo, Delphi 5.0 es la herramienta adecuada y en este libro podrá encontrar los fundamentos necesarios para ponerse en marcha.

Page 10: DELPHI 5

1. Instalación

1.1. Introducción El formato de distribución en el que se encuentra Delphi 5 es el CD, soporte que permite almacenar, por ejemplo, los más de 400 megabytes que ocupa la versión Enterprise, en la cual se incluye Delphi 5, Interbase Server, InstalIShield, TeamSource, los archivos imagen que permiten ejecutar todo ello desde el propio CD y múltiples archivos con información diversa. La ocupación real en nuestro disco no será tan elevada, ya que no tenemos por qué instalar todas las opciones; podemos dejar las partes que nos interesen en el CD, sin instalarlas en nuestro sistema.

1.2. Consideraciones previas Antes de proceder a describir el proceso de instalación de Delphi 5, no está de más que conozcamos las versiones existentes de este producto, así como los requerimientos que habrá de cumplir nuestro sistema.

1.2.1. Versiones de Delphi 5 Delphi es un producto único, basado en un entorno de desarrollo visual y un lenguaje orientado a objetos. Sin embargo existen tres ediciones distintas, que cuentan con elementos que difieren entre ellas. La versión más básica, denominada Standard, incluye los elementos comunes a las tres versiones. Se compone del entorno de desarrollo visual Delphi 5, el Borland Database Engine de 32 bits y todos los componentes Delphi necesarios para la creación de programas en Windows 95/98 y NT/2000 y el diseño de informes. El nivel intermedio de Delphi lo ocupa la versión Profesional, que además de contar con los elementos ya mencionados también incorpora el servidor local de Interbase, el código fuente de los componentes Delphi, algunos controles ActiveX de ejemplo y algunos componentes adicionales para el trabajo con bases de datos y creación de aplicaciones Internet. A la versión superior de Delphi 5 se le conoce como Enterprise y, además de incorporar todo lo descrito en los dos puntos anteriores, también se acompaña de una licencia de InterBase Server, un controlador de versiones de código fuente, controladores SQL para Oracle, Sybase, Informix, etc., y las herramientas SQL Explorer y SQL Monitor. También permite la creación de servidores Web y aplicaciones distribuidas usando DCOM, CORBA, etc. Como puede ver, esta versión está especialmente enfocada al desarrollo de aplicaciones para trabajo

Page 11: DELPHI 5

en entornos cliente/servidor, facilitando incluso un servidor de bases de datos para Windows, lo cual nos puede facilitar mucho el trabajo.

1.2.2. Requerimientos de Delphi 5 Como se ha comentado anteriormente, el contenido del CD en el que se distribuye Delphi 5 ocupa un volumen considerable de espacio, pero es cierto también que algunos elementos no tienen por qué ser copiados a nuestro sistema, como los manuales, que pueden visualizarse desde el propio CD, o el código fuente de la VCL y los ejemplos. La ocupación en disco oscilará entre los 80 megabytes, para una instalación típica de la versión Standard, y los alrededor de 200 para una instalación de la versión Enterprise. La instalación mínima que copiara al disco tan solo lo necesario, dejando el resto de archivos en el CD, tiene el inconveniente de que cada vez que necesitemos una de las herramientas no instaladas, el CD deberá encontrarse en la unidad. También tenemos la posibilidad de realizar una instalación personalizada, seleccionando los elementos a copiar a nuestro disco y controlando la ocupación final. Los requerimientos de memoria de Delphi 5 son los propios de una herramienta de este tipo, siendo el mínimo de 32 megabytes. Este mínimo, sin embargo, es poco realista, ya que con esa cantidad de memoria los accesos a disco durante procesos como la compilación serán continuos y el funcionamiento de Delphi resultará algo lento. En la práctica 64 megabytes de memoria es una configuración adecuada para el correcto funcionamiento de Delphi 5. El software necesario será Windows 95/98 o NT 4.0/2000. Si usamos NT 4.0, deberemos tener instalado los últimos Serviec Pack.

1.3. Instalación de Delphi 5 Conociendo ya los aspectos básicos tratados en los puntos anteriores, vamos a iniciar la instalación de Delphi 5 en nuestro sistema. Para ello bastará con insertar el CD-ROM en nuestro lector, lo que causará la autoejecución del programa de instalación. En caso de que tenga desactivada esta característica de Windows, tendrá que ejecutar el programa INSTALL que se encuentra en el directorio raíz del CD, lo que puede hacer cómodamente desde el Explorador de Windows o bien pulsando sobre el icono "Mi PC" y abriendo la carpeta correspondiente al lector de CD-ROM. Dependiendo de la versión que estemos instalando aparecerá una ventana con más o menos opciones. En la figura 1.1 puede ver el aspecto que muestra la ventana de instalación de la versión Enterprise.

Page 12: DELPHI 5

Pulse el botón correspondiente a la instalación de Delphi. Tras unos instantes, que se emplearán en la preparación de la información necesaria para llevar a cabo la instalación, verá aparecer una pantalla de bienvenida en la que se aconseja que no existan otras aplicaciones ejecutándose mientras se realiza el proceso de instalación, invitándonos a cerrar las que estén abiertas en ese momento. Tras pulsar el botón Next, tendrá que introducir el número de serie y la clave de activación del producto, datos que encontrará en la propia caja del CD. Una nueva pulsación del botón Next y se visualizará el acuerdo de licencia del producto, que habrá que aceptar pulsando el botón Yes. A continuación aparecerá el archivo con notas informativas de última hora. Tras leerlo pulse de nuevo el botón Next, lo que le llevará a la ventana que se muestra en la figura 1.2, en la que podrá elegir entre una instalación típica, completa o a medida.

Figura 1.1. Ventana inicial del programa de instalación La primera opción, instalación típica, copiará en nuestro sistema todos los archivos que necesita Delphi y las herramientas más habituales que le acompañan para funcionar de una forma independiente, sin necesidad de disponer del CD. La instalación compacta copiará en nuestro disco lo mínimo para poder funcionar. Por último tenemos la instalación a medida, que nos permite seleccionar los elementos a instalar. Al seleccionar este tipo de instalación aparecerá una ventana con unos apartados principales. Seleccionando uno de estos apartados y pulsando el botón Change, accederemos a una segunda ventana en la que individualmente podremos indicar qué partes se instalarán. En la figura 1.3 puede ver cómo se ha abierto la ventana de componentes Program Files," habiéndose seleccionado para instalación los archivos de programa principales, el editor de imágenes, etc., dejando en el CD los programas de ejemplo.

Page 13: DELPHI 5

Figura 1.2. Selección del tipo de instalación

Figura 1.3. Selección de los elementos a instalar Independientemente del método de instalación que se haya elegido, en la siguiente ventana que aparece, tras pulsar una vez más el botón Next, indicaremos si queremos o no instalar el cliente InterBase, que nos servirá para hacer pruebas locales de desarrollo con bases de datos InterBase. Previamente, si la versión que se instala es la Enterprise, deberemos elegir los controladores SQL que deseamos instalar. A continuación podremos seleccionar el camino de destino de la instalación. Mediante el botón Browse podemos especificar un directorio raíz a partir del cual se creará uno para cada elemento a instalar. El último paso en el que intervendremos en la configuración de la instalación será en la selección del nombre de grupo o carpeta. En

Page 14: DELPHI 5

esta carpeta encontraremos un icono para iniciar Delphi 5, como es lógico, y otros adicionales para acceder a archivos de ayuda y herramientas que acompañan a Delphi. Una pulsación más del botón Next nos llevará a una ventana en la que podremos ver una lista con todas las opciones de configuración, teniendo opción a volver hacia atrás y realizar modificaciones, pulsando el botón Back, o bien a iniciar el proceso de instalación, mediante el botón Install. Dicho proceso consistirá en la copia de todos los archivos seleccionados al camino de destino, así como la creación de los apartados necesarios en el archivo de registro de Windows, modificación de algunos parámetros de configuración y creación de la carpeta. La duración del trabajo de instalación dependerá principalmente de la velocidad de nuestro lector de CD-ROM, así como de la cantidad de elementos que se hayan seleccionado. En cualquier caso, y tal como se muestra en la figura 1.4, el Programa de instalación nos ira indicando en todo momento el curso del proceso. Cuando la instalación haya llegado a su fin, verá aparecer en pantalla una ventana en la que se indica que es necesario reiniciar el ordenador. Hágalo antes de intentar ejecutar Delphi, ya que de lo contrario no todos los componentes estarán adecuadamente configurados. En la figura 1.5 puede ver el aspecto del menú de Delphi 5 una vez terminada la instalación y reiniciado el sistema. Ya puede ejecutar Delphi, simplemente eligiendo la opción apropiada.

Figura 1.4. Proceso de copia de archivos al camino de destino

Page 15: DELPHI 5

Figura 1.5. Menú correspondiente a Delphi 5

2. El entorno

2.1. Introducción El primer paso que tendremos que dar, para poder trabajar con Delphi 5, será familiarizarnos con su entorno. En este entorno existen múltiples ventanas, conteniendo opciones, botones, propiedades, etc. Estas ventanas pueden ser redimensionadas, cerradas y cambiadas de posición como cualquier otra ventana Windows. En este capítulo vamos a adquirir las nociones necesarias para poder trabajar en el entorno de Delphi 5. Para ello haremos un recorrido por los elementos más importantes, aprenderemos a insertar componentes en un formulario, etc.

2.2. Elementos iniciales Al ejecutar Delphi, en pantalla inicialmente siempre aparecen los mismos elementos. Son precisamente los que utilizaremos con mayor frecuencia aunque, como veremos posteriormente, en el entorno pueden existir varias ventanas más. En la figura 2.1 puede ver el aspecto típico de Delphi cuando es cargado. Observe los nombres con los que se denomina a los apartados, cuya función vamos a comentar seguidamente, ya que esos nombres son los que se utilizan a lo largo de esta guía para hacer referencia a ellos.

2.2.1. Ventana principal En la parte superior de la pantalla, y habitualmente ocupando todo el ancho disponible, podemos ver una ventana que contiene el nombre del proyecto sobre el que se está trabajando, los botones de acción rápida, la Paleta de componentes y el menú principal. A esta ventana la denominamos ventana principal, ya que ella contiene la mayoría de los elementos que necesitaremos para trabajar con Delphi. Si minimizamos la ventana principal, las demás del entorno también desaparecerán y si la cerramos estaremos saliendo de Delphi. Los botones de acción rápida, como su propio nombre indica, nos permiten realizar determinadas operaciones de forma rápida, sin necesidad .de tener que acceder al menú o pulsar una combinación de teclas. Pulsando sobre los iconos que muestran estos botones podremos guardar el proyecto actual, ejecutar el programa o añadir un nuevo elemento al proyecto, por poner algunos ejemplos. La finalidad de cada uno de los botones puede deducirse del icono que utiliza, aunque bastará con situar el puntero del cursor sobre él para

Page 16: DELPHI 5

obtener una pequeña descripción. Observe que algunos de los botones disponen de una flecha que indica la existencia de una lista desplegable de opciones.

Figura 2.1. Elementos iniciales en el entorno de Delphi Personalización de los botones El área de botones de acciones rápidas está pensada para ahorrarnos trabajo, por eso encontramos en ella botones que nos dan acceso a las acciones más habituales. A pesar de esto, nosotros podemos configurar totalmente esta área, eliminando y añadiendo botones, así como modificando la disposición. Como podrá apreciar, los botones están agrupados según su funcionalidad. Existen varios que permiten iniciar un nuevo proyecto, recuperar un proyecto existente, guardar el actual y añadir y eliminar elementos del proyecto. Un segundo grupo de botones permiten ver un

Page 17: DELPHI 5

formulario, un módulo de código, etc. Si pulsa el botón derecho sobre el área de botones, con el fin de desplegar el menú emergente, verá que existen varias opciones que permiten mostrar/ocultar esas paletas de botones de forma individual. Observe que tanto el menú de opciones como la Paleta de componentes, y los distintos grupos de botones de acción rápida, tienen a su izquierda una doble línea vertical, elemento que indica que esos elementos están contenidos en un área que podemos fácilmente desplazar a donde nos interese dándole las dimensiones que deseemos. Todas las paletas de componentes son configurables, de tal forma que podemos tanto añadir nuevos botones como eliminar los existentes o cambiarlos de sitio. Para poder realizar esta tarea hay que abrir de nuevo el menú emergente y elegir la última opción, llamada Customize, que da paso a una ventana con varias páginas. La primera de ellas facilita la selección de las paletas de botones que estarán activas; la segunda, con el título Commands, contiene todos los comandos posibles agrupados por función. Es posible tomar cualquier comando de la lista de la derecha y arrastrarlo hasta la paleta de botones que nos interese para añadirlo, de tal forma que esté disponible directamente. De forma similar, podemos arrastrar un botón existente en una paleta fuera de ella, eliminándolo. En la figura 2.2 puede ver la ventana de personalización, así como las paletas de componentes dispuestas en la parte superior de la ventana principal y con algunos botones más que se han añadido según el procedimiento indicado.

Figura 2.2. Ventana de configuración de los botones de acciones rápidas

2.2.2. La Paleta de componentes A la derecha del área de botones, aunque podemos colocarla en cualquier otro punto de la ventana principal, encontraremos la Paleta de componentes. Esta paleta está compuesta por múltiples páginas, a

Page 18: DELPHI 5

las que accederemos mediante las pestañas que hay en la parte superior. Cada una de las páginas contiene componentes, que son los elementos básicos con los que construiremos nuestros programas. En un capítulo posterior tendremos ocasión de conocer la finalidad y funcionamiento de los componentes Delphi más habituales, con los que podremos construir prácticamente cualquier programa. También aprenderemos a usar algunos controles más avanzados que permiten mejorar la interfaz, haciendo que esté más en consonancia con el sistema operativo. Al igual que ocurre con el área de botones de acciones rápidas, la Paleta de componentes también puede ser personalizada. Si sitúa el puntero del ratón sobre la Paleta de componentes, no importa el punto, y pulsa el botón derecho del ratón verá que se abre un menú contextual con una opción llamada Properties. Seleccione esa opción, consiguiendo hacer aparecer la ventana que se muestra en la figura 2.3. La lista existente en la parte izquierda contiene los nombres de las páginas existentes, mientras que la lista de la derecha muestra los componentes existentes en la página seleccionada en la izquierda. Los botones situados en la parte inferior nos permiten alterar el orden de las páginas o de los componentes en una página, así como modificar el nombre, que podemos adaptar a nuestras preferencias, añadir y eliminar componentes.

Figura 2.3. Personalización de la Paleta de componentes de Delphi

Page 19: DELPHI 5

2.2.3. El formulario La ventana que aparece aproximadamente en el centro de la pantalla, según se ha mostrado en la figura 2.1, y que tiene por título Form1, recibe el nombre de formulario. Una formulario es una ventana que en una aplicación Delphi actúa como contenedor, en el cual se irán insertando los componentes que sean necesarios para solicitar datos, mostrar información o realizar cualquier otra tarea. Durante la ejecución, un formulario se comporta como cualquier otra ventana Windows, es decir, se mostrará con un título, unos botones que le permiten maximizar, minimizar o cerrar la ventana y un área de trabajo o área cliente en la que se encontrarán los controles con los que interactuará el usuario del programa. En realidad, todos estos elementos son configurables y, mediante las propiedades oportunas, podremos alterar el título de la ventana, la existencia o no de los citados botones, etc. Habitualmente en un proyecto Delphi existe más de un formulario, dando así lugar a un programa que utiliza más de una ventana. Posteriormente, en el capítulo 8, aprenderemos a gestionar un proyecto de estas características.

2.2.4. El Inspector de objetos Con este nombre se conoce a la ventana que inicialmente aparece en la parte izquierda de la pantalla, justo debajo de la ventana principal y a la izquierda del formulario. En el Inspector de objetos encontramos básicamente tres elementos diferentes: la lista de componentes, en la parte superior, la página de propiedades y la página de eventos. La lista desplegable existente en la parte superior del Inspector de objetos, la cual podremos abrir pulsando el botón que hay en el extremo derecho, contiene el nombre de cada uno de los componentes que existen en el formulario sobre el que estamos trabajando en ese momento. Al seleccionar cualquiera de esos componentes se mostrara en la parte superior no sólo su nombre sino también su tipo. En la figura 2.1 puede ver que el componente seleccionado es Form1, cuyo tipo es TForm1. Los componentes Delphi son elementos genéricos que han de servir para cualquier aplicación, por lo que han de ser flexibles en ciertos aspectos, permitiendo su personalización. La página Properties del Inspector de objetos muestra las propiedades del componente que tengamos seleccionado, así como los valores que actualmente contienen dichas propiedades. Alterando el valor de una propiedad podemos modificar el título del formulario, conseguir que no aparezcan los botones de minimizar o maximizar, etc. La página Events es la que contiene los nombres de los eventos soportados por el componente, así como los nombres de los procedimientos que se ejecutarán al generarse

Page 20: DELPHI 5

esos eventos. Mediante un evento podemos, por ejemplo, indicar la acción que se ha de realizar cuando se pulse un botón.

2.2.5. El menú principal Aunque las acciones más habituales que llevaremos a cabo mientras trabajemos con Delphi estarán disponibles en el área de botones de acciones rápidas, o bien en un menú contextual que podemos hacer aparecer en cualquier momento pulsando el botón secundario del ratón, existen otras muchas, usadas con menos frecuencia, que tendremos que buscar en el menú principal de Delphi, el cual ocupa la parte superior de la ventana principal. Está claro que las opciones del menú de Delphi las iremos conociendo a medida que las usemos, por lo que no tiene mucho sentido la descripción pormenorizada de cada una de ellas. Al desplegar un menú observe que muchas de las opciones tienen un icono a su izquierda y, en ocasiones, una combinación de teclas a su derecha. Estos elementos nos indican el icono que tiene el botón que podemos pulsar para conseguir el mismo efecto, sin necesidad de abrir el menú, o la combinación de teclas que hay que pulsar con la misma finalidad. Dedique un tiempo a recorrer las opciones del menú y ver las funciones existentes, las ventanas a las que puede acceder, etc. En éste y posteriores capítulos iremos conociendo algunas de estas opciones según lo precisemos.

2.3. El Depósito de objetos Al pulsar el botón New, que es el primero de la paleta de botones estándar, o seleccionar la opción del mismo nombre del menú File accederemos al Depósito de objetos, cuya ventana puede ver en la figura 2.4. La primera página de esta ventana contiene varios iconos que nos permiten la creación de una nueva aplicación, un nuevo formulario, un nuevo componente, una librería de enlace dinámico, etc. Las demás páginas del Depósito de objetos contienen formularios, proyectos, cuadros de diálogo y módulos de datos prefabricados que nosotros podemos usar en nuestras aplicaciones. Si queremos, por ejemplo, añadir a nuestro programa una ventana "Acerca de...", en lugar de insertar un formulario vacío y crearlo desde cero podemos abrir la página Forms del depósito de objetos y seleccionar el elemento "About box", de tal forma que a nuestra aplicación se añada un formulario en el que tan sólo tendremos que personalizar algunas propiedades.

Page 21: DELPHI 5

Figura 2.4. La ventana del Depósito de objetos Al igual que ocurre con otros elementos del entorno de Delphi, también el Depósito de objetos es totalmente configurable, siendo posible añadir nuevas páginas, modificar las existentes, almacenar en él nuestros propios formularios o prototipos de proyectos, etc. Como en casos anteriores, para acceder a la ventana de propiedades de! Depósito de objetos abra el menú contextual, pulsando el botón derecho del ratón, y seleccione la opción Properties.

2.4. Obtener ayuda El último menú existente en la ventana principal de Delphi, llamado Help, cuenta con una serie de opciones que nos permiten acceder a diferentes apartados de la ayuda. La primera de ellas da paso a la ayuda general de Delphi, mientras que la segunda abre la ayuda de las diferentes herramientas que acompañan a Delphi y la tercera a la ayuda de la API de Windows. Las tres opciones del segundo grupo son enlaces directos a páginas Web de Borland. La última opción muestra la típica ventana de información acerca de la aplicación, en este caso indicando la versión^del Delphi que estamos usando. En la figura 2.5 puede ver esta ventana.

2.5. Creación de la interfaz de un programa La creación de una aplicación con Delphi se inicia, generalmente, con el diseño de su interfaz, para lo cual será necesario manipular el formulario o los formularios con que cuenta el proyecto, insertando y manipulando componentes, propiedades y eventos, así como escribiendo el código que sea necesario. A continuación vamos a hacer un breve

Page 22: DELPHI 5

recorrido sobre los pasos que generalmente seguiremos para construir un programa, desde la configuración del formulario hasta la ejecución del programa.

Figura 2.5. Ventana con información acerca de la versión de Delphi

2.5.1. La ventana del programa Toda aplicación Windows de tipo estándar, que son la mayoría, cuenta con una ventana, un espacio de pantalla delimitado mediante un borde, en la cual se visualiza o solicita información. En caso de que la aplicación cuente con varias ventanas siempre existirá una que sea la inicial o ventana principal, desde la cual se accederá a las demás. Una ventana Windows cuenta con un borde, un fondo, un título, unos botones y un menú de sistema. Todos estos elementos no tienen por qué existir siempre, nos podemos encontrar con una ventana que tiene menú de sistema pero no botones de maximizar y minimizar. En un programa Windows desarrollado con Delphi las ventanas de la aplicación son los formularios, cuyas características estableceremos mientras estamos construyendo el programa, es decir, durante el diseño. La ventana principal será el formulario que hayamos creado en primer lugar, aunque como veremos después éste es un apartado configurable. El aspecto de un formulario, durante el diseño, no tiene por qué ser precisamente el aspecto que tendrá la ventana del programa cuando éste se ejecute y, de hecho, en la mayoría de las ocasiones no lo será. Por ejemplo, durante el diseño se muestra en el interior del formulario

Page 23: DELPHI 5

una matriz o rejilla de puntos, cuya finalidad es facilitar la alineación de los componentes en su interior. Hasta matriz de puntos no será visible durante la ejecución, ya que en ese momento el programa ya está terminado y no tiene sentido la función para la cual está pensada dicha matriz. Inicialmente las dimensiones de la ventana, durante la ejecución, serán las mismas que nosotros hayamos dado al formulario durante el diseño, aunque este aspecto también depende del valor que asignemos a ciertas propiedades. Podemos modificar las dimensiones que por defecto tiene el formulario simplemente situando el puntero del ratón en uno de sus extremos y arrastrándolo. De igual forma, también podemos modificar la posición del formulario en pantalla, estableciendo la posición en la que por defecto aparecerá la ventana al ejecutar el programa. Aunque durante el diseño en el formulario siempre existirán, en el extremo derecho, los botones de maximizado, minimizado y cierre, ello no implica que durante la ejecución estos botones aparezcan también, ya que es posible desactivarlos o hacerlos desaparecer, simplemente alterando el valor de la propiedad adecuada.

2.5.2. La rejilla de puntos Durante el diseño, el fondo del formulario cuenta por defecto con una rejilla de puntos cuya finalidad es, como se ha comentado antes, facilitar la alineación de múltiples componentes, de tal forma que éstos queden ajustados según nos interese. Al insertar un componente el formulario, tarea que vamos a tratar en el apartado siguiente, así como al desplazarlo de un punto a otro o al modificar sus dimensiones, los puntos de la rejilla serán por defecto la medida de incremento o decremento mínima. Tanto la distancia entre los puntos de la rejilla como el hecho de que todos los ajustes tengan como límite uno de esos puntos son aspectos configurables. Si selecciona la opción Environment Options del menú Tools, accederá a la ventana que se muestra en la figura 2.6, en cuya primera página existe un recuadro con las opciones que afectan a la rejilla. Desactivando la opción Display grid, que está activada por defecto, conseguiremos hacer desaparecer la rejilla de puntos del formulario, que aparecerá durante el diseño prácticamente con el mismo aspecto que tendrá en ejecución. En caso de que optemos por mantener la rejilla de puntos, que es lo habitual, los apartados Grid size X y Grid size Y nos permiten establecer la distancia, horizontal y vertical, respectivamente, entre dos puntos consecutivos. Por último, con la opción Snap to grid podemos indicar si deseamos que al mover un componente, o modificar sus dimensiones, dicha distancia sea la unidad mínima de trabajo.

Page 24: DELPHI 5

Figura 2.6. Ventana de preferencias con opciones de la rejilla de puntos.

2.5.3. Inserción de componentes Una parte muy importante del desarrollo de una aplicación con Delphi consiste en insertar el formulario los componentes apropiados, situándolos adecuadamente y estableciendo las propiedades que correspondan. Todos estos pasos, completamente visuales, se ven completados con la escritura de código que se asociará a eventos. Para insertar un componente en el formulario, lo primero que tendremos que hacer será seleccionarlo en la Paleta de componentes, para lo cual antes deberemos activar la página en la que se encuentre dicho componente. La selección del componente se efectúa simplemente pulsando sobre el icono que lo representa, con el botón principal del ratón, tras lo cual bastara con desplazar el puntero del ratón al lugar aproximado del formulario en que se desea insertar, pulsando de nuevo el mismo botón. El nuevo componente se ajustará al punto más cercano, en caso de que esté activada la opción correspondiente de la rejilla de puntos, y tomará unas dimensiones por defecto. El tamaño de un componente puede ser modificado en el mismo momento en que se inserta en el formulario, para lo cual, una vez seleccionado en la Paleta de componentes, habrá que desplazarse hasta el punto del formulario en que se desee realizar la inserción, pulsando el botón principal del ratón y manteniéndolo pulsado al tiempo que nos

Page 25: DELPHI 5

desplazamos a un punto opuesto, estableciendo las dimensiones deseadas. Al liberar el botón, veremos aparecer el componente con el tamaño que hayamos fijado. En ocasiones es necesario insertar en un mismo formulario múltiples componentes del mismo tipo, por ejemplo varios botones. En lugar de repetir el proceso anterior, seleccionando el componente, insertándolo y volviéndolo a seleccionar una y otra vez, podemos dejarlo seleccionado de forma fija. Para ello tendremos que pulsar la tecla <Mayús> antes de seleccionar el componente, tras lo cual no tendremos mas que desplazar el puntero hasta el formulario insertando tantos componentes como necesitemos, simplemente pulsando el botón principal del ratón cada vez. Para eliminar la selección del componente, y poder así trabajar con los componentes que se han insertado, deberemos pulsar sobre el icono que representa al puntero del ratón, que aparece en el extremo izquierdo de la Paleta de componentes.

2.5.4. Manipulación de los componentes El trabajo con un componente no termina con la inserción en el formulario, en muchos casos es necesario moverlo a otro punto, modificar su tamaño o realizar otras tareas que determinarán el comportamiento del componente durante la ejecución. Para manipular un componente lo primero que debemos hacer es seleccionarlo, situando el puntero del ratón sobre él y pulsando el botón principal. Un componente seleccionado se diferencia de los demás en que tiene un borde alrededor, con varios cuadraditos. El cambio de posición de un componente es muy simple, una vez que está seleccionado basta con arrastrarlo a la nueva posición, sin más. Para modificar su tamaño deberemos mover el puntero hasta uno de los cuadraditos que hay en el borde, arrastrando éste en la dirección que nos interese. En lugar del ratón también podemos usar el teclado para conseguir las mismas acciones. La combinación <Control+Flecha> nos permitirá desplazar punto a punto el componente seleccionado en la dirección que nos interese, mientras que con <Mayus+Flecha> podemos incrementar o decrementar su altura y anchura. Pulsando <Control+Mayús+Flecha> podremos desplazar el componente no punto a punto, sino utilizando el incremento que se haya fijado en la rejilla. Aunque lo habitual es manipular un solo componente cada vez, Delphi también nos permite seleccionar múltiples componentes de forma simultánea, de tal forma que podemos desplazarlos conjuntamente, así como modificar propiedades comunes. Para seleccionar varios componentes podemos usar dos métodos diferentes: mantener pulsada la tecla <Mayús> mientras con el botón principal del ratón vamos marcándolos, o bien trazar con el puntero del ratón, manteniendo el botón izquierdo pulsado, un recuadro que contenga total o parcialmente los componentes a seleccionar. Terminada la operación, podrá ver qué

Page 26: DELPHI 5

componentes son los que han resultado seleccionados fácilmente, ya que cada uno de ellos tendrá un recuadro a su alrededor. Cada vez que se inserta un nuevo componente éste toma unas propiedades por defecto, como pueden ser las dimensiones, título, etc. En caso de que nos interese insertar en un formulario un componente con las mismas propiedades de otro ya existente, en lugar de usar el método descrito en el punto anterior podemos realizar una operación de copiar y pegar. Para ello seleccionaremos el componente original y pulsaremos <Control+C>, copiándolo al portapapeles. A continuación pulsaremos sobre el fondo del formulario o el contenedor deseado y pegaremos el componente, mediante la combinación <Control+V>. Además de las tareas descritas hasta ahora, un componente también puede ser eliminado del formulario. Tras seleccionar el componente, lo único que habremos de hacer será pulsar la tecla <Supr> y el componente desaparecerá. Una vez que todos los componentes de un formulario se encuentran en la posición deseada y con las dimensiones necesarias, para evitar una modificación accidental podemos utilizar la opción Lock Controls del menú Edil, que los bloqueará de tal forma que no sea posible modificar posición o tamaño ni con el ratón ni con el teclado, aunque sí será posible hacerlo editando las propiedades correspondientes.

2.5.5. Modificación de propiedades Tras la inserción de un componente en un formulario, la tarea más habitual suele ser el establecimiento de propiedades, mediante las cuales se definen diferentes, características que, de una forma u otra, afectarán al aspecto del componente ó a su funcionamiento. Ciertas propiedades se modifican de forma indirecta, cuando manipulamos un control en un formulario, alterando, por ejemplo, su posición. Sin embargo, la mayor parte de las propiedades habrá que editarlas directamente sirviéndonos para ello del Inspector de objetos, ventana que describíamos brevemente al principio de este capítulo. Cada vez que en el formulario se selecciona un componente, automáticamente el Inspector de objetos muestra su nombre, en la parte superior, y sus propiedades y eventos, en las dos páginas con que cuenta. Cada página está dividida en dos columnas, conteniendo la de la izquierda el nombre de la propiedad o evento, un parámetro que no podemos modificar. La columna de la derecha es la que mantiene el valor de la propiedad o bien el nombre del método asedado al evento. El ancho de cada una de las columnas puede ser modificado, situando el puntero del ratón en la línea central que las separa y arrastrándola según nos interese. También podemos seleccionar un determinado componente desplegando la lista que hay en la parte superior del Inspector de objetos, en la que

Page 27: DELPHI 5

aparecerán los nombres de todos los componentes, existentes en el formulario sobre el que estamos trabajando. Tipos de propiedades Al modificar el valor de una propiedad, en el Inspector de objetos nos podemos encontrar con varias opciones diferentes, según las cuales podremos editar directamente dicho valor, seleccionar en una lista desplegable o bien usar una ventana específica para la edición de esa propiedad. También nos encontraremos con propiedades que son conjuntos u objetos, que cuentan a su vez con otras propiedades. En Pascal existe un tipo de dato, al que se denomina conjunto, que se caracteriza por poder contener varios valores de un conjunto predeterminado. Las propiedades que son de este tipo aparecen en el Inspector de objetos con un signo + a la izquierda del nombre, indicando así que es posible desplegar la propiedad para acceder a las subpropiedades. Esta misma técnica también se usa para permitir la modificación de las propiedades correspondientes a una propiedad que representa un objeto. La propiedad Bordericons del formulario es un conjunto y la propiedad HorzScrollBar es un objeto. Compruebe cómo puede desplegar estas propiedades, haciendo doble clic sobre el nombre, y a continuación modificar las subpropiedades. Ciertas propiedades pueden ser editadas en el Inspector de objetos de diversas formas. La propiedad Color, por ejemplo, puede ser modificada introduciendo directamente el nombre del nuevo color, usando el teclado, o bien desplegando la lista adjunta, seleccionado el color deseado, o bien haciendo doble clic sobre el valor, provocando la apertura de una ventana en la que visualmente podremos elegir el color. Otra propiedad similar es Font, que representa a un objeto. Podemos modificar esta propiedad utilizando un editor específico o bien desplegando una serie de subpropiedades.

2.5.6. Uso de los eventos El código de un programa Windows no se ejecuta de forma secuencial, sino que lo hace según los mensajes que recibe del propio sistema. Estos mensajes se generan, por ejemplo, al pulsar un botón, introducir un carácter o desplazar el puntero del ratón. En Delphi estos mensajes son gestionados automáticamente por cada componente y traducidos en eventos. Nosotros podemos asociar un método a un evento, de tal forma que cuando dicho evento se produzca se ejecute el código de ese método. No todos los mensajes de Windows tienen un evento equivalente en los componentes Delphi, pero en la mayoría de los casos, o en los más

Page 28: DELPHI 5

habituales, siempre encontraremos un evento apropiado para la finalidad que necesitemos. Pulsando sobre la pestaña Events del Inspector de objetos podremos abrir la página de eventos, en la que veremos los nombres de los eventos con los que cuenta el componente que tengamos seleccionado en ese momento en el formulario. A la derecha de cada uno de los eventos puede existir el nombre de un método, que será el que se ejecute en el momento en que se genere el evento. Haciendo doble clic sobre la columna derecha de un evento abriremos automáticamente la ventana de código situando el cursor en el método adecuado. En caso de que dicho evento no contase hasta ese momento con un método asociado, Delphi automáticamente creará el cuerpo de un nuevo método e introducirá su nombre en el Inspector de objetos, a la derecha del nombre del evento. Múltiples eventos pueden compartir un mismo método, siempre y cuando los eventos sean del mismo tipo. El tipo de un evento determina, básicamente, los parámetros que el método recibirá cuando sea llamado. El evento OnClick, por ejemplo, recibe un solo parámetro, mientras que el evento OnMouseMove recibe cuatro. Estos eventos no podrían, por tanto, compartir un mismo método.

2.5.7. Edición de código En un programa Delphi la interfaz se crea de una forma visual, tal y como estamos viendo en los últimos puntos de este capítulo. Sin embargo, siempre existirán acciones que necesiten la escritura de algo de código para poder realizarse. Imagine, por ejemplo, que usted quiere escribir un programa que muestre una ventana con un botón, de tal forma que al pulsarse éste aparezca un mensaje con un saludo. La parte visual consistiría en insertar el botón en el formulario, establecer el título, su posición y dimensiones, color, etc. Al pulsarse el botón, momento en el cual se generaría un evento, se debería ejecutar un método en el que previamente nosotros habríamos escrito el código necesario para mostrar el citado mensaje. La mayor parte del código de un programa Delphi estará siempre asociado a algún evento, al que accederemos mediante la página de eventos del Inspector de objetos o bien haciendo doble clic sobre el componente. En cualquier caso, Delphi se encargará de establecer el nombre de los métodos, determinar los parámetros necesarios y sus tipos, de tal forma que nosotros tan sólo deberemos introducir las sentencias que deseemos ejecutar, sin preocupamos por cómo se transferirá el control a dicho código, apartado del que se ocupará adecuadamente el componente afectado. A pesar de lo dicho, y tal y como veremos en un capítulo posterior, en Delphi también puede existir código que no esté directamente ligado a un determinado evento de un componente. Podemos, por ejemplo, escribir

Page 29: DELPHI 5

código para definir un nuevo objeto o crear un determinado procedimiento que después será usado desde el programa. El código de un programa Delphi se estructura en lo que denominan módulos, que son archivos de texto conteniendo código. Cada uno de los formularios de un proyecto tiene asociado un módulo, pudiendo además existir módulos independientes que nosotros añadamos para definir funciones, procedimientos u objetos. La edición de código en Delphi se realiza en la ventana de Editor de código, una ventana que normalmente permanece bajo el formulario en la que se está trabajando. Podemos alternar entre el formulario y el correspondiente módulo de código simplemente pulsando la tecla <F12>. El Editor de código de Delphi es un editor multi-archivo, lo que quiere decir que es capaz de mantener abiertos múltiples módulos de código de forma simultánea. En la parte superior de la ventana, como puede ver en la figura 2.8, existirá una pestaña por cada módulo abierto, lo que nos permitirá cambiar de forma rápida y simple entre el código de los distintos archivos. También disponemos de dos botones, en la parte superior derecha de la ventana, que facilitan la navegación adelante y atrás por los últimos puntos en los que hemos estado, de forma similar a los botones de una navegador Web. Por defecto a la izquierda del Editor de código se muestra el Explorador de código.

Figura 2.8. Ventana del editor de código de Delphi

Page 30: DELPHI 5

Para trabajar con el Editor de código de Delphi utilizaremos las mismas combinaciones de tecla que estamos acostumbrados a usar en otras aplicaciones, aunque podemos seleccionar uno de entre varios modos de funcionamiento diversos. Este editor tiene la característica de diferenciar en el código, usando colores y estilos, los diferentes elementos de cada sentencia, como pueden ser palabras reservadas, variables o comentarios. Todas estas características y algunas más, podremos personalizarlas mediante la ventana de opciones del editor, a la que podremos acceder desplegando el menú emergente, pulsando el botón derecho del ratón sobre la ventana del editor, y seleccionando la opción Properties. En la ventana, que cuenta con cuatro páginas, podremos configurar desde los colores de cada parte del código hasta el aspecto del cursor, pasando por los puntos de los fabuladores o la columna en la que se mostrará el margen derecho. El Explorador de código es una lista jerárquica en la que se muestran las clases, variables y, en general, todos los elementos existentes en el módulo de código seleccionado. Podemos hacer doble clic sobre cualquier elemento para ir directamente a él, sin necesidad de buscar secuencialmente por el código. Podríamos decir que el Explorador de código nos ofrece una imagen "a vista de pájaro" del módulo de código. Ventanas acoplables Al acceder al Editor de código hemos visto que existe otra ventana, el Explorador de código, acoplada a su izquierda. La ventana acoplada tiene, en este caso en su parte superior, una doble línea y un botón de cierre. Tomando la ventana por esa doble línea y arrastrándola podemos "sacar" la ventana de tal forma que sea una ventana independiente más. También podemos realizar la operación contraria, acoplando una ventana que inicialmente era independiente. Puede intentar, por ejemplo, acoplar el Inspector de objetos simplemente tomando la ventana por su barra de título y moviéndola hasta la posición deseada de la ventana a la que va a acoplarse. En la figura 2.9 puede ver cómo se han acoplado el Explorador de código, el Editor de código, el Gestor de proyectos y el Inspector de objetos. Podemos maximizar este conjunto de ventanas como si fuesen un todo.

Page 31: DELPHI 5

Figura 2.9. Varias ventanas acopladas La configuración actual del escritorio, disposición y tamaño de las diferentes ventanas, puede guardarse pulsando el botón Save current desktop, que se encuentra en la parte superior de la ventana principal, a la derecha de una lista desplegable. Al pulsar dicho botón deberá introducir un nombre para la configuración. Puede guardar varias configuraciones diferentes, seleccionando de la lista desplegable la que más le interese en cada momento. Esto le ahorrará tener que colocar las ventanas cada vez que trabaje en un nuevo proyecto.

2.5.8. Ejecución de un programa El fin último de todo el proceso que se está describiendo, desde la inserción del componente hasta la escritura del código, es conseguir un programa que sea posible compilar y ejecutar. Podemos realizar este proceso, ejecutando el programa desde el propio entorno de Delphi, simplemente pulsando la tecla <F9>. En caso de que el proceso de compilación se detenga por haber encontrado algún error en el código, el Editor de código mostrará en la parte inferior una lista con los errores encontrados, lista que podremos usar para desplazamos a las diferentes líneas que contienen esos errores.

Page 32: DELPHI 5

Suponiendo que no exista error alguno el programa se compilará y será ejecutado. En el momento en que éste termine, lo que conseguiremos generalmente cerrando la ventana principal, el control volverá automáticamente a Delphi. Habitualmente el proceso de ejecución de un programa desde el propio entorno de Delphi se repite una y otra vez, con el fin de ir viendo si el funcionamiento del programa es correcto y se ajusta a lo que nosotros deseamos conseguir. A pesar de ello no debe preocupamos, ya que la compilación del código por parte de Delphi es muy rápida.

3. Proyectos

3.1. Introducción Toda aplicación desarrollada con Delphi parte de un proyecto, nombre con el que se denomina al conjunto de formularios, módulos de código y cualquier otro elemento que forme parte del programa. El proyecto que se abre por defecto, al iniciarse Delphi o seleccionar la opción New Application, tan sólo cuenta con un formulario y el módulo de código asociado. A este proyecto mínimo nosotros podemos ir añadiendo otros elementos, como veremos en los puntos siguientes. Cada proyecto Delphi se compone de múltiples archivos, en los cuales se almacena información de cada formulario, el código de cada módulo, los recursos y la propia definición del proyecto. En los puntos siguientes conoceremos la mayoría de estos tipos de archivo.

3.2. Gestión del proyecto Además de saber cómo trabajar con un formulario o cómo modificar las propiedades de un componente, temas que se trataron en el capítulo anterior, es también de vital importancia que sepamos cómo gestionar el proyecto que deseamos crear. Comenzaremos por ver cómo se crea un nuevo proyecto y cómo podemos establecer un proyecto por defecto, para a continuación tratar el funcionamiento del Gestor de proyectos de Delphi y conocer los diferentes proyectos predefinidos que podemos encontrar en el Depósito de objetos.

3.2.1. El proyecto por defecto Cada vez que iniciamos un nuevo proyecto, al arrancar Delphi o utilizar la opción New Application del menú File, abrimos el proyecto por defecto, que puede ser tan simple como el que hay establecido inicialmente, con un solo formulario, o tan complejo como una aplicación completa enteramente desarrollada, ya que nosotros podemos indicar cuál deberá ser el proyecto que se utilice por defecto. Seleccionando la opción Repository del menú Tools accederemos a una ventana de personalización del Depósito de objetos. Eligiendo en la lista de la izquierda el elemento denominado Projects, en la lista de

Page 33: DELPHI 5

la derecha se mostrarán las plantillas de proyectos que existen actualmente en el depósito, cualquiera de las cuales puede ser establecida como proyecto por defecto. En la figura 3.1 puede ver cómo se ha seleccionado la plantilla Win95/98 Logo Application como proyecto por defecto, de tal forma que cada vez que se inicie un nuevo proyecto aparezcan los elementos característicos de una aplicación para Windows 95/98. Aunque, inicialmente, en el Depósito de objetos tan sólo existen los proyectos que se pueden ver en la figura 3.1, nosotros podemos añadir cualquier otro proyecto. Suponga, por ejemplo, que todas las aplicaciones que usted va a desarrollar van a contar con un formulario y elementos comunes, como pueden ser las opciones del menú o algunos componentes. Tras preparar una plantilla del proyecto con todos esos elementos comunes, habiendo partido de un proyecto en blanco, no tendremos más que seleccionar la opción Add To Repository del menú Project, indicando en la ventana que se muestra el título del proyecto, la descripción, la página del Depósito de objetos en la que deseamos que aparezca, el nombre del autor y opcionalmente un icono.

Figura 3.1. Selección de un proyecto por defecto

3.2.2. Uso del Gestor de proyectos Independientemente de cuáles sean los elementos existentes en el proyecto por defecto, una vez que éste ha sido creado nosotros podemos manipularlo, añadiendo o eliminando formularios o módulos de código.

Page 34: DELPHI 5

Para ello haremos uso de algunas opdones, como New Form o Add To Project y, además, utilizaremos el Gestor de proyectos de Delphi. Para hacer aparecer la ventana del Gestor de proyectos, que se muestra en la figura 3.2, deberemos utilizar la opción Project Manager del menú View, a no ser que en el área de botones de acciones rápidas exista un botón que nos permita realizar esa misma función. La ventana mostrada consta, básicamente, de una lista jerárquica en la que se enumeran los proyectos existentes y los elementos de cada uno de ellos. En la parte superior tenemos unos botones que nos permitirán añadir nuevos proyectos, eliminar cualquiera de los existentes o activar el seleccionado en ese momento. También puede seleccionarse un proyecto directamente, desde la lista desplegable que hay a la izquierda de los botones. En la barra de estado, situada en la parte inferior, podemos ver el nombre del archivo en que está contenido el elemento seleccionado, incluyendo el camino completo en el que se encuentra en disco. Por cada una de los elementos se indica el nombre del módulo de código, el nombre del formulario, en caso de que dicho módulo esté asociado a una, y por último el camino en que se encuentra el elemento. Pulsando el primer botón existente en la parte superior podremos añadir un nuevo proyecto al grupo actual. Esto nos permite trabajar simultáneamente con varios proyectos, lo cual es muy útil cuando existen dependencias entre ellos. Si abrimos el menú emergente correspondiente a un proyecto, pulsando el botón derecho del ratón tras haber situado correctamente el puntero, encontraremos una primera opción, Add, que nos permite añadir cualquier módulo o formulario ya existente en disco, simplemente facilitando el camino y nombre. La acción contraria, eliminar un elemento del proyecto, la podemos efectuar seleccionando en la lista el elemento que deseamos borrar y seleccionando la opción Remove from project de su menú emergente.

Page 35: DELPHI 5

Figura 3.2. La ventana del Gestor de proyectos de Delphi Para abrir un determinado formulario o módulo de código, basta con hacer doble clic sobre el elemento correspondiente en el Gestor de proyectos. Como ya sabemos, podemos alternar entre un formulario y su correspondiente módulo de código con tan sólo pulsar la tecla <F12>. Opciones de proyecto Mediante la opción Options, disponible en el menú emergente de cada proyecto, podremos acceder a la ventana de opciones de proyecto. Esta ventana cuenta con múltiples páginas, en las cuales podremos indicar cuál es el formulario principal del programa, en caso de que existan varias en el proyecto, el título de la aplicación, el icono con el que aparecerá en la barra de tareas o el Explorador, el archivo de ayuda y otra serie de opciones que afectarán al proceso de compilación y generación del ejecutable final. En la parte inferior de la ventana, que puede* ver en la figura 3.3, existe una opción llamada Default, que podemos activar si deseamos que las modificaciones que vamos a realizar en las opciones de proyecto sean tomadas como valores por defecto para proyectos posteriores.

3.2.3. Proyectos predefinidos Ya en un apartado anterior hemos visto cómo podíamos añadir al Depósito de objetos una plantilla de proyecto y cómo ésta podía convertirse en el proyecto por defecto, de tal forma que se utilizase al iniciar cada nuevo proyecto. En ocasiones, sin embargo, podemos necesitar iniciar un proyecto con unas características diferentes a las del proyecto por defecto. En este caso, en lugar de usar la opción New Application del menú File utilizaremos la opción New, que nos

Page 36: DELPHI 5

llevará a la ventana principal del Depósito de objetos. En ella podremos abrir la página Projects y seleccionar cualquiera de las plantillas disponibles.

Figura 3.3. Ventana de las opciones de proyecto Para crear un nuevo proyecto a partir de una plantilla del Depósito de objetos, lo único que habremos de hacer será un doble clic sobre el icono que representa al modelo de proyecto que nosotros queremos. También podemos seleccionar dicho icono y pulsar a continuación el botón OK. Acto seguido aparecerá una ventana, en la que tendremos que indicar el camino en el que deseamos alojar los archivos del nuevo proyecto que se va a crear. Lo habitual es utilizar un directorio independiente para cada proyecto, de tal forma que no se puedan dar confusiones entre los elementos de distintos proyectos.

3.3. El archivo de proyecto Delphi relaciona todos los elementos de un proyecto generando un módulo de código, que se almacena en un archivo con extensión DPR. Es a éste al que se llama archivo de proyecto y, al tratarse de un módulo de código, podemos editarlo como cualquier otro en el Editor de código de Delphi. Para ello bastará con seleccionar la opción View Source del menú Project, que abrirá una nueva página en la ventana del Editor de código conteniendo el archivo de proyecto.

Page 37: DELPHI 5

Por regla general, nunca necesitaremos manipular directamente el código del archivo de proyecto, ya que de ello se ocupa Delphi. En cualquier caso, su estructura es bastante simple como podemos ver en la figura 3.4. Básicamente, contiene una referencia a cada uno de los archivos que almacenan los módulos de código, tras la cláusula Uses, y una llamada al método CreateForm() por cada formulario a crear. La ejecución del programa se lleva a término en el momento en que se llama al método Run().

Figura 3.4. Archivo con el código de un proyecto simple

3.4. Archivos de formulario El contenido de cada uno de los formularios existentes en el proyecto: los componentes, valores de sus propiedades y los enlaces de los eventos con el código, se almacena de forma individual en un archivo con extensión DFM. A la hora de guardar un proyecto, Delphi siempre nos preguntará el nombre que deseamos dar a cada uno de los archivos. Un archivo DFM es, en realidad, un archivo de texto que, aunque no es habitual, puede ser editado manualmente. El contenido de este archivo se almacena en el ejecutable final como un recurso más, que durante la ejecución será usado para crear adecuadamente cada formulario. Para editar un archivo DFM, que es el código que define un formulario, tan sólo hay que pulsar la combinación <Alt+F12> mientras estamos en el formulario, o bien desplegar el menú emergente de ésta y seleccionando la opción View as Text. A partir de ese momento podemos

Page 38: DELPHI 5

modificar el contenido del archivo, lo que indirectamente afectará al diseño del formulario. Cuando hayamos finalizado podemos usar la misma combinación de teclas, o bien la opción View as Form del menú emergente del Editor de código, para convertir de nuevo el archivo de texto en un formulario. Ha de tener en cuenta que mientras tenga abierto el archivo DFM en el editor no le será posible acceder al módulo de código asociado al formulario. Podemos añadir nuevos formularios a nuestro proyecto de diversas formas. La opción New Form del menú File, o el botón equivalente, crearán un formulario vacío o el formulario que se haya fijado por defecto en las opciones del Depósito de objetos. Mediante la opción New del mismo menú podemos acceder al Depósito de objetos, seleccionando cualquiera de las plantillas de formularios ya existentes.

3.5. Módulos de código Todo formulario lleva siempre asociado un módulo de código, un archivo con extensión PAS y con el mismo nombre que el archivo DFM en el que se almacena la definición del formulario. La finalidad de este módulo de código, como su propio nombre indica, es contener el código correspondiente a los métodos asociados a los eventos de los componentes y del propio formulario. Adicionalmente, en un módulo de código pueden existir otros procedimientos, funciones y definiciones. Ya sabemos cómo podemos editar un archivo de este tipo, mediante el Editor de código. Un archivo PAS es en realidad un archivo de texto, al igual que los archivos DPR y DFM, por lo que puede ser editado con cualquier otro editor sin ningún problema. Además de los módulos de código asociados a cada uno de los formularios, en el proyecto pueden existir también módulos de código independientes, conteniendo tan sólo definiciones de objetos, procedimientos o funciones, que posteriormente podrán ser utilizados desde otros puntos. A todos los efectos, estos módulos independientes se gestionarán de igual forma que los que están asociados a un formulario. Cada vez que se añade un nuevo formulario al proyecto, de forma indirecta también se está añadiendo un módulo de código. Si deseamos insertar en el proyecto un módulo de código independiente, no tenemos más que abrir el Depósito de objetos y hacer doble clic sobre el icono Unit de la primera página.

3.6. Grupos de proyectos Como se ha comentado anteriormente, Delphi 5 cuenta con un Gestor de proyectos que permite trabajar de forma simultánea con varios proyectos. En un determinado momento, no obstante, sólo uno de ellos

Page 39: DELPHI 5

estará activado, mostrándose con un tipo de letra resaltado para diferenciarse del resto. Los grupos de proyecto son archivos con extensión BPG. En realidad son archivos de texto, conteniendo el código preciso para generar todos los proyectos que componen el grupo. Podemos ver este código simplemente seleccionando la opción View Project Group Source del menú emergente. Si trabajamos con un grupo de proyectos y guardamos el archivo BPG, posteriormente podremos volver a abrirlos con un solo paso, sin necesidad de recuperar de forma individual cada uno de ellos. Tan sólo tendremos que recuperar el archivo BPG, es decir, el grupo de proyectos.

3.7. Otros elementos de un proyecto Además de los elementos que se han citado en los puntos anteriores, un proyecto puede contar con otros archivos adicionales que no han de estar necesariamente asociados a un formulario ni contener código. Entre estos elementos podemos encontrar, por ejemplo, el archivo de ayuda, que se distribuirá conjuntamente con la aplicación, o los archivos de recursos. Estos últimos, que pueden contener iconos, imágenes, cadenas de texto y muchos otros elementos, sí que pasan a formar parte del ejecutable, aun cuando la referencia a ellos no se realiza mediante ninguna opción de menú ni botón del Gestor de proyectos, sino utilizando una directiva: {$RI}.

Page 40: DELPHI 5

4. Fundamentos de Object Pascal

4.1. Introducción A pesar de que la mayor parte del desarrollo de una aplicación con Delphi se realiza de forma visual, insertando componentes y modificando propiedades, ningún programa tendrá una aplicación práctica si tan sólo cuenta con una interfaz, sin un código de programa que sea capaz de procesar las entradas de datos y generar unos resultados, o lo que es lo mismo, sin que el programa sea interactivo. Consecuentemente, el conocimiento del lenguaje que utiliza Delphi es de suma importancia, ya que de lo contrario difícilmente podremos escribir nada de código. Este lenguaje, llamado Object Pascal, es un heredero del Turbo Pascal de Boriand y, seguramente, el Pascal más evolucionado que existe actualmente. Se trata de un lenguaje completamente estructurado, con orientación a objetos, claro y muy elegante en cuanto respecta a la claridad y eficacia del código. A lo largo de este capítulo vamos a conocer los fundamentos de Object Pascal con el fin de aprender a declarar variables, construir expresiones, conocer las estructuras de control y adquirir algunos conocimientos de las capacidades de orientación a objetos. Como se ha dicho, éstos serán los fundamentos que nosotros necesitemos para comenzar, pero Object Pascal es un lenguaje mucho más amplio y complejo, que difícilmente es posible abarcar en un libro de este tipo.

4.2. Estructura general El código de un programa Object Pascal se almacena en lo que se denominan módulos o units. Un módulo no es mas que un archivo de texto, generalmente con extensión PAS, que tiene una estructura general siempre idéntica, contando con una cabecera, en la que se establece el nombre del módulo, y un bloque principal, delimitado entre las palabras Begin y End, en el que se alojarán las sentencias o código a ejecutar. Las siguientes cuatro líneas de código, por ejemplo, serían un programa completo en Object Pascal. Program Prueba; Begin ShowMessage('Hola') ; End. En este programa existen una serie de palabras reservadas, que en el Editor de código de Delphi aparecerán en negrita. Estas palabras son Program, Begin o End, reconocidas directamente por el lenguaje Object

Page 41: DELPHI 5

Pascal, mientras que la línea que comienza con ShowMessage(), por el contrario, es una llamada a un procedimiento.

4.2.1. El punto y el punto y coma Si se fija en el programa anterior, o abre cualquier módulo de código en el editor de Delphi, observará que al final de algunas líneas existe un punto y coma, que se usa como separador de sentencias. El punto y coma se dispondrá, por regla general, al final de cada sentencia que no esté compuesta estrictamente de una palabra reservada, como Begin, tras la cual no existe este elemento. También se puede omitir el punto y coma en la sentencia anterior a la línea que contiene la palabra End. Ciertas construcciones, como el condicional lf..Then..Else, exigen que el punto y coma sea omitido en la sentencia anterior a la palabra Else. La palabra End, a pesar de ser una sentencia compuesta por una sola palabra reservada, por regla general siempre irá seguida de un punto o punto y coma. A medida que vayamos viendo código en Object Pascal, usted aprenderá intuitivamente cuándo debe y cuándo no debe utilizar un punto y coma. En el código de un programa pueden existir múltiples bloques, delimitados por las citadas palabras Begin y End y precedidos por un nombre. Al bloque más exterior, que se inicia con el primer Begin sin nombre y se termina con el End correspondiente, es al que se denomina bloque principal del módulo. Dentro de éste pueden existir otros bloques, por ejemplo definiendo métodos y fundones, tipos, etc. La palabra End del bloque principal, que determina el final del código, irá siempre seguida de un punto, en lugar de hacerlo con un punto y coma.

4.2.2. Módulos y la cláusula Uses Como se dijo en un capítulo anterior, una aplicación desarrollada con Delphi suele estar compuesta de múltiples módulos, cada uno de los cuales almacena el código de un formulario o cualquier otro código necesario, como puede ser el de la definición de objetos o procedimientos y funciones. La cabecera de un módulo se inicia con la palabra Unit y no con la palabra Program. En realidad, el módulo que contiene en su cabecera la palabra Program es un módulo más, en cuanto a su formato y contenido, pero se diferencia de los demás en que él es el módulo principal, por el que se iniciará la ejecución del programa. En un programa Delphi el módulo principal es el archivo de proyecto, un archivo con extensión DPR cuya estructura fue ya comentada brevemente en el capítulo anterior. El resto de los módulos de la

Page 42: DELPHI 5

aplicación, por tanto, contienen código que no será ejecutado directamente, hasta en tanto desde el módulo principal, u otro módulo en ejecución, se realice la llamada oportuna. La división del código de una aplicación en múltiples módulos es algo muy práctico, ya que permite un fácil mantenimiento del código, en contraposición a la construcción monolítica en un sólo archivo de texto que se utilizaba antes. Además de los módulos de código que nosotros mismos añadamos a nuestro proyecto, Delphi cuenta con una serie de módulos prefabricados, con código ya ejecutable, preparados para ser usados desde nuestros programas. El código de estos módulos se almacena en archivos con extensión DCU y en ellos podemos encontrar procedimientos, funciones, objetos y componentes a los que tan sólo tendremos que llamar para realizar una acción determinada. Para poder usar el código contenido en un cierto módulo desde otro módulo, es necesario incluir en el primero una cláusula llamada Uses, seguida del nombre del módulo o módulos a usar. Si abre en el Editor de código de Delphi el archivo de un proyecto cualquiera, podrá ver que tras la cláusula Uses se hace una referencia al módulo Forms, en el que se encuentra todo el código necesario para hacer funcionar un formulario, y también a cada uno de los módulos que existan en el proyecto.

4.3.2. Tipos Tanto para declarar una variable como para utilizar un valor constante, es necesario que conozcamos los diferentes tipos de datos con los que puede trabajar Object Pascal. Estos tipos nos permitirán trabajar con números, en diferentes precisiones, cadenas de caracteres, punteros, etc. Cada uno de los tipos está representado por una palabra clave que, de una forma más o menos clara, representa al tipo de dato. En la tabla 4.1 se enumeran los tipos más habituales.

Tipo Valores que puede contener

Byte Números enteros comprendidos entre 0 y 255.

Shortint Números enteros comprendidos entre -128 y 127.

Smallint Números enteros comprendidos entre -32768 y 32767.

Word Números enteros comprendidos entre 0 y 65535.

Integer Números enteros comprendidos entre -2147483648 y 2147483647.

LongInt Igual que Integer.

LongWord Números enteros entre 0 y 4294967295.

Cardinal Números enteros entre 0 y 2147483647.

Int64 Números enteros entre –2^63 y 2^63.

Page 43: DELPHI 5

Boolean True o False ocupando un byte de memoria.

ByteBool Igual que Boolean.

WordBool Igual que Boolean pero ocupando dos bytes de memoria.

LongBool Igual que Boolean pero ocupando cuatro bytes de memoria.

Single Números en coma flotante con hasta 8 dígitos.

Double Números en coma flotante con hasta 16 dígitos.

Real Igual que Double.

Extended Números en coma flotante con hasta 20 dígitos.

Comp Números enteros de hasta 20 dígitos.

Char Un carácter cualquiera.

AnsiChar Lo mismo que Char.

WideChar Carácter de 16 bits para soporte Unicode.

String Cadena de caracteres.

ShortString Cadena de un máximo de 255 caracteres.

AnsiString Cadena de caracteres.

PChar Puntero a secuancia de caracteres terminada con nulo.

PansiChar Lo mismo que PChar.

PwideChar Puntero a secuencia de caracteres de 16 bits.

Pointer Puntero genérico.

Variant Tipo de dato variable.

Tabla 4.1. Tipos de datos Object Pascal Los tipos enumerados en esta tabla pueden ser divididos en dos grupos, según sean tipos genéricos o tipos fundamentales. Los tipos genéricos, como Integer o String, no son iguales en Delphi 1 y en Delphi 5, ya que mientras en el primer caso un entero ocupa 16 bits, en el segundo se almacena en 32 bits, por lo que el rango de valores es diferente. Otro tanto ocurre con el tipo String, que puede almacenar una cadena de 255 caracteres o sin límite, dependiendo de la versión del compilador. En contraposición tenemos a los tipos fundamentales, como Shortint o AnsiString, cuyo tamaño es siempre el mismo. Además de los tipos que puede ver en la tabla 4.1, que son muchos, pueden existir muchos tipos más, ya que Object Pascal permite crear nuevos tipos de datos, como veremos posteriormente. Así, nos podemos encontrar con un tipo llamado TDateTime que nos permite el almacenamiento de fechas y horas.

Page 44: DELPHI 5

4.3.3. Declaración de variables La declaración de una variable puede tener lugar a diferentes niveles o ámbitos, como puede ser en ámbito de módulo, antes del bloque principal, o en ámbito local, en el interior de algún bloque de definición de procedimiento o función. En cualquier caso, el inicio de la sección de declaración de variables estará siempre marcado por la palabra Var. Esta irá seguida de una o más líneas, en cada una de las cuales es posible declarar una o más variables de un determinado tipo. Cada una de estas líneas estará compuesta del identificador de una variable y el tipo, separando ambos elementos por dos puntos. En caso de que vayamos a declarar varias variables del mismo tipo, podemos disponer los identificadores uno tras otro separándolos por coma, especificando al final el tipo. En caso de que la variable que se está declarando sea de tipo String, AnsiString o ShortString, de forma opcional podemos especificar, entre corchetes, el número de caracteres que como máximo podrá contener dicha cadena. Si usamos el tipo AnsiString y no especificamos una longitud máxima, el espado para la cadena se asignará dinámicamente según las necesidades. En el siguiente fragmento de código puede ver cómo se han declarado cuatro variables, una de tipo Byte, dos de tipo Integer y una de tipo String con un límite máximo de 25 caracteres. Var // Declaramos lo siguiente Contador; Byte; // Una sola variable N1, N2: Integer; // Dos variables del mismo tipo Nombre: String [25]; // Una cadena de 25 caracteres máximo

4.3.4. Matrices A veces es necesario mantener múltiples valores del mismo tipo, para los cuales, obviamente, podríamos declarar variables individuales. Esta técnica, sin embargo, no es útil cuando el número de variables es muy grande. Suponga, por ejemplo, que desea almacenar cien números para realizar unos cálculos. Desde luego no sería muy cómodo tener que declarar cien identificadores distintos, gestionándolos de forma individual en posteriores asignaciones y expresiones. En casos como el descrito es mucho más cómodo usar matrices, nombre con el que se conoce a un tipo de variable que se caracteriza por ser capaz de contener múltiples valores del mismo tipo, haciendo referencia a cada uno de ellos mediante un índice. Object Pascal permite crear matrices prácticamente de cualquier tipo, incluso es posible crear matrices de matrices, como veremos en un

Page 45: DELPHI 5

momento. Esto quiere decir que podemos crear matrices no sólo de números, sino también de cadenas, de objetos o de cualquier otro tipo que nosotros hayamos definido previamente. Declarar una matriz La declaración de una matriz es muy similar a la declaración de cualquier otra variable, si exceptuamos que delante del tipo, tras los dos puntos, habremos de disponer la palabra Array, indicando entre corchetes el rango de elementos que deseamos que tenga la matriz y, a continuación, la palabra Of seguida del tipo. En Object Pascal un rango de valores se escribe especificando el primer valor seguido de dos puntos y el último valor del rango. Así, si deseamos declarar una matriz de, por ejemplo, veinticinco elementos de tipo Byte, podríamos usar la sentencia siguiente: Matriz: Array[1..25] Of Byte; En este caso la variable Matriz es una matriz que contiene veinticinco elementos, con índices que van desde el 1 hasta el 25. Cada uno de los elementos es una variable de tipo Byte, que podemos usar en cualquier ámbito y expresión en la que se admita un valor de este tipo. La matriz anterior sería de una sola dimensión, es decir, podemos representarla como si fuese una lista de celdillas, en cada una de las cuales se almacena un valor individual. Podemos también definir matrices de dos, tres o cualquier otro número de dimensiones. Supongamos que deseamos tener una matriz en forma de tabla, con diez columnas por quince filas, lo que haría un total de ciento cincuenta casillas, en cada una de las cuales deseamos almacenar una cadena de caracteres. Podríamos declarar esta matriz de cualquiera de las dos formas siguientes: Cadenas1: Array[1..10, 1..151 Of String; Cadenas2: Array[I..10] Of Array[1..15] Of String; En el primer caso hemos facilitado el rango de la primera dimensión y el de la segunda dimensión en los mismos corchetes, separándolos por una coma. La segunda versión es algo más compleja y lo que se hace es declarar una matriz de diez elementos, cada uno de los cuales contiene una matriz de quince elementos de tipo String. En realidad el resultado final es el mismo, es decir, tendríamos una matriz bidimensional de cadenas de caracteres. De forma similar podríamos declarar matrices con cualquier otro número de dimensiones. Referencia a los elementos de una matriz Aunque en ocasiones, dependiendo del contexto, nos puede interesar hacer referencia a una matriz como un todo, en la mayoría de los casos necesitaremos referimos a uno solo de sus elementos, para lo cual

Page 46: DELPHI 5

deberemos utilizar el índice o índices que correspondan a dicho elemento. Para acceder a un determinado elemento de una matriz lo único que habremos de hacer es facilitar el índice, entre corchetes, detrás del identificador. En caso de que la matriz tenga múltiples dimensiones deberemos facilitar un índice para cada una de ellas, bien utilizando unos corchetes para cada índice o separándolos por comas dentro de una única pareja de corchetes. Suponiendo que tuviéramos declarada la matriz Cadenas 1 que se ha expuesto como ejemplo en el punto anterior, podríamos hacer referencia a uno de sus elementos de cualquiera de las dos formas siguientes. El resultado es equivalente y en ambos casos se mostraría el contenido de la cadena correspondiente a la columna 1 fila 5. ShowMessage(Cadenas[1,5]); ShowMessage(Cadenas[1][5]) ;

4.3.5. Definir nuevos tipos Los tipos de datos básicos de Object Pascal, tanto fundamentales como genéricos, pueden ser usados en nuestros programas para construir otros tipos de datos. Estos nuevos tipos pueden ser escalares, en caso de que puedan tomar un número determinado de valores, o bien tipos complejos o estructurados, en caso de que estén compuestos de varios elementos. Entre los tipos del primer apartado, escalares, tenemos los tipos enumerados y los subrangos, además de algunos de los tipos que ya conocemos, como Boolean, Integer, etc. En el segundo apartado, tipos estructurados, nos encontramos con los registros y los conjuntos. También pertenecen a este grupo de tipos las matrices, aunque no necesariamente sus elementos. Es decir, una matriz de enteros es un tipo estructurado, pero un elemento de esa matriz es un tipo escalar. La definición de nuevos tipos se efectúa previamente a la declaración de variables de esos tipos, como es lógico. El apartado del módulo en que se definen tipos se inicia con la palabra Type. Enumeraciones Cuando necesitemos crear una variable para contener un número determinado de valores, no muy extenso, en lugar de usar cualquiera de los tipos predefinidos de Object Pascal, que seguramente no se adecuarán a nuestras necesidades, lo único que hemos de hacer es declarar una enumeración. Suponga que necesita crear una variable que sea capaz de contener un día de la semana y no desea utilizar una variable numérica, puesto que ésta le permitirá asignar cualquier valor.

Page 47: DELPHI 5

La solución sería la declaración de una enumeración como la siguiente: Type { TDiaSemana no ea una variable, sino un identificador de tipo } TDiaSemanas (Lunes, Martes, Miércoles, Jueves, Viernes, Sábado, Domingo); Algo a tener en cuenta, tal y como se indica en un comentario en el fragmento anterior, es que el idendficador TdiaSemana no es una variable, al igual que no lo es la palabra Integer o la palabra String. Este nuevo identificador es un tipo de dato y, como tal, puede ser usado para declarar variables de ese tipo. La forma de declarar variables del tipo TDiaSemana sería idéntica a la ya vista para cualquier tipo de dato, siendo posible también la creación de matrices de este nuevo tipo. En el siguiente fragmento puede ver dos ejemplos de declaración y uso del nuevo tipo. Var Hoy: TDiaSemana; Incidencias: Array[1..5] 0£ TDiaSemana; begin Hoy := Domingo; Incidencias[1] := Miércoles; El nuevo tipo también puede ser usado como índice de una matriz, al igual que cualquier otro tipo escalar. Suponga que desea definir una matriz de enteros en la que poder anotar los gastos diarios. Lo más lógico es que el índice de esta matriz no sea un número, sino uno de los elementos de la enumeración que hemos definido antes. En el siguiente fragmento puede ver cómo se declara la matriz Gastos y cómo se asigna un valor a uno de sus elementos. Observe que para declarar la matriz no es necesario especificar un índice de inicio y fin, ya que el propio tipo TDiaSemana ya cuenta con unos límites, que son los identificadores Lunes y Domingo. var Gastos: ArraytTDiaSeinana] 0£ integer; begin Gastos[Lunes] := 1500; Subrangos En caso de que necesitemos declarar una variable de un tipo ya existente, pero en la que no queremos permitir la asignación de todos los valores, sino sólo de un determinado rango de ellos, será necesario definir un subrango. Este tipo de dato le será útil, por ejemplo, si precisa una variable numérica que tan sólo pueda contener

Page 48: DELPHI 5

los valores 1 a 100, o una variable TDiaSemana a la que sólo sea posible asignar días laborables. La definición de un subrango es muy simple y, de hecho, ya la conocemos, puesto que para establecer los índices de una matriz hemos estado usando subrangos. En el siguiente fragmento puede ver cómo se crean los dos tipos que se acaban de comentar, declarando una variable de cada uno de ellos a la que posteriormente se asigna un valor. Observe que la segunda asignación no es válida, ya que el valor facilitado no está incluido en el subrango TLaborable. { Definimos los tipos } TUnoCien = 1..100; TLaborable = Lunes..Viernes; Var { Y una variable de cada tipo ) N; TUnoCien; Inicio: TLaborable; begin N := 34; // Asignación correcta Inicio := Domingo; // Esto generaría un error Al igual que los tipos enumerados, los subrangos pueden ser usados como índices de una matriz. También podemos crear matrices de un tipo subrango, como de cualquier otro tipo escalar. Conjuntos Podríamos definir un conjunto como una variable capaz de contener uno, varios, todos o ninguno de los elementos de un determinado tipo escalar. Dicho tipo escalar, que se utiliza como base del conjunto, no deberá tener más de 256 valores diferentes, por lo que no es posible usar como base para un conjunto tipos como Integer. No se ha de confundir un conjunto con una matriz, ya que a pesar de ser ambos tipos estructurados, no son equivalentes. Un conjunto no contiene un número de elementos predeterminado y, como se ha dicho, puede estar vacío o contener todos los elementos del tipo base. A diferencia de lo que ocurre con una matriz, no es posible acceder a los elementos individuales de un conjunto como si fuesen variables independientes. Sin embargo, el lenguaje cuenta con una serie de operadores que permiten añadir, eliminar y comprobar la existencia de un elemento en el conjunto. La declaración de un conjunto se construye con las palabras Set Of, seguidas del tipo base. Por ejemplo, suponga que desea declarar una variable que sea un conjunto de días, en la que pueda almacenar los días de la semana en los que ha hecho una copia de seguridad de su disco. La declaración del tipo, la variable y posterior asignación podrían ser las siguientes:

Page 49: DELPHI 5

TDiasSemana = Set Of TDiaSemana; Var Copias: TDiasSemana; begin Copias := [Lunes, Jueves, Domingo]; En este fragmento se asume que el tipo TDiaSemana ya está declarado, en forma de enumeración, por lo que la variable Copias puede contener cualquier combinación de los valores de dicha enumeración. Registros Un registro es también un tipo estructurado o complejo, caracterizándose por contener en su interior múltiples campos o variables, cada una de las cuales puede ser de un tipo diferente, lo que le diferencia de una matriz. Este tipo de dato es muy útil cuando necesitamos mantener una serie de datos de forma conjunta, generalmente porque estén relacionados de alguna forma. Imagine, por ejemplo, que desea crear una agenda telefónica en la que anotar nombres de personas, su número de teléfono y su dirección. En lugar de definir una variable separada para cada dato, es mucho más lógico crear un nuevo tipo de dato, un registro, que nos permita gestionar esos valores conjuntamente. Para definir un registro facilitaremos primero, como es habitual, el identificador con el que se le va a conocer, seguido de un signo = y la palabra Record. En las líneas siguientes tendremos que especificar cuáles serán los componentes del registro, para lo cual iremos facilitando identificadores y tipos para cada miembro, como si estuviésemos declarando variables. La declaración de cada miembro estará terminada con un punto y coma, y el fin de la definición del registros indicará mediante la palabra End. Por tanto, podríamos declarar el registro para mantener los datos de nuestra agenda telefónica de la siguiente forma: Type { Declaramos un registro } TFormAgenda = Record Nombre: String; { Nombre de la persona } Dirección; String; < su dirección > Telefono: String; { y su teléfono } Personal: Boolean; { Indicador de teléfono personal } End; Teniendo ya el registro definido, podemos declarar una variable de tipo TFormAgenda de igual forma que haríamos con cualquier otro tipo. El problema, sin embargo, lo encontraremos a la hora de acceder a los miembros de esa variable, ya que lo habitual es hacer referencia a uno de ellos y no al registro como un todo.

Page 50: DELPHI 5

Siempre que necesitemos acceder a un miembro de un registro, una propiedad, evento o método de un objeto, como pueda ser un componente y, en general, cada vez que para acceder a un elemento tengamos que referimos antes a otro en el que está contenido, deberemos usar el operador de cualificación, que es un punto. Este operador se usará como separador entre los dos identificadores, tal y como puede ver en el siguiente fragmento de código. Var UnaForm: TFormAgenda; begin UnaForm.Nombre :a 'Pedro García'; UnaForm.Dirección := 'Avda. de la Paz, 24'; UnaForm.Telefono := '348123'; UnaForm.Personal :s True; Al escribir este código comprobará que tras introducir el nombre de la variable, UnaForm en este caso, y el punto de separación, Delphi abre una pequeña ventana emergente en la que enumeran los miembros del tipo. Esto evita que tengamos que recordar los nombres asignados evitando fallos también de tecleo, ya que podemos seleccionar directamente el elemento de esa lista. Los miembros de un registro pueden ser de cualquier tipo, incluidas matrices, otros registros e incluso objetos. También es posible crear matrices de registros. Todo esto puede crear una cierta complejidad a la hora de referirse a un determinado miembro. La regla que hemos de tener clara es que siempre hay que especificar primero el identificador de la variable que actúa como contenedor para, a continuación y separado por un punto, escribir el identificador de la variable contenida. Si ésta a su vez es un contenedor tan sólo hemos de repetir el proceso. Observe las siguientes declaraciones y accesos a miembros. Hemos modificado la definición del registro TFormAgenda y creado un nuevo registro, cuya finalidad es facilitarnos el agrupamiento del prefijo, número de teléfono y un indicador. Posteriormente creamos una matriz para cien formas de agenda y asignamos valores a algunos miembros del primer formulario. Type { Registro para mantener un número de teléfono, con su prefijo, y un indicador de si es un teléfono personal ) TTelefono = Record Prefijo, Numero: String; Personal: Boolean; End; { Declaramos un registro } TFormAgenda = Record Nombre: String; { Nombre de la persona } Dirección: String; { su dirección )

Page 51: DELPHI 5

Telefono: Array[l..5] Ot TTelefono; { y sus teléfonos } End; Var { Matriz para cien formularios de agenda ) Formularios: Array[l..1001 Of TFormAgenda; begin Formularios[1].Nombre != 'Pedro García'; Formularios[1].Dirección := 'Avda. de la Paz, 24'; Formularios[1].Telefonodl .Prefijo := '953'; Formularios[1].Telefono[1].Numero := '348123'; Formularios[1].Telefonoll].Personal := True; Formularios[1].Telefono[2] .Prefijo := '953'; Formularios[1].Telefono[2].Numero := '346539'; Formularios[1].Telefono[2].Personal := False;

4.3.6. Constantes y literales No todos los valores que se usan en un programa son facilitados mediante variables, en muchas ocasiones es necesario usar constantes. Estas constantes pueden aparecer en forma de literales o bien representadas mediante un identificador, que nosotros habremos declarado previamente. Una constante puede ser prácticamente de cualquiera de los tipos que hemos conocido hasta ahora: numéricos, de cadena, conjuntos, etc. Las constantes literales numéricas son las más simples, ya que se componen de la secuencia de dígitos correspondiente, sin más. Las literales de tipo cadena se caracterizan por ir delimitadas entre comillas simples, como hemos podido ver en algunos de los ejemplos anteriores. Los conjuntos, por su parte, se representan literalmente mediante los valores del tipo base, que irán separados por comas y entre corchetes. También anteriormente vimos un ejemplo de este tipo de notación. El uso de constantes literales en el código es una técnica no muy aconsejable, ya que si su valor ha de ser modificado nos veremos forzados a buscar todas las apariciones de la constante en el código. Además, si no se incluyen los necesarios comentarios la constante puede convertirse en un elemento oscuro, al no saber exactamente qué es lo que representa. En Object Pascal cualquier constante literal puede ser sustituida por un identificador, que habrá sido previamente declarado. Esta declaración se llevará a cabo en un apartado que se inicia con la palabra Const y en el cual existirá una línea por constante, en la que se especificará el identificador que va a representarla y el valor que va a contener, separando ambos elementos mediante un signo =. Estos identificadores podrán ser posteriormente usados en el mismo contexto en el que se utilizarían las literales, con la única diferencia de que la expresión será bastante más clara.

Page 52: DELPHI 5

Suponga que necesita asignar a una variable el número de minutos que tiene un día, para lo cual deberá multiplicar el número de horas del día por el número de minutos de una hora. En lugar de usar la expresión 24 * 60, que es totalmente correcta, podríamos declarar y utilizar las constantes que se exponen como ejemplo en el siguiente fragmento de código. Const HorasDia = 24; MinutosHora = 60; Var Minutos: Integer; Begin Minutos := HorasDia * MinutosHora; Aunque en este ejemplo las dos constantes contienen un valor numérico, podemos asignarles perfectamente cualquier otro tipo de valor, como puede ser una cadena o un conjunto. Constantes con tipo Las constantes explicadas y puestas como ejemplo en el punto anterior, HorasDia y MinutosHora, son conocidas como constantes sin tipo, ya que a la hora de declararlas simplemente se les asigna un valor, pero no se especifica su tipo, como se hace al declarar una variable. Las constantes de este tipo no pueden contener, por ejemplo, una matriz de valores, o un registro. La declaración de una constante con tipo se lleva a cabo en el apartado Const, como cualquier otra constante, pero el formato de la declaración es idéntico al de una variable, debiendo especificarse el tipo. Tras éste, y separado por un signo =, facilitaremos el valor que deseamos asignar. Las constantes con tipo pueden ser de cualquier tipo que esté permitido para una variable, por lo que es posible crear constantes que contengan matrices de valores, registros, objetos, etc. En el siguiente fragmento de código puede ver cómo se declara una constante conteniendo una matriz de cadenas. Const CadenaMes: Array[1..12] Of String = ('Ene', 'Feb', 'Mar', 'Abr', 'Hay', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'NoV, 'Dio'); Observe que los valores han sido delimitados por unos paréntesis y separados por comas, ya que la constante no contiene un sólo valor, sino una matriz. Esta misma notación se utilizaría en caso de que necesitásemos crear una constante de algún tipo de registro.

Page 53: DELPHI 5

4.4. Ámbito de los identificadores Los identificadores que se declaran y usan en el código de un programa Delphi pueden tener distintos ámbitos, según lo cual estarán o no disponibles en determinados puntos del programa. Básicamente existen dos ámbitos: global y local. Un identificador global es aquel que está disponible desde cualquier punto del código del programa, incluso si dicho punto se encuentra en un módulo distinto. Un identificador local, por el contrario, tan sólo está disponible en el interior del bloque en que se ha declarado.

4.4.1. Identificadores locales Cualquier identificador que se declare en el interior de un procedimiento, una función, la definición de un objeto, o la parte de implementación de un módulo, es un identificador local que, por tanto, sólo puede ser usado por el código correspondiente al bloque en que se ha efectuado dicha declaración, así como por los bloques que puedan existir en su interior. El caso más claro de un identificador local lo encontraremos en la definición de tipos o declaración de variables que se realiza en el cuerpo de un procedimiento o función. Suponga, por ejemplo, que escribimos un procedimiento en cuyo interior declaramos una variable, como se muestra a continuación. { Procedimiento Espera } Procedure Espera; Var N: Integer; { Declaramos una variable entera > Begin N := 5; {Le asignamos un valor } End; La variable N es una variable local, que se caracteriza por ser creada automáticamente cuando la ejecución del programa llega a este procedimiento y ser destruida cuando dicho procedimiento llega a su fin. Por tanto, la variable N sólo existe mientras el procedimiento Espera se está ejecutando, por lo cual es lógico pensar que no es posible usarla desde ningún otro punto del código, como puede ser desde otro procedimiento o desde el mismo bloque principal del módulo. Ámbito de módulo Un identificador también puede ser creado al nivel de módulo, fuera de cualquier procedimiento o función, antes del bloque principal de código. Los apartados Const, Var y Type pueden ser dispuestos en un módulo en dos secciones diferentes: la de interfaz y la de implementación.

Page 54: DELPHI 5

La sección de interfaz de un módulo sirve para definir todos aquellos elementos que el módulo pone a disposición de terceros, mientras que en la sección de implementación, que se inicia con la palabra Implementation, se codifica todo aquello que es interno y que no debe ser usado nada más que por el propio módulo. Declarando un identificador en la parte de implementación de un módulo estaremos creando un identificador que es local al módulo, pero que puede ser usado tanto por el código escrito en el bloque principal como por el contenido en cualquier procedimiento o función que definamos en el mismo módulo. Este identificador, que a pesar de ser local está disponible para todo el código del módulo, tiene un ámbito especial al que se denomina ámbito de módulo. Una variable con ámbito de módulo es útil, por ejemplo, para compartir datos entre los distintos procedimientos y funciones de un mismo módulo. En el siguiente fragmento puede ver cómo se declara una variable con este ámbito, que posteriormente es usada desde diferentes puntos del código. Implementation Var N: Integer; { Procedimiento Espera } Procedure Espera; Begin N := 5; { Asignamos un valor a N } End; { Procedimiento Duplica } Procedure Duplica; Begin N := N * 2; { Multiplicamos por dos el valor ) End; En este ejemplo los procedimientos Espera() y Duplica() están trabajando con la misma variable, que es creada automáticamente al iniciarse el módulo y no al entrar en el cuerpo de ningún procedimiento o función.

4.4.2. Identificadores globales Si deseamos definir un identificador que esté disponible para toda la aplicación y cualquier programa que utilice el módulo en que nosotros vamos a realizar la declaración, es necesario crear un identificador global. Estos identificadores, al igual que los que tienen ámbito de módulo, se declaran fuera de cualquier procedimiento o función, más

Page 55: DELPHI 5

concretamente en la parte de interfaz del módulo, que se inicia con la palabra Interface. Obviamente, una variable global es accesible desde cualquier procedimiento o función definida en el mismo módulo, además de serlo desde cualquier otro punto, como se ha dicho. A medida que vayamos creando programas en Delphi iremos usando identificadores globales, definidos en una serie de módulos que ya acompañan a Delphi, que nos facilitarán muchos aspectos del trabajo.

4.4.3. Problemas de accesibilidad Queda claro, según las explicaciones dadas en los apartados anteriores, que una variable local tan sólo es accesible desde el interior del bloque en que se ha declarado, y cualquier intento de hacer referencia a ella desde un punto extemo a dicho bloque generará un error que el compilador nos comunicará adecuadamente. Los problemas reales de accesibilidad tienen lugar cuando en un determinado ámbito, como puede ser un procedimiento, se declara un identificador, por ejemplo una variable, cuyo nombre ya estaba en uso por otro identificador extemo, declarado al nivel de módulo o global. Tomemos como basé el código siguiente, en el que existen dos procedimientos que modifican una variable llamada N. Sin embargo, dichos procedimientos no están haciendo referencia a la misma variable, ya que mientras el primero asigna el valor a una variable local, el segundo lo está asignando a la variable declarada a nivel de módulo. Implementation Var N: integer; { Procedimiento Espera } Procedure Espera; Var N: integer; Begin M := 5; { Asignamos un valor a N } End; { Procedimiento Duplica } Procedure Duplica; Begin N := N * 2; { Multiplicamos por dos el valor } End; Suponiendo que en el procedimiento EsperaO necesitásemos la variable N local que hemos declarado pero, a pesar de ello, quisiésemos modificar

Page 56: DELPHI 5

el valor de la variable N que hay al nivel de módulo, tendríamos que crear una referencia inequívoca, para lo cual utilizaríamos el operador de cualificación, facilitando primero el nombre del módulo y a continuación el nombre de la variable. De esta forma, la siguiente versión del procedimiento EsperaO sí modificaría la variable N que hay al nivel de módulo, asumiendo que el módulo se llama Unitl. { Procedimiento Espera } Procedure Espera; Var N: integer; Begin N := 5; { Asignamos un valor a N } Unitl.N := 3; { Modificamos la variable a nivel de módulo } End; La misma técnica la podríamos usar si necesitásemos acceder a una variable global que perteneciese a un módulo diferente del actual. En cualquier caso, para que no haya problemas, lo mejor es evitar la duplicidad de identíficadores.

4.5. Expresiones La finalidad de las constantes y las variables, en la mayoría de las ocasiones, es representar a unos valores que actuarán como operandos de una expresión, en base a la cual se desea obtener un determinado resultado. Para componer estas expresiones, además de los operandos, que serían esos valores, también necesitaremos unos operadores, mediante los cuales indicaremos las operaciones que deseamos realizar. Por tanto, podríamos definir una expresión como una combinación de operandos y operadores en el orden correcto y apropiado para obtener un resultado único. Este resultado puede ser de tipo numérico, una cadena, un valor de tipo Boolean, etc. Seguramente la expresión más simple que podemos crear es la de asignación, en la cual toman parte dos operandos: el valor a asignar y la variable de destino, y un operador: el de asignación. Éste, según hemos visto en algunos ejemplos anteriores, se representa mediante los caracteres :=. Las expresiones que es posible crear en Object Pascal las podemos agrupar principalmente en tres grandes grupos: expresiones aritméticas, relaciónales y lógicas. Para componer una expresión de cada uno de estos tipos, disponemos de una serie de operadores, que vamos a conocer en los siguientes puntos.

4.5.1. Operadores aritméticos

Page 57: DELPHI 5

Se denomina aritmética a cualquier expresión en la cual intervienen uno o más operadores aritméticos y cuyo resultado final es numérico. Los operadores aritméticos son binarios, lo cual quiere decir que actúan sobre dos operandos, uno dispuesto a la izquierda del operador y otro dispuesto a la derecha. Los operadores + y - también puede ser usados como operadores unarios, disponiéndolos delante de un número para indicar su signo. En la tabla 4.2 se enumeran los distintos operadores aritméticos, indicándose la operación que efectúan. Es posible construir expresiones aritméticas en las que toman parte más de un operador aritmético, obviamente disponiendo también los operandos correspondientes.

Operador Resultado de la operación que lleva a cabo.

+ La suma de los dos operandos.

- El primer operando menos el segundo.

* El producto de los dos operandos.

/ El cociente de dividir el primer operando entre el segundo.

Div El cociente de una división entrera del primer operando entre el segundo.

Mod El resto de una división entera del primer operando entre el segundo.

Tabla 4.2. Operadores aritméticos

Todos los operadores aritméticos, a excepción del de división, generan un resultado del mismo tipo al que pertenecen los operandos. Es decir, si sumamos dos enteros obtenemos un resultado entero y si sumamos dos números reales obtenemos un número real. El operador de división siempre genera un resultado de tipo real, a pesar de que los operandos sean enteros. Esto significa que no podemos asignar el resultado de una división a una variable entera, se generaría un error. En caso de que deseemos obtener una división entera, sin parte decimal, en lugar del operador/usaremos el operador div.

4.5.2. Operadores relaciónales Una expresión relacional es aquella en la que se comprueba la relación existente entre dos operandos, generándose un resultado de tipo Boolean según que la relación se cumpla o no se cumpla. Si comprobamos, por ejemplo, la relación de igualdad entre dos variables, el valor devuelto será True si efectivamente los valores que almacenan son iguales o False en caso contrario. Los operandos de una expresión relacional no han de ser necesariamente numéricos, como sí es obligatorio en una expresión aritmética, y

Page 58: DELPHI 5

podemos perfectamente comprobar la relación existente entre dos cadenas, por poner un ejemplo. Todos los operadores relaciónales, que se enumeran en la tabla 4.3, son binarios, lo que significa que actuarán siempre sobre dos operandos. En una misma expresión es posible combinar operadores aritméticos y relaciónales, siempre que la composición se haga de forma correcta.

Operador El resultado es True en caso de que ...

= Ambos operandos sean iguales.

<> Los operandos no sean iguales.

< El primer operando sea menor que el segundo.

<= El primer operando sea menor o igual que el segundo.

> El primer operando sea mayor que el segundo.

>= El primer operando sea mayor o igual que el segundo.

Tabla 4.3. Operadores relaciónales

Como se ha comentado anteriormente, los operandos de una expresión relacional no tienen necesariamente que ser numéricos, siendo posible, por ejemplo, comprobar si dos cadenas son iguales, o si una es mayor que la otra. Los operandos de una expresión relacional pueden ser, a su vez, otras expresiones, generalmente de tipo aritmético. En el siguiente fragmento de código puede ver cómo se comprueba una cierta relación, con el operador >, entre el resultado generado por dos expresiones aritméticas. El resultado final, de tipo Boolean, es almacenado en una variable. Var Resultado: Boolean; begin Resultado := 2*5 > 14 Div 2; {El resultado es True } ...

4.5.3. Operadores lógicos En ocasiones nos podemos encontrar con varios resultados de tipo Boolean, por regla general obtenidos de varias expresiones relaciónales, necesitando saber si todos ellos son True, si al menos uno de ellos es True, etc. Esto nos permitirá comprobar en un sólo paso múltiples expresiones relaciónales, sabiendo si todas ellas se cumplen, si se cumple alguna, etc.

Page 59: DELPHI 5

A este tipo de expresiones, en las cuales se trabaja tan sólo con los valores True y False, se las denomina expresiones lógicas o booleanas. Para componerlas usaremos los operadores lógicos que se muestran en la tabla 4.4, que por regla general formarán parte de una expresión más compleja en la que finalmente se obtendrá un valor de tipo Boolean.

Operador Resultado que se genera

Not True si el operando es False o viceversa.

And True si los dos operandos son True, False en caso contrario.

Or True si uno de los dos operandos son True, False si ambos son False.

Xor True si sólo uno de los dos operandos es True.

Tabla 4.4. Operadores lógicos

El operador Not es un operador unario, que irá seguido de un operando sobre el que actuará negándolo, es decir, invirtiendo su valor. La expresión Not True devolvería el valorFalse y, de forma análoga, la expresión Not False devolvería True. Los otros tres operadores son binarios, actuando sobre dos operandos que serán facilitados a la izquierda y derecha del operador, sin importar su orden.

4.5.4. Otros operadores Además de los operadores que se han englobado en las tres categorías anteriores, Object Pascal cuenta con otros adicionales que nos permiten, por ejemplo, concatenar dos cadenas, comprobar la existencia de un elemento en un conjunto u obtener la dirección en la que se almacena el valor que hay contenido en una variable. Concatenación de cadenas La concatenación de cadenas consiste en unir la secuencia de caracteres de dos o más cadenas para generar una sola, cuya longitud final será la suma de las longitudes de cada una de las cadenas que actúan como operandos. En caso de que estemos usando como destino, para almacenar la cadena resultante, una variable de tipo ShortString, si dicha cadena supera los 255 caracteres parte del resultado se perderá. Para concatenar dos cadenas se utiliza el operador +, que en este caso no actuará como operador aritmético, sumando dos números, sino como operador de cadena. En el siguiente fragmento de código puede ver cómo se concatenan dos cadenas, representadas mediante constantes, almacenándose el resultado en una variable, cuyo contenido es a continuación mostrado en una ventana.

Page 60: DELPHI 5

Const Cadenal = 'Cadena 1.'; Cadena2 = 'Cadena 2.'; Var Cadena: String; begin Cadena := Cadenal + Cadena2; ShowMessage(Cadena) ; Operadores entre bits Estos operadores son útiles cuando se necesita manipular los bits individuales de un dato, que habrá de ser necesariamente un tipo entero. En Delphi, desarrollando un programa Windows, serán muy pocas las ocasiones en las que se necesiten este tipo de operaciones. Los operadores de manipulación de bits actúan de forma individual sobre cada uno de los bits de los operandos, generando un resultado del mismo tipo. En la tabla 4.5 se enumeran estos operadores, indicándose la operación que lleva a cabo cada uno de ellos. Observe que los primeros cuatro operadores son los mismos que anteriormente vimos para construir expresiones lógicas.

Operador Operación que se realiza

Not Inversión del valor de cada bit del operando.

And Operación lógica And bit a bit.

Or Operación lógica Or bit a bit.

Xor Operación lógica Xor bit a bit.

Shr Desplazamiento de los bits hacia la derecha.

Shl Desplazamiento de los bits hacia la izquierda.

Tabla 4.5. Operadores de bits

El operador Not actúa sobre un sólo operando, invirtiendo el estado de todos sus bits, de tal forma que los que estén a cero pasarán a estar a uno y viceversa. Los tres operadores siguientes actúan sobre dos operandos, realizando una operación lógica entre los bits de cada uno de ellos, obteniéndose un nuevo conjunto de bits que será el resultado. Los dos últimos operadores tienen por finalidad desplazar los bits del operando hacia la derecha o izquierda, para lo cual tendremos que facilitar a la izquierda del operador el operando sobre el que se va a actuar y a la derecha un número indicando cuantos bits se van a desplazar. Operadores de conjuntos Ya sabemos lo que es un conjunto, cómo se define y cómo se crea una literal de este tipo, incluyendo entre corchetes los distintos elementos del tipo base que van a formar el conjunto. Además del

Page 61: DELPHI 5

operador de asignación, existen otros operadores que también pueden ser usados con conjuntos, permitiendo realizar operadones tales como la unión, intersección o diferencia. Los operadores para trabajo con conjuntos son algunos de los que ya conocemos como operadores aritméticos, pero al trabajar sobre operandos de tipo conjunto actúan de forma diferente. En la tabla 4.6 se enumeran estos operadores y se comenta la acción que llevan a cabo.

Operador Operación que realiza

+ Unión de los conjuntos.

- Diferencia del primer conjunto menos el segundo.

* Intersección de los conjuntos.

In Comprobación de un elemento en un conjunto.

Tabla 4.6. Operadores para trabajo con conjuntos

Los tres primeros operadores toman dos operandos de tipo conjunto y devuelven uno nuevo, que contendrá la unión, diferencia o intersección, tal y como se ha indicado. El último operador toma como primer parámetro un elemento del tipo base y como segundo un conjunto, devolviendo un valor de tipo Boolean mediante el cual se indica si dicho elemento está o no incluido en ese conjunto. En el siguiente fragmento de código puede ver cómo partiendo de un conjunto de días se realizan diversas operaciones, cuyos resultados son almacenados en variables. Delante de cada expresión se ha incluido un comentario indicando el resultado que se almacenará en la variable. Type { Una enumeración } TDiaSemana = (Lunes, Martes, Miércoles, Jueves, Viernes, Sábado, Domingo) ; { Un conjunto que tiene como base a esa enumeración } TDias = Set Of TDiaSemana; Const { Dos constantes con tipo. La primera contiene los días de consulta, y la segunda los dias de oficina } Consulta: TDias = [Lunes, Miércoles, Jueves]; Oficina: TDias ° (Martes, Jueves, Viernes]; Var { Una serie de variables para almacenar los resultados } Trabajados, SoloConsulta, Ambos: TDias; Resultado: Boolean; begin

Page 62: DELPHI 5

{ Trabajados = Lunes, Martes, Miércoles, Jueves y Viernes } Trabajados := Consulta + Oficina; { SoloConsulta = Lunes y Miércoles } SoloConsulta := Consulta - Oficina; { Ambos = Jueves } Ambos := Consulta * Oficina; { Resultado = False } Resultado := Lunes In Ambos; ... Operadores de trabajo con punteros Object Pascal es un lenguaje muy flexible que, además de permitir trabajar con variables en la forma habitual existente en cualquier otro lenguaje, también facilita el uso de punteros. Un puntero es una variable especial, en el sentido de que el valor que almacena no es un dato de nuestra aplicación, sino una dirección de memoria en la cual se encontrará ese dato. Existen dos operadores que debemos conocer para poder trabajar con punteros, que son @ y A. El primero de ellos, dispuesto delante del identificador de una variable, obtiene la dirección de memoria en la cual se encuentra almacenado el valor. Dicha dirección puede ser almacenada en una variable puntero del mismo tipo que el valor. Por ejemplo, suponga que tiene una variable de tipo Integer y desea obtener y almacenar su dirección, para lo cual necesitará un puntero de tipo Integer. La declaración de este puntero y la posterior asignación se realizarían de la siguiente forma. Var N: Integer; { Esta variable es un puntero a un valor de tipo Integer, pero no una variable numérica de ese tipo } PunteroN: ^Integer; begin PunteroN := @N; Observe la declaración de la variable PunteroN. Delante del tipo, Integer, hemos colocado el símbolo ^, lo que indica que esa variable es un puntero a un valor de tipo Integer. Por lo tanto no podríamos asignar directamente a PunteroN un valor numérico, como sí que podemos hacer con la variable N. En la posterior asignación se usa el operador @ para obtener la dirección en la que almacena el valor la variable N, asignándola al puntero. El operador ^ nos servirá en el momento en que necesitemos acceder al valor cuya dirección está almacenada en un puntero. Suponga que desea asignar un valor a la variable N, cuya dirección está almacenada en PunteroN, podría hacerlo de cualquiera de las dos formas siguientes:

Page 63: DELPHI 5

N := 5; PunteroN^ := 5; Las dos asignaciones son equivalentes y, de hecho, al asignar un valor a N también lo estaremos asignando al valor a donde apunta PunteroN ya que, en realidad, existe un sólo valor entero al que estamos haciendo referencia con dos variables.

4.5.5. Orden de prioridad Cuando se escriben expresiones de una cierta complejidad, en la que intervienen múltiples operadores incluso de tipos distintos, hay que tener en cuenta un factor al que se denomina orden de prioridad o preferencia de los operadores. Será este factor el que determine el orden en que se irán evaluando las diferentes subexpresiones, generando resultados parciales que, a su vez, intervendrán como operandos en otras expresiones, hasta finalmente generar un único resultado. El orden de prioridad de los operadores de Object Pascal es el que se indica en la tabla 4.7. Dicho orden puede ser alterado siempre que nos interese mediante el uso de paréntesis. Cualquier subexpresión que esté contenida entre paréntesis será evaluada antes que cualquier otra que no lo esté. Si existen varias subexpresiones entre paréntesis, éstas serán evaluadas de izquierda a derecha. También está permitido el uso de varios niveles de paréntesis, de tal forma que la evaluación siempre comenzará por el nivel más interior.

Nivel Operadores

1 Not, @

2 And, Shl, Shr, *, /, Div y Mod

3 Or, Xor, + y -

4 =, <, <=, >, >=, <>, in

Tabla 4.7. Nivel de prioridad de los operadores en Object Pascal.

Siempre que en una expresión existan varios operadores con el mismo nivel de prioridad, las subexpresiones asociadas a éstos serán evaluadas de izquierda a derecha, a no ser que existan unos paréntesis que alteren este orden. A la hora de componer una expresión compleja, tal y como se está describiendo, hemos de tener en cuenta que la longitud máxima permitida por Object Pascal es de 126 caracteres. Raramente escribiremos una expresión de este tamaño, pero en caso de que lo necesitemos nos veremos obligados a dividirla en varias subexpresiones.

Page 64: DELPHI 5

4.6. Estructuras de control El código de un programa no se ejecuta, por regla general, de forma secuencial, desde la primera sentencia hasta la última. Lo habitual es que en cada caso se ejecuten unas sentencias u otras, dependiendo de ciertas condiciones, y también que algunas sentencias se ejecuten cíclicamente, creando bucles. Estos dos elementos, los condicionales y los bucles son los que básicamente nos permitirán controlar el flujo de ejecución de un programa. Tradicionalmente en un entorno como DOS, el programa tenía un control absoluto del sistema y era responsable de todo el proceso por el cual se ejecutaban unas sentencias u otras dependiendo de las pulsaciones de teclado, o movimientos de ratón que se realizasen. En Windows y entornos similares, es el propio sistema el que se ocupa de gran parte del trabajo y el código de nuestro programa se ejecuta cuando Windows le transfiere el control. Como se dijo anteriormente, la práctica totalidad del código que nosotros escribamos estará asociado a algún evento, por lo que su ejecución tendrá lugar cuando dicho evento se produzca y no mientras tanto. Será en el interior de esos bloques de código, procedimientos y funciones, en los cuales nosotros usaremos las siguientes construcciones de control.

4.6.1. Condicionales La estructura condicional por excelencia, existente en la práctica totalidad de lenguajes de alto nivel, es la sentencia If..Then..Else. En Object Pascal, además, disponemos también de la construcción Case..Of, de suma utilidad como veremos en un momento. Para componer una sentencia condicional, en su versión más simple, tendremos que facilitar tras la palabra If una expresión cuyo resultado sea de tipo Boolean, y tras la palabra Then una sentencia a ejecutar en caso de que dicha expresión tenga como resultado el valor True, o dicho de otra forma, cuando la expresión sea cierta. Observe la siguiente sentencia condicional, muy simple, en la cual se comprueba si el valor de una variable, llamada N, es mayor que cinco. En caso de que la evaluación de esta expresión dé como resultado True, porque efectivamente la variable contenga un valor mayor que cinco, se ejecutará la llamada al procedimiento ShowMessage(), que ha sido dispuesto detrás de la palabra Then. If N > 5 Then ShowMessage('Invalido'); Aunque en este ejemplo se ha escrito en una sola línea toda la sentencia condicional, realmente una línea lógica de código no termina hasta en tanto no se encuentre el punto y coma, por lo cual podríamos dividir la línea anterior dejándola como se muestra a continuación.

Page 65: DELPHI 5

if N > 5 Then ShowMessage('Invalido'); Aquí se ha usado lo que se conoce como identación, escribiendo la llamada a ShowMessage() algo más adentro que la palabra If. Esto hace que visualmente sea muy rápido darse cuenta de que esa línea es dependiente de la anterior y que, en este caso concreto, no se ejecutará siempre, sino sólo cuando la expresión sea cierta. En el caso que se ha puesto como ejemplo tan sólo se ejecuta una sentencia cuando la condición se cumple. En la práctica, es posible que necesitemos ejecutar múltiples sentencias. En este caso tendremos que crear un bloque, mediante las palabras Begin y End, delimitando las sentencias que se habrán de ejecutar. A continuación puede ver un fragmento de código en el que además de mostrarse un mensaje, en caso de que la condición se cumpla, también se modifica el valor de N. If N > 5 Then Begin ShowMessage('invalido'); N := 0; End; Las sentencias que en nuestro programa existan tras el condicional If serán ejecutadas siempre, independientemente de que el resultado de la expresión sea cierto o falso. Está claro que si detrás del código anterior escribimos cualquier otra sentencia, ésta no se verá afectada por la condición, al encontrarse fuera del bloque. A veces, sin embargo, puede interesamos que una o varias sentencias se ejecuten sólo en caso de que el resultado de la expresión sea falso. Suponga que deseamos completar el código de los ejemplos anteriores, de tal forma que se muestre un mensaje si el valor es inválido y otro cuando sea válido. El siguiente código podría parecer adecuado, pero su funcionamiento no será el esperado. If N > 5 Then ShowMessage('Invalido'); { Esta sentencia se ejecutará siempre } ShowMessage('Valido') ; La segunda llamada a ShowMessage() se realizará siempre, por lo que aun cuando N sea mayor que cinco y se haya mostrado el primer mensaje, a continuación se mostrará el segundo mensaje. La solución a este problema la encontraremos en la palabra Else, que como parte de una sentencia condicional es la encargada de ejecutar el código que le sigue sólo en caso de que la expresión haya sido False. De esta manera, lo único que necesitamos es realizar la modificación siguiente.

Page 66: DELPHI 5

If N > 5 Then ShowMessage('Invalido') Else ShowMessage('Valido'); Observe que se ha eliminado el punto y coma que había tras la primera llamada a ShowMessage(), ya que delante de la palabra Else no debe existir punto y coma. Ahora el resultado de la ejecución de este código sí será el esperado. No existe ninguna restricción en cuanto al tipo de sentencia que puede ir tras la palabra Then o la palabra Else, por lo cual puede ser a su vez otra sentencia de tipo condicional. Esto nos permitiría crear decisiones anidadas, como la que se muestra a continuación. If N > 5 Then ShowMessage('Invalido') Else If N > 3 Then ShowMessage('Casi valido') Else ShowMessage('Valido'); Al crear construcciones de este tipo hemos de tener en cuenta que la parte Else del condicional siempre pertenece al último If que se haya escrito, debiéndose delimitar adecuadamente, en caso necesario, cada uno de los bloques a ejecutar mediante las palabras Begin y End. Decisiones múltiples con operando común Cuando en una sentencia condicional es necesario comprobar múltiples veces la misma expresión, modificando tan sólo uno de los dos operandos, como ocurre por ejemplo en el último fragmento de código propuesto como ejemplo, es posible sustituir la secuencia de decisiones anidadas por otra construcción mucho más clara, como es Case..Of. Tras la palabra Case deberemos disponer el identificador del selector, nombre con el que se conoce al operando común, que va a ser evaluado respecto a múltiples operandos. A continuación, tras la palabra Of, crearemos una lista de valores constantes a comparar con el selector, disponiendo tras cada uno de los elementos la sentencia a ejecutar en caso de que la expresión sea cierta. La construcción finalizará con la palabra End. Al igual que con la sentencia If, Case también permite el uso de la palabra Else para permitir la ejecución de una o varias sentencias que, en este caso, tomarían el control siempre que no se cumpliese ninguna de las condiciones dispuestas en la lista. A continuación puede ver un ejemplo de uso de esta sentencia.

Page 67: DELPHI 5

Case N 0f 0..3: ShowMessage('Válido'); 4, 5: ShowMessage('Casi válido') Else ShowMessage('Inválido'); End; Si el valor N está comprendido entre cero y tres se mostrará el primer mensaje, si es cuatro o cinco el segundo y en caso contrario el tercero. Observe que el primer valor de la lista es un subrango y el segundo una enumeración. Obviamente también podemos disponer valores simples, siempre que éstos sean del mismo tipo que el selector, debiendo ambos siempre pertenecer al grupo de los escalares.

4.6.2. Bucles Un bucle es una estructura de control de repetición que nos permite ejecutar una misma sentencia múltiples veces. El número de veces que se ejecuta un bucle puede ser conocido de antemano, en cuyo caso se suele usar un bucle por contador, o bien no serlo, utilizándose entonces un bucle condicional. Al igual que ocurre con las sentencias condicionales que hemos conocido en el punto anterior, podemos asociar varias sentencias a un bucle delimitando un bloque, para lo cual dispondremos la palabra Begin antes de la primera sentencia ejecutable y la palabra End detrás de la última. Bucles por contador Para crear un bucle de este tipo usaremos la instrucción For, existente también en otros lenguajes, como BASIC o C. A este bucle se le llama así por estar controlado mediante una variable numérica, que actuará como contador y cuyo identificador facilitaremos tras la palabra For. El número de veces que se repetirá el bucle dependerá de los valores inicial y final que asignemos al contador, sabiendo que éste se irá incrementando o decrementando en una unidad a cada vuelta. La estructura general de este tipo de bucle sería la siguiente: For Contador := Inicio To Fin Do SentenciaAEjecutar; En caso de que el valor de inicio sea mayor que el de fin y, por tanto, el contador deba ir decrementándose en lugar de incrementándose, cambiaremos la palabra To por DownTo. El contador de un bucle de este tipo debe ser necesariamente de tipo escalar y haberse declarado como una variable local al bloque en el que se va a ejecutar el bucle. Es decir, si vamos a crear un bucle en

Page 68: DELPHI 5

el interior de un determinado procedimiento, no podremos utilizar como contador una variable que está declarada al nivel de módulo. Bucles condicionales Existen dos tipos diferentes de bucles condicionales, según que la expresión que va a controlar la ejecución del bucle se evalúe al principio, antes de entrar en él, o al final. Un bucle del primer tipo tiene sentido cuando las sentencias sólo han de ejecutarse en caso de que la expresión sea cierta, de tal forma que si no es así no llegará a ejecutarse ni una sola vez. Un bucle en el que se compruebe la expresión al final, por el contrario, será ejecutado al menos una vez, ya que primero se ejecutan las sentencias que hay en su interior y después se comprueba si hay que seguir o no. La estructura del primer tipo de bucle condicional es la que se muestra a continuación. Como puede ver, tras la palabra While se facilita la expresión condicional, de tal forma que mientras el resultado sea True se estará ejecutando continuamente la sentencia a ejecutar. While expresión Do SentenciaAEjecutar; El segundo tipo se inicia con la palabra Repeat, tras la cual se escribirán la sentencia o sentencias que van a formar parte del bucle. Al final de éste facilitaremos la sentencia condicional, de tal forma que el bucle terminará en el momento en que la expresión sea cierta. Observe esta diferencia, ya que en el caso anterior el bucle se repite mientras la expresión es cierta y en éste hasta que la expresión es cierta. Repeat SentenciaAEjecutar; Until expresión; Control del bucle Aunque en principio un bucle se estará ejecutando según el número de veces que se haya fijado, o bien basándose en el resultado de la expresión condicional facilitada, el código incluido en el bucle puede alterar su ejecución, haciendo que ésta se reinicie con la siguiente iteradón o bien que se abandone totalmente. Mediante la instrucción Continué provocaremos que el resto de las sentencias del bucle no se ejecuten, transfiriendo el control de nuevo a la primera sentencia. Previamente se comprobará la condición o se actualizará el correspondiente contador, dependiendo del tipo de bucle.

Page 69: DELPHI 5

Desde el interior del bucle también podemos provocar su finalización, simplemente usando la instrucción Break. Lógicamente esta instrucción, al igual que la anterior, no será introducida en el código de un bucle como sentencia independiente, sino que irá necesariamente asociada a algún tipo de condicional. En el siguiente fragmento de código puede ver cómo se usan estas dos instrucciones. Suponiendo que deseamos solicitar diez números entre uno y nueve para sumarlos, hemos incluido un condicional que permite la terminación del bucle, en caso de que se introduzca el valor cero, y otro que evita sumar números mayores que nueve. Var Contador, Dato, Suma: Integer; begin Suma := O; For Contador := 1 To 10 Do Begin Dato := PideNumero; { Si el número es cero salimos del bucle } If Dato = 0 Then Break; { Si es mayor que nueve lo ignoramos } If Dato > 9 Then Continue; { En caso contrario lo acumulamos } Suma := Suma + Dato; End;

4.7. Procedimientos y funciones Un procedimiento o una función es un conjunto de sentencias, de líneas de código, al cual se asigna un identificador, de tal forma que desde cualquier otro punto del módulo, o incluso de la aplicación, es posible ejecutar esas sentencias tan sólo haciendo referencia al identificador. Tanto un procedimiento como una función pueden recibir parámetros que le permitan al código trabajar con los datos apropiados en cada caso. Una función, además, devuelve un parámetro mediante el cual es posible facilitar un resultado. Podemos decir que una función es un procedimiento capaz de devolver un parámetro, siendo esa la única diferencia entre ambos. En los puntos siguientes se hará referencia siempre a un procedimiento, pero todo es aplicable también a las funciones.

4.7.1. Definición Para definir un procedimiento tendremos que usar la palabra Procedure, tras la cual dispondremos el identificador por el que se va a conocer

Page 70: DELPHI 5

al procedimiento. Si lo que deseamos crear es una función, utilizaremos la palabra Function en su lugar. La línea será terminada con un punto y coma, que seguirá al identificador. Un procedimiento es un bloque en el cual es posible definir tipos, declarar constantes y variables y escribir código ejecutable. Los apartados de definiciones y declaraciones se iniciarán con las palabras Type, Const y Var que ya conocemos, mientras que el inicio del bloque de código se marcará con la palabra Begin, que irá emparejada con su correspondiente End dispuesto al final del bloque. Todas las definiciones de tipos y declaraciones de variables que se efectúen en el interior de un procedimiento son locales a dicho procedimiento, por lo que no estarán accesibles desde ningún otro punto del módulo ni del programa. Este tema fue tratado anteriormente, en el punto dedicado al ámbito de los identifícadores. La finalidad principal de un procedimiento es evitar la repetición de un código que va a ser usado repetidas veces, aislándolo y asignándole un nombre que facilite su ejecución en cualquier punto en el que sea necesario. Por ejemplo, imagine que está escribiendo un programa en el que en ocasiones tiene necesidad de provocar una espera de cinco segundos. En lugar de codificar las sentencias necesarias cada vez, sería mucho más eficiente la creación del procedimiento siguiente. { Procedimiento que causa una espera de 5 segundos } Procedure Espera; Var Ahora: Longint; Begin { Tomamos el número de mi 11 segundos transcurridos desde el inicio de Windows, devueltos por el procedimiento GetTickCount } Ahora := GetTickCount; { Mientras no hayan transcurrido 5 segundos } While GetTickCount - Ahora < 5000 Do { estamos dando vueltas en el bucle sin hacer nada } Continué; y End; Una vez creado, un procedimiento puede ser llamado desde cualquier punto del módulo, si fue definido en la parte de implementación, o desde cualquier parte de la aplicación, si se declaró en la parte de interfaz del módulo. La sentencia de llamada es tan simple como la siguiente. Espera; { Esperamos cinco segundos }

Page 71: DELPHI 5

4.7.2. Parámetros de entrada Aunque existen procedimientos como el que acabamos de definir, cuyo código realiza siempre exactamente lo mismo en cada llamada, en muchas ocasiones el funcionamiento del procedimiento deberá adaptarse a unos ciertos parámetros particulares, que serán facilitados en cada llamada. Esto permite que un mismo procedimiento realice tareas ligeramente diferentes, haciéndole mucho más flexible y útil. Por ejemplo, imagine que en su programa necesita realizar esperas de cinco segundos, como se dijo anteriormente, pero en ciertos puntos esas esperas deben ser de dos segundos. Obviamente podríamos definir un nuevo procedimiento, llamándole de forma diferente, que se encargase de la nueva espera. Aunque esto solucionaría el problema en ese momento, desde luego no se trata de una solución definitiva, porque sería necesario escribir un procedimiento específico para cada tiempo de espera, lo cual es absurdo. Lo más lógico sería enviar al procedimiento EsperaQ un parámetro, indicándole el número de segundos que debe esperar. Los parámetros que recibe un procedimiento han de declararse tras el identificador, entre paréntesis, siguiendo el mismo formato que usaríamos para declarar cualquier variable. Por tanto, tendríamos que facilitar el identificador y el tipo de cada parámetro, separando uno de otro con un punto y coma, como si se encontrasen en líneas distintas. A continuación puede ver cómo quedaría el procedimiento Espera() después de haberle añadido un parámetro, que se utiliza en el código para controlar el fin del bucle de espera. { procedimiento que causa una espera de N segundos } Procedure Espera(Segundos: Integer); Var Ahora: Longint; Begin { Tomamos el número de mllisegundos transcurridos desde el inicio de Windows, devueltos por el procedimiento GetTickCount ) Ahora := GetTickCount; { Mientras no hayan transcurrido los N segundos } While GetTickCount - Ahora < Segundos*1000 Do { estamos dando vueltas en el bucle sin hacer nada } Continue; End; Ahora al llamar a este procedimiento tendríamos que facilitar un parámetro entero, indicando el número de segundos que deseamos

Page 72: DELPHI 5

esperar. Este parámetro se dispondría entre paréntesis, como puede ver en la siguiente sentencia. Espera(5); { Una espera de cinco segundos } Parámetros por valor y por referencia Tal y como hemos declarado nuestro procedimiento Espera(), éste recibe un parámetro de tipo entero al que nosotros hemos llamado Segundos. Este identificador, que en el procedimiento usamos como si se tratase de una variable local, toma su valor automáticamente, valor que es facilitado en el momento en que se realiza una llamada al procedimiento. Sin embargo, lo que la variable Segundos contiene es en realidad una copia del valor que se facilitó. Obviamente en la llamada Espera(5); lo que se pasa al procedimiento es una constante, y una copia de 5 es 5. Suponga que, por el contrario, llamamos a Espera() enviando como parámetro una variable, a la que previamente hemos asignado el número de segundos de espera. SegundosAEsperar := 5; Espera(SegundosAEsperar); En este caso lo que recibe el procedimiento es una copia del contenido de la variable, lo cual quiere decir que aunque nosotros modifiquemos en EsperaQ el valor de Segundos, la variable SegundosAEsperar no se verá afectada en ningún momento. Para que un procedimiento tenga acceso a la variable que se ha pasado como parámetro, con el fin de que pueda modificar su valor, al definir la secuencia de parámetros que va a recibir dicho procedimiento habrá que disponer la palabra Var delante de aquellos parámetros que se desean pasar por referencia, en forma de variables, y no por valor. Observe la definición del siguiente procedimiento, que recibe dos parámetros: un entero por valor, del cual se calculará el cuadrado, y otro entero por referencia, al cual se asignará el resultado. Si este segundo parámetro no se hubiese pasado por referencia, la asignación que se realiza a Resultado no tendría sentido alguno. De esta forma, sin embargo, esa asignación modifica la variable que se pasó originalmente como parámetro, desde cualquier otro punto del programa. { Este procedimiento calcula el cuadrado del primer parámetro, devolviendo el resultado en el segundo } Procedure Cuadrado (Factor: Integer; Var Resultado: Integer); Begin Resultado := Factor * Factor; End;

Page 73: DELPHI 5

Al llamar a un procedimiento que cuenta con parámetros por referencia, hemos de tener en cuenta que no es posible pasar una constante en su lugar, ya que una constante no tiene una ubicación en memoria, como las variables. Por tanto, tendremos que declarar una variable para poder pasarla como parámetro, tal y como se muestra a continuación. Var N: Integer; begin Cuadrado(8, N); Tras esta llamada la variable N contendría el valor 64, que ha sido calculado y asignado en el procedimiento Cuadrado().

4.7.3. Parámetros de salida Aunque, como acabamos de ver, un procedimiento puede devolver datos mediante el uso de parámetros por referencia, siempre que es necesario devolver un único resultado lo normal es escribir una función y no un procedimiento. Una función se caracteriza por devolver siempre un valor, cuyo tipo se indica al final de la cabecera, tras el identifícador y los parámetros, si es que existen. Así, podríamos declarar una función llamada Cuadrado() de la siguiente forma. Function Cuadrado(Factor: Integer): Integer; La devoludón del valor se realiza mediante una asignación a una variable especial existente en toda función, cuyo nombre es Result. Esta variable, que es del tipo que se ha indicado como retomo de la función, puede ser usada a lo largo de todo el código de la función como cualquier otra variable local, sabiendo que su valor final será el que devuelva la función. Según esto, podríamos definir la función y realizar una llamada tal y como se muestra en el siguiente fragmento de código. Está claro que si una función necesita devolver más de un parámetro, deberá además utilizar la técnica vista anteriormente de pasar parámetros por referencia. { Este procedimiento calcula el cuadrado del primer parámetro, devolviendo el resultado en el segundo } Function Cuadrado(Factor: Integer): integer; Begin Result := Factor * Factor; End; procedure TForml.Form1Create(Sender: TObject);

Page 74: DELPHI 5

Var N: Integer; begin N := Cuadrado(8); end;

5. Fundamentos de la Programación Orientada a Objeto 5.1 Introducción Objetos, objetos y más objetos, vivimos en un mundo de objetos. La Programación Orientada a Objetos no es nada nuevo, surgió hace ya varios años aunque por entonces no pudo, por sus necesidades de potencia de computo, desplazar a la llamada programación convencional. Lenguajes como SIMULA y SmallTalk se consideraron entre los pioneros en esta tendencia y comenzaron a brindar a los programadores nuevas y deseadas características como robustez, verdadera modularidad y reuso, así como un importante acercamiento entre el oscuro mundo de los bits y el real mundo de los objetos. Hacia finales de los 80 el desarrollo de la potencia de computo y la complejidad del software creció trayendo consigo un redespertar en esta tendencia y provocando que lenguajes de los llamados convencionales como C y Pascal se les incluyera la posibilidad de los objetos surgiendo los llamados “Leguajes Híbridos” como el popularísimo C++ y el Object Pascal motivo de estudio en este libro. El hecho de que muchos de nosotros trabajemos con objetos no quiere decir que estemos programando Orientado a Objetos. Hay una diferencia sustancial entre “Programar usando objetos” y “Programar Orientado a Objetos”. En Visual Basic, por ejemplo, se suele trabajar con un montón de objetos (Componentes) sin que ello signifique que estamos en presencia de un lenguaje orientado a objetos. El lector podrá preguntarse entonces: ¿cuándo, realmente, programamos orientado a objetos? Programamos orientado a objetos cuando somos capaces de modelar el problema que se nos presenta en términos de objetos y sus relaciones. Es decir cuando cada entidad en nuestro software es un objeto que brinda determinados servicios estrictamente definidos siendo además responsable de su integridad y funcionamiento ante condiciones anormales.

5.2 Tipos de datos abstractos. Cuando nos enfrentamos a un problema de la vida real generalmente chocamos con la complejidad y la diversidad de esta, por lo que la capacidad de distinguir las partes esenciales de las no esenciales se convierte en algo extremadamente importante para que logremos éxito en nuestro propósito. Finalmente, cuando hacemos esto, obtenemos lo que se llama un modelo abstracto del problema en cuestión y que no es más que el conjunto de representaciones propias de los elementos que intervienen en la solución de este. Es esto lo que llamamos abstracción.

Page 75: DELPHI 5

El modelo abstracto en cuestión incluye los datos relevantes y las operaciones identificadas que actúan sobre ellos. Vemos un ejemplo: Suponga usted que su jefe le indica que debe realizar un programa para controlar los empleados de su empresa. Los empleados en si encierran muchas propiedades y de ellas unas pocas serían: Nombre Edad Sexo Fecha de Ingreso a la Empresa Salario Cargo Color del pelo Color de la piel Estado Civil Número de hijos Usted encuentra que hay algunas que son irrelevantes para los requerimientos del programa que su jefe le ha pedido y obtiene finalmente un modelo abstracto de empleado que contiene aquellas y solo aquellas propiedades que son relevantes al problema en cuestión como pudieran ser en este caso: Nombre Edad Fecha de Ingreso a la Empresa Salario Cargo Por supuesto no vasta con enunciar las propiedades del empleado abstracto sino que también hay que definir una serie de operaciones inherentes a su comportamiento como podrían ser: crear un empleado, eliminar un empleado, calcular el salario atendiendo a su fecha de ingreso y su cargo. Debes asegurarte además que el acceso a los datos ocurra única y exclusivamente a través de las operaciones definidas garantizando con ello la integridad de estos. Por ejemplo no debe permitirse nunca en este caso que el programador acceda directamente al dato fecha sino que lo haga a través de la operación “establecer fecha” que validará si la nueva fecha es aceptable. Por tanto la estructura de datos solo puede ser accedida por medio de operaciones definidas y al conjunto de estas operaciones se le suele llamar “interface”. Ya en la implementación del modelo de solución del problema se crean instancias del tipo de datos y se llenan con datos reales. Visto todo esto estamos en condiciones de definir formalmente lo que en la literatura se conoce como “Tipo de dato abstracto”.

Page 76: DELPHI 5

Llamamos “Tipo de dato abstracto” a una estructura de datos que es únicamente accedida a través de operaciones definidas.

5.3 Clases. La Programación Orientada a Objetos descansa en el concepto de clase. Conceptualmente llamamos clase a un tipo de dato abstracto y su parcial o total implementación. Hablando menos formalmente una clase es una plantilla que describe la representación de un tipo de objeto y su comportamiento, Consecuentemente, las clases definen las propiedades y el comportamiento de conjuntos de objetos. Veamos, por ejemplo, la declaración de la clase Empleado atendiendo al ejemplo de Tipo de Dato Abstracto visto anteriormente. type Empleado = class private Nombre: string; Edad: byte; FechaIE: string; Salario: real; Cargo: string; public constructor Create( ANombre:string; AEdad:byte; AFechaIE:string; ASalario:real; ACargo:string); function CalculaSalario: real; end;

5.4 Objetos Ya hablamos previamente de instancias de la clase empleado y de sus datos reales, es a estas intancias a lo que se le denomina objetos. Un objeto es, por tanto, una instancia de la clase a la que se le asocia un estado determinado relacionado con el valor de sus propiedades. En el fragmento de código siguiente y a modo de ejemplo declaramos un par de objetos (instancias) de la clase empleado: ... var Secretaria: Empleado; Director: Empleado; begin // Creación del objeto secretaria. Secretaria := Empleado.Create(“Neli”, 20, “23/4/2000”, 250.50, “Secreataria”); // Creación del objeto director.

Page 77: DELPHI 5

Director := Empleado.Create(“Franklin”, 27, “23/4/2000”, 375, “Director”) … end; Tenemos hasta ahora dos conceptos fundamentales de la programación orientada a objetos, los conceptos de clase y objeto. La programación orientada a objetos es entonces la implementación de tipos de datos abstractos, es decir, la implementación de clases. Vale preguntarse ahora ¿cómo interactúan los objetos?

5.5 Mensajes En la programación orientada a objetos un programa no es más que una colección de objetos interactuantes. Esa interacción se produce a través de mensajes. Un mensaje es un pedido al objeto de que ejecute uno de sus métodos y consta de nombre del objeto y nombre del método a ejecutar. Un mensaje podría ser “Secretaria calcúlese el salario” lo que en Object Pascal sería : ... Salario := Secretaria.CalculaSalario; ... Donde “secretaria” es el nombre del objeto y CalculaSalario es el nombre del método invocado. Ver un programa como una colección de objetos que interactúan entre sí enviándose mensajes y cambiando su estado durante la ejecución es en extremo importante para poder entender como funciona el paradigma de los objetos.

5.6 Relaciones Supongamos, con propósitos ilustrativos, que queremos construir un programa de dibujo y consideremos dos clases inicialmente, la clase Punto y la clase círculo: … type Punto = class private x, y: real; public constructor Create( Ax, Ay: real ); procedure SetX( Ax: real ); // Cambia los valores de x procedure SetY( Ay: real ); // Cambia los valores de y end; Círculo = class

Page 78: DELPHI 5

private x, y, Radio: real; public constructor Create( Ax, Ay, ARadio: real ); procedure SetX( Ax: real ); // Cambia los valores de x procedure SetY( Ay: real ); // Cambia los valores de y procedure SetRadio( ARadio: real ); // Cambia el valor del radio end; … Comparando ambas definiciones de clase notamos que las propiedades que definen el punto (x,y) se repiten en ambos casos. En el correspondiente al punto definen el punto en sí y en el círculo definen el centro. Ambas clases además definen un procedimiento llamado set que nos permite cambiar los valores de los datos. Lo realmente interesante aquí es que la clase círculo “añade” la propiedad radio a la definición de punto. Es decir modelando este problema en particular para nosotros un círculo “es un” punto al que se le ha “añadido” un radio. Esto define lo que se llama una relación del tipo “es un”. Desde el momento en que se define una relación de este tipo ( B es un A ) entonces las instancias(objetos) de la clase B también se comportarán como instancias de la clase A. Para el caso de nuestro ejemplo el círculo también se comportará como un punto. En otros casos serán necesarias las relaciones del tipo “es parte de”. Definamos ahora la clase de tipo rectángulo: … Type Rectángulo = Class Private VSI: Punto; // Vértice superior izquierdo VID: Punto; // Vértice inferior derecho public Constructor Create( xsi, ysi, xii, yii: real ); End; … El punto VSI “es parte de” el rectángulo y el punto VID “es parte de” el rectángulo así podemos ver que hay clases que se componen de otras. Invirtiendo el sentido de esta relación obtenemos entonces que el rectángulo “tiene un” Punto VSI y “tiene un” punto VID definiendo esta vez lo que se conoce como relación del tipo “tiene un”.

5.7 Herencia Con la herencia podemos hacer uso de la relación del tipo “es un”. Hagamos entonces que el círculo herede del punto. ...

Page 79: DELPHI 5

type Punto = class private x, y: real; public constructor Create( Ax, Ay: real ); procedure SetX( Ax: real ); // Cambia los valores de x procedure SetY( Ay: real ); // Cambia los valores de y end; Circulo = class( Punto ) private Radio: real; public constructor Create( Ax, Ay, ARadio: real ); procedure SetRadio( ARadio: real ); // Cambia los valores del radio. end; … // Implementación del Punto constructor Punto.Create( Ax, Ay: real ); begin x := Ax; y := Ay; end; procedure Punto.SetX( Ax: real ); begin x := Ax; end; procedure Punto.SetY( Ay: real ); begin y := Ay; end; // Implementación del círculo constructor Circulo.Create( Ax, Ay, ARadio: real ); begin inherited Create( Ax, Ay ); // Se llama al constructor heredado. Radio := ARadio; end; procedure Circulo.SetRadio( ARadio: real ); begin Radio := Radio; end;

Page 80: DELPHI 5

Nótese que esta vez para definir la herencia se añade entre paréntesis a la palabra reservada class el nombre de la clase de la cual queremos heredar y de esta manera: Circulo = class(Punto) … end; La clase círculo por tanto, hereda todas la propiedades y métodos de la clase punto siendo innecesario rescribirlos. A nivel de objetos ahora podemos usar el círculo como si fuese un punto: var C: Circulo; begin ... C := Circulo.Create(2,2,8); … C.SetX( 7 ); // Se invoca al método SetX heredado del punto C.SetY( 5 ); // Se invoca al método SetY heredado del punto C.SetRadio( 10 ); // Añadido por el Círculo … end. Definamos más formalmente la herencia: Llamamos herencia al mecanismo que permite que un objeto B herede propiedades de una clase A. Los objetos de la clase B tienen entonces acceso a los atributos y métodos de la clase A sin necesidad de redefinirlos. Otros términos muy usados son los de Subclase y Superclase. Si B hereda de A entonces A se conoce como “superclase” de B y B como “subclase” de A. Se puede encontrar también en la literatura términos como “clases padre”, “clases hijas” y otras denominaciones.

5.8 Asignación dinámica de memoria. Object Pascal es un lenguaje fuertemente tipificado pues en él toda variable ha de ser declarada antes de usarse. Ello implica que al declarar la variable automáticamente el compilador reserva espacio para ella. La declaración: var i: integer;

Page 81: DELPHI 5

tendrá como resultado que se reserve el espacio correspondiente a un entero y se “enlace” el nombre i a un tipo “integer”. Todo esto le brinda al compilador la facilidad de chequear la consistencia de los tipos de datos antes de la ejecución. Una expresión como esta sería declarada ilegal por el compilador: ... i := ‘cadena’; ... por estar asignando a i un tipo de dato incompatible con el integer. A este tipo de asignación de memoria es a la que se le llama “asignación estática”. Por otro lado existen lenguajes de programación que no obligan a declarar las variables antes de usarlas permitiendo introducirlas cuando se necesiten. Así cuando tecleamos: i = 4 el interprete es capaz de darse cuenta que como a la variable i se le esta asignando un entero, el tipo de i debe ser entero por tanto en estos casos el tipo de i se conoce en el momento en que su valor es establecido. A este tipo de asignación de memoria es a la que se le llama “asignación dinámica”.

5.8 Polimorfismo El polimorfismo le permite a una entidad ya sea una variable, una función u objeto, adoptar una variedad de representaciones. Un ejemplo sencillo de polimorfismo en variables ocurre en un lenguaje que no es fuertemente tipificado cuando utilizamos las siguientes sentencias: ... i = 49 rem utilizamos a i como un número entero. ... i = i + 1 I = “casa” rem cambiamos a cadena. Para las funciones ocurre cuando tenemos una función que realiza diferentes tareas bajo un mismo nombre. A esto se le suele llamar sobrecarga de funciones, veamos un ejemplo. Supongamos que tenemos una función área que calcula el área de un círculo. El código del ejemplo está escrito atendiendo a la sintaxis de c++ por ser este un lenguaje que soporta sobrecarga de funciones. … double area(double radio ) // El tipo de datos double equivale a un número real. { return (3.14*radio*radio); }; …

Page 82: DELPHI 5

y luego una función área más adelante que calcula el área de un rectágulo: ... double area( double a, double b ) { return a * b; }; Debido a que la lista de parámetros de la función es diferente el compilador puede deducir el uso correcto de la función chequeando los tipos reales de estos. ... double ac; double ar; … ac = area( 4.6 ); // Se usa la función área correspondiente al círculo. ar = area( 6.8, 90 ); // Se usa la función área correspondiente al rectángulo. ... En el caso de los objetos el polimorfismo permite que un objeto escoja los métodos correctos. Definamos una nueva función llamada “área” para las clases Punto y Circulo: type Punto = class … public … function area: real; virtual; end; type Circulo = class( Punto ) … public … function area: real; override; end; … function Punto.area; begin Result := 0; end; ...

Page 83: DELPHI 5

function Circulo.area; begin Result := 3.14*Radio*Radio; end; ... La inclusión de la palabra virtual a continuación de la función “area” del punto significa que el código de dicha función se anexará en tiempo de ejecución del programa. Lo mismo ocurre con lo “herederos” de la clase punto que reimplementen esa función haciendo uso de la sentencia override. Sin una declaración del tipo virtual en este caso sería imposible ejecutar un código como este: ... var i: byte; P: Punto; C: Circulo; List: TList; begin P := Punto.Create(2,2); C := Circulo.Create(3,3,10); List := TList.Create; List.Add( P ); List.Add( C ); for i := 0 to List.Count - 1 do begin writeln( Punto(List.Items[i]).Area ); end; readln; end. Nótese que para este ejemplo hemos utilizado la clase TList incluida en la unit classes de la versión 5.0 de Delphi que permite manipular una lista polimórfica (Objetos diferentes) y que contiene una propiedad llamada items por la que se puede acceder a los objetos en su interior mediante un índice (Ejemplo: List.Items[1] accede al objeto # 1). A la lista se le han insertado mediante el método List.Add un objeto Punto y uno Círculo. La línea writeln( Punto(List.Items[i]).Area ); es la que exhibe el comportamiento polimórfico. Cuando en la ejecución se recorre la lista primero se imprimirá 0 que es el resultado de invocar la función area del punto y luego 314 como resultado de invocar la función área para el circulo. La sentencia Punto( List.Items[i].Area ) hace lo que se llama una “reinterpretación de tipos” permitiendo que todo objeto de la lista se interprete como una instancia de Punto o descendiente de este en el árbol jerárquico. El Polimorfismo es entonces en términos de objetos la posibilidad de que la variable que representa la instancia (en el ejemplo anterior

Page 84: DELPHI 5

List.Items[i] ) represente a objetos de diferentes tipos todos creados mediante una definición estática.

6. Manipulación de componentes

6.1. Introducción En los tres capítulos anteriores, dedicados al entorno, los elementos de un proyecto y el lenguaje Object Pascal, hemos adquirido todos los conocimientos necesarios para comenzar a desarrollar programas con Delphi. Ya sabemos cómo crear un proyecto y gestionar los elementos contenidos en él. También hemos aprendido a gestionar los componentes de un formulario, editar propiedades y escribir el código asociado a los eventos. Para escribir este código utilizaremos el lenguaje Object Pascal, cuyos fundamentos también hemos conocido. Lo único que nos falta, por tanto, es ir tratando los diferentes componentes con que cuenta Delphi, conociendo la finalidad de cada uno de ellos, sus propiedades, eventos y métodos. Sin embargo, antes de proceder con este trabajo, que abordaremos en el próximo capítulo, vamos a tratar en el presente las generalidades comunes a la mayor parte de los componentes Delphi. De lo que se trata en este capítulo es de conocer las propiedades más habituales, los eventos y los métodos que están disponibles en prácticamente todos los componentes. Esto nos permitirá, por una parte, aprender a utilizar cualquier componente con una gran facilidad y, por otra, nos evitará tener que repetir los mismos conceptos con cada componente. 6.2.Tipos de componentes En una aplicación Delphi es posible utilizar tanto componentes Delphi, conocidos como VCL, como componentes ActiveX, también llamados OCX o componentes OLE. En la etapa de diseño, mientras estamos trabajando con los componentes en el formulario no notaremos diferencia alguna entre unos componentes y otros. Existe una diferencia fundamental entre un componente VCL y un ActiveX, y es que mientras el código del primero pasa a formar parte del ejecutable que genera Delphi, con lo cual el componente está incluido en la propia aplicación, el segundo se mantiene en un archivo separado, con extensión OCX o DLL, que es necesario distribuir e instalar junto con la aplicación. La mayor parte de los componentes que encontraremos en la Paleta de componentes de Delphi son del tipo VCL.

6.3. Instalación de un componente

Page 85: DELPHI 5

Además de los componentes que encontramos en el entorno de Delphi una vez completada la instalación, es posible añadir cualquier otro componente que nos interese utilizar. Hay miles de componentes freeware y shareware que podemos instalar en Delphi. El proceso de instalación de un componente es muy simple y se inicia seleccionando la opcióninstall component del menú Component. En la ventana que aparece, similar a la mostrada en la figura 6.1, pulsaremos el botón Browse para seleccionar el módulo en que se encuentra el componente.

Figura 6.1. Instalación de un nuevo componente Si el componente a instalar es ActiveX, entonces usaremos la opción Import ActiveX control, que dará paso a la ventana mostrada en la figura 6.2. En la lista que ocupa la parte superior aparecen todos los controles ActiveX registrados en el sistema. Bastará con seleccionar el que deseamos instalar para que debajo aparezcan los nombres de clase. Aunque inicialmente estos componentes se alojarán en la página ActiveX de la Paleta de componentes, podemos indicar cualquier otra página como destino.

6.4. Propiedades Desde el punto de vista del usuario de un componente, que es el nuestro, las propiedades son como los miembros de un registro, es decir, son elementos pertenecientes a un objeto. Por tanto, para acceder a una propiedad de un componente usaremos la notación que ya conocemos, en la cual el punto actúa como separador entre el nombre del componente y el de la propiedad. Aunque la mayoría de las propiedades son de lectura y escritura, existen algunas que son de sólo lectura y, más raramente, algunas de sólo escritura. También hay que distínguir entre las propiedades que

Page 86: DELPHI 5

están disponibles tanto en la fase de diseño como en ejecución y aquellas que tan sólo lo están en ejecución, no apareciendo en el Inspector de objetos mientras estamos diseñando. Por regla general, las propiedades de sólo lectura están disponibles sólo en ejecución.

Figura 6.2. Instalación de un control ActiveX

6.4.1. Acceso a los miembros de un objeto Cuando es necesario acceder desde el código de un programa a múltiples propiedades, eventos o métodos de un mismo componente, en lugar de disponer repetidamente el nombre de éste, seguido de un punto y el identificador que corresponda, podemos utilizar la sentencia With. Esta sentencia nos permite ahorrar bastante "tecleo", ya que disponiendo tras ella el identificador de un objeto, ya sea componente o un registro, podemos a continuación hacer referencia a sus miembros sin necesidad de volver a escribir el nombre de dicho objeto. Utilizando la sentencia With, el fragmento de código del capítulo anterior en el que se asignaban valores a varios miembros de un registro, llamado UnaFicJia, quedaría de la forma siguiente: var UnaForm: TFormAgenda;

Page 87: DELPHI 5

Begin With UnaForm Do Begin Nombre := 'Pedro García'; Direccion := 'Avda. de la Paz, 24'; Telefono := '348123'; Personal := True; End;

6.4.2. Posición y dimensiones del componente Tal y como se comentaba al principio de este capítulo, vamos a conocer a continuación las propiedades más habituales de \a mayoría de los componentes, lo que nos permitirá, en capítulos posteriores, tratar los elementos específicos de cada componente sin necesidad de repetir varias veces lo mismo. Comencemos tratando las propiedades en las que se almacena la información de posición y dimensiones del componente. Todos los componentes están colocados en una determinada posición del formulario, incluso aquellos que en ejecución no estarán visibles. La posición de un componente se almacena en las propiedades Left, columna, y Top, fila. El contenido de estas dos propiedades será un número entero cuya unidad de medida es el punto de pantalla, que contiene la coordenada horizontal y vertical, respectivamente, en que se haya situado el componente. Estas coordenadas son relativas al espacio o superficie del formulario, en el cual el punto 0,0 es la esquina superior izquierda. Los componentes visuales, a los que se conoce como controles, además de una posición también cuentan con un tamaño, según el cual ocupan un espacio más o menos amplio en el formulario. El ancho y alto del control, expresado también en (puntos, se almacena en las propiedades Width y Height, respectivamente. Cuando en la fase de diseño se mueve o modifica el tamaño de un componente, automáticamente se actualiza el contenido de las propiedades que se acaban de citar. De igual forma, puede editar el contenido de estas propiedades, en el Inspector de objetos, modificando la posición y las dimensiones del componente. Aunque el punto en el que aparece un control y su tamaño es algo que se suele fijar durante el diseño, también podemos alterar estas propiedades durante la ejecución. De esta forma, con una simple asignación, podemos establecer la posición y dimensiones del componente. En caso de que la aplicación que se desarrolla vaya a utilizarse en distintos sistemas, usando diferentes resoluciones de pantalla, además de unas dimensiones fijas, facilitadas en las propiedades Width y Height, también pueden establecerse unos límites de tamaño. Esta es la finalidad de la propiedad Constraints que, como verá en el Inspector

Page 88: DELPHI 5

de objetos, cuenta con cuatro subpropiedades: MaxHeight, MaxWidth, MinHeight y MinWidth. Las dos primeras contendrán el alto y ancho máximos, mientras que las segundas indicarán el alto y ancho mínimos. Por defecto los controles no cambian de tamaño automáticamente, por lo que en principio el contenido de la propiedad Constraints no parece ser de mucha utilidad. Será en combinación con la propiedad Anchors cuando encontremos el sentido. Dicha propiedad es un conjunto que puede tener los valores akLeft, akTop, akRight y akBottom, especificando los puntos a los que estará "anclado" el control. Por defecto los controles se anclan de forma relativa a su lado izquierdo y el margen superior, es decir, la propiedad Anchors contiene los valores akLeft y akTop. Esto provoca que mantenga fija su posición en el interior del formulario, a pesar de que las dimensiones de éste se modifiquen arrastrando la esquina superior izquierda. Si damos el valor True a las subpropiedades akRight y akBottom de Anchors anclaremos el control de forma relativa también a los otros de márgenes del contenedor, de tal forma que al cambiar su tamaño automáticamente se redimensiona también el control, utilizándose como valores límite los entregados en la propiedad Constraints. El formulario, al ser un componente visual, cuenta también con las propiedades que se acaban de explicar, incluidas Constraints y Anchors.

6.4.3. Títulos, colores y tipos de letra Todos aquellos componentes que cuentan con un título, como puede ser el formulario, un botón, un grupo, etc., disponen de una propiedad llamada Caption en la que se almacena una cadena de caracteres conteniendo dicho título. Esta propiedad es de lectura y escritura y podemos acceder a ella tanto durante el diseño como durante la ejecución. Asignando un valor a la propiedad Caption de un formulario, por poner un ejemplo, alteraremos el título que aparece en la barra de título de la ventana. El color que se utiliza para el fondo de los controles es un aspecto también configurable, gracias a la propiedad Color. Esta propiedad almacena un valor de tipo TColor, mediante el cual podemos obtener o fijar el color que nos interese. Existen una serie de constantes ya definidas representando a los colores más habituales. Si queremos que nuestro formulario tenga el fondo blanco, por ejemplo, no tenemos mas que asignar el valor clWhite a la propiedad Color, ya sea durante el diseño o la ejecución. La mayoría de los sistemas actuales son capaces de representar miles e incluso millones de colores para los cuales, obviamente, no existe una constante definida. Podemos componer cualquier color durante la ejecución mediante la macro RGB, a la que pasaremos tres parámetros enteros indicando la intensidad dé rojo, verde y azul. Cada uno de los componentes del color podrá tener una intensidad comprendida entre O y

Page 89: DELPHI 5

255, permitiéndonos así crear más de dieciséis millones de colores o tonalidades diferentes. Durante el diseño también podemos acceder a toda la paleta de colores disponible. Para ello, en lugar de seleccionar uno de los valores de la lista desplegable, haremos doble clic en el valor de la propiedad Color, en el Inspector de objetos, lo que causará la aparición de una ventana de selección de color. En caso de que el componente con el que estamos trabajando disponga de una propiedad Caption oText, habitualmente también contará con una propiedad llamada Font que sirve para seleccionar el tipo de letra a utilizar. La propiedad Font es en sí misma un objeto y, como tal, cuenta con una serie de propiedades, que se enumeran en la tabla 6.1. En el Inspector de objetos podemos acceder a estas propiedades de la forma que se explicó en el capítulo 2, desplegando las subpropiedades de Font. En ejecución la propiedad Font será un objeto con otras propiedades, por lo que para acceder a ellas usaremos el operador de cualificación, el punto, o bien la sentencia With vista anteriormente.

Propiedad Contenido

Color Color del texto.

Height Altura de la letra.

Name Nombre del tipo o fuente.

Pitch Tipo de espaciado.

Size Tamaño en puntos.

Style Estilo.

Tabla 6.1. Propiedades de un objeto TFont

La propiedad Style es un conjunto mediante el cual es posible definir el estilo para el texto. Este conjunto puede contener los valores fsBoíd, fsitalic, fsUnderline y fsStrikeQut, activando el estilo de negrita, cursiva, subrayada o tachada, respectivamente.

6.4.4. Estado visual y de acceso Un componente visual, un control, puede aparecer en ejecución en varios estados diferentes. De principio puede estar o no visible, aunque en modo de diseño siempre sea visible. Además el control puede estar activo o no, según lo cual el usuario podrá interactuar o no con él. Mediante la propiedad Visible podemos saber y alterar el estado de visibilidad de un control. Esta propiedad es de tipo Boolean, conteniendo el valor True en caso de que el control sea visible. Bastará dar el valor False para hacerlo no visible. |

Page 90: DELPHI 5

La propiedad Enabled también es de tipo Boolean y es la que determina si el control está o no accesible para el usuario del programa. El valor True, que es el que esta propiedad toma por defecto, indica que el control está activo. Por regla general, aquellos controles que no están accesibles se muestran en la ventana de forma diferente, por ejemplo con un color de texto más claro.

6.4.5. Orden de acceso a los controles Cuando en una ventana existen múltiples controles, normalmente el usuario puede pasar de unos a otros mediante la tecla <Tab>. Es el programador, usted, el que establece, normalmente durante el diseño, cual será el orden de acceso a esos controles. La propiedad TabOrder, con la que cuentan todos los controles capaces de tener el foco de entrada de Windows, contiene un entero comprendido entre cero y el número de controles de ese tipo existentes en el formulario menos uno. Cuando la ventana sea mostrada el foco lo tendrá aquel control que tenga el TabOrder cero y, al pulsar la tecla <Tab>, se pasará el foco al control siguiente según el valor de esta propiedad. A medida que se van insertando controles en un formulario, Delphi va a asignando automáticamente los valores oportunos a la propiedadTabOrder, de tal forma que, inidalmente, el orden de acceso a los controles en ejecución será el orden en que se han insertado en el formulario en la fase de diseño. Podemos modificar este orden alterando el contenido de la propiedad TabOrder directamente o bien usando la opción Tab Order del menú emergente del formulario. AI seleccionar esta opción aparecerá una ventana como la que se muestra en la figura 6.3, en la que mediante el ratón podremos fácilmente establecer el orden de acceso. No todos los controles del formulario tienen por qué estar accesibles en la forma que se ha descrito, de forma secuencial mediante la tecla <Tab>. Dando el valor False a la propiedad TabStop podemos excluir cualquier control de la secuencia de acceso.

Page 91: DELPHI 5

Figura 6.3. Orden de acceso a los controles de un formulario

6.4.6. Contenedores y contenidos acoplables En un capítulo previo vimos que algunas ventanas del entorno de Delphi, como el Explorador de código o el Inspector de objetos, podían ser acopladas dentro de otras, como el Editor de código. Esta mecánica, en la que unas ventanas actúan como contenedores de otras que son contenidas de forma acoplada, puede ser usada también en nuestras propias aplicaciones. Para que una ventana o un cierto control actúe como contenedor de ventanas acopladas es necesario dar el valor Truc a la propiedad DockSite. Disponen de esta propiedad el formulario y los controles que son capaces de actuar como contenedores, como puede ser TPanel. Cualquier ventana o control que pueda ser acoplado en un contenedor deberá asignar el valor dkDock a la propiedad DragKind y el valor dmAutomatic a la propiedad DragMode. Sólo con esto el control podrá ser arrastrado, durante la ejecución, de un contenedor a otro o del interior de un contenedor a una ventana independiente.

6.5. Eventos Los eventos, al igual que las propiedades, son miembros de un objeto, concretamente de un componente y, por tanto, es posible acceder a ellos de igual forma, disponiendo el identificador del componente, el operador de cualificación y el identificador que corresponde al evento, al que es posible asignar un valor. La diferencia entre una propiedad y un evento es que este último contiene referencias a procedimientos y no valores enteros, cadenas o conjuntos.

Page 92: DELPHI 5

La referencia al procedimiento que se almacena en un evento es usada en un determinado momento, cuando se produce ese evento, para realizar una llamada, lo que permite que nuestro programa ejecute el código que proceda. No todos los eventos son iguales y, por ello, los procedimientos que utilicemos para responder a un evento tampoco lo serán. La diferencia siempre la encontraremos en los parámetros que toma dicho procedimiento. Todos los procedimientos asociados a eventos reciben, como primer parámetro, un objeto TObject llamado Sender. Esta variable es una referencia al objeto que ha generado el evento, variable que podemos usar para acceder a sus propiedades, eventos y métodos. La existencia de este parámetro permite que un mismo procedimiento sea usado por múltiples eventos de distintos objetos. El tipo de evento más habitual es TNotifyEvent, que se caracteriza por no tomar ningún parámetro adicional al que se acaba de describir. En los puntos siguientes conoceremos algunos de los eventos más comunes.

6.5.1. El evento OnClick Éste es el evento más común que podemos encontrar, existiendo en la mayoría de los controles. Se produce al realizarse una pulsación, que puede venir del ratón o del teclado. Cada vez que se pulsa el botón izquierdo del ratón sobre cualquier punto del formulario, se elige una opción de menú, se pulsa la barra espadadora o la tecla <Intro> sobre un botón, etc., se genera un evento OnClick. Al igual que otros muchos eventos, éste es de tipo TNotifyEvent, por lo que tan sólo recibe el parámetro Sender comentado en el punto anterior.

6.5.2. Eventos de ratón El ratón es sin duda el dispositivo de entrada por excelencia en Windows, ya que mediante él es posible controlar prácticamente la totalidad de las funciones de una aplicación, si exceptuamos la entrada de datos tales como textos. Cada vez que movemos el ratón, pulsamos o liberamos cualquiera desus botones estamos generando un evento. E1 evento OnMouseMove lo recibe un control cuando el puntero del ratón se está desplazando sobre él. Este evento, de tipo TMouseMoveEvent, cuenta con tres parámetros adicionales mediante los cuales podemos saber el estado de ciertas teclas especiales, como <Mayús>, <Control> o <Alt> y la posición del puntero del ratón, relativa al área del control que recibe el evento. Al pulsar y liberar un botón cualquiera del ratón se Generan dos eventos, llamados OnMouseDown y OnMouseUp. Ambos son de típoTMouseEvent y además de los parámetros comentados para el evento

Page 93: DELPHI 5

anterior reciben también un parámetro, llamado Button, mediante el cual podemos saber cual ha sido el botón pulsado. Este parámetro puede tomar los valores mbLeft, mbRight o mbMiddIe, según que se trate del botón izquierdo, derecho o medio. El último evento asociado exclusivamente al ratón es OnDblClick, que se produce cuando se hace doble clic sobre un componente con el botón izquierdo del ratón. Este evento es de tipo TNotifyEvent, no disponiendo, por tanto, de ningún parámetro adicional. Una sola actuación sobre el ratón puede generar múltiples eventos. Ha de tenerse en cuenta que al pulsar el botón izquierdo del ratón sobre el formulario, por ejemplo, además del evento OnMouseDown, de pulsación y el evento OnMouseUp, de liberación, también se generará un evento OnClick.

6.5.3. Eventos de teclado Algunos controles, como el formulario o el componente TEdit, tienen la capacidad de recibir eventos también del teclado. Estos eventos nos permiten, por ejemplo, interceptar las pulsaciones de tecla evitando la introducción de caracteres inválidos. Cada vez que se pulsa una tecla que se corresponde directamente con un carácter, como puede ser una letra, número, signo de puntuación, la tecla <Intro>, etc., se genera un evento OnKeyPress. El procedimiento que aseciemos a este evento recibirá un parámetro de tipo Char, llamado Key, conteniendo el carácter pulsado. Este parámetro se recibe en forma de variable, lo que nos permite modificarlo en el procedimiento. Existen muchas teclas que no generan un evento OnKeyPress, porque no están asociadas a un carácter. Entre estas teclas están, por ejemplo, las de función y las de edición, como <Insert> o <Supr>. Estas teclas, al igual que las que sí generan un evento OnKeyPress, siempre generan un evento OnKeyDown al ser pulsadas y un evento OnKeyUp al ser liberadas. Estos eventos también reciben un parámetro llamado Key, aunque en este caso de tipo Word. Este parámetro contiene lo que se conoce como código virtual de tecla, un código único que nos permite saber qué tecla es la que se ha pulsado. Existen una serie de constantes definidas para representar a los códigos virtuales de tecla, que podrá encontrar en la ayuda que Delphi incorpora sobre la API de Windows. Además se recibe otro parámetro, llamado Shift, mediante el cual podremos saber si están pulsadas las teclas <Mayús>, <Control>, <Alt> o alguno de los botones del ratón.

6.5.4. Otros eventos Anteriormente se describió el método por el cual era posible pasar de un control a otro, mediante la tecla <Tab>, haciendo activo uno de los controles del formulario. También podemos dar el foco de entrada a un

Page 94: DELPHI 5

control seleccionándolo directamente, mediante el ratón o una tecla de acceso rápido. Cada vez que un control se convierte en el componente activo del formulario, éste recibe el evento OnEnter. De igual forma, el control que deja de estar activo recibe el evento OnExit. Ambos eventos son de tipo TNotifyEvent, lo que significa que no cuentan con ningún parámetro adicional. Una de las operaciones más habituales en Windows es la de arrastrar y soltar, técnica por la cual es posible copiar un archivo, abrir un programa con un cierto documento, eliminar un cierto elemento, etc. Nuestros programas Delphi también pueden realizar operaciones de arrastrar y soltar, existiendo múltiples eventos relacionados con ello. Cuando se inicia una operación de arrastrar y soltar, el control que va a ser arrastrado recibe un evento OnStartDrag, que se puede utilizar para permitir o no tal operación. A medida que el control va siendo arrastrado, todos los controles sobre los que se va desplazando reciben un evento OnDragOver. Respondiendo adecuadamente a este evento cada control puede indicar, mediante el cambio del icono del cursor, si permite o no que el objeto sea soltado sobre él. En el momento en que finalmente el objeto es soltado, el control sobre el que se ha soltado recibe un evento OnDragDrop, mientras que el control que se arrastró recibe un evento OnEndDrag.

6.6. Métodos Un método es un procedimiento o función perteneciente a un objeto, en este caso a un componente. Por tanto, para llamar a ese método es necesario especificar primero el nombre del objeto al que pertenece, de igual forma que lo hacemos al acceder a una propiedad o a un evento. Conozcamos los métodos más comunes, existentes prácticamente en todos los componentes. Anteriormente conocimos una propiedad, llamada Visible, mediante la cual podíamos saber si un cierto control estaba visible o no, así como modificar ese estado. Los componentes que disponen de esta propiedad también cuentan habitualmente con los métodos Show() y Hide(), que nos permiten mostrar y ocultar, respectivamente, el control de igual forma que si asignásemos los valores True o False a la propiedad Visible. También en un punto anterior hablábamos sobre el orden de acceso a los controles de un formulario y cómo pasar el foco de entrada, haciendo activo un determinado control, mediante la tecla <Tab>. Todos los controles cuentan con un método, llamado CanFocus(), que nos permite saber si ese control puede tomar o no el foco de entrada. Si este método devuelve el valor True eso quiere decir que el control puede

Page 95: DELPHI 5

tomar el foco, caso éste en que podemos acceder a él mediante la tecla <Tab>, una pulsación de ratón o bien mediante código, llamando al método SetFocus(). Este último método hace activo el control que nosotros deseemos y la llamada es tan simple como se muestra a continuación. Button1.SetFocus; Los controles en un formulario cuentan, además de con una posición y unas dimensiones, con un orden en el eje Z o de profundidad, según el cual al superponer dos o más controles unos aparecen sobre otros. Este orden, establecido en modo de diseño, puede ser modificado en ejecución mediante los métodos BringToFront() y SendToBack(). El primero de ellos lleva el control al primer nivel, situándolo sobre todos los demás, mientras que el segundo tiene el efecto contrario, y sitúa el control debajo de todos los demás.

7. Componentes más habituales

7.1. Introducción Los primeros cinco capítulos de esta guía han servido para sentar las bases necesarias para comenzar a desarrollar programas con Delphi. En este capítulo vamos a conocer los componentes más habituales de Delphi, aquellos que se utilizan con más frecuencia en la mayoría de las aplicaciones. Partiendo de que ya conocemos las propiedades, eventos y métodos más comunes, cuya existencia o disponibilidad podemos comprobar en el Inspector de objetos y ayuda de Delphi, acometeremos directamente las particularidades de cada uno de los componentes, tras lo cual se propondrá un ejemplo práctico de su uso.

7.2.Trabajar con el formulario Uno de los componentes más importantes, existente por regla general en todas las aplicaciones, es el formulario, que representa a la ventana en la que desarrollará la acdón del programa. Este componente no tenemos que insertarlo, forma parte de los elementos del proyecto y ya en un capítulo anterior aprendimos a crear nuevos formularios, seleccionarlos del Depósito de objetos, etc. El formulario actúa como un contenedor en el que podemos insertar componentes. Algunos de estos componentes pueden, a su vez, ser contenedores, almacenando otros componentes. Veamos en los siguientes puntos cuáles son los apartados principales que debemos conocer acerca de los formularios.

7.2.1. Aspectos visuales del formulario

Page 96: DELPHI 5

El formulario es un elemento visual, una ventana, que cuenta con un título, unos botones en la parte superior, un borde, un color de fondo, etc. Todos estos elementos tienen su correspondencia en una serie de propiedades, algunas de las cuales ya conocemos. El título del formulario se almacena en la propiedad Caption, su posición en las propiedades Left yTop, sus dimensiones en Width y Height, su estado en Visible y Enabled, el color de fondo en la propiedad Color y el tipo de letra en la propiedad Font. El resto de los aspectos visuales de un formulario encuentran su reflejo en otras propiedades, cuyos valores y funcionamiento vamos a conocer a continuación. Botones de la ventana Los botones que aparecen en la parte superior del formulario, cuya finalidad es facilitar ciertas operaciones como cerrar la ventana, maximizarla o minimizarla, pueden o no aparecer dependiendo del valor que asignemos a la propiedadBorderIcons. Esta propiedad es un conjunto, que puede contener ninguno, uno, varios o todos los valores que se muestran en la tabla 7.1. Para evitar que aparezcan los botones de maximizar y minimizar, deberemos excluir las constantes biMinimize y biMaximize del conjunto, ya que excluyendo sólo una de ellas, los botones aparecerán, aunque el que se ha excluido aparecerá desactivado.

Constante Icono que representa al botón

biMinimize Minimizar

biMaximize Maximizar

biSystemMenu Cerrar y menú de sistema

biHelp Ayuda

Tabla 7.1 Valores del conjunto BorderIcons

El icono correspondiente al botón de ayuda, representado por un signo de interrogación, sólo es mostrado en caso de que no estén visibles los de maximizar y minimizar. Borde de la ventana El área o espacio que ocupa el formulario en pantalla se encuentra delimitada por un borde que, además de ser un elemento meramente visual cuya finalidad es trazar esos límites, también desempeña otras funciones, ya que dependiendo del tipo de borde que tenga el formulario, en ejecución será posible o no modificar su tamaño. Al igual que los botones, el tipo de borde es un elemento que se suele establecer en modo de diseño, seleccionando en el Inspector de objetos

Page 97: DELPHI 5

el valor adecuado para la propiedad BorderStyle. Los valores posibles, enumerados en la tabla 7.2, aparecerán en la lista desplegable. Al modificar esta propiedad, al igual que ocurre con Bordericons, no observaremos cambio alguno en el formulario en modo de diseño, será al ejecutar el programa cuando aparezca el tipo de borde seleccionado.

Constante Tipo de borde

bsNone Sin borde. La ventana no puede ser redimensionada, además no tendrá barra de título ni botones en la parte superior.

bsSingle Borde simple que no permite redimensionar.

bsSizeable Borde grueso que permite redimensionar.

bsDialog Borde grueso que no permite redimensionar.

bsToolWindow Borde simple que no permite redimensionar y que cuenta con una barra de título más pequeña.

bsSizeToolWin Borde grueso que permite redimensionar y que cuenta con una barra de título más pequeña.

Tabla 7.2. Posibles valores para la propiedad BorderStyle

Los estilos bsToolWindow y bsSizeToolWin están enfocados a la creación de ventanas especiales, como cajas de herramientas o paletas flotantes. Se caracterizan porque la barra de título es más estrecha que la normal. Estilo del formulario Cada vez que se inicia un nuevo proyecto o se añade un formulario con la opción New Form, el formulario creado tiene el estilo normal o por defecto, representado por la constante fsNormal. Este estilo de formulario es el de una ventana independiente, que no actúa como contenedor de otras ni está contenida en otra ventana, que además es solapable, es decir, que puede ser ocultada total o parcialmente por otras ventanas. La propiedad FormStyle es la que determina el estilo del formulario y, como se acaba de decir, el valor por defecto que toma es fsNormal. Además de éste, esta propiedad puede tomar tres valores más. El estilo fsStayOnTbp nos será útil cuando deseamos crear una ventana que siempre se visualizará sobre todas las demás, no siendo posible ocultarla. Por regla general, las ventanas que cuentan con este estilo están en pantalla durante un breve espacio de tiempo, por ejemplo mostrando una presentación de entrada al programa. Los otros dos estilos existentes sonfsMDIForm yfsMDIChild, cuya finalidad es permitir la creación de aplicaciones MDI. El término MDI (Múltiple Document Interface/ínteríaz de documento múltiple) hace

Page 98: DELPHI 5

referencia a un tipo de aplicación muy común en Windows, compuesta por una ventana principal, a la que se denomina principal o padre, capaz de contener en su interior múltiples ventanas, a las que se denomina hijas. La ventana que va a actuar como padre tendrá el estilo fsMDIForm y tan sólo puede existir una en el proyecto, mientras que cada una de las ventanas hija tendrá el estilo fsMDIChild. Posición inicial del formulario Aunque por defecto, al ejecutar un programa Delphi, la ventana principal aparece en la misma posición y con las mismas dimensiones fijadas en la fase de diseño, esto no tiene por qué ser necesariamente así. Tanto la posición inicial como el tamaño vendrán determinadas por el valor que asignemos a la propiedad Position, que puede ser cualquiera de los mostrados en la tabla 7.3. El valor tomado por defecto es poDesigned.

Constante Posición y tamaño inicial de la ventana

poDefault Determinados automáticamente por Windows.

poDesigned Los establecidos en modo de diseño. poDefaultPosO

nly Posición determinada por Windows, tamaño fijado en modo de diseño.

poDefaultSizeOnly

Tamaño determinado por Windows, posición fijada en modo de diseño.

poScreenCenter

Tamaño original y posición centrada en la pantalla.

poDesktopCenter

Tamaño original y posición centrada en el escritorio.

Tabla 7.3. Posibles valores para la propiedad Position

Mediante el valor poScreenCenter podemos conseguir que el formulario aparezca siempre en el centro de la pantalla, aún cuando ésta tenga unas dimensiones diferentes a las que tenía en modo de diseño, lo que suele ocurrir cuando el programa se ejecuta en un ordenador distinto del que se utilizó para su desarrollo. En caso de que se trabaje en un sistema con varios monitores, el monitor en que se centrará la ventana dependerá del valor que se dé a la propiedad DefauítMonitor. Los valores posibles son dmDesktop, dmPrimary, dmMainForm y dmActiveForm, según se desee ignorar la existencia de varios monitores, colocar la ventana en el monitor primario, en el monitor en que se encuentre el formulario principal o en el monitor en que se encuentre el formulario activo, respectivamente. Otra propiedad que también afecta a la posición y dimensiones del formulario es WindowState, cuyos posibles valores se enumeran en la tabla 7.4. El valor por defecto, wsNormal, permite que el formulario sea mostrado según la propiedad Position, mientras que los otros dos valores causarán que la ventana aparezca ocupando todo el espacio

Page 99: DELPHI 5

disponible en pantalla, wsMaximized, o bien en forma de icono, wsMinimized.

Constante Estado inicial de la ventana

wsNormal Normal

wsMaximized Maximizada

wsMinimized Minimizada

Tabla 7.4. Posibles valores para la propiedad WindowState

Icono del formulario En caso de que el formulario cuente con un menú de sistema o control, en la parte superior izquierda de la ventana aparecerá un pequeño icono que, al ser pulsado, desplegará un menú de opciones. Este mismo icono representará al formulario en caso de que este sea minimizado. Por defecto Delphi utiliza el mismo icono para todos los formularios, a no ser que nosotros asignemos uno diferente mediante la propiedad Icon. Esta propiedad es en realidad un objeto de tipo Tlcon, que cuenta con sus propiedades, eventos y métodos. En modo de diseño podemos asignar un icono diferente de una forma muy fácil, usando la ventana de edición de la propiedad Icón. En esta ventana podremos recuperar cualquier icono con la simple pulsación de un botón, icono que pasará a formar parte de nuestro programa. También podemos alterar el icono de la ventana en ejecución, recuperando un icono de un archivo en disco o un recurso del programa. El objeto Tlcon, al que accedemos mediante la propiedad Icón, cuenta con un método llamado LoadFromFile() que nos permite recuperar un icono de un archivo estándar ICO. La siguiente sentencia, por ejemplo, establecería como icono del formulario Form1 el contenido del archivo ALARMA.ICO. Form1.Icon.LoadFromFile('ALARMA.ICO');

7.2.2. Eventos de un formulario Además de los eventos generales, como los generados por el ratón y el teclado, un formulario cuenta con otros eventos específicos que debemos conocer para poder aprovecharlos. Todos estos eventos cuentan como primer parámetro con el TObject llamado Sender, que en este caso representa al formulario que recibe el evento. Cuando se crea un formulario, antes de ser visualizada, ésta recibe el evento OnCreate. El método asociado a este evento podemos usarlo para realizar cualquier proceso de inicialización previo a la

Page 100: DELPHI 5

visualización, como puede ser establecer la posición dependiendo de las dimensiones de la pantalla, asignar el título, etc. Tras la creación, lo normal es que el formulario sea visualizado, momento en el que se genera el evento OnShow. De forma análoga, cuando el formulario es ocultado, ya sea alterando el valor de la propiedad Visible o utilizando el método Hide(), se genera un evento OnHide. A diferencia del evento OnCreate, que se produce sólo cuando el formulario es creado, los dos eventos anteriores pueden producirse múltiples veces, en caso de que el formulario sea alternativamente mostrado y ocultado. En Windows es posible trabajar simultáneamente con más de un programa y cambiar de una ventana a otra en el momento en que nos interese. En el momento en que un formulario se convierte en la ventana activa se genera un evento OnActivate. De forma similar, cuando el formulario deja de estar activo, porque cambiemos a otro programa, se produce un evento OnDeactivate. Generalmente las ventanas disponen en la parte superior un botón conteniendo una X, que el usuario puede usar para cerrarla en cualquier momento. Cuando esta acción se produce, el formulario recibe una secuencia de varios eventos, entre los cuales se encuentranOnCloseQuery y OnClose. El primero de ellos se produce antes de hacer efectivo el cierre de la ventana. Lo podemos usar para realizar cualquier tarea previa a esta operación, pudiendo incluso impedirla. Además del parámetro Sender, este evento también está acompañado de un evento de tipo Boolean, llamado Candóse, mediante el cual podemos impedir el cierre de la ventana. Por defecto el valor de este parámetro es True, pero nosotros podemos asignarle el valor False cancelando la operación de cierre. En caso de que el cierre de la ventana se lleve a término, tras el evento OnCloseQuery se produce un evento OnClose. Este evento recibe también un parámetro adicional, llamado Action, al que podemos asignar cualquiera de los valores mostrados en la tabla 7.5, indicando así la acción que deseamos llevar a cabo.

Constante Acción a llevar a cabo

caNone Ninguna, no se cierra la ventana.

caHide Ocultar la ventana, que puede hacerse visible de nuevo.

caFree Destruir la ventana liberando la memoria ocupada.

caMinimize Minimizar la ventana, pero no cerrarla.

Tabla 7.6. Valores posibles para el parámetro Action

Page 101: DELPHI 5

En caso de que el tipo de borde que hayamos establecido lo permita, el usuario podrá alterar el tamaño de la ventana redimensionándola, para lo cual la tomará con el cursor del ratón por uno de los bordes y la estirará o encogerá. Cuando esta operación de cambio de tamaño haya terminado, el formulario recibirá un evento OnResize, que nosotros podemos aprovechar para realizar cualquier operación de ajuste, por ejemplo reposicionando los controles del formulario. Un formulario Delphi cuenta con un área de trabajo, el fondo del formulario, que puede ser utilizada como superficie de dibujo. En caso de que deseemos dibujar algo en ese espacio, nos interesa el evento OnPaint, que se genera cada vez que Windows comunica al formulario que debe dibujarse a sí mismo.

7.2.3. Métodos de un formulario Además de los métodos generales comentados en el capítulo anterior, como Show(), Hide(), SetFocus(), etc., un formulario cuenta con bastantes más métodos, gran parte de ellos dedicados a la gestión de los componentes que contiene en su interior. Una forma alternativa de mostrar un formulario, que estudiaremos más a fondo en el capítulo 8, consiste en utilizar el método ShowModalQ, que muestra un formulario en un estado al que se denomina modal. En este estado la ventana impide el acceso a cualquier otra ventana del mismo programa, de tal forma que el usuario se ve forzado a cerrarla antes de poder continuar. El propio entorno de Delphi cuenta con muchas ventanas de este tipo, como podrá comprobar. Si selecciona la opción New del menú File, haciendo aparecer el Depósito de objetos, verá que no le es posible acceder al formulario o a la ventana de código, hasta en tanto no cierre el Depósito de objetos. Esto es una ventana modal.

7.2.4. En la práctica Veamos en la práctica el uso de algunos de los apartados que hemos conocido hasta ahora, principalmente en lo que respecta a los eventos del formulario. Para ello vamos a escribir un programa simple en el que el formulario se visualizará, sin ningún componente en su interior, y podrá ser redimensionado y cerrado. Abra la página de eventos en el Inspector de objetos y haga doble clic sobre el evento OnResize. Esto provocará la apertura del Editor de código, en el que el cursor aparecerá posicionado en el interior del procedimiento correspondiente a ese evento, que quedará como se muestra a continuación. procedure TForm1.FormResize(Senders TObject); begin Caption := IntToStr(Width) + ', ' +IntToStr(Height); End;

Page 102: DELPHI 5

La única línea que escribimos nosotros es la asignación a la propiedad Caption, ya que la cabecera y finalización del método son generados automáticamente por Delphi. Observe que delante de la propiedad Caption no se ha facilitado el identificador de ningún componente y, sin embargo, estamos haciendo referencia a la propiedad Caption del formulario. En este caso podemos omitir el nombre del formulario, Form1, porque estamos haciendo referencia a sus propiedades desde un método que pertenece al propio formulario. Observe que en la cabecera del método, tras la palabra Procedure, encontramos el nombre de tipo del formulario y el nombre del método. En este procedimiento lo que hacemos es mostrar como título de la ventana las dimensiones actuales que tiene ésta. Para ello tomamos el contenido de las propiedades Width y Height, que es numérico, y lo convertimos a cadena mediante la función lntToStr(). Las dos cadenas resultantes son concatenadas, mediante el operador +, generando una única cadena que es la que se asigna a la propiedad Caption. Haga ahora doble clic sobre el evento OnDbIClick, en el Inspector de objetos, creando el procedimiento siguiente, en el que simplemente se muestra una ventana con un mensaje indicando que se ha realizado una doble pulsación sobre el formulario. procedure TForm1.FormDblClick(Sender: TObject); begin ShowMessage('Has hecho una doble pulsación'); end; Por último vamos a crear el método correspondiente al evento OnCloseQuery, para lo cual deberá hacer doble clic sobre ese evento en el Inspector de objetos. En este caso de lo que se trata es de preguntar al usuario si realmente quiere salir del programa, evitándolo en caso de que su respuesta sea "no". Para hacer esta pregunta vamos a usar el método MessageBox() del objeto Application. La variable Application, que es un objeto, representa a nuestra aplicación y es creada automáticamente por Delphi cuando se inicia el programa. Mediante Application podemos acceder a múltiples propiedades y métodos, entre ellos al método MessageBox(). Éste permite mostrar una ventana con una serie de botones y devuelve un valor indicando el botón que pulsó el usuario. En el código siguiente puede ver cómo se muestra una ventana con dos botones. Sí y No, asignando al parámetro Candóse el valor False en caso de que la respuesta sea No. procedure TForm1.FormCloseQuery(Sender: Tobject); var CanClose: Boolean; begin If Application.MessageBox('¿Desea salir?', 'Prueba', MB_YESNO) = ID_NO Then

Page 103: DELPHI 5

CanClose := False; end; Al ejecutar el programa observe que en la parte superior aparecen las dimensiones del formulario, ya que el evento OnResize se produce cada vez que ésta cambia de tamaño, hecho que ocurre también en el momento en que el formulario es creado. Si modifica el tamaño del formulario podrá comprobar que los valores de las propiedades Width y Height son mostrados en la barra de título. Haga doble clic sobre el formulario para comprobar que el evento OnDbIClick funciona adecuadamente. Por último pulse sobre el botón de cierre y conteste negativamente a la pregunta de si desea salir. Como verá, la ventana no se cierra, ya que al asignar el valor False al parámetro CanClose del evento OnCloseQuery lo estamos impidiendo.

Figura 7.1. El programa de ejemplo preguntando si se desea salir

7.3. Botones Entre los controles más habituales que podemos encontrar en una ventana, junto con los menús, tenemos los botones que el usuario puede pulsar para realizar una determinada acción. Estos botones aparecen como un área rectangular tridimensional, con un título en su interior. En Delphi el botón está representado por el componente TButton, que encontramos en la página Standard de la Paleta de componentes. Al insertar un control de este tipo en el formulario, el título,

Page 104: DELPHI 5

almacenado en la propiedad Caption, será el nombre del componente, que se almacena en la propiedad Name. Podemos modificar dicho título estableciendo el que a nosotros nos interese, simplemente editando el valor de la propiedad Caption en el Inspector de objetos.

7.3.1. Tecla de acceso rápido Los botones, al igual que otros controles que cuentan con un título, pueden contar con una tecla de acceso rápido que, utilizada junto con la tecla <Alt> permite, en este caso, pulsar el botón sin necesidad de desplazarse hasta él. La tecla de acceso rápido ha de ser necesariamente uno de los caracteres que forma parte del título. Para indicar al control qué carácter será el seleccionado le antepondremos a éste el carácter &. Al hacerlo, observe que en el botón ese carácter aparece con un subrayado, indicando así que se trata de la tecla de acceso rápido.

7.3.2. El evento de pulsación Si disponemos un botón en un formulario, sin más, su funcionalidad es prácticamente nula ya que, aunque el usuario puede pulsarlo y visualmente aparecerá pulsado, no se producirá ninguna operación en consecuencia. Lo normal es que asociemos un método al evento OnClick del botón, ejecutando el código que proceda en el momento en que el usuario lo pulse. El evento OnClick es el evento principal de un botón y se produce cuando éste es pulsado, indistintamente de cómo se haya hecho. Podemos pulsar el botón utilizando el cursor del ratón, la tecla de acceso rápido, desplazándonos hasta él con la tecla <Tab> y pulsando la barra espadadora, con la tecla <Intro> si es el botón por defecto o con la tecla <Esc>, si es el botón de cancelación. En todos estos casos se generará el evento igualmente. Como ya sabemos, el evento OnClick es un evento de notificación, que tan sólo cuenta con el parámetro Sender general a todos los eventos. Este parámetro será una referencia al botón que ha sido pulsado y nos será muy útil en caso de que compartamos un mismo método entre varios botones.

7.3.3. Botón por defecto y de cancelación En un formulario pueden existir múltiples controles TButton, tantos como deseemos, que pueden ser pulsados según los métodos comentados en el punto anterior. De todos ellos dos pueden ser activados de una forma especial, que consiste en pulsar la tecla <Intro> o la tecla <Esc>.

Page 105: DELPHI 5

Se denomina botón por defecto a aquel que será pulsado automáticamente, generando el correspondiente evento OnClick, en el momento en que pulsemos la tecla <Intro>, independientemente del control que esté activo en ese mo-mento. De esta forma, podemos encontramos en un control TEdit, por ejemplo introduciendo un valor, y pulsar la tecla <Intro> para pulsar el botón por defecto. Para que un botón actúe como botón por defecto habremos de dar el valor True a la propiedad Default. Por defecto el valor de esta propiedad es False, lo cual es lógico, ya que no tendría sentido la existencia de múltiples botones por defecto en el mismo formulario, ya que al pulsar la tecla <Intro> no se sabría cuál pulsar. El botón de cancelación es aquel que se pulsa, además de por los métodos normales, mediante la tecla <Esc>. Para que un botón actúe como botón de cancelación deberemos dar el valor True a la propiedad Cancel, que por defecto es False.

7.3.4. En la práctica Para ver en funcionamiento el uso del control TButton, vamos a insertar en un formulario un control TEdit, cuyas características conoceremos posteriormente, y tres controlesTButton, con los títulos y teclas de acceso rápido que se pueden ver en la figura 7.2. El primer botón no tiene ninguna característica especial, mientras que el segundo es el botón por defecto, para lo cual hemos dado el valor True a la propiedad Default, y el tercero es el botón de cancelación, por lo cual la propiedad Cancel tiene ese mismo valor.

Figura 7.2. Aspecto del formulario con los tres controles TButton Una vez que haya insertado los cuatro componentes, los haya situado adecuadamente y asignado los valores correspondientes a las propiedades Caption, Default y Cancel, vamos a proceder a la escritura del código que se habrá de ejecutar con la pulsación de cada uno de los botones. Como lo único que deseamos comprobar es cuál de los tres botones se ha pulsado, bastará con mostrar una ventana con un mensaje que bien podría ser el propio título del botón. Para mostrar esta ventana usaremos el procedimiento ShowMessage() que conocimos anteriormente.

Page 106: DELPHI 5

Por tanto, el código de los tres métodos correspondientes al evento Click de cada uno de los botones sería prácticamente idéntico, ya que en los tres casos nos limitaríamos a llamar al procedimiento ShowMessage() pasando como parámetro el contenido de la propiedad Caption del botón que se haya pulsado. Puesto que todos los eventos vienen acompañados de un primer parámetro, llamado Sender, que hace referencia al objeto que ha generado el evento, lo lógico es que, en lugar de escribir tres métodos, escribamos sólo uYio, compartido por los tres botones, en el cual usemos el mencionado parámetro para obtener el título del botón. El parámetro Sender es de tipo TObject, un tipo del que están derivados todos los objetos de Delphi, incluidos los componentes y controles. TObject es, por tanto, un ascendiente común, y por eso se utiliza para representar a cualquier objeto de forma genérica. Al utilizar este parámetro, sin embargo, es necesario realizar una conversión previa al tipo que corresponda, con el fin de poder acceder a sus miembros, en este caso a sus propiedades. Para realizar una conversión de un tipo de objeto a otro, realizando las comprobaciones necesarias, usaremos el operador As. Este operador es binario, tomando por lo tanto dos operandos. El primero de ellos, dispuesto a la izquierda, será el identificador del objeto a convertir, mientras que el segundo, dispuesto a la derecha, será el tipo al que se desea convertir. En caso de que la conversión sea satisfactoria el valor obtenido podrá ser usado sin mayor problema, como si Sender fuese realmente del tipo que a nosotros nos interesa. En caso de que la conversión no fuese válida se generaría una excepción. Haga doble clic sobre el primer botón, abriendo así la ventana de código y creando el método correspondiente al evento por defecto de un TButton, que es el evento OnClick. Escriba el código que se muestra a continuación. procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage((Sender As TButton).Caption); end; Observe que la conversión de Sender al tipo TButton, mediante el operador As, se ha encerrado entre paréntesis, ya que la prioridad del operador de cualificación, el punto, es mayor que la del operador As, lo que causaría que la expresión no fuese válida. Para terminar seleccione en el formulario los otros dos botones de forma conjunta, pulsando sobre uno y luego sobre el otro mientras mantiene pulsada la tecla <Mayús>, abra la página de eventos en el Inspector de objetos y despliegue la lista adjunta al evento OnClick,

Page 107: DELPHI 5

seleccionando de ella el único método existente en este momento. De esta forma lo que hacemos es compartir el método entre los tres controles. Cuando el usuario pulse un botón cualquiera se llamará siempre al mismo método, pero pasando un valor diferente para el parámetro Sender. Ya puede ejecutar el programa, pulsando <F9>, y realizar diversas pruebas con él. Estando el foco de entrada en el control TEdit, que fue el que insertamos en primer lugar, pulse la tecla <Intro> o la tecla <Esc>, con el fin de comprobar el funcionamiento del botón por defecto y de cancelación. Puede también pulsar conjuntamente la tecla <Alt> junto con las teclas <N>, <D> y <C>, pulsando cada uno de los botones mediante su tecla de acceso rápido. Lógicamente también puede desplazar el puntero del ratón hasta situarlo sobre un botón y pulsarlo, el resultado será siempre el mismo.

7.4. Etiquetas de texto El control que acabamos de conocer, al igual que otros muchos, cuenta con una propiedad Caption que nos permite mostrar un título. Este sirve para que el usuario del programa sepa qué función es la que desempeña el botón. Otros controles, como puede ser el TEdit que utilizamos en el ejemplo anterior, no cuentan con esta propiedad. Si necesitamos adjuntar un título a un control que no dispone de la propiedad Caption, o si simplemente deseamos disponer un texto cualquiera en el formulario, podemos utilizar el control TLabel. Este control, que se distingue del anterior en que no puede tomar el foco de entrada, cuenta con una propiedad Caption a la que podemos asignar el texto que deseamos mostrar. Además de las propiedades particulares que se van a comentar a continuación, este control cuenta también con muchas de las propiedades generales que ya conocemos. A pesar de ser un control que no puede tomar el foco de entrada, no podemos acceder a él mediante la tecla <Tab>, TLabel si recibe eventos de ratón.

7.4.1. Tamaño del control Al igual que todos los controles, TLabel tiene unas dimensiones que se almacenan en las propiedades Width y Height. Estas dimensiones las podemos fijar nosotros, tanto en la fase de diseño como de ejecución, o bien podemos dejar que sea la propia etiqueta la que establezca el tamaño en proporción al texto que se desea mostrar. La propiedad AutoSize, que por defecto toma el valor True, es la que controla este comportamiento del control TLabel. Con su valor por defecto.True, el tamaño de la etiqueta se ajusta automáticamente al contenido de la propiedad Caption, algo que puede comprobar fácilmente

Page 108: DELPHI 5

en modo de diseño modificando dicho contenido, podrá ver que los límites del control se amplían o reducen adecuadamente. Si damos el valor False a la propiedad AutoSize, las dimensiones del control TLabel serán siempre las que nosotros establezcamos, y en ningún momento se realizará un ajuste automático a no ser que nosotros asignemos de nuevo el valor True.

7.4.2. Alineación del texto En caso de que las dimensiones del control TLabel no sean exactamente las necesarias para mostrar el título, es decir, si la propiedad AutoSize tiene el valor False, podemos seleccionar mediante la propiedad Alignment la alineación del texto en el espacio disponible. Esta propiedad puede tomar cualquiera de los valores enumerados en la tabla 7.6, siendo taLeftJustify el tomado por defecto.

Constante Alineación del texto

taLeftJustify A la izquierda taRightJustif

y A la derecha

taCenter Al centro

Tabla 7.6. Valores posibles para la propiedad Alignment

7.4.3. Otras propiedades de TLabel Por defecto, el control TLabel muestra el contenido de la propiedad Caption en una sola línea, lo que en ciertas ocasiones puede ser insuficiente para nuestras necesidades. Si lo deseamos podemos indicar al control que el texto se divida automáticamente en varias líneas, respetando el ancho que nosotros asignemos. Para ello bastará con dar el valor True a la propiedad WordWrap. Al igual que otros controles, TLabel cuenta con un color de fondo, que especificamos en la propiedad Color, y un color de texto, que vendrá dado por la propiedad Color de la propiedad Font. Si deseamos que la etiqueta de texto, nombre con el que se conoce habitualmente al control TLabel, sea transparente, de tal forma que no se borre el fondo sobre el que se va a dibujar el texto, deberemos dar el valor True a la propiedad Transparent, que por defecto tiene el valor False. A pesar de que, como se ha dicho antes, el control TLabel no puede tomar el foco de entrada, sí que es posible asignarle una tecla de acceso rápido, precediendo cualquiera de los caracteres del título con el carácter &, tal y como se explicó anteriormente para los botones. Esto tiene sentido sólo cuando la etiqueta de texto se está usando como título de otro control, de tal forma que al pulsar la tecla de acceso rápido, el control que tomará el foco será el que está asociado

Page 109: DELPHI 5

al TLabel y no el TLabel en sí. Para que la etiqueta de texto sepa a qué control debe pasar el foco cuando se pulse la tecla de acceso rápido, deberemos asignar a la propiedad FocusControl el nombre de dicho control.

7.4.4. En la práctica Vamos a ver cómo podemos usar el control TLabel con un pequeño ejemplo. Comenzaremos insertando en un formulario tres controles TLabel y dos controles TEdit, tal y corrió se muestra en la figura 7.3. Asigne los valores adecuados a la propiedad Caption de cada control TLabel, teniendo en cuenta las teclas de acceso rápido de los dos primeros.

Figura 7.3. Aspecto del formulario para probar el control TLabel Asigne a la propiedad FocusControl de la primera etiqueta, Label1, el nombre del controlTEdit que tiene debajo, Edit1, y haga lo mismo con la segunda etiqueta, asignando a la propiedad FocusControl el nombre del segundo TEdit. Dé el valor False a la propiedad AutoSize de la tercera etiqueta y modifique su tamaño de tal forma que ocupe prácticamente todo el formulario, de extremo izquierdo a extremo derecho. A continuación haga doble clic sobre la etiqueta, para abrir el método correspondiente al evento OnClick, y escriba el código siguiente. procedure TForm1.Label3Click(Sender: TObject); begin With Label3 Do // Asumir la referencia a Label3 Case Alignment 0f // Según el valor de la propiedad // asignamos otro distinto taLeftJustify: Alignment := taCenter; taCenter: Alignment := taRightJustify; taRightJustifyrAlignment := taLeftJustify; End; end;

Page 110: DELPHI 5

Como puede ver, lo que se hace es alterar el valor de la propiedad Alignment del formulario, de tal forma que en ejecución el texto irá saltando del extremo izquierdo al centro, de ahí al extremo derecho y de ahí de nuevo al izquierdo con cada pulsación. Ejecute el programa y compruebe cómo mediante la tecla de acceso rápido de la etiqueta de texto puede pasar de un control TEdit a otro. Observe también el comportamiento del TLabel que hay en la parte inferior del formulario, que cambia su alineación con cada pulsación de ratón.

7.5. Petición de datos La finalidad del control TEdit, que hemos usado en dos ejemplos anteriores, es permitir la entrada de un dato por parte del usuario. Este dato, en forma de cadena de caracteres, se almacenará en la propiedad Text, a la cual es posible dar un valor inicial durante el diseño. A diferencia de los dos controles anteriores, TButton y TLabel, el control TEdit no cuenta con una propiedad Caption, por lo cual se suele disponer una etiqueta de texto adjunta que sirve como título. Cuando durante la ejecución el usuario modifica el contenido de un TEdit, la propiedad Modified de éste toma el valor True. Desde el código de nuestro programa podemos comprobar el valor de esta propiedad y, en caso de que proceda, recuperar la entrada realizada por el usuario leyendo la propiedad Text.

7.5.1. Longitud del texto Por defecto el control TEdit no pone ningún límite a la cantidad de texto que el usuario puede introducir y, en caso de que sea necesario, desplazará el contenido actual del control a medida que se introducen nuevos caracteres. Si lo deseamos, podemos limitar el número de caracteres que es posible introducir en un TEdit mediante la propiedad MaxLength. Esta propiedad, de tipo entero, contiene por defecto el valor cero, indicando que no hay un límite establecido. Asignando cualquier otro número estaremos fijando la longitud máxima de la propiedad Text.

7.5.2. Selección de texto Mientras se edita el contenido de un control TEdit, el usuario puede marcar una porción del texto, mediante la combinación de la tecla <Mayús> y los cursores o bien con el ratón, con el fin de realizar alguna operación que le afecte, como puede ser eliminarlo o copiarlo al portapapeles. En cualquier momento podemos saber qué texto es el que hay seleccionado en el control, para lo cual disponemos de las propiedades

Page 111: DELPHI 5

SelStart, SelLength y SelText. La primera de ellas contiene el carácter a partir del cual se ha marcado, sabiendo que el primero de los existentes es el carácter cero. La segunda propiedad contiene el número de caracteres que hay marcados, mientras que la tercera contiene el texto. El valor de estas propiedades también puede ser establecido por el código de nuestro programa, seleccionando automáticamente el texto que nos interese. Si deseamos marcar todo el texto contenido actualmente en el control, en lugar de editar las propiedades SelStart y SelLength podemos realizar una simple llamada al método SelectAII(). Las operaciones de copiar, cortar y pegar con el portapapeles, que se realizan mediante teclado con unas combinaciones de teclas que ya conocemos, pueden ser también realizadas mediante código gracias a la existenda de varios métodos al efecto. El método ClearSelection() elimina el texto que hay seleccionado, de igual forma que si el usuario hubiese pulsado la tecla <Supr>. El método CopyToClipboard() copia el texto al portapapeles, mientras que CutToClipboard() lo copia al portapapeles y lo elimina del TEdit. PasteFromClipboard(), finalmente, nos permite recuperar el texto que hay en el portapapeles, insertándolo en el TEdit en la posición actual del cursor. El texto que hay seleccionado en un control TEdit se distingue del resto por estar con los colores invertidos, efecto que desaparece en el momento en que abandonamos el control, dando el foco de entrada a cualquier otro de los existentes en el formulario. Al volver de nuevo al control TEdit, la marca del texto vuelve a aparecer. Este comportamiento se debe a que por defecto la propiedad HideSelection tiene el valor True, indicando al control TEdit que oculte la selección de texto cuando no esté activo. Si damos a esta propiedad el valor False, el texto seleccionado estará siempre destacado del resto, aún cuando el control no esté activo.

7.5.3. Texto de sólo lectura y oculto Aunque, por regla general, el contenido de un control Tedit puede ser editado por el usuario durante la ejecución, en ocasiones nos puede interesar usar este control sólo para mostrar un valor y no para permitir su edición. Para conseguir esto bastará con asignar el valor True a la propiedad ReadOnly, haciendo así que el control TEdit sea de sólo lectura. Este control puede ser usado para solicitar cualquier tipo de dato, pero a veces lo deseable es que el dato que se está introduciendo no sea visible para terceras personas que puedan existir alrededor del usuario. Esto ocurre, por ejemplo, cuando se solicita una clave de entrada a una aplicación, que debe ser algo confidencial. Mediante la propiedad PasswordChar podemos conseguir que el control TEdit vaya

Page 112: DELPHI 5

representando cada uno de los caracteres que introduce el usuario mediante un cierto símbolo, como puede ser un asterisco, una interrogación, etc. En realidad podemos asignar a PasswordChar cualquier carácter, el que deseemos.

7.5.4. Otras propiedades de TEdit Dependiendo del valor que demos a la propiedad AutoSelect, que por defecto es True, al acceder a un control TEdit, por ejemplo mediante la tecla <Tab>, el contenido actual será automáticamente seleccionado o no. Si deseamos que al seleccionar un texto esta selección se mantenga aún cuando nos desplacemos a otros controles, deberemos dar el valor False a esta propiedad. Al igual que el control TLabel, TEdit también dispone de una propiedad AutoSize, aunque su funcionamiento es ligeramente diferente, ya que asignándole el valor True no conseguimos que el control adecué su tamaño al del texto que contiene, sino que lo que indicamos es que la altura del Tedit se ajuste a la necesaria para poder mostrar el tipo de letra que se haya seleccionado. Por defecto la propiedadAutoSize tiene el valor True, por lo que si modificamos la propiedad Font, alterando el tamaño de letra, no tendremos que preocupamos de ajustar apropiadamente el alto del control. La introducción de ciertos datos puede requerir que todas las letras sean facilitadas en mayúsculas, o en minúsculas, con el fin de evitar posteriores fallos de comparación. Mediante la propiedad CharCase, que puede tomar los valores mostrados en la tabla 7.7, podemos forzar que todos los caracteres aparezcan en mayúsculas o en minúsculas. El valor por defecto para esta propiedad es ecNormal, lo que permite una combinación de ambas.

Constante Indica

ecNormal Se permiten tanto mayúsculas como minúsculas.

ecUpperCase Todas las letras son convertidas a mayúsculas.

ecLowerCase Todas las letras son convertidas a minúsculas.

Tabla 7.7. Posibles valores para la propiedad CharCase

7.5.5. Control de la entrada El componente TEdit no realiza ningún tipo de comprobación previa a la admisión de los caracteres que el usuario pueda introducir, ni cuenta con propiedad alguna que nos permita indicar qué caracteres son los válidos. Éste es, sin embargo, un aspecto que podemos controlar nosotros mismos, gracias a que cada vez que el usuario pulsa una tecla, antes de que el correspondiente carácter sea almacenado en la

Page 113: DELPHI 5

propiedad Text del TEdit, se genera un evento OnKeyPress, en el que nosotros podemos realizar las comprobaciones que sean necesarias. El evento OnKeyPress, que ya fue comentado en el capítulo anterior, recibe un parámetro en forma de variable, lo que nos permite no sólo saber qué carácter es el que se ha pulsado, sino que además podemos modificarlo, realizando una conversión, o anularlo, asignando el carácter nulo. También podemos usar el evento OnKeyDown para detectar la pulsación de ciertas teclas especiales, a las que haremos referencia mediante los códigos de tecla virtual, un código que no tiene nada que ver con el código ASCII de cada carácter.

7.5.6. En la práctica Veamos en la práctica algunos de los apartados que hemos conocido sobre el control TEdit en los puntos anteriores. Vamos a partir insertando en un formulario tres controles TEdit, que estarán encabezados por tres TLabel, y tres TButton. El aspecto del formulario será el que se muestra en la figura 7.4. El primer control TEdit lo utilizaremos para comprobar el funcionamiento de las propiedades MaxLength, a la que Figura 7.4. Formulario para comprobar el funcionamiento de Tedit vamos a asignar el valor 10, y PasswordChar, a la que asignaremos el valor *. De esta forma, en este control podremos introducir un máximo de diez caracteres que, además, no serán visibles siendo sustituidos por asteriscos.

Figura 7.4. Formulario para comprobar el funcionamiento de TEdit A continuación tenemos un TEdit que vamos a utilizar para ver cómo podemos controlar la entrada de caracteres, permitiendo tan sólo la introducción de dígitos numéricos. Abra la página de eventos en el Inspector de objetos y haga doble clic sobre el evento OnKeyPress, escribiendo el código que se muestra a continuación. En él se

Page 114: DELPHI 5

comprueba que el carácter sea un dígito numérico o bien la tecla de borrado hacia atrás, a la que corresponde el código ocho, asignando al parámetro Key el valor #0 en caso contrario, ignorando así la pulsación. Observe cómo se usa un conjunto y el operador In para simplificar esta comprobación. procedure TForm1.Edit2KeyPress(Sender: TObject; var Key: Char); begin // Si no es un carácter permitido If Not (Key In ['0'. .'9', #8]) Then Key := #0; // lo ignoramos end; El último control TEdit que hemos insertado lo usaremos para probar los métodos de copiar, borrar y pegar el texto seleccionado. A la propiedad AutoSelect del control TEdit vamos a asignar el valor False, para evitar que la selección del texto se pierda al pulsar un botón o cambiar a otro de los TEdit que hay en el formulario. Será al pulsar cualquiera de los tres botones que hemos dispuesto debajo cuando se realice la operación de copiado, borrado o pegado, gracias al código que asociaremos al evento OnClick de cada uno de los botones, como se muestra seguidamente. procedure TForm1.Button1Click(Sender: TObject); begin Edit3.CopyToClipboard; // Copiarnos al portapapeles Edit3.SetFocus; // y devolvemos el foco al TEdit end; procedure TForm1.Button2Click(Sender: TObject); begin Edit3.ClearSelection; // Borramos el texto marcado Edit3.SetFocus; end; procedure TForm1.Button3Click(Sender: TObject); begin Edit3.PasteFrom Cllpboard; // Copiamos del portapapeles Edit3.SetFocus; end; Observe que tras realizar la operación que proceda se hace una llamada al método SetFocus(), con el fin de devolver el foco de entrada al control TEdit. Dados todos los pasos anteriores, no tiene mas que pulsar la tecla <F9> para ejecutar el programa y comprobar su funcionamiento.

7.6. Entrada de texto El control TEdit es el de uso más habitual cuando en un programa es necesario solicitar una entrada del usuario, pero este control tiene

Page 115: DELPHI 5

una limitación: la entrada no puede exceder de una línea, a pesar de que esa línea puede tener una longitud superior al tamaño del propio control en el formulario. Cuando necesitemos solicitar una entrada de texto más amplia, en varias líneas, en lugar de un TEdit podemos usar un control TMemo. Este control es muy similar al que acabamos de conocer en los puntos anteriores y cuenta con muchas propiedades que ya hemos visto, como ReadOnly, MaxLength, Text, SelStart, etc., así como con los métodos relacionados con las operaciones del portapapeles y los mismos eventos que ya conocemos.

7.6.1. Barras de desplazamiento A pesar de que un control TMemo puede tener unas dimensiones tales que le permitan mostrar múltiples líneas de texto, es difícil saber de antemano cuantas líneas van a existir, con el fin de adecuar la altura, y, en cualquier caso, no siempre podremos dar al control el tamaño necesario como para poder mostrar todo el texto, ya que el espacio disponible en la pantalla es limitado. Cuando en una ventana se desea mostrar algo que excede de las dimensiones máximas disponibles, lo que se hace es mostrar unas barras de desplazamiento, en el eje horizontal, vertical o ambos, cuya finalidad es permitir el movimiento de la ventana sobre la información a mostrar, o el desplazamiento de la información al interior de la ventana, según deseemos verlo. Un control TMemo también puede contar con barras de desplazamiento, permitiendo trabajar con líneas más largas que el espacio horizontal disponible y con más líneas de las que es posible mostrar de forma simultánea. La existencia o no de las barras de desplazamiento dependerá del valor que asignemos a la propiedad ScrollBars, que será uno de los mostrados en la tabla 7.8. El valor tomado por defecto es ssNone, por ello inicialmente el TMemo no cuenta con barras de desplazamiento.

Constante Barras de desplazamiento a mostrar

ssNone Ninguna

ssVertical Vertical

ssHorizontal Horizontal

ssBoth Vertical y Horizontal

Tabla 7.8. Posibles valores para la propiedad ScrollBars

En caso de que el control no disponga de una barra de desplazamiento horizontal, lo habitual es que el texto que se va introduciendo sea dividido automáticamente, saltando de una línea a otra. Esto ocurrirá

Page 116: DELPHI 5

a no ser que demos el valor False a la propiedad WordWrap, que por defecto es True.

7.6.2. Trabajando con líneas de texto El texto almacenado en un control TMemo es accesible mediante la propiedad Text, al igual que en un control TEdit. Existe, sin embargo, un método mediante el cual podemos acceder a ese texto línea a línea, lo que en ciertas situaciones puede ser mucho más útil. El control TMemo cuenta con una propiedad, llamada Lines, que es un objeto de tipoTStrings. Este objeto cuenta con una serie de propiedades y métodos que nos permiten acceder a cualquiera de las líneas de texto, añadir nuevas líneas, eliminarlas, etc. La propiedad más interesante del objeto TStrings es seguramente Strings, una matriz de cadenas mediante la cual podemos acceder individualmente a las líneas de texto. Podemos saber cuantas líneas de texto hay en un determinado momento mediante la propiedad Count. La propiedad Strings es la propiedad por defecto del objeto TStrings al que hace referencia Lines, lo que quiere decir que podemos acceder a una línea de texto simplemente disponiendo su índice, entre corchetes, tras la propiedad Lines, sin necesidad de especificar nada más. De esta forma, las dos sentencias siguientes serían equivalentes. Texto := Memo1.Linea.Strings[1]; Texto := Memol.Lines[1] ; Los métodos Clear(), Add(), Append() e Insert() nos permiten eliminar todas las líneas de texto, añadir una nueva línea o insertarla, respectivamente. Dos de los métodos más útiles de un TStrings son SaveToFile() y LoadFromFile(), que permiten guardar y recuperar las líneas de texto en un archivo en disco. Los dos métodos toman como único parámetro el nombre del archivo en el que se va a guardar o del que se va a recuperar. Durante el diseño podemos acceder al contenido de la propiedad Lines mediante un editor específico, en el que podremos introducir las líneas de texto que deseemos mostrar inicialmente en el control.

7.6.3. Otras propiedades de TMemo Al editar múltiples líneas de texto en un control, podemos necesitar la inserción de ciertos caracteres cuyas teclas tienen un significado especial en una ventana. La pulsación de la tecla <Intro> normalmente activa el botón por defecto, si es que éste existe, pero también se puede utilizar en un control Tmemo para producir un salto de línea. De forma similar, la tecla <Tab> se usa para pasar de un control a otro de la ventana, pero en un TMemo puede ser usado para insertar un fabulador.

Page 117: DELPHI 5

La función de las dos teclas citadas, <Intro> y <Tab>, mientras está activo un control TMemo dependerá de los valores que contengan las propiedades WantReturns y WantTabs. La primera de ellas tiene por defecto el valor True, mientras que la segunda tiene el valor False. Asignando a WantReturns el valor True conseguiremos que la pulsación de la tecla <mtro> en el interior de un TMemo produzca un salto de línea, no la pulsación del botón por defecto que pueda haber en el formulario. Para pulsar dicho botón previamente tendremos que abandonar el TMemo, o bien utilizar directamente el puntero del ratón o la tecla de acceso rápido. De forma análoga, la asignación del valor True a la propiedad WantTabs tendrá como resultado que la pulsación de la tecla <Tab> en el interior de un TMemo no produzca la activación del siguiente control, según el orden de acceso, sino la inserción en el texto de un fabulador.

7.6.4. En la práctica Como en casos anteriores, vamos a servirnos de un pequeño programa ejemplo que nos ilustre el uso de este nuevo control que acabamos de conocer, con el fin de ver cómo podemos aprovechar sus posibilidades. Inserte en un formulario un control TMemo, ocupando la mayor parte del espacio disponible, y a continuación inserte a su derecha tres botones, a los que vamos a asignar los títulos Borrar, Guardar y Recuperar. Dé el valor True a la propiedad WantTabs del TMemo, a fin de que podamos insertar fabuladores. A continuación tendremos que hacer un doble clic sobre cada uno de los tres botones, escribiendo el código que deseamos ejecutar cuando sean pulsados en ejecución. A continuación se muestran los métodos asociados al evento OnClick de cada botón. Como puede ver, simplemente se realizan tres llamadas, a los métodos Clear(), SaveToFile() y LoadFromFile(). procedure TForm1.Button1Click(Sender:TObject); begin Memo1.Lines.Clear; // Eliminamos el contenido end; procedure TForm1.Button2Click(Sender: TObject); begin // Guardamos el contenido actual Memol. Lines.SaveToFile ('memo.txt' ); end; procedure TForm1.Button3Click(Sender: TObject); begin // Recuperamos el contenido del archivo

Page 118: DELPHI 5

Memol.Lines.LoadFromFile( 'memo.txt' ); end; Una vez escrito el código ya puede ejecutar el programa e introducir cualquier texto en el TMemo, que podrá guardar en un archivo, recuperar y eliminar pulsando los botones adecuados. En la figura 7.5 se muestra el aspecto del programa en funcionamiento, mostrando un texto recuperado del archivo MEMO.TXT.

Page 119: DELPHI 5

Figura 7.5. Aspecto del programa en funcionamiento

7.7. Selección de opciones Aunque, como se indicaba anteriormente, el control Tedit es el más usado en Windows a la hora de solicitar información al usuario de un programa, no es el único. Cuando lo que se espera del usuario es una respuesta tan simple como sí o no, verdadero o falso, activado o desactivado, en lugar de un control TEdit podemos usar un TCheckBox. El control TCheckBox es adecuado para mostrar y permitir la entrada de cualquier información de tipo Boolean, que es posible representar con tan sólo dos estados. Estos dos estados se muestran visualmente mediante una casilla, que puede contener o no en su interior una marca. La propiedad Checked es la más importante de este control, ya que ella nos permite tanto saber el estado actual del control como modificarlo. El tipo de esta propiedad es Boolean y contendrá el valor True, en caso de que el control esté marcado, o el valor False, en caso contrario. En realidad un control TCheckBox puede tomar tres estados diferentes, en caso de que demos el valor True a la propiedad AllowGrayed, que por defecto es False. Estos tres estados son activado, desactivado e indeterminado, que se visualizan como el recuadro con la marca, sin la marca y con un relleno gris. Para comprobar cuál es el estado actual del control o modificarlo deberemos utilizar la propiedad State, que tomará cualquiera de los valores mostrados en la tabla 7.9.

Constante Estado del control

cbChecked Activado

cbUnchecked Desactivado

cbGrayed Indeterminado

Page 120: DELPHI 5

Tabla 7.9 Posibles valores para la propiedad State.

7.7.1. En la práctica Como acabamos de ver, el funcionamiento de un control TCheckBox es bastante simple y basta con que conozcamos la propiedad Checked para actuar en consecuencia. Veamos con un pequeño programa de ejemplo cómo podemos usar en la práctica este control. Inserte en un formulario tres controles TCheckBox, a los que vamos a asignar los títulos 'Botón de maximizar', 'Botón de minimizar' y 'Menú de sistema', modificando en el Inspector de objetos el contenido de la propiedad Caption. El tercer TCheckBox tendrá inicialmente su propiedad Checked a True, ya que el formulario va a contar de principio con un menú de sistema. Las otras dos, por el contrario, no estarán activas ya que modificaremos la propiedad Bordericons del formulario para ocultar los botones de maximizar y minimizar. A continuación hacemos un doble clic sobre cada uno de los controles, para abrir el método correspondiente al evento OnClick de cada uno de ellos, escribiendo el código que se muestra a continuación. procedure TForm1.CheckBox1Click(Sender: TObject); begin { Si está marcada la opción } If CheckBox1.Checked Then { Activamos el botón de maximizar } BorderIcons := BorderIcons + [biMaximize] Else { en caso contrario lo desactivamos } BorderIcons := BorderIcons - [biMaximize]; end; procedure TForm1.CheckBox2Click(Sender: TObject); begin { Lo mismo con el botón minimizar } If CheckBox2.Checked Then BorderIcons := BorderIcons + [biMinimize] Else BorderIcons := BorderIcons - [biMinimize]; end; procedure TForm1.CheckBox3Click(Sender: TObject); begin { Y el menú de sistema } If CheckBox3.Checked Then BorderIcons := BorderIcons + [biSystemMenu] Else BorderIcons := BorderIcons - [biSystemMenu]; end;

Page 121: DELPHI 5

Como puede ver, lo que hacemos es añadir o eliminar de la propiedad Bordericons el elemento correspondiente al TCheckBox que se ha pulsado, según que la propiedad Checked contenga el valor True o False. Al ejecutar el programa, podrá ver cómo en el formulario aparecen o desaparecen los botones en la parte superior, según las opciones que nosotros activemos. En la figura 7.6 se muestra el programa en funcionamiento.

7.8. Selección de opciones exclusivas Al realizar pruebas con el ejemplo anterior, habrá podido comprobar que es posible desactivar todos los TCheckBox o activarlos los tres de forma simultánea. Esto quiere decir que las opciones que representan esos controles no son exclusivas entre sí, es decir, la existencia de un menú de sistema en la ventana, no impide que ésta tenga también un botón para maximizar.

Figura 7.6. El programa de ejemplo en funcionamiento En ocasiones, sin embargo, nos puede interesar representar opciones que son exclusivas entre sí, de tal forma que sólo sea posible seleccionar una de ellas, al tiempo que obligatoriamente una debe estar activa. Suponga, por ejemplo, que desea modificar el programa anterior para que le permita seleccionar el tipo de borde del formulario, según lo cual asignará un valor u otro a la propiedad BorderStyle. Está claro que siempre deberá existir un valor seleccionado, ya que esa propiedad ha de contener un valor, y también es lógico que no sea posible la selección de más de un valor de forma simultánea, ya que tan sólo puede existir un tipo de borde en el formulario. Para solicitar una información como la descrita, en lugar de usar el control TChecRBox deberemos usar el control TRadioButton, que es muy similar tanto visualmente como en lo referente a su funcionamiento. Este control también cuenta con una propiedad Checked, en la que se almacena el estado del control, y a diferencia de un TCheckBox tan sólo son posibles dos estados, por lo que no existen las propiedades AllowGrayed yState. Por lo demás, unTRadioButton funciona de forma muy

Page 122: DELPHI 5

similar a un TCheckBox, si exceptuamos el hecho de que al activar uno automáticamente se desactiva el que en ese momento estuviese activo.

7.8.1. En la práctica Para ver en funcionamiento el control TRadioButton, vamos a insertar en un formulario cuatro controles de este tipo, asignando a la propiedad Caption los títulos 'bsNone', 'bsSingle', 'bsSizeable' y 'bsDialog'. El tipo de borde inicial del formulario será simple, por lo que seleccionaremos el valor bsSingle de la lista desplegable adjunta a la propiedad BorderStyle. El TRadioButton activo por defecto será el que tiene por título ''bsSingle', por lo que daremos el valor True a su propiedad Checked. Haga doble clic sobre el primerTRadioButton, con el fin de abrir el Editor de código por el método correspondiente al evento OnClick, y escriba el código que se muestra a continuación, en el cual se comprueba qué TRadioButton es el que está activo, asignando a la propiedad BorderStyle del formulario el valor apropiado. procedure TForm1.RadioButton1Click(Sender: TObject); begin { Según el control que esté pulsado } If RadioButton1.Checked Then { asignamos un valor u otro a BorderStyle } BorderStyle := bsNone Else If RadioButton2.Checked Then BorderStyle := bsSingle Else If RadioButton3.Checked Then BorderStyle := bsSizeable Else BorderStyle := bsDialog; end; A continuación seleccione en el formulario los otros tres TRadioButton de forma conjunta, abra la página de eventos del Inspector de objetos y despliegue la lista adjunta al evento OnClick, seleccionando el único elemento que aparece. De esta forma conseguiremos que el evento OnClick de los cuatro controles compartan un mismo procedimiento. Ya puede ejecutar el programa. Observe cómo cambia el tipo de borde a medida que selecciona un TRadioButton u otro. Compruebe que no le es posible activar dos opciones al mismo tiempo, lo que sí era posible con eITCheckBox. Ésta es la principal diferencia entre ambos controles.

Page 123: DELPHI 5

Figura 7.7. Aspecto del programa con los controles TRadioButton

7.9. Grupos de opciones exclusivas Conociendo ya el funcionamiento del control TRadioButton, nos puede surgir una cuestión de forma inmediata. ¿Qué ocurre si deseamos solicitar varios datos diferentes, cada uno de los cuales tiene una serie de opciones exclusivas entre sí?. Suponga, por ejemplo, que en el programa anterior además de seleccionar el tipo de borde, también quiere permitir el establecimiento del estado del formulario, seleccionando el valor adecuado para la propiedad WindowState. Si simplemente añadimos tres nuevos controles TRadioButton, asignando los títulos apropiados, nos encontraremos con un problema, y es que al seleccionar un tipo de borde se desactivará el estado de la ventana que hubiese seleccionado y viceversa. Es decir, las opciones de borde y de estado, que no son exclusivas, no pueden seleccionarse de forma simultánea. Para evitar este problema tendríamos que agrupar de alguna manera los controles TRadioButton del tipo de borde, por una parte, y los del estado de la ventana por otra. Esto se puede hacer mediante los controles TGroupBox o TPanel, que conoceremos después. Existe, sin embargo, un método aún más simple, que consiste en utilizar el control TradioGroup en lugar del control TRadioButton. Un control TRadioGroup es capaz de almacenar múltiples elementos, cada uno de los cuales se muestra como un TRadioButton independiente. Su gestión, sin embargo, se realiza de forma conjunta y mucho más eficiente. Los elementos de un TRadioGroup se delimitan con un borde en cuya parte superior puede existir un título general, que podemos establecer modificando el valor de la propiedad Caption.

7.9.1. Opciones existentes y opción activa Para establecer las opciones existentes en un control TRadioGroup tendremos que editar el contenido de la propiedad ítems. Esta propiedad es un objeto TStrings, cuyo funcionamiento ya conocemos por haberlo tratado anteriormente, al explicar las propiedades del control TMemo.

Page 124: DELPHI 5

En modo de diseño basta con hacer doble clic sobre la propiedad ítems para abrir el editor específico con el que cuenta, añadiendo los títulos de las opciones que deseemos mostrar. En ejecución, podremos usar los métodos Add(), Append(), Insert() y Clear() para añadir o eliminar elementos. Los elementos existentes en un control TRadioGroup se numeran con un índice, cuyo punto de partida es el cero. Este índice nos servirá para saber qué opción es la que está activa o bien para establecerla, mediante la propiedad Itemindex. Este método de activar una opción o comprobar qué opción es la que está activa es mucho más eficiente que el visto anteriormente para el control TRadioButton, en el que era necesario ir comprobando el valor de la propiedad Checked de cada control hasta saber cuál de ellos era el que estaba activo. En caso de que el número de elementos contenidos en el grupo sea muy extenso, podemos optar por dividirlos en varias columnas. Para ello bastará con que asignemos a la propiedad Columns el número de columnas que deseamos.

7.9.2. En la práctica Con el fin de ver en la práctica cómo podemos utilizar un control TRadioGroup, vamos a insertar en un formulario dos controles de este tipo, asignando a la propiedad Items los elementos que se pueden ver en la figura 7.8. El primero contiene las opciones de tipo de borde para el formulario, mientras que el segundo almacena las opciones relativas a los estados de la ventana. El orden en que se han dispuesto estos valores no es aleatorio, corresponde al orden que ocupa cada una de las constantes en las enumeraciones TFormBorderStyle y TWindowState.

Figura 7.8. Aspecto del formulario con los dos controles TRadioGroup Modifique la propiedad Itemindex del primer grupo, asignándole el valor 1, y la del segundo grupo, asignándole el valor 0. Establezca también el tipo de borde inicial para el formulario, que será

Page 125: DELPHI 5

bsSingle. A continuación haga doble clic sobre un TRadioGroup y luego sobre el otro, escribiendo el código siguiente. { Al seleccionar cualquiera de las opciones del primer grupo ) procedure TForm1.RadioGrouplClick(Sender: TObject); begin { Asignar el valor apropiado a BorderStyle } BorderStyle : = TFormBorderStyle(RadioGroup1.Itemindex); end; { Al seleccionar una opción del segundo grupo } procedure TForm1.RadioGroup2Click(Sender: TObject); begin { Modificamos el valor de WindowState } WindowState := TWindowState (RadioGroup2.Itemindex); end; Observe cómo se asigna a las propiedades BorderStyle y WindowState directamente un valor que es entero, contenido en la propiedad Itemindex de cada TRadioGroup, realizando previamente la conversión necesaria. Esta asignación es posible porque las opciones han sido dispuestas en el orden en que fueron definidas las constantes de los tipos TForm1BorderStyle y TWindowState, de tal forma que el valor O en la propiedad ItemIndex del primer grupo se corresponde con la constante bsNone, el valor 1 con la constantebsSingle, y así sucesivamente. Ejecute el programa, pulsando la tecla <F9>, y realice diversas pruebas. Observe cómo al modificar el estado de la ventana ésta se maximiza o minimiza, y cómo toma el tamaño que tenía originalmente cuando se selecciona el estilo normal. El tamaño de la ventana puede ser modificado, simplemente estableciendo un tipo de borde bsSizeable.

7.10. Listas de elementos Otro de los controles que nos permite mostrar listas de elementos de los cuales es posible seleccionar uno o varios, aunque de forma ligeramente diferente, es TListBox. Este control es lo que se conoce habitualmente como una lista, un recuadro en el cual aparecen una serie de líneas de texto siendo posible la selección de una o varias de ellas. El funcionamiento del control TListBox es parecido al del control TRadioGroup que acabamos de conocer, aunque visualmente su aspecto es bastante diferente.

7.10.1. Contenido de la lista Los elementos con los que cuenta una lista están contenidos en la propiedad ítems, que es un objeto de tipo TStrings, al igual que la

Page 126: DELPHI 5

propiedad del mismo nombre del control TRadioGroup. Por tanto, ya sabemos cómo gestionar los elementos en la fase de diseño, mientras que durante la ejecución usaremos los métodos siguientes: • Add(): Toma como parámetro una cadena de texto, añadiéndola como nuevo elemento al final de la lista. • lnsert(): Toma dos parámetros, indicando el primero la posición en la que se desea insertar el elemento, y el segundo el texto a añadir. • Delete(): Este método nos permite eliminar un elemento de la lista, para lo cual deberemos facilitar su índice. • Clear(): No necesita parámetros, elimina todas las cadenas existentes en la lista. • lndex0f(): Toma como parámetro una cadena, devolviendo el índice que ésta ocupa en la lista o -1 en caso de no encontrarse. • Move(): Facilita el desplazamiento de un elemento de una posición a otra, para lo cual deberemos facilitar el índice en que se encuentra actualmente y el índice correspondiente al punto al que se desea mover. • Exchange(): Este método es similar al anterior, tomando como parámetros dos índices con el fin de intercambiar las posiciones de los elementos correspondientes. • SaveToFile(): Guarda el contenido de la lista a un archivo en disco, cuyo nombre hemos de facilitar. • LoadFromFile(): Este método es complementario al anterior, permitiendo recuperar la lista de elementos del archivo cuyo nombre se facilita como parámetro. Aunque mediante los métodos Add(), lnsert(), Move() y Exchange() es posible establecer la posición de cada uno de los elementos según nos interese, dicha posición también dependerá del valor que tenga la propiedad Sorted del control TListBox. Si dicha propiedad tiene el valor True los elementos de la lista estarán ordenados, de tal forma que, por ejemplo, al añadir un nuevo elemento éste no va al final de la lista, sino a la posición que le corresponde según el orden establecido.

7.10.2. Elementos seleccionados En una lista puede haber seleccionado un sólo elemento o varios, dependiendo del valor que asignemos a la propiedad MultiSelect. Por defecto esta propiedad contiene el valor False, indicando así que no es posible seleccionar varios elementos. Dándole el valor True activaremos esta posibilidad.

Page 127: DELPHI 5

Si se activa la posibilidad de seleccionar múltiples elementos de la lista, nos encontramos con dos métodos diferentes de realizar esta selección. El método a utilizar dependerá del valor dado a la propiedad ExtendedSelect, que también es de tipo Boolean. El valor True en esta propiedad indica que se usará la selección extendida, lo que significa que el usuario puede utilizar el botón izquierdo del ratón junto con las teclas <Mayús> y <Control> para realizar la selección, de forma similar a como se haría, por ejemplo, en el Explorador de Windows. Si la propiedad ExtendedSelect tiene el valor False, la selección de los elementos se realizará simplemente pulsando sobre ellos con el botón izquierdo del ratón, pudiendo eliminar la selección de igual forma. El método por el cual podremos saber qué elemento o elementos hay seleccionados dependerá de que esté permitida o no la selección múltiple. Si la propiedad MultiSelect tiene el valor False, el índice del único elemento seleccionado posible se encontrará en la propiedad Itemindex, al igual que ocurría con el control TRadioGroup. El valor -1 indica que no hay en ese momento ningún elemento seleccionado. En caso de que esté permitida la selección múltiple, mediante la propiedad SelCount podremos saber cuántos elementos hay seleccionados, mientras que en la propiedad Selected, que es una matriz de enteros, encontraremos los índices de cada uno de los elementos.

7.10.3. Otras propiedades de TListBox Cuando una lista contiene más elementos de los que le es posible mostrar de forma simultánea, automáticamente aparece una barra de desplazamiento vertical que nos permite acceder al contenido completo de la propiedad Items. Podemos determinar y fijar cuál es el primer elemento que está visible en un determinado momento, en la parte superior de la ventana, mediante la propiedad Topindex, que almacena el índice de dicho elemento. Al desplazar los elementos que contiene la lista, podrá observar posiblemente que el elemento que está en la parte superior o el situado en la parte inferior aparecen cortados, viéndose sólo un trozo del texto. Esto es debido a que por defecto el alto de la lista no es un múltiplo de la altura de cada elemento, ya que la propiedad IntegralHeight contiene el valor False. Podemos dar el valor True a esta propiedad si deseamos evitar el citado efecto.

7.10.4. En la práctica El programa ejemplo que vamos a construir en este caso nos permitirá añadir elementos a la lista, durante la ejecución, con el texto que nosotros facilitemos. También podremos eliminar un elemento que hayamos previamente seleccionado o bien vaciar totalmente la lista. Los elementos podrán aparecer en el orden en que se van añadiendo o bien ordenados.

Page 128: DELPHI 5

Iniciaremos el trabajo insertando en un formulario los controles que puede ver en la figura 7.9. Un TListBox y un TEdit, ambos precedidos de un TLabel que le sirve como título, tres controles TButton para permitir las diferentes acciones y un TCheckBox, mediante el cual indicaremos si deseamos o no que los elementos estén ordenados. Edite el orden de acceso mediante la tecla <Tab>, haciendo que el primer control en activarse sea el TEdit. A continuación dé el valor True a la propiedad Default del primer botón, haciendo así que la pulsación de la tecla <Intro> tenga como resultado la inserción en la lista del elemento cuyo texto se haya introducido en el TEdit. Por último, en cuanto a propiedades se refiere, asigne el valor False a la propiedad Enabled del segundo botón, ya que la función de borrado de un elemento sólo debe estar activa en caso de que haya un elemento seleccionado, lo que en principio no ocurrirá puesto que la lista estará vacía. La pulsación del botón Añadir, que se producirá al pulsar la tecla <Intro> ya que es el botón por defecto, tendrá como resultado que el texto introducido por nosotros en el control TEdit, disponible en la propiedad Text, sea añadido como un elemento más a la lista. Para ello tendremos que escribir el código siguiente en el método asociado al evento OnClick de este botón.

Figura 7.9. Aspecto del formulario para probar el control TListBox Observe que tras añadir el nuevo elemento se realiza una llamada al método SelectAII() del TEdit, con el fin de seleccionar todo su contenido y facilitar así la introducción de un nuevo valor. { Al pulsar el botón Añadir } procedure TForml.Button1Click(Sender: TObject); begin { Añadimos a la lista el texto existente en el control TEdit } ListBox1.Items.Add(Edit1.Text); { y marcamos todo el contenido de dicho control } Edit1.SelectAll;

Page 129: DELPHI 5

end; Inicialmente el botón Borrar estará desactivado, activándose sólo cuando al pulsar sobre la lista seleccionemos un elemento. Por ello deberemos controlar el evento OnClick deI TListBox, asignando a la propiedad Enabled del botón el valor True o False, dependiendo de que exista o no un elemento seleccionado en ese momento, lo que sabremos inspeccionando el valor de la propiedad Itemindex, que deberá ser distinto de -1. { Al pulsar en la lista } procedure TForml.ListBox1Click(Sender: TObject); begin { Activamos o desactivamos el botón de Borrar según haya o no seleccionado un elemento } Button1.Enabled := ListBox1.Itemindex <> -1; end; Una vez que el botón esté activado podremos pulsarlo, provocando el borrado del elemento seleccionado en la lista. Para ello bastará con realizar una llamada al método Delete() de la propiedad ítems, pasando como parámetro el índice del elemento a borrar, que al ser el elemento seleccionado encontraremos en la propiedad ítem índex. { Al pulsar el botón Eliminar } procedure TForml.Button2Click(Sender: TObject); begin { Eliminamos el elemento seleccionado } ListBoxI.Items.Delete(ListBox1.Itemindex); { y desactivamos el botón de borrar } Button2.Enabled := False; end; El código asociado al botón Vaciares el más simple, ya que consta de una simple llamada al método Clear(), que elimina todo el contenido del objeto TStrings, o lo que es lo mismo, de la lista. { Al pulsar el botón Vaciar } procedure TForml.Button3Click(Sender: TObject); begin { Dejamos vacía la lista } ListBox1.Items.Clear; end; Por último tendremos que controlar la pulsación sobre el TCheckBox, activando o desactivando el orden de los elementos en la lista. Para ello bastará con asignar a la propiedad Sorted el valor que contenga la propiedad Checked del TCheckBox, como puede ver a continuación. ( Al activar o desactivar la opción Ordenada } procedure TForm1.CheckBox1Click(Sender: TObject);

Page 130: DELPHI 5

begin { Damos el valor adecuado a la propiedad Sorted } ListBox1.Sorted := CheckBox1.Checked; end; Al ejecutar el programa lo primero que deberá hacer será añadir algunos elementos a la lista, introduciendo un texto en el TEdit y pulsando la tecla <Intro>. A continuación puede pulsar sobre el TCheckBox para activar la ordenación de los elementos. Si deja activada esta opción, al añadir nuevos elementos éstos se colocarán directamente en las posiciones que le correspondan según el orden establecido. Pruebe también a seleccionar un elemento de la lista, momento en que se activará el botón Borrar, y eliminarlo.

7.11. Listas desplegables En el programa de ejemplo anterior hemos usado un control TEdit conjuntamente con un TListBox, permitiendo así la inserción de elementos en la lista. Existe un control, conocido como lista combinada, que es la unión de un TEdit con un TListBox, al que Delphi llama TComboBox. Básicamente, un TComboBox incorpora las funciones de ambos controles y, por tanto, cuenta con propiedades que ya conocemos, como Text, MaxLength, ítems, Itemindex, etc. Tanto el aspecto como el funcionamiento de una lista combinada dependerá del estilo que tenga, según el valor que se asigne a la propiedad Style. El estilo por defecto es csDropDown, que corresponde al de un campo de edición en el que es posible introducir texto como si de un TEdit se tratase, con un botón en el extremo derecho que despliega una lista con los elementos contenidos actualmente. Si deseamos que el usuario pueda seleccionar un valor de la lista, desplegándola, pero no que pueda introducir un texto, el estilo a utilizar es csDropDownList. Con este estilo el botón que despliega la lista aparece totalmente unido al recuadro de edición. Por último tenemos el estilo csSimple, en el cual la lista combinada aparece como un TEdit sin botón ni lista desplegable adjunta. En un TComboBox con este estilo es posible utilizar las teclas de cursor arriba y abajo para recorrer los elementos de la lista, ya que ésta existe aunque no sea visible directamente.

7.11.1 En la práctica Para comprobar el funcionamiento del control TComboBox, vamos a diseñar un programa en el que podamos añadir elementos a la lista introduciendo el texto directamente en el propio control, sin necesidad de un TEdit auxiliar. Sobre el TComboBox vamos a situar un control TLabel, que utilizaremos para ir indicando el número de

Page 131: DELPHI 5

elementos que contiene la lista en cada momento. Debajo de la lista combinada vamos a insertar un TButton cuya finalidad será eliminar todo el contenido, con una llamada al método Clear() de la propiedad ítems. Por último, a la derecha del TComboBox vamos a insertar un control TRadioGroup con tres elementos, que nos van a permitir cambiar en ejecución el estilo de la lista combinada, lo cual nos servirá para observar las diferencias entre uno y otro. Puesto que el texto de los nuevos elementos se va a introducir usando el propio TComboBox, deberemos controlar cada pulsación de tecla, mediante el evento OnKeyPress, para que en el momento en que se detecte la pulsación de la tecla <Intro> se proceda a la inserción del texto en la lista, actualizando el contador que mostraremos en el TLabel e ignorando la pulsación. El código que deberemos escribir es el siguiente. { Con cada pulsación de tecla en el ComboBox } procedure TForm1.ComboBox1KeyPress (Sender: TObject; var Key: Char); begin If Key = #13 Then { Si la tecla es <Intro> } With ComboBox1 Do Begin Items.Add(Text); { Añadimos el nuevo elemento } { Mostramos el número de elementos } Label1.Caption :=IntToStr (Items. Count) + ' elementos'; SelectAll; { Seleccionamos el texto } Key := #0; {e ignoramos la pulsación de tecla } End; end; El único botón existente en el formulario, titulado Vaciar, nos permitirá dejar vacía la lista combinada, para lo cual bastará con realizar una llamada al método Clear(). También modificaremos la propiedad Caption deITLabel, con el fin de indicar el número de elementos existentes en ella. { Al pulsar el botón Vaciar } procedure TForm1.Button1Click(Sender: TObject); begin { Dejamos vacía la lista } ComboBox1.Items.Clear; Label1.Caption := '0 elementos'; end; Por último tendremos que controlar el evento OnClick del control TRadioGroup, con el fin de establecer el estilo adecuado para la lista combinada según la opción que se seleccione. Como hicimos en un

Page 132: DELPHI 5

ejemplo anterior, esta asignación se realizará de forma directa, llevando a cabo una conversión de tipo, como puede ver a continuación. { Al seleccionar un elemento del TRadioGroup } procedure TForm1.RadioGroup1Click(Sender: TObject); begin { Fijamos el estilo que corresponda } ComboBox1.Style : =TComboBoxStyle(RadioGroup1.Itemindex); end; Ya puede ejecutar el programa, pulsando <F9>, y realizar pruebas con el control TComboBox. Añada elementos y compruebe su existencia desplegando la lista. Modifique el estilo y observe las diferencias existentes entre uno y otro. En la figura 7.10 puede ver el aspecto del programa en funcionamiento.

Figura 7.10. Aspecto del programa en funcionamiento

7.12. Controles contenedores Todos los controles que hemos conocido hasta ahora, si exceptuamos el control TLabel, tienen como finalidad interactuar de alguna forma con el usuario, permitiéndole seleccionar una opción o introducir algún tipo de información, ya sea tecleándola o bien seleccionándola de una lista. Cuando en un formulario existen muchos controles, lo normal, con el fin de aclarar la visualización, es que se creen grupos, delimitándolos de forma clara. En este apartado es donde nos pueden ayudar los controles TGroupBox y TPanel. Tanto uno como otro son controles que actúan como contenedores, lo que les permite alojar en su interior a otros controles. Los controles que deseemos que formen parte de un grupo, ya esté construido en base a un TGroupBox o a un TPanel, deben ser insertados directamente en el interior del control contenedor. No sirve de nada, por tanto, tomar un control que ya está en el formulario y desplazarlo hasta situarlo sobre el contenedor, ya que lo que estamos haciendo es

Page 133: DELPHI 5

superponerlo, pero no incluirlo. Es fácil saber cuándo un cierto control está o no insertado en un contenedor, ya que basta con mover el TGroupBox o TPanel. Todos aquellos controles que estén insertados también se desplazarán, mientras que los que no lo estén quedarán inalterados. Tanto el control TGroupBox como el TPanel, cuentan con una propiedad Caption a la que podemos asignar un título. La diferencia está en que mientras el primero muestra ese título en la parte superior del control, en el borde, el segundo lo hace en el interior, por defecto en el centro del control.

7.12.1. Alineación del contenedor Los controles que actúan como contenedores, como TGroupBox y TPanel, cuentan con una propiedad, llamada Align, mediante la cual es posible mantenerlos alineados en una cierta posición del formulario, ajustándose automáticamente en caso de que ésta cambie de tamaño, ya sea durante el diseño o la ejecución. En la tabla 7.10 puede ver los valores que puede tomar la propiedad Align. Todos ellos implican algún cambio en el tamaño del control, puesto que éste se ha de ajustar a un cierto margen del formulario, o bien ocupando todo el espacio disponible en ésta.

Constante El control se ajusta a ...

alNone Nada, mantiene se posición y dimensión originales.

alTop El margen superior de la ventana.

alBottom El margen inferior de la ventana.

alLeft El margen izquierdo de la ventana.

alRight El margen derecho de la ventana.

alClient Todo el espacio disponible en la ventana.

Tabla 7.10. Posibles valores para la propiedad Align

Una modificación de la propiedad Align durante la ejecución, realizando una simple asignación, tiene como consecuencia el cambio inmediato de la posición del control.

7.12.2. Elementos de realce El control TPanel es más atractivo, visualmente hablando, que el control TGroupBox, ya que cuenta con un borde cuyo aspecto y anchura podemos configurar. La existencia o no del borde dependerá del valor que tome la propiedad BorderStyle, que será bsNone o bsSingle.

Page 134: DELPHI 5

El borde del control puede contar un biselado exterior y otro interior, que puede aparecer resaltado o hundido. Mediante las propiedades BevelOuter y Bevelinner podremos seleccionar el tipo de biselado, mientras que con la propiedad BevelWidth estableceremos su anchura. En la tabla 7.11 se muestran los valores que pueden tomar las propiedades BevelOuter y Bevelinner.

Constante Tipo de biselado

bvNone Ninguno

bvRaised Resaltado

bvLowered Hundido

Tabla 7.11. Valores para las propiedades BevelOuter y Bevelinner

7.12.3. En la práctica Realmente, los dos controles que acabamos de conocer son más elementos de interfaz que elementos con los que sea posible interactuar en ejecución y, si exceptuamos la propiedad Align, no hay prácticamente nada más relevante en estos controles. Por ello, el programa que vamos a hacer a continuación estará centrado en demostrar el uso de la propiedad Align, permitiendo desplazar un TPanel de un margen a otro del formulario. Inserte en un formulario un control TPanel y ajuste las propiedades de realce, vistas en el punto anterior, según sus propias preferencias. Dé a la propiedad Align el valor alBottom, haciendo así que el control se ajuste a la parte inferior del formulario. Para permitir el movimiento del TPanel en ejecución vamos a usar una técnica muy habitual en Windows, como es la de arrastrar y soltar. Para ello lo primero que haremos será dar el valor dmAutomatic a la propiedad DragMode del TPanel, lo que nos permitirá iniciar la operación sin necesidad de tener que llamar al método BeginDrag(). A continuación escribiremos el código siguiente, que irá asociado a los eventos OnDragOver y OnDragDrop del formulario. { Al arrastrar un objeto sobre el formulario } procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin { Si el objeto es un TPanel } If Source Is TPanel Then Accept := True; { lo aceptamos } end; ( Al soltar el TPanel sobre el formulario } procedure TForm1.FormDragDrop(Sender, Source: TObject; X, Y: Integer);

Page 135: DELPHI 5

begin { Según el punto en que se haya soltado } If x < 50 Then { establecemos una alineación u otra } Panel1.Align := alLeft Else If X > Width - 50 Then Panel1.Align := alRight Else If Y < 50 Then Panel1.Align := alTop Else If Y > Height - 50 Then Panel1.Align := alBottom; end; El evento OnDragOver se produce cuando un objeto está siendo arrastrado sobre un control. En este caso, el evento lo recibe el formulario cuando el panel se arrastra sobre el. En el método asociado a este evento primero comprobamos si el objeto que está siendo arrastrado, que se recibe en el parámetro Source, es del tipo TPanel, para lo cual utilizamos el operador Is. En caso afirmativo damos el valor True al parámetro variable Accept, indicando así que aceptamos ese objeto. Esto provoca que el icono del puntero del ratón cambie, indicando la nueva situación. Cuando el objeto es soltado, el control que lo recibe, que en este caso es el formulario, provoca un evento OnDragDrop. En ese momento comprobamos el punto en que ha sido soltado el panel y, de acuerdo con él, asignamos un valor u otro a la propiedad Align, haciendo que el TPanel se ajuste al margen apropiado. Ejecute el programa, en cuyo formulario tan sólo existe el panel en la parte inferior. Pínchelo y arrástrelo hasta los bordes del formulario, soltándolo en cada uno de ellos. Podrá comprobar cómo automáticamente se ajusta al margen que corresponda. En la figura 7.11 puede ver el programa en ejecución.

Figura 7.11. El programa con el TPanel en ejecución

Page 136: DELPHI 5

7.13. Barras de desplazamiento En algunos de los ejemplos que hemos usado para comprobar el funcionamiento de ciertos controles, hemos podido ver la existencia de barras de desplazamiento. Los controles TMemo y TListBox, por ejemplo, las utilizan para permitir el desplazamiento de los datos cuando la información a mostrar es más extensa que el espacio disponible en el control. No todos los controles cuentan con barras de desplazamiento, pero mediante el control TScrollBar nosotros podemos añadirlas. Obviamente, en estos casos el desplazamiento de la información no se producirá de forma automática, seremos nosotros los que deberemos ocupamos de este trabajo. Una barra de desplazamiento puede ser tanto horizontal como vertical, aspecto que fijaremos mediante la propiedad Kind del TScrollBar, a la que asignaremos el valor sbVertical o sbHorizontal, según las necesidades que tengamos.

7.13.1. Límites y posición de la barra Una barra de desplazamiento cuenta con un cursor en su interior, que marca la posición actual dentro de un rango cuyos extremos están representados por los extremos de la propia barra. Tanto la posición del cursor en la barra como sus límites tienen una correspondencia con valores numéricos, que serán los que nosotros usemos en nuestros programas. Los valores extremos, mínimo y máximo, entre los cuales se desplazará el cursor de la barra habremos de asignarlos a las propiedades Min y Max, respectivamente. La posición del cursor en un determinado momento dependerá del valor de la propiedad Position, que deberá encontrarse dentro del rango delimitado por los límites anteriores. La posición en una barra de desplazamiento puede modificarse mediante código, asignando un valor a la propiedad Position, o bien por una actuación del usuario sobre la barra. Este cambio genera un evento OnChange, que nosotros podemos aprovechar para en consecuencia realizar los ajustes u operaciones que sean necesarias.

7.13.2. Incrementos grandes y pequeños Trabajando con una barra de desplazamiento es posible alterar la posición actual de varias formas, tanto con el ratón como con el teclado. Una de las formas consiste en pulsar sobre las flechas que hay en los extremos, lo que equivale a pulsar las teclas de desplazamiento del cursor. En ambos casos se produce un incremento o decremento en la posición del cursor en la barra, cuya magnitud vendrá determinada por el valor de la propiedad SmallChange.

Page 137: DELPHI 5

También podemos pulsar en el interior de la barra, con el puntero del ratón, a un lado u otro de donde se encuentra el cursor en ese momento. Esto equivale a utilizar las teclas <RePág> y <AvPág>, que producen un incremento o decremento de tantas unidades como se haya especificado en la propiedad LargeChange. Inicialmente las propiedadesSmallChange y LargeChange contienen el mismo valor, pero lo normal es dar a la segunda un valor superior al de la primera, de tal forma que sea posible avanzar o retroceder en la barra de desplazamiento bien línea a línea, o página a página.

7.13.3. En la práctica Para ver en funcionamiento este nuevo control vamos a insertar en un formulario un TGroupBox, que nos servirá como contenedor. En su interior insertaremos dos controles TScrollBar, uno horizontal y otro vertical, que situaremos adecuadamente en la parte inferior y derecha del recuadro. También vamos a situar en el interior del grupo un control TRadioButton, asignando a su propiedad Caption una cadena vacía. Fuera del TGroupBox, por ejemplo a su derecha, vamos a insertar cuatro controles TLabel. Los dos primeros serán simplemente unos títulos, que contendrán las cadenas 'Posición X' y 'Posición Y'. Los otros dos nos servirán, en ejecución, para mostrar la columna y línea en que se haya posicionado el TRadioButton en el interior del TGroupBox. La finalidad de las barras de desplazamiento, en este caso, será permitimos desplazar el TRadioButton al punto que nos interese. Por tanto, la primera barra de desplazamiento, que será la horizontal, tendrá como valor máximo el ancho disponible en elTGroupBox, mientras que el máximo de la segunda vendrá dado por la altura. Estos valores, que podríamos obtener en la fase de diseño realizando algunos cálculos, pueden ser asignados en ejecución de una forma mucho más cómoda. Haga doble clic sobre el fondo del formulario, en un punto en el que no exista control alguno, con el fin de abrir el método correspondiente al evento OnCreate que, como sabemos, se produce al crear el formulario. Éste es el punto adecuado para calcular los valores máximos de las barras de desplazamiento, además de dar el valor adecuado a la propiedad Position dependiendo de la posición en que se encuentre en ese momento el control TRadioButton. El código necesario es el que se muestra a continuación. { Al crear el formulario } procedure TForm1.FormCreate(Sender: TObject); begin { Calculamos el valor máximo para las dos barras de desplazamiento }

Page 138: DELPHI 5

ScrollBar1.Max := GroupBox1.Width - ScrollBar2.Width - RadioButton1.Width; ScrollBar2.Max := GroupBox1.Height – ScrollBar1.Height - RadioButton1.Height; { y fijamos su posición inicial, que será la que tenga el TRadioButton en el interior del TGroupBox } ScrollBar1.Position := RadioButton1.Left; ScrollBar2.Position := RadioButton1.Top; end; Cada vez que se produzca una modificación en cualquiera de las dos barras de desplazamiento, deberemos automáticamente actualizar la posición del TRadioButton, así como mostrar la nueva posición en las etiquetas que hemos dispuesto a tal efecto. En este caso utilizaremos el eventoOnChange de ca daTScrollBar y el código necesario es el siguiente. { Cada vez que se produzca un cambio en una barra de desplazamiento } procedure TForm1.ScrollBar2Change(Sender: TObject); begin { Actualizamos la posición del TRadioButton } RadioButton1.Top := ScrollBar2.Position; { y mostramos la nueva coordenada } Label3.Caption := IntToStr(ScrollBar2.Position); end; { Lo mismo con la otra barra de desplazamiento } procedure TForm1.ScrollBar1Change(Sender: TObject); begin RadioButton1.Left := ScrollBar1.Position; Label4.Caption := IntToStr(ScrollBar1.Position); end; Al ejecutar el programa observará que el cursor de la barra de desplazamiento que ha insertado en primer lugar parpadea, indicando que tiene el foco de entrada. En ese momento podemos utilizar el teclado para desplazarnos, aunque como es lógico también tenemos la posibilidad de utilizar el ratón. En la figura 7.12 puede ver el programa en funcionamiento. Compruebe los diversos métodos por los cuales puede modificar la posición en la barra de desplazamiento.

Page 139: DELPHI 5

Figura 7.12. El programa con los controles TScrollBar

7.14. Elementos gráficos A diferencia de los controles con los que hemos estado trabajando hasta ahora, que se encontraban en la primera página de la Paleta de componentes, el control TShape se encuentra en la página siguiente, llamada Additional. Mediante dicho control podremos insertar en un formulario elementos gráficos, tales como rectángulos, cuadrados, óvalos o círculos. Estas entidades gráficas cuentan con un borde y un fondo, cuyos atributos podemos también modificar.

7.14.1. Figura a dibujar Con el mismo control TShape es posible representar diversas figuras o formas, dependiendo del valor que se dé a la propiedad Shape. En la tabla 7.12 se enumeran los valores existentes, indicándose la figura que se dibujará en cada caso.

Constante Figura a dibujar

stRectangle Rectángulo

stSquare Cuadrado

stRoundRect Rectángulo con las esquinas redondeadas.

stRoundSquare Cuadrado con las esquinas redondeadas.

stEllipse Elipse.

stCircle Círculo.

Tabla 7.12. Valores posibles para la propiedad Shape

Page 140: DELPHI 5

La propiedad Shape puede ser establecida tanto en modo de diseño, seleccionando uno de los valores de la lista desplegable adjunta a la propiedad, o bien en ejecución, realizando una simple asignación.

7.14.2. Brocha y lápiz El tipo de trazo y color del borde, así como el color y trama de relleno que se utilizan, son aspectos que dependen de dos propiedades, Pen y Brush, que contienen una referencia a un objeto de tipo TPen y TBrush, respectivamente, a los que se conoce habitualmente como lápiz y brocha. El lápiz es el que se utiliza para trazar el borde, mientras que la brocha se usa para pintar el relleno. Como objetos que son, estas propiedades cuentan con otras propiedades, que serán las que nos permitan establecer los atributos que a nosotros nos interesen. Un objeto TPen dispone de una propiedad Color, mediante la cual podemos especificar el color, una propiedad Mode, que sirve para facilitar la realización de operaciones lógicas, una propiedad Style, en base a la cual seleccionaremos el tipo de trazo, y una propiedad Widh, cuya finalidad es indicar el grueso del lápiz. Los valores posibles para la propiedad Style son los que se enumeran en la tabla 7.13.

Constante Tipo de trazo

psSolid Sólido.

psDash Guionado.

psDot Punteado.

psDashDot Guionado y punteado alternativamente.

psDashDotDot Guionado y punteado alternativamente.

psClear Ninguno.

psInsideFrame Sólido en el interior de la figura.

Tabla 7.13. Posibles valores para la propiedad Style de TPen

Las dos propiedades con que cuenta un objeto de tipo TBrush son Color y Style, mediante las cuales podremos indicar el color y tipo de relleno que deseamos. En la tabla 7.14 se enumeran los estilos o tramas de relleno que podemos utilizar.

Constante Trama de relleno

bsSolid Sólida.

bsClear Ninguna.

Page 141: DELPHI 5

bsHorizontal Líneas horizontales.

bsVertical Líneas verticales.

bsFDiagonal Líneas diagonales en sentido \.

BsBDiagonal Líneas diagonales en sentido /.

bsCross Líneas horizontales y vertivales cruzadas.

bsDiagCross Líneas diagonales cruzadas.

Tabla 7.14. Posibles valores para la propiedad Style de TBrush

7.14.3. En la práctica La mejor forma de hacemos una idea de las posibilidades de este control, consiste en probar todos los posibles valores para las propiedades Shape y Style de la brocha y lápiz. Esto es lo que haremos en parte en el programa que se propone como ejemplo, para el cual insertaremos en un formulario un control TShape, un TRadioGroup con todos los valores de la tabla 7.12 y un segundo TRadioGroup conteniendo los valores de la tabla 7.14. Tendremos que asignar el valor 0 a la propiedad Itemindex de ambos controles, con el fin de que aparezca seleccionada por defecto la primera opción en cada caso. Cada vez que se modifique, en ejecución, una de las opciones de los TRadioGroup, el código de nuestro programa deberá modificar adecuadamente la propiedad Shape del TShape o bien la propiedad Style de la brocha. Para ello tendremos que utilizar los eventos OnClick de los dos controles TRadioGroup, en cuyos métodos escribiremos las dos sentencias siguientes. { Al cambiar de figura } procedure TForm1.RadioGroup1Click(Sender: TObject); begin { Dar a la propiedad Shape el valor adecuado } Shape1.Shape := TShapeType(RadloGroup1.ItemIndex); end; { Al cambiar el tipo de relleno } procedure Tform1.RadioGroup2Click(Sender: TObject); begin { Asignamos a la propiedad Style del objeto Brush el valor correspondiente } Shape1.Brush.Style : = TBrushStyle(RadioGroup2.ItemIndex); end; Tan sólo con esto ya podemos ejecutar el programa, cuyo aspecto se muestra en la figura 7.13, y comprobar el aspecto que toma el control TShape a medida que vamos seleccionando las distintas opciones disponibles.

Page 142: DELPHI 5

Figura 7.13. Aspecto del programa con el control TShape

7.15. Imágenes de mapas de bits No todos los elementos gráficos son tan simples como para poder ser representados mediante un control TShape, si exceptuamos las entidades más normales, como son los cuadrados, rectángulos y círculos. Suponga, por ejemplo, que desea dibujar algo más complejo, o simplemente que desea mostrar en el formulario el contenido de un archivo gráfico. Estas son funciones para las cuales no le será útil el control TShape. Mediante el control TImage podemos mostrar una imagen en el formulario, bien recuperándola de un archivo en disco, que puede estar en formato BMP, ICO o WMF, o bien dibujándola nosotros mismos en ejecución. La imagen que se muestra es el contenido de la propiedad Picture, que es un objeto de tipo TPicture, mientras que la superficie de trabajo, en la que podemos dibujar, está representada por la propiedad Canvas, que es un objeto de tipo TCanvas.

7.15.1. Mostrar imágenes contenidas en archivos El objeto TPicture al que hace referencia la propiedad Picture del control TImage, cuenta con los métodos LoadFromFileQ y SaveToFile() que ya conocemos de otros objetos. La diferencia es que, en este caso, estos métodos están encaminados a recuperar y guardar imágenes, en lugar de líneas de texto. Por tanto, para recuperar un archivo que se encuentra en disco lo único que tendremos que hacer será una llamada al método LoadFromFile(), pasando como único parámetro una cadena conteniendo el nombre del archivo a recuperar. Una vez que hemos recuperado una imagen, las propiedades Height y Width del TPicture nos sirven para conocer sus dimensiones originales.

Page 143: DELPHI 5

Estas dimensiones no tienen por qué ser coincidentes con las del control TImage, caso éste en que se pueden seguir varios caminos. Por defecto, cuando se recupera una imagen ésta se visualiza con su tamaño original, partiendo de la esquina superior izquierda del control TImage. En caso de que la imagen sea más pequeña que el control, parte de éste quedará vacío. Si es la imagen la que cuenta con unas dimensiones mayores, sólo será visible parcialmente, en la medida de lo que sea posible mostrar en el control. Si damos el valor True a la propiedad AutoSize del control TImage, conseguiremos que éste ajuste automáticamente sus dimensiones a las de la imagen que va a mostrar. El efecto contrario, que sea la imagen la que adecué su tamaño al del control, lo conseguiremos dando el valor True a la propiedad Stretch. Por último, podemos dar el valor a la propiedad Center si deseamos que la imagen se muestre con su tamaño original pero centrada en el control.

7.15.2. La superficie de dibujo El área de dibujo representada por un control TImage es accesible a través de la propiedad Canvas, que contiene una referencia a un objeto TCanvas. Este tipo de objeto cuenta con una serie de propiedades y métodos que facilitan las operaciones de dibujo. Entre las propiedades de un objeto TCanvas encontramos dos, llamadas Pen y Brush, que representan al lápiz y la brocha usadas para dibujar. Ya conocemos el funcionamiento de estas dos propiedades, que hemos tratado anteriormente al trabajar con el control TShape. También contamos con una propiedad Font, cuyo funcionamiento también conocemos, que nos servirá para fijar los atributos de texto en caso de que deseemos imprimir algo en el área de dibujo. Para dibujar en el interior de un TCanvas, además de fijar las características del lápiz, brocha y letra, tendremos que utilizar sus métodos de dibujo. Los más habituales son los que siguen: • MoveTo(X,Y): Establece el punto de dibujo en la posición indicada por X,Y. Esta posición queda como punto de partida para otras operaciones posteriores que no indiquen una posición de inicio. • LineTo(X,Y): Traza una línea desde el punto actual hasta X,Y. • Rectangle(Xl,Yl,X2,Y2): Traza un rectángulo según las coordenadas facilitadas. • RoundRect(Xl,Yl,X2,Y2,X3,Y3): Traza un rectángulo con una esquina en X1,Y1 y la opuesta en X2,Y2, redondeando los vértices como si fuesen cuartos de una elipse con el ancho de X3 y el alto de Y3.

Page 144: DELPHI 5

• TextOut(X,Y,cadena): Imprimir la cadena de texto en la posición indicada.

7.15.3. En la práctica Para ver en funcionamiento este nuevo control, vamos a señar un programa que nos permita dibujar a mano alzada sobre una pequeña superficie, facilitando el almacenamiento y recuperación de los dibujos. Inserte en el formulario un control TPanel, dando el valor alBottom a la propiedad Align, con el fin de alinearlo en el margen inferior. A continuación inserte un control TImage y dé el valor alClient a la misma propiedad, consiguiendo así que ocupe todo el resto del espacio disponible En el control TPanel vamos a insertar un TEdit, con el que podremos facilitar el nombre y camino del archivo a recuperar o guardar, y dos botones, con los títulos Guardar y Recuperar, que serán los encargados de realizar esas dos acciones. Cuando se pulse el botón izquierdo del ratón sobre el área de dibujo, deberemos entrar en el estado de dibujo, de tal forma que a medida que se mueva el ratón vayamos dibujando líneas continuas. Este proceso finalizará en el momento en que se libere el botón del ratón. Para controlar el estado en el que nos encontramos en cada momento, vamos a definir al nivel de módulo una variable a la que vamos a llamar Dibujando, de tipo Boolean. A continuación escribiremos el código siguiente en los métodos correspondientes a los eventos OnMouseDown, OnMouseMove y OnMouseUp del control TImage. // Al pulsar un botón del ratón pocedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // Si es el botón izquierdo If Button = mbLeft Then Begin Dibujando := True; // Activamos el dibujo // y fijamos el punto inicial Image1.Canvas.MoveTo(X, Y) ; End; end; // Al liberar el botón del ratón procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // Si es el botón izquierdo If Button = mbLeft Then Dibujando := False; // desactivamos el dibujo end;

Page 145: DELPHI 5

//A medida que el ratón se mueve procedure TForml.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin // Si estamos dibujando trazamos una línea hasta la nueva posición If Dibujando Then Image1.Canvas.LineTo(X, Y); end; Cuando se pulse el botón Guardar realizaremos una llamada al método SaveToFile() de la propiedad Picture del control TImage, pasando como parámetro la cadena de texto contenida en la propiedad Text del control TEdit. De forma análoga, al pulsar el botón Recuperar llamaremos al método LoadFromFile(), tal y como se muestra a continuación. // Al pulsar el botón Salvar procedure Tform1.Button1Click(Sender: TObject); begin Image1.Picture.SaveToFile(Edit1.text); end; // Al pulsar el botón Cargar procedure TForm1.Button2Click(Sender: TObject); begin Image1.Picture.LoadFromFile(Edit1.text); end; Ya puede ejecutar el programa, que en principio mostrará un área de dibujo con el mismo color de fondo que el formulario. Puede optar por facilitar el nombre de un archivo en disco, recuperando una imagen ya existente, o bien por iniciar un dibujo desde cero. En la figura 7.14 puede ver el programa en funcionamiento, mostrando el contenido de un archivo BMP.

Page 146: DELPHI 5

Figura 7.14. Aspecto del programa de dibujo en ejecución

7.16. Listas de unidades, carpetas y archivos En varios de los ejemplos que hemos utilizado hasta ahora, ha sido necesario facilitar el nombre de un archivo en disco, con el fin de guardar o recuperar de él alguna información. En estos ejemplos hemos optado porque el nombre del archivo sea fijo o bien por solicitarlo en un control TEdit. Este método, sin embargo, no es el más cómodo a la hora de buscar un archivo, ya que obliga, concretamente al usuario del programa, a utilizar otra aplicación para poder componer la secuencia de directorios que forman el camino hasta llegar al archivo deseado. Mediante los controles que vamos a conocer en los apartados siguientes, podremos permitir la selección de una de las unidades disponibles en el sistema, el desplazamiento entre directorios y la selección de un archivo, todo ello de forma Visual, sin necesidad de tener que escribir nada. Los tres controles se encuentran en la página Win 3.1 de la Paleta de componentes. Los tres controles que vamos a conocer a continuación son derivados de TComboBox o TListBox, contando con muchas propiedades que ya conocemos, como Items, Itemindex, Text, etc. En los siguientes puntos sólo se tratarán las propiedades específicas de cada control, dando por asumidas las demás.

7.16.1. Una lista de unidades El control TDriveComboBox es una lista combinada en la que se muestran las unidades de disco disponibles en el sistema, pudiendo seleccionar cualquiera de ellas. Este control es en realidad un derivado deTComboBox, que toma sus elementos automáticamente en el momento de la creación, inspeccionando el sistema. Como cualquier otra lista, podemos obtener el texto de cada uno de los elementos accediendo a ellos mediante la propiedad ítems y saber qué elemento es el que está seleccionado a través de Itemindex. Sin embargo, la propiedad que más nos interesará de este control será Drive, de tipo Char, que contiene la letra correspondiente a la unidad seleccionada en cada momento. Esta letra podrá ser mayúscula o minúscula, dependiendo del valor que asignemos a la propiedad TextCase, que puede ser tcLowerCase o tcUpperCase. Un control TDriveComboBox puede estar asociado con un TDirectoryListBox, cuyo funcionamiento se trata en el apartado siguiente, de tal forma que en el momento en que seleccionemos una nueva unidad en el primero, automáticamente se cambie a dicha unidad en el segundo. Para ello lo único que hemos de hacer es asignar a la propiedad DirList del TDriveComboBox el nombre del TDirectoryListBox.

Page 147: DELPHI 5

7.16.2. Una lista de carpetas Para mostrar una lista de directorios o carpetas, tendremos que usar el controlTDirectoryListBox, que es un derivado de TListBox. Este control muestra la jerarquía de directorios correspondiente a la unidad facilitada en la propiedad Drive, permitiendo el cambio de un directorio a otro. La propiedad más interesante de este control es Directory, ya que mediante ella podemos recuperar el camino completo que está actualmente seleccionado en el TDirectoryListBox, sin necesidad de tener que acceder a los elementos individuales de la lista. Este directorio puede ser mostrado automáticamente en un control TLabel si lo deseamos, simplemente asignando a la propiedad DirLabel el nombre de dicho control. Al igual que unTDriveComboBox se puede asociar con un TDirectoryListBox, éste puede asociarse con un TFileListBox, de tal forma que al cambiar de directorio automáticamente se muestren los archivos contenidos en él. Para ello deberemos asignar a la propiedad FileList el nombre del TFileListBox a asociar.

7.16.3. Una lista de archivos El último de los tres controles, TFileListBox, nos permite ver los archivos existentes en un cierto camino, que habremos de facilitar en la propiedad Directory. Este valor se toma automáticamente si este control está asociado a un TDirectoryListBox. Aunque por defecto se muestran todos los archivos que hay en el directorio, nosotros podemos limitar esta selección modificando el contenido de la propiedad Mask, a la que asignaremos una cadena conteniendo una o más referencias de selección. En caso de que sean varias las referendas, deberemos separar unas de las otras mediante un punto y coma. También podemos limitar los archivos mostrados en el TFiieListBox según sus atributos, mediante la propiedad FileType. Esta propiedad es un conjunto que puede contener los valores que se muestran en la tabla 7.15, según los cuales se seleccionarán unos archivos u otros.

Constante Tipo del archivo

ftNormal Normal.

ftReadOnly De sólo lectura.

ftSystem De sistema.

ftHidden Oculto.

ftArchive Archivado.

ftDirectory Directorio.

ftVolumeID Identificador de volumen.

Tabla 7.15. Valores posibles en el conjunto FileType

Page 148: DELPHI 5

El nombre del archivo que se encuentra seleccionado en la lista puede ser obtenido de forma fádl mediante la propiedad FileName, que además contiene todo el camino completo, unidad y directorio incluidos. Si deseamos obtener sólo el nombre del archivo, podemos usar las propiedades ítems e Itemindex.

7.16.4. En la práctica Veamos con un pequeño ejemplo lo simple que es utilizar estos tres controles de forma conjunta, en este caso con el fin de construir un visor de archivos gráficos, que nos permita ir explorando el sistema y visualizando el contenido de los archivos BMP, ICO y WMF. Insertaremos en el formulario un control TDriveComboBox, en la parte superior izquierda; un TDirectoryListBox, debajo del anterior y ocupando el resto de la parte izquierda; un TFileUstBox, a la derecha del primero, y un control TImage, justo debajo de la lista de archivos. Asocie la lista de unidades con la de directorios, dando el valor adecuado a la propiedad DirList, y haga lo mismo asociando la lista de directorios con la de archivos, en este caso con la propiedad FileList. Con el fin de mostrar en la lista de archivo tan sólo aquellos que podemos abrir, vamos a asignar a la propiedad Mask la cadena '*.bmp;*.ico;*.wmf'. Por último, dé el valor True a la propiedad Stretch del control TImage, con el fin de que las imágenes que se recuperen ajusten su tamaño al del control, permitiéndonos ver la imagen completa. Cada vez que se seleccione, en ejecución, uno de los archivos mostrados en el TFileListBox, éste generará un evento OnClick, que nosotros aprovecharemos para abrir el archivo y mostrar su contenido. Para ello tendremos que escribir el código que se muestra en el método siguiente. { Al seleccionar un archivo de la lista } procedure TForm1.FileListBox1Click(Sender: Tpbject); begin // lo cargamos en el TImage Image1.Picture.LoadFromFile(FileListBox1.FileName); end; No es necesario hacer nada más, puesto que los controles que muestran las unidades, directorios y archivos se sincronizan automáticamente. Puede ejecutar el programa, que mostrará un aspecto similar al de la figura 7.15, y recorrer su sistema visualizando el contenido de los archivos que se listen en el control TFileUstBox, que habrán de ser necesariamente imágenes de mapas de bits, iconos o meta-archivos.

Page 149: DELPHI 5

Figura 7.15. Aspecto del visor de archivos gráficos

7.17. Eventos periódicos Hasta ahora todos los componentes que hemos conocido han sido controles, es decir, componentes visuales con los que es posible interactuar en ejecución. No todos los componentes de Delphi son de este tipo, existen también algunos que sin ser visibles desempeñan funciones muy importantes, como tendremos ocasión de ver, por ejemplo, en el capítulo dedicado a los controles de acceso a bases de datos. Uno de los componentes no visuales de Delphi es TTimer, un componente cuya única finalidad es generar un evento a intervalos regulares con la frecuencia que nosotros mismos establezcamos. Este componente es el primero de la página System. La frecuencia con la que el componente TTimer generará el evento OnTimer dependerá del valor que asignemos a la propiedad Interval, que por defecto es 1000. Esta medida está expresada en milisegundos, lo que quiere decir que por defecto el evento se producirá una vez cada segundo. Al igual que otros componentes, éste también cuenta con la propiedad Enabled, que nos permite desactivarlo evitando así la generación del evento.

7.17.1. En la práctica Qué mejor ejemplo para comprobar el funcionamiento de un componente que se llama TTimer que un reloj digital, que cuenta con una serie de

Page 150: DELPHI 5

opciones principales, que son visibles en todo momento; basándose en ellas es posible acceder a listas de subopciones. El menú emergente, por el contrario, no está visible normalmente, apareciendo tan sólo cuando el código del programa lo solicita. Delphi es capaz de mantener una asociación entre un menú emergente y un componente, mostrándolo en el momento en que se pulsa el botón derecho del ratón sobre dicho componente.

7.18.1. Diseño de un menú principal Para crear un menú principal deberemos insertar en el formulario un componente TMainMenu, que encontraremos en la página Standard de la Paleta de componentes. Aunque es posible insertar múltiples TMainMenu en el formulario, tan sólo el primero de ellos será el que se muestre, ya que una ventana tan sólo puede contar con un menú principal. El siguiente paso consistirá en diseñar el menú, para lo cual tendremos que hacer doble clic sobre el propio componente TMainMenu o bien sobre el valor de la propiedad ítems, en el Inspector de objetos. En ambos casos aparecerá el editor de menús, una ventana en la que podremos ir introduciendo opciones y desplazándonos entre ellas construyendo nuestro menú. Inicialmente el Editor de menús aparecerá vacío, mostrando tan sólo un recuadro azul en el que podremos iniciar la introducción del título de la opción que deseemos definir. Observe que en el Inspector de objetos existen una serie de propiedades que hacen referencia a la opción que se está definiendo. Introduzca el título de la primera opción principal, que se llamará por ejemplo &Archivo, y pulse la tecla <Intro>, lo que le desplazará a la línea siguiente, en la que podrá iniciar la introducción de las subopciones. Vamos a añadir tres opciones, a las que vamos a llamar &Guardar, &Recuperar y &Salir. Inserción y borrado de opciones En cualquier momento podemos eliminar opciones del menú o bien insertar otras nuevas entre algunas existentes. La primera operación la realizaremos simplemente pulsando la tecla <Supr>, mientras que la segunda la efectuaremos con una pulsación de la tecla <Insert>. Sitúese encima de la última opción que hemos insertado, &Salir, y pulse la tecla <Insert>, lo que provocará la apertura de un espacio en blanco. Inserte en ese espacio un guión y pulse <mtro>, verá como automáticamente en el menú aparece una línea de separación entre las dos primeras opciones y la última. Teclas de acceso rápido

Page 151: DELPHI 5

Además de las letras que precedemos con un signo & en los títulos de las opciones y que pueden ser utilizadas conjuntamente con la tecla <Alt> para acceder de una forma más rápida, cada opción puede contar además con una tecla "caliente" o de acceso rápido. Sitúese en la opción &Recuperar y a continuación despliegue la lista adjunta a la propiedad ShortCut, en el Inspector de objetos. Verá aparecer una serie de teclas y combinaciones de tecla. Seleccione la combinación <Control+C>, que aparecerá indicada en el menú a la derecha de la opción. De igual forma puede asociar una tecla de acceso rápido a las demás opciones. Opciones con más opciones Ciertas opciones de un menú pueden dar acceso a otras listas de opciones, lo cual se indica mediante una flecha apuntando hacia la derecha. El ejemplo más cercano lo podemos encontrar en la opción Reopen del menú File de Delphi. Para añadir una lista de opciones a una opción del menú deberemos pulsar la combinación <Control+Flecha der>, o bien desplegar el menú emergente y seleccionar la opción Créate Submenu. En cualquiera de los dos casos se abrirá una nueva lista a la derecha de la que ya teníamos, en la que podemos introducir las opciones que deseemos. Respuesta a la selección de una opción Al igual que un botón o una lista, un menú genera un evento OnClick cuando se selecciona una cierta opción. Para acceder al código del método asociado a una de las opciones existentes, lo único que tenemos que hacer es seleccionarla durante el diseño en el mismo formulario, tras haber cerrado el Editor de menús. También podemos hacer doble clic sobre la opción deseada en el propio Editor de menús, continuando posteriormente con la tarea de diseño. Observe que cada una de las opciones de un menú es un objeto diferente, concretamente de tipo TMenultem, y por tanto genera su propio evento, lo cual es lógico, ya que de lo contrario tendríamos que buscar alguna forma de saber qué opción es la que" se seleccionó.

7.18.2. Construcción de un menú emergente El método de diseño de un menú emergente es muy similar al que acabamos de ver para un menú principal, aunque existen algunas diferencias. La primera de ellas es que el componente a utilizar no es un TMainMenu, sino un TPopupMenu. Un menú emergente, a diferencia de un menú principal, no cuenta con una barra de opciones principales de la que se despliegan listas de opciones, sino que está compuesto de una sola lista de opciones, aunque éstas pueden a su vez dar paso a otras/creando subopciones según lo visto en un punto anterior. En la figura 7.17 puede ver un momento del diseño de un menú emergente.

Page 152: DELPHI 5

Figura 7.17. Diseño de un menú emergente Desplegar el menú emergente Como se comentaba anteriormente, un menú emergente puede ser desplegado automáticamente, al pulsar el botón derecho sobre el componente al que está asociado, o bien ser abierto mediante código, con una llamada al método Popup() del propio TPopupMenu, al que pasaremos como parámetros dos enteros indicando la posición en la que deseamos hacer aparecer la ventana. El método más habitual es el primero y también el más cómodo y simple. Para conseguir que un menú emergente se despliegue de forma automática, lo primero que tenemos que hacer es dar el valor True a la propiedad AutoPopup, trabajo que ya tenemos hecho puesto que ese es el valor por defecto de dicha propiedad. El segundo paso consistirá en crear un enlace entre un componente y el menú, para lo cual asignaremos a la propiedad PopupMenu del primero el nombre del segundo. Es posible asociar un TPopupMenu al formulario, un botón o prácticamente cualquier otro control.

7.18.3. En la práctica El uso de los menús de opciones es muy simple, ya que prácticamente no cuentan con ningún elemento adicional a los que ya conocemos. Practique diseñando diversos menús, tanto de tipo TMainMenu como TPopupMenu, y escribiendo algo de código asociado al evento OnClick de cada opción, con el fin de comprobar su funcionamiento.

Page 153: DELPHI 5

8. Depuración y excepciones

8.1. Introducción Mientras se desarrolla y ejecuta una aplicación, generalmente surgen errores que le impiden funcionar en la forma en que nosotros esperábamos. Los errores posibles los podemos agrupar en tres bloques: errores de compilación, errores de ejecución y errores lógicos. Los errores de compilación, como su propia denominación indica, son detectados por Delphi en el momento en que se compila el programa. En estos casos no se permite la ejecución, y en la parte inferior de la Ventana de código se abre una lista con la descripción del error o errores encontrados, permitiéndonos el desplazamiento a la línea apropiada. Estos errores se producen cuando se teclea mal un identificador, una expresión no es válida, hemos olvidado cerrar unos paréntesis, etc. Por su parte, los errores de ejecución no son detectables durante la compilación y surgen de forma esporádica mientras el programa se ejecuta. Si no está adecuadamente controlado, un error de ejecución puede terminar interrumpiendo la ejecución de la aplicación. Estos errores se producen, por ejemplo, cuando se intenta abrir un archivo y éste no existe, se realiza una conversión no válida, etc. Para controlar estos errores tendremos que conocer y controlar excepciones, un tema que trataremos más adelante en este capítulo. El tercer grupo de errores, a los que llamamos lógicos, no son detectados por Delphi durante la compilación ni tampoco por el programa durante la ejecución. Estos errores no hacen nada incorrecto sintácticamente hablando pero, sin embargo, impiden que el programa funcione de forma normal. Errores de ese tipo se producen cuando en una expresión se ha usado la variable que no correspondía, se llama a un procedimiento o función con un parámetro que no se esperaba, etc. El único método por el cual podemos detectar y encontrar estos errores, aparte de la propia intuición, consiste en llevar a cabo una sesión de depuración.

8.2. Proceso de depuración En la actualidad, para depurar un programa se siguen básicamente dos técnicas combinadas entre sí, que consisten en ejecutar el código sentencia a sentencia e inspeccionar valores de variables, objetos, etc. Este proceso puede ser llevado a cabo mediante una aplicación independiente, a la que se llama depurador, o bien desde el propio entorno de desarrollo, según los casos. Delphi incorpora un depurador integrado, por lo que no es necesario abandonar el entorno de diseño para poder depurar el código de nuestro programa. A pesar de ello, también tenemos la posibilidad de incluir

Page 154: DELPHI 5

en el ejecutable la información de depurado necesaria para poder utilizar un depurador separado, como es Turbo Debugger. El depurador integrado de Delphi permite tanto la ejecución paso a paso, como la evaluación de expresiones e inspección de objetos, lo que facilita en gran medida el depurado de un programa.

8.2.1. Estado de ejecución El entorno de desarrollo de Delphi puede encontrarse en tres estados diferentes, a los que vamos a llamar modo de diseño, de ejecución y de pausa. El primer estado es el inicial, en el cual podemos modificar propiedades, escribir código y realizar todas las tareas propias de diseño. Cuando ejecutamos un programa desde el propio entorno de Delphi, éste pasa al estado de ejecución. Este modo se distingue del anterior porque en la barra de título de la ventana principal de Delphi aparece la palabra Running, y también por la activación y desactivación de algunos de los botones de acciones rápidas. En este modo el botón de ejecución está desactivado, al igual que los de ejecución paso a paso, mientras que está activo el botón de pausa o parada. El tercer estado posible, al que hemos llamado de pausa, se activa cuando estando en modo de ejecución se pulsa sobre el botón Pause o se selecciona la opción correspondiente del menú Run. En este modo la palabra Running en la barra de título es sustituida por la palabra Stopped. También se producen cambios en losbotones de acciones rápidas, desactivándose el de parada o pausa y activándose los de ejecución. En el momento en que se inicia la ejecución de un programa desde el propio entorno de desarrollo de Delphi, en realidad estamos ejecutando dos programas, el nuestro y el propio depurador integrado. En estado de ejecución es nuestro programa el que tiene el control, mientras que en estado de pausa el control lo tiene el depurador. A medida que se va ejecutando paso a paso, el control pasa alternativamente de nuestro programa al depurador y viceversa, de tal forma que podemos ir viendo el resultado generado por nuestro programa al tiempo que inspeccionamos código, expresiones y contenido de objetos.

8.2.2. Ejecución paso a paso Como se ha dicho anteriormente, una de las técnicas más habituales de depuración en la actualidad es la ejecución paso a paso, ya que permite ir viendo el resultado generado por la ejecución de cada sentencia, al tiempo que se está analizando el código. A diferencia de lo que ocurre con otras herramientas de desarrollo visual, Delphi no cuenta con una ventana separada en la que se lleva a cabo el proceso de depuración. Por tanto, la ejecución paso a paso se

Page 155: DELPHI 5

efectúa en la misma ventana del Editor de código, resaltando en cada momento la línea que se va a ejecutar. La ejecución paso a paso habitualmente parte del modo de pausa, al que podemos llegar de diversas formas. Una de ellas consiste en detener la ejecución del programa cuando nos interese, pulsando el botón Pause, y otra en ejecutar hasta una determinada sentencia, por ejemplo colocando un punto de parada. En cualquier caso, una vez que estemos en el modo de pausa tendremos acceso a los comandos de depuración que vamos a ver a continuación. Ejecución simple de una sentencia Al encontramos en modo de pausa, en la ventana del Editor de código aparecerá destacada sobre las demás la línea que se ejecutará en el siguiente paso. Si no ve esta línea, utilice la opción Show Execution Point del menú Run para hacerla aparecer. Para ejecutar la sentencia marcada puede pulsar el botón Trace Into, en el área de botones de acciones rápidas, puede utilizar la opción del mismo nombre del menú Run o bien puede simplemente pulsar la tecla <F7>. En cualquier caso, el control se pasará de forma temporal a nuestro programa, que ejecutará la sentencia, generando el resultado que corresponda, y devolverá de nuevo el control al depurador, permitiéndonos así ver cuál será la siguiente sentencia que se ejecutará. En caso de que la sentencia que se va a ejecutar sea una llamada a un método, procedimiento o función de nuestro propio programa, al utilizar el comando Trace Into el punto de ejecución pasará al cuerpo de ese bloque de código, facilitándonos su depuración sentencia a sentencia. Ejecución por procedimientos Si estamos centrados en la depuración de un cierto bloque de código, como puede ser el cuerpo de un procedimiento, al ejecutar cada sentencia nos puede interesar que el depurador no pase el control a cada método, procedimiento o función al que se realice una llamada, ya que ello puede desviar nuestra atención. En este caso, en lugar de usar el comando Trace Into utilizaremos el comando Step Over, que tenemos accesible tanto como una opción del menú Run, como con un botón. También podemos ejecutar este comando simplemente pulsando la tecla <F8>. Suponiendo que el punto de ejecución se encuentre en una sentencia que contiene una llamada a un procedimiento de nuestro programa, al utilizar el comando Step Over automáticamente se ejecutará todo el código de ese procedimiento, de tal forma que el punto de ejecución

Page 156: DELPHI 5

pasará a la sentencia siguiente del mismo bloque en el que nos encontramos. Ejecución hasta un punto Aunque no es en sí un comando de ejecución paso a paso, una operación muy útil durante la depuración consiste en transferir el control a nuestro programa, continuando con la ejecución, hasta llegar a una cierta sentencia. Esto nos permite, por ejemplo, saltar todos los ciclos de un bucle, o simplemente iniciar la ejecución del programa, desde el estado de diseño, deteniéndola al llegar a esa sentencia. Para ejecutar hasta un determinado punto del programa, lo primero que tenemos que hacer es desplazar el cursor, en el Editor de código, hasta dicho punto. Hecho esto podemos pulsar ¡a tecla <F4>, o bien usar la opción Run to Cursor del menú Run. Terminar la ejecución La mejor forma de finalizar la ejecución de un programa, cuando está en curso un proceso de depuración, consiste en pulsar la tecla <F9>, ejecutando el programa de forma normal, y a continuación seleccionar la opción adecuada para salir de la aplicación de forma normal. Existe, sin embargo, otro método que nos permite terminar la ejecución en el punto en que nos encontremos en ese momento. Para ello bastará con pulsar la combinación de teclas <Control+F2> o bien seleccionar la opción Program Reset del menú Run.

8.2.3. Puntos de parada Ya hemos visto dos modos mediante los cuales podemos pasar del estado de ejecución al de parada, mediante los comandos Pause Program y Run to Cursor. Un método alternativo a éstos consiste en disponer en el código puntos de parada, unas marcas que indican al depurador que cuando llegue a una determinada sentencia pare automáticamente la ejecución del programa, permitiéndonos iniciar o continuar el proceso de depuración a partir de ese punto. Para activar un punto de parada nos tendremos que desplazar hasta la sentencia en que deseamos disponerlo, pulsando a continuación la tecla <F5>. La línea de código se destacará del resto, cambiando de color. También podemos realizar esta misma operadón mediante la opción Debug->Toggle Breakpoint |del menú emergente del Editor de código. Una vez que hemos colocado los puntos de parada en las sentencias que nos interesen, pueden ser tantas como necesitemos, podemos iniciar la ejecución del programa de forma normal. En el momento en que se vaya a ejecutar cualquiera de las líneas de código marcadas, el depurador tomará el control pasando al estado de pausa.

Page 157: DELPHI 5

En el momento en que un punto de parada ya no nos sea útil o no nos interese, podremos eliminarlo exactamente de la misma forma en que lo activamos, es decir, pulsando la tecla <F5> o con la citada opción del menú emergente. Puntos de parada condicionales Los puntos de parada que se acaban de explicar son puntos de parada incondicionales, puesto que detienen la ejecución del programa siempre que se llega a ellos. Aunque esto será lo apropiado en muchos casos, en otros, sin embargo, puede resultar en una parada continua del programa, porque la sentencia de código en que se ha dispuesto la marca se ejecute con mucha frecuencia. Para evitar esto podemos colocar un punto de parada condicional, que se distingue del tipo anterior en que no pasa al estado de pausa siempre, sino cuando una cierta condición se cumple o bien cuando por ese punto de parada se ha pasado un número determinado de veces. Lo primero que tendremos que hacer para colocar en el código un punto de parada condicional, será desplazar el cursor hasta la sentencia en que se va a situar. A continuación seleccionaremos la opciónAdd BreakpointoSource Breakpoint del menú Run, que hará aparecer la ventana que se muestra en la figura 8.1. Lo primero que vemos en esta ventana es el nombre del módulo y la línea de código en que se va a colocar el punto de parada, datos que podemos modificar manualmente, aunque no es lo habitual. A continuación encontramos dos apartados, llamados Condition y Pass count, en los cuales podemos introducir una expresión condicional, que se evalúe a True o False, y un numero entero, respectivamente.

Figura 8.1 Creacion de un punto de parada condicional En la expresión condicional que se utiliza para el punto de parada no se pueden realizar llamadas a procedimientos y todos los

Page 158: DELPHI 5

identificadores que se utilicen, de variables u objetos, han de estar accesibles desde el punto en que se ha dispuesto el punto de parada. Al ejecutar el programa, cada vez que se pasa por el punto de parada se evalúa la expresión, de tal forma que si el resultado es True se detiene la ejecución, y se pasa al estado de pausa. El segundo apartado de la ventana, Pass count, lo podemos utilizar para introducir un número entero que establecerá el número de veces que se pasará por el punto de parada antes de que éste provoque la parada efectiva del programa. En ejecución el depurador va decrementando este contador que nosotros hemos dispuesto, cada vez que se pasa por el punto de parada, de tal forma que cuando el valor llega a ser 1 se pasa al modo de pausa. Mediante la opción Debug Windows->Breakpoints del menú View podemos abrir una ventana onteniendo todos los puntos de parada que hemos dispuesto en nuestro código. De esta forma es mucho más fácil mantener un control, ya que además del nombre del módulo y la línea en que se encuentra cada marca, también se muestra la expresión y el contador, en caso de que el punto de parada sea condicional. Esta ventana cuenta con un menú emergente muy útil, en el que encontraremos opciones que nos permitirán desplazarnos directamente al código en el que se encuentra un punto de parada, añadir, eliminar, activar y desactivar marcas.

8.2.4. Inspección de valores y evaluación de expresiones Aunque el solo hecho de poder ejecutar el código paso a paso puede ser de gran utilidad, a la hora de buscar los errores lógicos que puedan existir en un programa, esta tarea se ve mucho más simplificada desde el momento en que el depurador nos permite inspeccionar el contenido de variables y objetos, así como evaluar expresiones conociendo su resultado. Las opciones que vamos a conocer a continuación sólo están disponibles en el estado de pausa, cuando se ha detenido en un punto la ejecución del programa que se está depurando. Para conocer el contenido de una variable o propiedad de forma inmediata, lo único que hemos de hacer es situar el cursor del ratón sobre ella. De forma automática aparecerá una pequeña ventana indicando el valor actual. Si deseamos conocer el contenido de una variable o evaluar una expresión no tenemos más que pulsar la combinación <Control+F7>, o bien usar el comando Evaluate/Modify del menú Run. Esta misma opción también está disponible en el menú emergente, al que accederemos pulsando el botón derecho del ratón estando sobre el código. La ventana de evaluación y modificación, cuyo aspecto puede ver en la figura 8.3, cuenta con un primer apartado, titulado Expression, en el que nosotros introduciremos el identificador de la variable o la

Page 159: DELPHI 5

expresión cuyo contenido o resultado queremos conocer. A continuación tendremos que pulsar <Intro>, o el botón Evalúate, lo que hará aparecer en el apartado Result el resultado de la evaluación de la expresión, o bien el contenido actual de la variable.

Figura 8.3. Evaluación inmediata de una expresión En caso de que lo que se haya evaluado sea una variable, además de poder ver su contenido también podermos modificarlo, introduciendo el nuevo valor en el apartado New Value y pulsando el botón Modify. Esta posibilidad nos permite realizar pruebas con diversos valores sin necesidad de tener que modificar el código. Visualizadores Cuando se desea conocer el resultado de una expresión o contenido de una variable con cierta frecuencia, y no sólo en un momento puntual del proceso de depuración, en lugar de usar el método que acabamos de ver lo que haremos será abrir la ventana de visualizadores, añadiéndole un nuevo elemento. Para ello pulsaremos la combinación <Control+F5> o bien seleccionaremos la opción Add Watch del menú Run. En la Ventana que aparece, cuyo aspecto puede ver en la figura 8.4, facilitaremos el identifícador de la variable o la expresión a examinar, tras lo cual bastará con pulsar <Intro> o la tecla Ok. En ese momento verá aparecer una lista con un elemento, conteniendo el identifícador de variable o expresión y su valor actual.

Page 160: DELPHI 5

Figura 8.4. Definición de un elemento visualizador. Mediante las opciones existentes en la parte inferior de la ventana, podremos seleccionar el tipo para mostrar el dato, por ejemplo podemos mostrar el contenido de una variable numérica en hexadecimal, el número de dígitos con que deseamos que aparezca y un factor de repetición, que se utiliza principalmente al trabajar con punteros y matrices. La ventana que contiene los visualizadores cuenta con un menú emergente, en el que encontraremos opciones que nos permitirán modificar las expresiones, eliminar y añadir elementos de la lista.

8.2.5. Pila de llamadas Cuando la ejecución del programa se encuentra detenida en un determinado punto, hasta el que se ha llegado mediante un punto de parada o alguno de los comandos de ejecución paso a paso que ya conocemos, podemos no tener claro cómo se ha llegado hasta ese punto. Esto suele ocurrir, sobre todo, cuando depuramos un procedimiento que es llamado desde distintos puntos de la aplicación. Este problema encuentra una solución rápida y simple en la opción Debug Windows->Call Stack del menú View, que hace visible la ventana que contiene la pila de llamadas. Esta ventana, cuyo aspecto se muestra en la figura 8.5, muestra en orden inverso los nombres de los procedimientos por los que se ha pasado hasta llegar al punto actual, lo que nos permite saber de forma inmediata cual ha sido la secuencia que se ha seguido.

Page 161: DELPHI 5

Figura 8.5. La pila de llamadas

8.3. Control de excepciones Entre los grupos de errores que se comentaban al principio de este capítulo, había una categoría a los que denominábamos errores de ejecución. Estos errores no son detectados durante la compilación y difícilmente pueden solucionarse en una tarea de depuración, ya que más que errores nuestros, de codificación o lógica, son errores que vienen provocados por alguna causa externa. Tome cualquiera de los ejemplos del capítulo anterior, en los quese guarda o recupera información en un archivo en disco, por ejemplo el programa de dibujo a mano alzada. A continuación facilite un nombre de archivo inexistente y pulse el botón Recupe rar. Verá aparecer una ventana indicando que no se encuentra el archivo y que se produce una excepción. Éste es un error de ejecución. Una de las formas más eficientes de controlar los errores de ejecución consiste en interceptar las excepciones que producen, actuando en consecuencia. El control de excepciones en Object Pascal es muy simple gracias a la construcción Try/Except, que vamos a conocer a continuación.

8.3.1. La construcción Try/Except En el código de un programa, sólo algunas sentencias o bloques de sentencias son susceptibles de generar errores de ejecución. Una simple asignación a una variable, por poner un ejemplo, nunca generará una excepción. Si está expresada correctamente se ejecutará o, en caso contrario, producirá un error de compilación o dará pie a un error lógico. Lo primero que tenemos que hacer para poder controlar los errores de ejecución, es identificar los bloques de código en que pueden tener lugar, ya que serán esos bloques los que haya que proteger mediante la construcción Try/Except. Son susceptibles de generar errores de ejecución, por ejemplo, las sentencias en que se accede a un dispositivo extemo, como puede ser el disco, o bien cuando se realiza una asignación de memoria.

Page 162: DELPHI 5

La sintaxis de Try/Except es la que se muestra en el fragmento siguiente, en el que podemos distinguir unas sentencias a proteger, que son las dispuestas detrás de la palabra Try, y unas sentencias que se ejecutarán sólo en caso de que se produzca una excepción, que son las escritas a continuación de la palabra Except. La construcción se finaliza con la palabra End, de tal forma que la ejecución siempre continuará con la sentencia siguiente, tanto si se ha generado la excepción como si no. Try SentenciaAProteger; Except SentenciaDeControl; End; Tanto tras la palabra Try como tras la palabra Except podemos disponer múltiples sentencias sin necesidad de crear un bloque Begin/End, ya que las palabras Try/Except forman un bloque y Except/End forman otro.

8.3.2. Clases de excepciones Mediante un bloque Try/Except/End, como el mostrado en el punto anterior, podemos controlar cualquier tipo de excepción, sin importar su tipo. Es posible, sin embargo, ejecutar unas sentencias u otras según que la excepción haya venido provocada por una causa u otra. Para poder llegar a diferenciar las excepciones tendremos que conocer sus clases. Las excepciones, al igual que otros muchos elementos de Delphi, son objetos que tienen una cierta jerarquía, existiendo una clase de excepción base o genérica de la cual se van derivando clases más específicas. Existe, por ejemplo, una clase de excepción llamada EIntError de la que están derivadas todas las clases de excepciones relacionadas con números enteros, como ERangeError o EDivByZero. Para controlar la clase de excepción, detrás de la palabra Except podemos disponer una o varias líneas con la sintaxis OnClaseExcepcion Do, donde ClaseExcepcion seríá EIntError, EDivByZero o cualquier otra clase de excepción existente. Detrás de la palabra Do podemos disponer una sentencia o un bloque, que se ejecutará tan sólo en caso de que la excepción que se ha producido sea de la clase indicada.

8.3.3. En la práctica Para comprobar el funcionamiento de las excepciones y ver cómo podemos controlarlas, vamos a diseñar un pequeño, programa que nos permita realizar divisiones, para lo cual tendremos que facilitar el dividendo y divisor.

Page 163: DELPHI 5

Inserte en un formulario tres controles TEdit, uno debajo de otro, precedidos de tres controles TLabel con las cadenas de texto 'Dividendo', 'Divisor' y 'Cociente'. Inserte a la derecha un control TButton, con el título 'Calcular'. Dé el valor True a la propiedad ReadOnly del tercer TEdit, ya que éste será sólo para mostrar el resultado de la operación y no para que el usuario introduzca un valor. Seleccione conjuntamente los dos primeros controles TEdit, abra la página de eventos en el Inspector de objetos y haga doble clic sobre el evento OnKeyPress, en cuyo método escribiremos el código que se muestra a continuación, con el fin de controlar la entrada de caracteres. { Con cada pulsación en los controles TEdit } procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin { Si no es un dígito numérico ni la tecla de borrado } If Not (Key In ['0'..'9',#8]) Then Key := #0; { ignoramos la pulsación } end; Por último haga doble clic sobre el TButton, para abrir el método correspondiente al evento OnClick y escriba el código siguiente. { Al pulsar el botón Calcular } procedure TForm1.Button1Click(Sender; TObject); Var Dividendo, Divisor, Cociente: Integer; begin Try { Obtenemos el dividendo y el divisor } Dividendo := StrToInt(Edit1.Text); Divisor := StrTolnt(Edit2.Text); { Realizamos la operación } Cociente := Dividendo Div Divisor; { y mostramos el resultado } Edit3.Text := IntToStr(Cociente); Except { Si se produce una excepción } { según la clase mostramos un mensaje u otro } On EDivByZero Do ShowMessage('El divisor no puede ser cero'); On EConvertError Do ShowMessage ('El dividendo o divisor no son válidos'); On EIntError Do ShowMessage('Se produce un error desconocido'); End; end; Como puede ver, controlamos tres clases de excepciones diferentes. La primera de ellas, EDivByZero, se producirá en el momento en que

Page 164: DELPHI 5

facilitemos como divisor el número 0, realizando una división por cero. La segunda, EConvertError, tiene lugar cuando hay un fallo de conversión, lo que puede ocurrir si introducimos como dividendo o divisor un número demasiado grande. Por último tenemos la clase EIntError, que es una excepción más genérica que engloba cualquier otro error con números enteros. Al ejecutar el programa podrá, básicamente, realizar tres comprobaciones: realizar una división sin problemas, facilitando un dividendo y un divisor normales; provocar una división por cero, dando como divisor el número cero, o bien provocar un error de conversión, introduciendo en el dividendo o divisor un número extenso. En la figura 8.6 puede ver el programa en funcionamiento indicando que se ha producido un error en la introducción de datos.

Figura 8.6. El programa indicando que se ha producido una excepción

Page 165: DELPHI 5

9. Uso de múltiples ventanas

9.1. Introducción Los ejemplos que se han propuesto hasta ahora, en capítulos anteriores, han sido tan simples que estaban compuestos de sólo un formulario. En la práctica, la mayoría de los programas se sirven de múltiples ventanas, lo podemos ver, por ejemplo, en Delphi. Por regla general siempre existe una ventana principal, en la que se encuentra el menú de opciones, y una serie de ventanas accesorias que pueden estar visibles siempre o sólo cuando son necesarias. En el capítulo anterior, por ejemplo, vimos cómo podíamos abrir ventanas con puntos de parada, visualizadores, una pila de llamadas, etc., ventanas que habitualmente no están visibles a diferencia del formulario, el Editor de código o el Inspector de objetos. Un programa puede utilizar múltiples ventanas simplemente para solicitar una determinada información, como puede ser el nombre del archivo que se desea recuperar o el color que se quiere utilizar, o bien para facilitar la edición de múltiples documentos. Las ventanas del primer tipo se suelen construir como cuadros de diálogo, nombre con el que se conoce a aquellas ventanas que no pueden ser redimensionadas, no cuentan con botones de maximizar o minimizar y que además son modales, no permitiendo seguir trabajando con la aplicación hasta en tanto no son cerradas. Un cuadro de diálogo es lo que aparece, por ejemplo, cuando seleccionamos la opción de abrir un proyecto existente. En caso de que el programa sea capaz de permitir el trabajo con múltiples documentos de forma simultánea, éstos puebien en una interfaz de documento múltiple, que se conoce como MDI. Delphi utiliza una ventana independiente para cada formulario y para el código, Microsoft Word 97, por ejemplo, usa una interfaz MDI.

9.2.Trabajo con múltiples formularios Ya en un capítulo anterior conocimos las opciones que nos permitían crear nuevos formularios, bien partiendo de cero o desde diseños almacenados en el Depósito de objetos. Cada vez que añadimos un nuevo formulario al proyecto, también se añade un nuevo módulo de código, conteniendo la definición del formulario y el código asociado a sus eventos. Cuando una aplicación cuenta con varios formularios, es muy normal tener la necesidad de hacer referencia a uno desde otros. De hecho, esto es fundamental ya que de lo contrario no sería posible ir mostrando los formularios adicionales desde la ventana principal. Para poder hacer referencia a un formulario desde un módulo de código que no es el suyo, deberemos añadir a la cláusula Uses de dicho módulo el nombre del módulo en el que está definida el formulario al que deseamos acceder. La forma más fácil de hacer esto es utilizar la

Page 166: DELPHI 5

opción Use Unit del menú File, que nos mostrará una ventana en la que podremos seleccionar directamente el módulo a usar.

9.2.1. El formulario principal del programa En un proyecto tan sólo puede existir un formulario principal, que es aquella que se muestra inicialmente cuando se ejecuta el programa. Por defecto el formulario inicial es aquel que existía al comenzarse el proyecto o el que se insertó en primer lugar si es que el proyecto estaba vacío. Realmente el formulario principal no cuenta con ninguna diferencia especial respecto a los otros formularios y cualquiera de ellos puede actuar como formulario principal. Para establecer el formulario principal del proyecto deberemos utilizar la opción Options del menú Project, que dará paso a una ventana como la que se muestra en la figura 9.1. En esta ventana existe una lista desplegable, en la parte superior, en la que podremos indicar qué formulario actuará como principal.

Figura 9.1. Selección del formulario principal del proyecto

9.2.2. Formularios creados automáticamente y formularios disponibles El resto de los formularios del proyecto, aquellos que no son el formulario principal, pueden ser creadas automáticamente, al iniciarse

Page 167: DELPHI 5

el programa, o bien quedar como formularios disponibles, siendo nosotros responsables de su creación y liberación. Un formulario autocreado se crea en el código incluido por Delphi en el archivo de proyecto, de tal forma que nosotros podemos utilizar ese formulario en cualquier momento, sin tener que preocupamos de ningún proceso de preparación. Por tanto podemos mostrarla, acceder a sus propiedades y componentes. Los formularios disponibles son aquellos que no se crean inicialmente, contando el programa tan sólo con una plantilla o definición de sus características. Para poder utilizar un formulario disponible lo primero que tendremos que hacer será crearlo, tras esta operación el formulario podrá ser utilizada como cualquier otro, sin diferencias con los formularios creados automáticamente. Cuando el formulario deje de sernos útil deberemos destruirlo, liberando la memoria que ocupa. Para indicar a Delphi qué formulario deberán ser creados automáticamente y cuáles quedarán como formularios disponibles, utilizaremos la misma opción que nos lleva a la venta mostrada en la figura 9.1. En esta ventana la lista de la izquierda contiene los formularios que se crean automáticamente, mientras que la de la derecha almacena aquellas que quedarán disponibles. Podemos utilizar los botones centrales para desplazar formularios de una lista a otra, también podemos arrastrar y soltar las formularios. Creación de un formulario disponible Como se ha dicho anteriormente, un formulario disponible no puede ser utilizado directamente puesto que lo que existe en el programa es un tipo y no una variable, y un tipo no puede ser usado nada más que para declarar variables de este tipo. Por ello no puede usar el identificador TForm2, por poner un caso, para acceder a un formulario, de igual forma que no usa el identificador Integer para acceder a un número. La declaración de una variable que va a contener una referencia a un formulario se realiza según la sintaxis que ya conocemos, no hay ninguna novedad. La única diferencia está en que la variable no contiene un valor ordinario, como puede ser un número o una cadena, sino una referencia a un objeto. Al declarar la variable, por tanto, lo que estamos haciendo es reservar espacio para almacenar esa referencia, pero no estamos realmente creando el objeto. La creación de un formulario se realizará mediante un constructor, un método especial que se encarga de asignar la memoria necesaria y llevar a cabo todo el proceso de inicialización preciso. Este constructor se llama Create() y necesita como único parámetro una referencia a un componente, que será el dueño del formulario que se

Page 168: DELPHI 5

está creando. Si deseamos que el formulario sea dueño de sí mismo y, por tanto, de su memoria y recursos, pasaremos como parámetro la palabra Self. Suponiendo que tenemos un proyecto con dos formularios, la declaración siguiente crearía una variable para contener una referencia al segundo formulario, mientras que la única sentencia existente crearía el formulario y almacenaría la referencia en la variable formulario. A partir de ese momento podemos utilizar esta variable para acceder al formulario. Var Formulario: TForm2; Begin Formulario := TForm2.Create(Self); Al igual que es posible declarar múltiples variables de un mismo tipo, también podemos crear varios formularios a partir de la misma plantilla disponible. Esto nos permite, por ejemplo, construir aplicaciones capaces de trabajar con múltiples documentos. Destrucción de un formulario Cuando un formulario ha sido creado automáticamente por el código del propio proyecto, no tenemos por qué preocuparlos, por su destrucción, ya que de ello se encargará autocmáticamente el programa. Sin embargo, cuando el formulario lo hemos creado nosotros mismos, según lo visto en el punto anterior, deberemos ser nosotros los que la destruyamos, liberando la memoria que ocupa. Para destruir un formulario no tenemos más que realizar una llamada al método Release() que, tras unas comprobaciones en las que se asegurará que no está en ejecución ningún método asociado al formulario, liberará la memoria que se ocupó durante la creación. Si deseamos que un formulario sea automáticamente destruido cuando el usuario la cierre, podemos utilizar el evento OnClose, asignando al parámetro variable Action el valor caFree.

9.2.3. Visualización de un formulario A1 igual que otros componentes, un formulario cuenta con un método Show() que nos permite hacerla visible, así como un método Hide() mediante el cual podemos ocultarla. También podemos acceder directamente a la propiedad Visible, asignándole el valor True o False según deseemos mostrar u ocultar el formulario, respectivamente. Un formulario que se muestra por los métodos anteriores es una ventana no modal, lo que significa que el usuario puede acceder a otras ventanas del mismo programa sin necesidad de cerrar ésta. Este es el tipo lógico de visualización cuando la ventana va a servir para editar

Page 169: DELPHI 5

un documento o simplemente mostrar una información. Sin embargo, si la ventana que se muestra modifica en algún aspecto el entorno del programa, abriendo un archivo, alterando opciones generales, etc., no debería permitirse el acceso al resto de las funciones de la aplicación hasta en tanto el formulario no sea cerrado. Para conseguir que un formulario tome el control absoluto de la aplicación mientras está visible, no permitiendo el acceso al resto de las ventanas, lo único que hemos de hacer es mostrarla como ventana modal. Para ello bastará con utilizar el método ShowModal(), en lugar del método Show().

9.2.4. MDI Anteriormente comentamos brevemente lo que era MDI, una interfaz de documento múltiple cuya finalidad es facilitar la edición simultánea de varios documentos, bien sean éstos del mismo tipo o de tipos diferentes. En una interfaz MDI existe una ventana principal o marco, que es la que cuenta con el menú de opciones, la barra de botones, línea de estado, etc., y una o más ventanas hija. Las ventanas hija MDI siempre se visualizan en el interior de la ventana marco, no pudiendo extenderse más allá de ella. Si minimizamos una ventana hija, ésta se mostrará como un icono en la parte inferior de la ventana marco. Si la maximizamos, ocupará todo el espacio disponible en dicha ventana. Ya sabemos cómo definir un cierto formulario como ventana marco o ventana hija MDI, mediante la propiedad FormStyle, y también hemos aprendido a crear formularios dinámicamente, mediante el constructor Create(). Partiendo de estos conocimientos, necesitamos saber muy poco más para poder crear una aplicación con interfaz MDI. Aquellas formularios que van a actuar como ventanas hija deberán ser definidas como formularios disponibles y no autocreadas, ya que durante la ejecución, por regla general, será necesario crear múltiples ventanas hija, realizando las llamadas necesarias al constructor Create(). La ventana principal o marco cuenta con una serie de propiedades y métodos que facilitan la gestión de las ventanas hija. La propiedad MDIChildCount contiene el número de ventanas hija existentes, mientras que ActiveMDIChild almacena una referencia a la ventana hija que en ese momento tiene el foco de entrada. Mediante los métodos Cascade() y Tile() podemos disponer las ventanas hija existentes en ese momento solapadas unas sobre otras o bien en forma de mosaico, mientras que el método Arrangelcons() nos permite ordenar las ventanas que en ese momento estén minimizadas.

Page 170: DELPHI 5

9.3. Cuadros de diálogo de uso común La mayoría de las aplicaciones Windows cuentan con opciones que permiten guardar y recuperar información, imprimir datos, seleccionar tipos de letra o colores, etc. Para cada una de estas funciones sería necesario diseñar un determinado tipo de formulario que, en el caso de Delphi, podría almacenarse en el Depósito de objetos y ser usado posteriormente en cualquier otra aplicación. Sin embargo, las funciones citadas son tan comunes que Windows incorpora una serie de cuadros de diálogo que facilitan la petición de esos datos. Estos cuadros de diálogo están representados en Delphi mediante una serie de componentes que vamos a ver a continuación, por lo que lo único que tendremos que hacer será insertarlos en el formulario y usarlos. Los componentes que permiten el acceso a los cuadros de diálogo de uso común de Windows están en la página Dialogs de la Paleta de componentes.

9.3.1. Abrir y guardar archivos Seguramente las dos funciones más habituales en una aplicación, indistintamente de su tipo, son las de guardar y recuperar información. Para ello lo normal es solicitar al usuario un nombre de archivo, permitiendo adicionalmente especificar una unidad y directorio de destino. Toda esta información, que en programas anteriores hemos solicitado directamente en un control TEdit, puede ser gestionada de una forma mucho más atractiva, visualmente hablando, mediante los componentes TOpenDialog y TSaveDialog. Estos dos componentes cuentan con muchos elementos en común, siendo prácticamente iguales en su funcionamiento. Las propiedades de que disponen tienen por finalidad configurar el aspecto y comportamiento de la ventana en la que se solicitará el nombre del archivo, ventana que se hará visible en el momento en que realicemos una llamada al método Execute(). Este método devolverá un valor de tipo Boolean, que será True en caso de que el usuario haya seleccionado un archivo o False en caso contrario. La ventana mostrada por estos dos componentes tendrá un título, cuyo texto podemos facilitar en la propiedad Title. Mediante la propiedad InitialDir tenemos la posibilidad de fijar un directorio inicial, mientras que FileName nos servirá para asignar un nombre de archivo por defecto. A la salida, cuando el usuario haya cerrado la ventana, utilizaremos esta misma propiedad para obtener el nombre del archivo seleccionado.

Page 171: DELPHI 5

Por defecto, en la lista que ocupa la parte central del cuadro de diálogo se mostrarán los nombres de todos los archivos existentes en el directorio actual. Si lo deseamos podemos limitar el número de archivos visibles, facilitando unas referencias de selección, que asignaremos a la propiedad Filter. En modo de diseño nos resultará muy fádl trabajar con esta propiedad, ya que cuenta con un editor específico qué nos permite introducir el título de cada filtro y la referencia a utilizar. Estos datos son convertidos posteriormente en una cadena, que es el valor que se asigna a la propiedad Filter. En caso de que facilitemos varios filtros, de tal forma que en ejecución el usuario pueda seleccionar el que más le interese, podemos indicar cual será el filtro usado por defecto mediante la propiedad FilterIndex. Cuando en un cuadro de diálogo de este tipo se introduce el nombre de un archivo tecleándolo directamente, en lugar de seleccionándolo de una lista, es habitual omitir la extensión, que se da por asumida. En Delphi, por ejemplo, no es necesario indicar que los módulos tienen extensión PAS y los proyectos extensión DPR. Si queremos facilitar una extensión por defecto, que será la que se use siempre que el usuario no indique otra diferente, no tenemos mas que asignarla a la propiedad DefaultExt, omitiendo el punto inicial. Ciertos aspectos del funcionamiento de los cuadros de diálogo TOpenDialog y TSaveDialog vendrán determinados por los valores que contenga la propiedad Options. Esta propiedad es un conjunto que puede estar formado por los valores que se enumeran en la tabla 9.1. Algunas de las opciones sólo son aplicables a uno de los dos componentes y, de hecho, no aparecerán en el Inspector de objetos si no son aplicables. La opción ofOverwritePrompt, por ejemplo, sólo tiene sentido al guardar un archivo y no al recuperarlo.

Constante Significado

ofAllowMultiSelect Permitir la selección de múltiples archivos.

ofCreatePrompt Si se intenta abrir un archivo que no existe pregunta si se desea crear.

ofFileMustExist El archivo seleccionado debe existir.

ofHideReadOnly Ocultar la opción de abrir como sólo lectura.

ofNoChangeDir Restablecer el directorio original.

ofNoValidate No comprobar la validez del nombre introducido.

ofOverWritePrompt Si al guardar se selecciona un archivo existente, preguntar si se desea sobreescribir.

ofReadOnly Marca la opción de abrir como sólo lectura.

ofPathMustExis El camino seleccionado debe existir.

ofShowHelp Mostrar un botón de ayuda.

Tabla 9.1 Opciones para TopenDialog y TsaveDialog

Page 172: DELPHI 5

Con el fin de realizar una prueba muy simple, inserte en un formulario un componente TOpenDialog o TSaveDialog y un componente TButton. A continuación establezca el título para la ventana, introduciéndolo en la propiedad Title, y facilite también un filtro, por ejemplo asignando a la propiedad Filter el valor 'Archivos de texto | *.txt'. A continuación haga doble clic sobre el TButton, con el fin de abrir el método asociado al evento OnClick, y escriba el código siguiente. procedure TForm1.Button1Click(Sender: TObject); begin If OpenDialog1.Execute Then ShowMessage('Se ha seleccionado un archivo') Else ShowMessage('Se ha salido sin elegir un archivo'); end; Al ejecutar el programa tan sólo podrá pulsar el botón, ya que éste será el único control del formulario. Al hacerlo aparecerá el cuadro de diálogo común que se muestra en la figura 9.2, pudiendo seleccionar un archivo cualquiera. Al salir aparecerá un mensaje indicándonos si hemos elegido o no un archivo.

Figura 9.2. Aspecto del cuadro de diálogo TOpenDialog

9.3.2. Tipos y atributos de letra Cuando necesitemos solicitar al usuario de nuestro programa la selección de un tipo, tamaño y atributos para el texto, el método más fácil consiste en usar el componente TFontDialog. Este componente muestra un cuadro de diálogo en el que es posible seleccionar uno de los tipos de letra disponibles en el sistema, elegir un tamaño, indicar atributos y color.

Page 173: DELPHI 5

La propiedad más interesante de este componente es Font, ya que mediante ella podemos indicar cuáles serán las opciones seleccionadas, antes de mostrar el cuadro de diálogo, y posteriormente recoger las opciones elegidas por el usuario, una vez que la ventana ha sido cerrada. Antes de mostrar el cuadro de diálogo podemos también limitar el tamaño de letra seleccionable, indicando un mínimo y un máximo en las propiedadesMinFontSize y MaxFontSize. Por último, podemos limitar o modificar las opciones disponibles mediante la propiedad Options, que puede contener los elementos que se enumeran en la tabla 9.2.

Constante Significado

fdAnsiOnly Mostrar sólo tipos de letras con conjunto de caracteres Windows.

fdEffects Mostrar las opciones de tachada, subrayada y color del texto.

fdFixedPitchOnly Mostrar sólo tipos de letras de espaciado fijo.

fdForceFontExist

Avisar al usuario en caso de que seleccione un tipo de letra no disponible.

fdLimitSize Limitar el tamaño de la letra al rango facilitado en MinFontSize y MaxFontSize.

fdNoFaceSel No seleccionar un tipo de letra inicial.

fdNoOEMFonts Mostrar sólo tipos de letra no vectoriales. fdScalableOnl

y Mostrar sólo tipos de letra escalables.

fdNoSimulations No mostrar tipos de letras que sean simulaciones.

fdNoSizeSel No seleccionar un tamaño de letra inicial.

fdNoStyleSel No seleccionar un estilo inicial. fdNoVectorFon

ts Igual a fdNoOEMFonts.

fdShowHelp Mostrar un botón de ayuda. fdTrueTypeOnl

y Mostrar sólo tipos de letra TrueType.

fdWysiwyg Mostrar sólo tipos de letra disponibles en pantalla e impresora.

Tabla 9.2. Opciones del componente TFontDialog Hagamos una simple comprobación del funcionamiento de este componente, incluyendo en el formulario un TFontDialog y un TButton. Haga doble clic sobre el botón y escriba el código siguiente. procedure TForm1.Button1Click(Sender: TObject); begin FontDialog1.Font := Button1.Font; If FontDialog1.Execute Then Button1.Font := FontDialog1.Font;

Page 174: DELPHI 5

end; Como puede ver, seleccionamos inicialmente en el cuadro de diálogo el tipo, tamaño y estilo de letra del propio botón, mostrando a continuación la ventana que puede ver en la figura 9.3. En caso de que se pulse el botón Aceptar, se asigna al botón la propiedad Font del cuadro de diálogo, aplicando así la selección que hayamos efectuado.

Figura 9.3. Aspecto del cuadro de diálogo TFontDialog

9.3.3. Selección de colores El funcionamiento del cuadro de diálogo TColorDialog, cuya finalidad es permitir la selección de un color, es parecido al que hemos visto en el apartado anterior, en el sentido de que dispone de una propiedad, llamada Color, que nos servirá tanto para especificar el color seleccionado inicialmente al mostrar el formulario como para recuperar el color que se eligió, una vez que la ventana ha sido cerrada. Además de la propiedad Color, el componente TcolorDialog tan sólo cuenta con otra propiedad relevante, llamada Options, mediante la cual podemos alterar el comportamiento del cuadro de diálogo. Esta propiedad puede tomar los elementos que se indican en la tabla 9.3.

Page 175: DELPHI 5

Constante Significado

cdShowHelp Mostrar un botón de ayuda.

cdFullOpen Abrir inicialmente la paleta de color.

cdPreventFullOpen

Impedir la apertura de la paleta de color.

Tabla 9.3. Opciones para el componente TColorDialog

El cuadro de diálogo de selección de color tiene dos partes. En la primera, que siempre se muestra abierta y ocupa la parte izquierda de la ventana, existe una matriz dé rectángulos con una serie de colores, de los cuales podemos elegir directamente cualquiera de ellos. La segunda parte de la ventana, que puede estar o no abierta, es la paleta de color, y en ella se muestran todos los colores posibles. Vamos a sustituir en el formulario de la prueba anterior el componente TFontDialog por un TColorDialog y a modificar el código asociado al evento OnClick del botón, para que quede como se muestra a continuación. En la figura 9.4 puede ver el cuadro de diálogo de selección de color, que nos permitirá modificar el color del fondo del formulario, gracias al código que hemos escrito. procedure TForm1.Button1Click(Sender: TObject); begin ColorDialog1.Color := Color; If ColorDialog1.Execute Then Color := ColorDialog1.Color; end;

9.3.4. Opciones de impresión y configuración de impresora Existen dos cuadros de diálogo relacionados con la impresión. Uno de ellos nos servirá para solicitar opciones de impresión tales como el rango de páginas a imprimir o el núméro de copias. El segundo permitirá al usuario realizar tareas de configuración, tales como la selección del tamaño del papel, su orientación, el cargador que desea utilizar, etc. Mediante el componente TPrintDialog accederemos al cuadro de diálogo de opciones de impresión. Como es habitual, antes de mostrar esta ventana podemos configurar algunos aspectos de su funcionamiento. La mayoría de las propiedades que vamos a ver nos sirven tanto para seleccionar un valor por defecto, antes de mostrar el cuadro de diálogo, como para obtener, posteriormente, la selección que ha efectuado el usuario.

Page 176: DELPHI 5

Figura 9.4. Aspecto del cuadro de diálogo TColorDialog A la hora de imprimir un documento, el usuario puede tener la posibilidad de indicar qué parte es la que desea imprimir. Podemos conocer la selección del usuario gracias a la propiedad PrintRange, que contendrá uno de los valores mostrados en la tabla 9.4. En caso de que esta propiedad tenga el valor prPageNums, el rango de páginas a imprimir vendrá determinado por las propiedades FromPage y ToPage. Podemos limitar el número de páginas que el usuario puede seleccionar dando un valor mínimo a la propiedad MinPage y un valor máximo a la propiedad MaxPage.

Constante Se imprimirá

prAllPages Todo el documento.

prSelection La parte del documento que esté seleccionada.

prPageNums Un rango de páginas.

Tabla 9.4. Valores posibles para la propiedad PrintRange

En la ventana de opciones de impresión también es posible facilitar un número de copias, que recuperaremos mediante la propiedad Copies, y una indicación de si éstas han de imprimirse en secuencia, todas las copias de una misma página, o bien ordenadas, todas las páginas de una copia. Al igual que en los componentes que ya hemos visto en los puntos anteriores, en éste también existe una propiedad Options capaz de contener una serie de elementos, mediante los cuales podemos activar o desactivar algunas de las opciones del cuádro de diálogo de impresión.

Page 177: DELPHI 5

En la tabla 9.5 se enumeran estas opciones, indicándose la finalidad de cada una de ellas.

Constante Significado

poHelp Mostrar un botón de ayuda.

poPageNums Permitir la selección de un rango de páginas.

poSelection Permitir la impresión del texto seleccionado.

poPrintToFile Mostrar una opción de impresión a archivo.

poWarning Mostrar un mensaje de aviso si no hay impresora instalada.

Tabla 9.5 Opciones para el componente TPrintDialog

Tenga en cuenta que el cuadro de diálogo de opciones de impresión tan sólo sirve para solicitar unos datos, pero no lleva a cabo ningún proceso de impresión, de igual forma que el cuadro de diálogo de apertura de un archivo tan sólo es útil para obtener un nombre de archivo, pero no para realizar la apertura en sí. En la figura 9.5 puede ver el aspecto de este cuadro de diálogo.

Figura 9.5. Aspecto del cuadro de diálogo TPrintDialog El componente TPrinterSetupDialog, por su parte, nos permite mostrar un cuadro de diálogo de configuración de impresora. Este es el componente más simple de los existentes en la paleta Dialogs, ya que tan sólo disponemos del método Execute() para hacer aparecer la

Page 178: DELPHI 5

ventana, sin contar con opciones ni propiedades adicionales. En la figura 9.6 puede ver el aspecto de este cuadro de diálogo.

Figura 9.6. Aspecto del cuadro de diálogo TPrinterSetupDialog

9.3.5. Búsquedas y sustituciones Las aplicaciones en las que se trabaja con documentos de texto suelen fadlitaroperaciones de búsqueda y búsqueda y sustitución. Para iniciar una operación de este tipo, el usuario tiene que introducir inicialmente alguna información, como la cadena a buscar, la cadena que se desea poner en su lugar y otras opciones adicionales. Mediante los componentes TFindDialog y TreplaceDialog podremos solicitar al usuario los datos necesarios para realizar una búsqueda o una búsqueda y sustitución. Estos dos cuadros de diálogo son muy similares, como vamos a ver a continuación. Antes de mostrar el cuadro de diálogo podemos configurar su funcionamiento, para lo cual daremos los valores adecuados a la propiedad Options. En la tabla 9.6 se enumeran las opciones disponibles, indicándose la finalidad de cada una de ellas. Como podrá observar, algunasde las opdones tan sólo son aplicables a la operación de búsqueda y sustitución. La propiedad Options también puede ser utilizada, una vez que el usuario cierra la ventana, para comprobar la activación de algunas opciones.

Constante Significado

frDown Realizar la búsqueda hacia abajo.

frFindNext Se ha pulsado el botón de Buscar siguiente.

Page 179: DELPHI 5

frHideMatchCase Ocultar la opción de coincidencia entre mayúsculas y minúsculas

frDisableMatchCase Desactivar la opción de coincidencia entre mayúsculas y minúsculas.

frHideWholeWord Ocultar la opción de buscar palabras completas.

frDisableWholeWord Desactivar la misma opción anterior.

frHideUpDown Ocultar los botones de selección de dirección.

frMatchCase La opción de coincidencia entre mayúsculas y minúsculas está activada.

frWholeWord La opción de palabras completas está activada.

frReplace Indica que se ha pulsado el botón Sustituir.

frReplaceAll Indica que se ha pulsado el botón Sustituir todo.

frShowHelp Mostrar un botón de ayuda.

Tabla 9.6. Opciones para TFindDialog y TReplaceDialog

El texto a buscar será facilitado en la propiedad FindText, mientras que el texto a colocar en su lugar, en el caso del componente TReplaceDialog, lo encontraremos en la propiedad ReplaceText. A diferencia de otros cuadros de diálogo, estos dos no son cerrados automáticamente cuando el usuario ha introducido la información solicitada, sino que permanecen abiertos mientras está en curso la operación de búsqueda o búsqueda y sustitución. Cada vez que se pulsa el botón Buscar siguiente el componente genera un evento OnFind, que nosotros deberemos aprovechar para leer el contenido de la propiedad FindText y realizar la búsqueda en el texto. De forma análoga, cuando se pulsa el botón Reemplazar se genera un evento OnReplace, momento en el cual tomaremos el contenido de la propiedad ReplaceText y lo dispondremos en el texto en lugar de la cadena buscada. La ventana será cerrada cuando se pulse el botón Cerrar o Cancelar. En la figura 9.7 puede ver el aspecto del cuadro de diálogo de búsqueda y sustitución.

Page 180: DELPHI 5

Figura 9.7. Aspecto del cuadro de diálogo TReplaceDialog

9.4. En la práctica Con el fin de ver en la práctica cómo podemos utilizar múltiples formularios en un proyecto, así como algunos de los cuadros de diálogo de uso común que hemos conocido, vamos a diseñar un programa que nos permita editar archivos de texto, con la posibilidad de tener abiertos varios archivos de forma simultánea en un entorno MDI. El texto de cada ventana estará contenido en un control TMemo, por lo que no es posible el uso de atributos en el texto. Sin embargo, cada ventana puede tener un tipo de letra y color diferentes al resto.

9.4.1. Diseño del formulario principal Comenzaremos diseñando lo que será el formulario principal del proyecto, que actuará como ventana marco de la interfaz MDI. Este formulario será el que existe inicialmente en el proyecto, al que vamos a asignar el título 'Multi Editor'. Deberá dar el valor fsMDIForm a la propiedad FormStyle, a fin de que el formulario actúe como ventana padre de las demás. El siguiente paso consistirá en el diseño de un menú de opciones, desde el cual será posible recuperar un archive, guardarlo, crear nuevos archivos, modificar el tipo de letra y el color de fondo de la ventana y gestionar las múltiples ventanas hija que puedan existir. Inserte en el formulario un componente TMainMenu, haga"doble clic sobre él y diseñe el menú que se muestra en la figura 9.8. Observe que a las opciones del menú Archivo, que serán las que se utilicen con una mayor frecuencia, se les ha asignado una tecla de acceso rápido.

Figura 9.8. Opciones del menú principal del programa

Page 181: DELPHI 5

Para permitir las diversas operaciones que se deducen de las opciones del menú, tendremos que insertar en el formulario un componente TSaveDialog, un TOpenDialog, un TfontDialog y un TColorDialog. El formulario quedará finalmente, en la fase de diseño, tal y como se muestra en la figura 9.9. A diferencia de otros ejemplos anteriores, en éste la ventana sí que podrá ser redimensionada y contará con los botones de maximizar y minimizar.

Figura 9.9. Aspecto del formulario principal en la fase de diseño Asigne a la propiedad Title del componente TSaveDialog la cadena 'Guardar archivo de texto', y a la misma propiedad del componente TOpenDialog la cadena 'Recuperar archivo de texto'. Asigne a la propiedad Fllter de ambos componentes la cadena 'Archivos de texto | *.txt'. Active en el componente TSaveDialog las opciones ofOverwritePrompt y ofPathMustExist. Active en el componente TopenDialog la opción ofFileMustExist.

9.4.2. Diseño del formulario hijo El siguiente paso que daremos será añadir al proyecto un nuevo formulario, para lo cual podemos pulsar el botón New Form que hay en el área de acciones rápidas. También podemos seleccionar esta opción del menú File. Este formulario será el que actúe como hijo y, en ejecución, podrán existir múltiples copias de el abiertas. Por ello esta ventana no deberá ser creada automáticamente, al iniciarse el programa. Acceda a la ventana de opciones de proyecto y desplace el segundo formulario de la lista de ventanas autocreadas a la lista de ventanas disponibles.

Page 182: DELPHI 5

Puesto que este formulario va a ser una ventana hija MDI, deberemos dar el valor fsMDIChild a la propiedad FormStyle, Por lo demás, no tendremos que alterar ninguna propiedad más del formulario. Para visualizar y permitir la edición del texto insertaremos en el formulario un control TMemo, que ocupará todo el espacio disponible en la ventana, por lo que daremos el valor alClient a la propiedad Align. Este control contará con una barra de desplazamiento vertical, siendo preciso, por tanto, modificar la propiedad ScrollBars seleccionando el valor ssVertical. También modificaremos la propiedad WantTabs, asignándole el valor True.

9.4.3. El código del formulario principal Procedamos ahora con la escritura del código necesario para hacer que nuestro programa funcione según el diseño que estamos esbozando. Lo primero que tendremos que hacer es utilizar la opción Use Unit del menú File, para añadir al módulo de código del primer formulario una referencia a la segunda, lo que nos permitirá acceder a la definición de TForm2. El título de cada una de las ventanas hija será algo que estableceremos durante la ejecución. En caso de que la ventana se cree abriéndose un archivo existente, el título será el nombre del archivo. Si, por el contrario, la ventana se ha creado vacía, asignaremos como título la palabra Nuevo, seguida de un número que se irá incrementando secuencialmente. Este contador lo declararemos al nivel de módulo, delante de cualquier método. Var Contador: Integer; Creación de un nuevo archivo La primera opción que encontramos en el menú Archivo es la opción Nuevo, cuya finalidad es crear una ventana hija vacía, permitiéndonos así la creación de un nuevo archivo de texto. Seleccione esta opción en el formulario, durante el diseño, para abrir el Editor de código por el método correspondiente, en el que escribiremos el código que se muestra a continuación. { Al pulsar la opción Muevo } procedure TForm1.Nuevo1Click(Sender: TObject); begin { creamos un nuevo formulario bijo } With TForm2.Create(Self) Do Begin { Incrementamos el contador } Inc(Contador); { Establecemos el titulo } Caption := 'Nuevo ' + IntToStr(Contador);

Page 183: DELPHI 5

Show; { y la mostramos } End; End; Básicamente creamos el formulario, con una llamada al consructor Create(), asignándole a continuación un título y mostrándolo, con una llamada al método Show(). Observe que los formularios hijos MDI se visualizan como ventanas no modales, ya que de lo contrario no sería posible cambiar de un documento a otro en ejecución. Guardar un archivo La opción Guardar del menú Archivo no debe estar accesible en caso de que no exista actualmente una ventana hija abierta, ya que de lo contrario se podría llamar al método correspondiente cuando en realidad la operación no puede llevarse a cabo. Con el fin de que esta opción aparezca activada o desactivada según proceda/ vamos a hacer doble clic en el menú Archivo, desde el Editor de menús, para abrir el método correspondiente al evento OnClick de este menú, que se produce cuando accedemos a él para desplegarlo. En este método insertaremos una sentencia, como puede ver a continuación, en la que asignaremos a la propiedad Enabled de la opción el valor True o False, dependiendo de que exista o no un formulario hijo activo en ese momento. { Al desplegar el menú Archivo } procedure TForm1.Archivo1Click(Sender: TObject); begin { Desactivamos la opción Guardar en caso de que no existe una ventana hija activa } Guardar1.Enabled := ActiveMDIChild <> Nil; end; Si la opdón Guardar está activa y la seleccionamos, lo primero que tendremos que hacer será solicitar un nombre de archivo, para lo cual utilizaremos el componente TSaveDialog. A continuación obtendremos una referencia a la ventana hija que esté activa en ese momento, mediante la propiedad ActiveMDIChild, realizando la necesaria conversión al tipo TForm2. Esto nos permitirá acceder al control TMemo que contiene el texto y realizar una llamada al métodoSaveToFile(). A continuación asignaremos a la propiedad Caption del formulario el nombre del archivo seleccionado y, por último, daremos el valor False a la propiedad Modified del control TMemo, para indicar que no se ha modificado desde la última grabación. El método completo es el que se muestra a continuación. { Al pulsar la opción Guardar } procedure TForm1.Guardar1Click(Sender: TObject); begin { Si se selecciona un nombre de archivo } If SaveDialog1.Execute Then

Page 184: DELPHI 5

{ Accedemos al formulario hijo activo } Try With (ActiveMDIChild As TForm2) Do Begin {e intentamos guardar su contenido } Memo1.Lines.SaveToFile(SaveDialog1.FileName); { Ponemos como título el nombre de archivo } Caption := SaveDialog1.FileName; { Desactivamos el indicador de modificado } Memo1.Modified := False; End; Except { Si se produce un fallo lo indicamos } ShowMessage('Se produce un error al guardar'); End; end; Observe que el bloque en el que se intenta guardar el archivo ha sido protegido adecuadamente a fin de evitar que un fallo, como puede ser que el disco esté lleno, provoque un mal funcionamiento del programa. Recuperar un archivo La opción Recuperar del menú Archivo es la complementaria a la anterior. Con ella podremos abrir una ventana con el contenido de un archivo que nosotros indiquemos. Abra el método correspondiente al evento OnClick de esta opción y teclee el código siguiente. { Al pulsar la opción Recuperar } procedure TForm1.Recuperar1Click(Sender: TObject); begin { Si se selecciona un archivo } If OpenDialog1.Execute Then { Creamos una nuevo formulario hijo } Try with TForm2.Create(Self) Do Begin { y cargamos en ella el archivo indicado } Memo1.Lines.LoadFromFile(OpenDialog1.FileName); { Ponemos como título el nombre del archivo } Caption := OpenDialog1.FileName; { Desactivamos el indicador de modificado } Memo1.Modified := False; { y mostramos el formulario } Show; End; Except { Si se produce un error lo indicamos } ShowMessage('Se produce un error al cargar'); End; end; Tras solicitar un nombre de archivo a abrir, creamos una nueva ventana hija y accedemos al contro TMemo para poder recuperar el contenido de ese archivo. A continuación asignamos el título de la ventana, damos

Page 185: DELPHI 5

el valor False a la propiedad Modified del control TMemo y mostramos la nueva ventana hija. Como en el caso anterior, el proceso de recuperación del archivo ha sido protegido ante una posible excepción. Salida del programa Hasta ahora nunca habíamos dispuesto en un programa una opción o botón que nos permitiese salir, ya que para ello es posible pulsar el botón que automáticamente muestra el formulario en el extremo superior derecho. En este caso, sin embargo, hemos incluido una opción Salir en el menú Archivo, cuya finalidad será cerrar el formulario principal y con el todos los formularios hijos. Para ello bastará con realizar una simple llamada al método Close() del formulario, tal y como puede ver en el método siguiente. { Al pulsar la opción Salir } procedure TForm1.Salir1Click(Sender: TObject); begin Close; { cerramos la ventana } end; Cambio del tipo de letra La primera opción del menú Estilo es Letra, una opción que nos permitirá seleccionar cualquier tipo, tamaño y estilo de letra para mostrar el texto de la ventana que en ese momento tengamos activa. Para ello lo primero que haremos será obtener los valores actuales de la propiedad Font del control TMemo, asignándolos a la propiedad Font del componente TFontDialog. Seguidamente mostraremos el cuadro de diálogo y, en caso de que se pulse el botón Aceptar, realizaremos la asignación contraria, tal y como puede ver seguidamente. { Al pulsar la opción Letra } procedure TForm1.Letra1Click(Sender: TObject); begin {Hacemos referencia al formulario hijo activo } With ActiveMDIChild As TForm2 Do Begin {tomando el tipo de letra actual} FontDialog1.Font := Memo1.Font; { y permitiendo modificarlo } If FontDialog1.Execute Then Memo1.Font := FontDialog1.Font; End; end; Cambio del color de fondo Para permitir cambiar el color de fondo de la ventana hija que tengamos activa, utilizaremos un método muy similar al anterior, si exceptuamos que en este caso usaremos un componente TColorDialog y que

Page 186: DELPHI 5

el valor lo obtendremos de la propiedad Color y no de la propiedad Font. El código de este método sería el que se muestra a continuación. { Al pulsar la opción Color } procedure TForm1.Color1ClicktSender: TObject); begin { Accedemos al formulario hijo activo } With ActiveMDIChild As TForm2 Do Begin { tomando el color de fondo } ColorDialog1.Color := Memo1.Color; { y permitiendo seleccionar otro } If ColorDialog1.Execute Then Memo1.Color := ColorDialog1.Color; End; end; Gestión de las ventanas hija Por último encontramos en el menú una opción Ventana, conteniendo tres opciones que nos permiten disponer las ventanas hija en forma de cascada, en forma de mosaico o bien organizar los iconos de aquellas ventanas que estén minimizadas. El código de los métodos correspondientes a los eventos OnClick de estas tres opciones es tan simple como una llamada a los métodos Cascade(), Tile() y Arrangelcons(), como puede ver. { Al pulsar la opción Cascada } procedure TForm1.Cascada1Click(Sender: TObject); begin Cascade; { Llamamos al método Cascade } end; { Al pulsar la opción Mosaico } procedure Tform1.Mosaico1Click(Sender: TObject); begin Tile; { Llamamos al método Tile } end; { Al pulsar la opción Organizar iconos } procedure TForm1.OrganizarIconos1Click(Sender: TObject); begin Arrangelcons; { Llamamos al método ArrangeZcons } end;

9.4.4. El código del formulario hijo También desde el módulo de código del formulario hija necesitaremos acceder al formulario principal, por lo que el primer paso que daremos, una vez que hayamos abierto en el Editor de código la página

Page 187: DELPHI 5

apropiada, será seleccionar la opción Use Unit del menú File, seleccionando el único módulo existente en la lista. Como hemos podido ver en el punto anterior, los métodos que se encargan de guardar y recuperar el texto de una ventana utilizan la propiedad Modified del control TMemo, asignándole el valor False con el fin de indicar que no se han realizado modificaciones tras haber realizado la recuperación o salvaguarda. Esta propiedad tomará automáticamente el valor True en el momento en que el usuario del programa modifique el texto. Por tanto, en el momento en que se desee cerrar una ventana hija el texto puede estar modificado, lo que podemos comprobar fácilmente con la propiedad comentada. En caso de que esto sea así deberíamos permitir al usuario guardar el texto, salir sin guardar o bien cancelar la operación de cierre de la ventana. El mejor punto para poder hacer esto es el método correspondiente al evento OnóloseQuery, cuyo código se muestra seguidamente. { Antes de proceder con el cierre del formulario } procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin { Comprobamos si el texto se ha modifcado } If Memo1.Modified Then { en caso afirmativo preguntamos si se desea guardar } Case Application.MessageBox('¿Desea guardar el texto modificado?',Pchar('Cerrar ' + Caption), MB_YESNOCANCEL) Of { en caso afirmativo } ID_YES: Form1.Guardar1Click(Self); { Guardar el texto } { Si se pulsa cancelar } ID_CANCEL: CanClose := False; { no cerramos la ventana } End; end; Como puede ver, en caso de que el texto se haya modificado usamos el método MessageBox() para preguntar al usuario si desea guardarlo o no, permitiéndole también cancelar. Si la respuesta del usuario es afirmativa realizamos una llamada al método GuardarlClick(), que se encargará de guardar el texto. Si se ha pulsado el botón de cancelación, asignamos el valor False a la propiedad CanClose, indicando así que no se debe continuar con el proceso de cierre. La última respuesta posible es que no se quiera guardar el texto, caso éste en el que no hacemos nada, por lo que el formulario se cierra y el texto se pierde. Cuando se pulsa el botón de cierre de una ventana hija MDI lo que ocurre es que ésta se minimiza, pero no se destruye. Si deseamos que al pulsar dicho botón la ventana se cierre y se libere la memoria que ésta ocupa, tendremos que utilizar el evento OnClose, asignando al

Page 188: DELPHI 5

parámetro Action el valor caFree, indicando que deseamos liberar el formulario. { Al cerrar el formulario } procedure TForm2.Form1Close(Sender: TObject; var Action: TCloseAction); begin Action := caFree; { liberar la memoria asignada } end;

9.4.5. Probando el programa Ya tenemos finalizado nuestro programa editor, tan sólo hemos de pulsar la tecla <F9> para ejecutarlo y verlo en funcionamiento. Inicialmente la ventana marco aparecerá vacía. Si pulsamos <Control+N> se creará una ventana vacía, en la que podremos introducir el texto que deseemos. Pulsando <Control+R> se abrirá un cuadro de diálogo en el que podremos seleccionar un archivo cualquiera, recuperando el texto que éste contiene. Pruebe el resto de las opciones, guardando un texto que haya escrito partiendo de cero, modificando el tipo de letra, el color de fondo y jugando con las ventanas, disponiéndolas en cascada, mosaico, minimizándolas, etc.

Figura 9.10. Aspecto del programa en funcionamiento

10. Trabajo con base de datos

10.1. Introducción Uno de los aspectos más interesantes de Delphi es su facilidad para tratar bases de datos en diversos formatos, sin necesidad de gue el programador tenga conocimiento alguno sobre la estructura de cada tipo. Para trabajar con una base de datos básicamente tendremos que

Page 189: DELPHI 5

completar dos procesos: la creación de la base de datos, apartado en el cual se definen sus campos, tipos, índices, etc., y su posterior uso en nuestra aplicación. Para crear o definir bases de datos podemos usar el Database Desktop, una herramienta que acompaña a Delphi y a la que podemos acceder desde el menú Tools, ó bien una herramienta específica para el tipo de base de datos a crear. A lo largo de este capítulo conoceremos los fundamentos del Database Desktop y crearemos una base de datos de ejemplo. Disponiendo ya de una base de datos, podemos acceder a ella desde nuestras aplicaciones mediante los componentes de bases de datos. Estos componentes, que conoceremos en el capítulo siguiente, nos permitirán especificar los datos a los que deseamos acceder, editarlos, etc. Comencemos a trabajar con el Database Desktop en los puntos siguientes, ejecutándolo desde el propio entorno de Delphi. Tras un momento, en el que se mostrará una ventana con datos acerca del programa, nos encontraremos con el entorno de trabajo de esta herramienta, cuyo aspecto se muestra en la figura 10.1.

10.2. Gestión de alias Las bases de datos son archivos, o grupos de archivos, que pueden estar localizados en un directorio de nuestro disco, sin son bases de datos locales, o en un servidor, en caso de que estemos trabajando en un entorno cliente/servidor. Con el fin de no tener que recordar el camino y nombre de los archivos en que se encuentra una determinada base de datos cada vez que es necesario usarla, lo habitual es definir un alias. Un alias o mote es un identificador asociado a una base de datos, de tal forma que cada vez que se quiere acceder a ella, en lugar del camino en el que se encuentra y el nombre de archivo, usamos dicho alias. Mediante la opción Alias Managerdel menú Tools accederemos a la ventana de gestión de alias. En la figura 10.2 puede ver los distintos apartados de esta ventana.

Page 190: DELPHI 5

Figura 10.1. Aspecto del entorno del Database Desktop

Fig 10.2 Ventana de gestion de alias del Database Desktop

Page 191: DELPHI 5

10.2.1. Creación de un nuevo alias El primer paso que daremos para crear un nuevo alias será pulsar el botón New, lo que hará que la mayoría de los apartados del formulario queden en blanco. A continuación facilitaremos un identificador para el alias, es decir, el nombre con el que vamos a conocer a la base de datos. Seguidamente seleccionaremos el tipo de controlador, en el apartado Driver type, que puede ser cualquiera de los controladores que tengamos instalado en nuestro sistema. Delphi tiene un motor de bases de datos capaz de trabajar con controladores de diferentes tipos. Al motor de bases de datos de Delphi se le conoce habitualmente como BDE(Borland Database Engine) y los controladores propios son los llamados estándar o controladores IDAPI. Estos son los controladores más eficientes, ya que están optimizados para trabajar con Delphi. Mediante ellos podremos trabajar con bases de datos de dBase, Paradox, Access y texto. Los formatos de bases de datos, sin embargo, no están limitados a los mencionados, ya que el BDE está preparado para trabajar con otros controladores diferentes. Mediante los controladores ODBC, por ejemplo, podemos acceder a bases de datos locales en otros formatos, mientras que con los controladores incluidos en el paquete SQL Links es posible acceder a bases de datos remotas. También es posible prescindir del BDE, utilizando los componentes de acceso directo a InterBase o incluso los ADO (Active Data Objects), aunque son dos opciones que no veremos en esta guía. Dependiendo del controlador que hayamos seleccionado, en la ventana de la figura 10.2 se solicitarán unos datos u otros. Elija de la lista desplegable el tipo STANDARD, con el fin de crear un alias para una base de datos con tablas Paradox. Al seleccionar este controlador tan sólo será necesario facilitar un dato más, el camino o directorio en el que están almacenadas o se almacenarán las tablas de esta base de datos. Dados los pasos anteriores ya puede pulsar el botón OK, momento en el que se le preguntará si desea guardar los nuevos alias en el archivo de configuración del BDE, a lo que responderemos afirmativamente.

10.2.2 Modificación y eliminación de alias Desde la ventana de gestión de alias también podemos modificar un alias existente, así como eliminar alias que ya no nos interesen. En cualquiera de los dos casos, lo primero que tendremos que hacer será seleccionar el alias a modificar o borrar, para lo cual desplegaremos la lista adjunta al apartado Database Alias, eligiendo el alias deseado.

Page 192: DELPHI 5

Si lo que queremos es eliminar el alias cuya información se está mostrando en la ventana, bastará con pulsar el botón Remove. El alias y todos sus datos desaparecerán del registro del BDE. En caso de que lo que necesitemos sea realizar alguna modificación, tan sólo tenemos que desplazarnos hasta el apartado correspondiente, introducir el nuevo valor y terminar pulsando OK, a fín de que los cambios se guarden en el citado archivo de configuración.

10.3. Creación de una tabla Como se comentaba al principio, una de las finalidades principales para las cuales utilizaremos el Database Desktop, aunque como veremos después hay más posibilidades, será para crear las tablas de las bases de datos necesarias para nuestros programas. Para ello tendremos que abrir el menú File y seleccionar la opción New, que nos dará paso a tres nuevas opciones de las cuales seleccionaremos Table. Lo primero que tendremos que hacer, al inidar la creación de una nueva tabla, será indicar su tipo. Para ello el Database Desktop visualizará una ventana como la de la figura 10.3, con una lista en la que podremos seleccionar cualquiera de los tipos disponibles. En nuestro caso vamos a elegir el formato de Paradox 7.

Figura 10.3. Selección del tipo de tabla a crear Dependiendo del tipo de la tabla que creemos, ésta soportará unos tipos de campos u otros, la existencia de chequeos de validación, índices de unos tipos u otros, etc.

10.3.1. Definición de los campos de la tabla Una vez que hemos indicado el tipo de tabla a crear, el Database Desktop mostrará una ventana en la que podremos ir especificando los distintos campos o columnas que forman cada registro de la tabla. Por regla general deberemos indicar para cada campo un nombre, tipo y longitud, pudiendo adicionalmente especificar si formará o no parte de un índice, si es un campo requerido, etc. En la figura 10.4 puede ver

Page 193: DELPHI 5

el aspecto de la ventana mostrada al crear una nueva tabla de tipo Paradox 7.

Figura 10.4. Definición de los campos de una tabla Tras introducir el nombre de un campo, que en este caso podrá contar con un máximo de veinticinco caracteres, pulsaremos la tecla <Intro> o la teda <Tab> para acceder a la columna Type. En esta columna lo más cómodo es pulsar la barra espaciadora o el botón derecho del ratón, desplegando así una lista con todos los tipos posibles. Como podrá ver, podemos definir campos para contener cadenas de caracteres, números de diferentes precisiones, fechas, objetos OLE e incluso gráficos. En caso de que el tipo de campo lo requiera, deberemos facilitar una longitud máxima. Si seleccionamos, por ejemplo, el tipo Alpha en una tabla Paradox, que nos permite introducir cadenas de caracteres, tendremos que indicar cuántos caracteres como máximo será posible almacenar en el campo.

10.3.2. Propiedades de la tabla La zona derecha de la ventana de definición de tabla, ilustrada en la figura 10.4, contiene las propiedades de la tabla.

Page 194: DELPHI 5

Existen varios campos de propiedades, como son los chequeos de validación de campos, los índices secundarios, etc. Podemos seleccionar el grupo de propiedades que deseamos ver desplegando la lista que hay en la parte superior, eligiendo cualquiera de los elementos existentes. Inicialmente el grupo mostrado es el de chequeos de validación, propiedades que se aplican a cada campo individual de la tabla. Los chequeos de validación nos permiten, como su propio nombre indica, validar la entrada de datos en los campos. Para ello disponemos de cinco propiedades diferentes, mediante las cuales podemos indicar si se ha de introducir necesariamente un valor en el campo, cuál será el valor mínimo y el valor máximo permitido, qué valor se tomará por defecto si el usuario no introduce uno nuevo, y cuál será la plantilla de entrada de datos. El apartado Picture, en el que podemos introducir una plantilla o máscara de entrada de los datos en el campo que tenemos seleccionado en ese momento, tiene justo debajo un botón mediante el cual podemos acceder a la ventana mostrada en la figura 10.5. En esta ventana se facilita la construcción de máscaras de entrada de datos y su comprobación de validez y funcionamiento. En la figura 10.5 puede ver cómo se ha introducido una plantilla que facilite la introducción de un número de teléfono, comprobando su funcionamiento en el apartado Sample Value. Al pulsar el botón OK la cadena se pasa automáticamente a la propiedad Picture.

Fig 10.5 Definición de una máscara de entrada para un campo

Page 195: DELPHI 5

Tenga en cuenta que la mayoría de las propiedades comentadas son aplicables sólo a tablas de tipo Paradox. Una tabla de dBase, por ejemplo, no cuenta con chequeos de validación.

10.3.3. Índices En una tabla de tipo Paradox pueden existir dos tipos de índices: el índice principal o clave y uno o más índices secundarios. El índice principal está siempre formado por el primer o los primeros campos de la tabla. Podemos indicar qué campos formarán parte de este índice pulsando cualquier carácter en la columna Key, lo que hará aparecer un asterisco indicativo de que ese campo forma parte del índice. El índice principal establece el orden inicial en el que estarán los registros de la tabla, ya que será el índice que se utilice por defecto. Los valores que forman parte de un índice principal deben ser únicos, una repetición de un valor clave causaría una violación de esa clave, en este caso una duplicación. También podemos contar en una tabla de este tipo con índices secundarios, que pueden ser activados en el momento en que a nosotros nos interese. Para definir un índice secundario lo primero que tendremos que hacer será seleccionar de la lista de propiedades el elemento Secondary Indexes, que mostrará una lista en la parte derecha de la ventana con los nombres de los índices secundarios existentes en ese momento. Inicialmente esta lista estará dada, como es lógico, ya que la tabla es nueva. La definición de un nuevo índice secundario la iniciaremos pulsando el botón Define, que se encuentra justo debajo de la lista desplegable de propiedades. Al hacerlo aparecerá una ventana como la que puede ver en la figura 10.6. En la lista de la izquierda están los campos disponibles en la tabla, mientras que en la de la derecha se enumeran los que forman parte del índice que deseamos definir. Tendremos, por lo tanto, que mover de una lista a otra el campo o campos que formarán el índice secundario. Por último pulsaremos el botón OK, lo que hará aparecer una ventana en la que tendremos que facilitar el nombre que deseamos asignar al índice. Este nombre se mostrará en la lista de índices secundarios de la tabla.

Page 196: DELPHI 5

Figura 10.6. Definición de un índice secundario

10.3.4. Guardar la tabla Cuando hayamos terminado de definir todos los campos y propiedades de la tabla, tendremos que pulsar el botón Save As para guardarla en disco, dejándola así disponible para cualquier uso posterior. Al pulsar dicho botón aparecerá la ventana que se muestra en la figura 10.7.

Figura 10.7 Guardando la nueva tabla

Page 197: DELPHI 5

En ella lo primero que haremos será desplegar la lista que hay en la parte inferior derecha, seleccionando el alias correspondiente a la base de datos en la que deseamos almacenar la tabla. Este alias determinará el disco y directorio en el que será efectivamente grabada la información. Hecho esto tan sólo debemos facilitar el nombre de la tabla, que en este caso será el nombre de un archivo con extensión DB, pulsando finalmente el botón OK.

10.4. Modificar la estructura de una tabla Desde el Database Desktop no sólo podemos crear nuevas tablas, también podemos modificar la estructura de tablas ya existentes, además de editar su contenido como veremos en el próximo punto. La modificación de una tabla que ya existe, y que posiblemente contiene datos, es siempre un tema delicado. En caso de que se trate de añadir nuevos campos no surge ningún problema. Si lo que deseamos hacer es modificar el tipo de un campo, en caso de que el tipo antiguo y el nuevo no sean compatibles no será posible realizar la necesaria conversión. La eliminación de un campo siempre resultará en una pérdida de datos, como es lógico. En casos como éste el Database Desktop nos avisará antes de realizar la operación, tal y como puede ver en la figura 10.8.

Figura 10.8. Aviso de pérdida de datos durante una reestructuración Para modificar la estructura de una tabla utilizaremos la opción Restructure del submenú Utilities, que se encuentra en el menú Tools.

Page 198: DELPHI 5

Tras seleccionar la tabla que se desea reestructurar nos encontraremos en la misma ventana que se utilizaba para la creación, pudiendo añadir nuevos campos, modificar o eliminar los existentes.

10.5. Editar el contenido de una tabla Aunque, por regla general, ésta será una tarea a realizar desde la aplicación que nosotros desarrollemos, el Database Desktop también nos permite editar el contenido de una tabla, como si de un pequeño gestor de bases de datos se tratara. Esta opción es muy útil, ya que nos permite, por ejemplo, comprobar si nuestro programa introduce los datos adecuadamente en la base de datos. Con el fin de abrir una tabla puede pulsar el primer botón que aparece en la barra que hay debajo del menú, o bien utilizar la opción Open del menú File, eligiendo la opción Table. En cualquiera de los dos casos se abrirá la ventana de apertura de tabla, en la que podremos seleccionar un alias, facilitando así el camino en el que se almacena la base de datos, y posteriormente elegir una de las tablas. Dado el paso anterior veremos aparecer en pantalla una ventana en forma de tabla, conteniendo tantas columnas como campos existan y tantas filas como registros contenga la tabla. En esta ventana podremos añadir datos a la tabla, así como modificar los ya existentes. En la figura 10.9 puede ver el aspecto de la ventana de edición.

Figura 10.9. Ventana de edición de una tabla

Page 199: DELPHI 5

La modificación del contenido de una tabla es una tarea muy simple con el Database Desktop. Inicialmente nos encontramos en el modo de visualización, por lo que podemos movemos por la tabla pero no realizar tareas de edición, tales como modificar un dato, añadir un nuevo registro o borrar un registro existente. Para pasar al modo de edición bastará con pulsar la tecla <F9> o el botón que está en el extremo derecho de la parte superior de la ventana.

10.5.1. Edición de datos Encontrándonos en el modo de edición, lo cual está indicado por el botón citado anteriormente y un pequeño letrero en la barra de estado, en la parte inferior de la ventana, podemos realizar diversas tareas de edición con la tabla que tenemos abierta. Para añadir un nuevo registro deberemos desplazamos hasta el final de la tabla, pulsando la <Flecha abajo> cuando nos encontremos en el último registro. Esto provocará que se añada un registro en blanco, en el que podremos introducir los valores que deseemos de forma inmediata. Si deseamos insertar un registro en una posición determida, en lugar de hacerlo al final de la tabla, deberemos situarnos en el punto que nos interese, pulsando a continuación la tecla <Insert>, que abrirá un espacio vacío desplazando todos los registros inferiores. Hecha esta operación, podemos introducir los valores para el registro de igual forma que hacíamos en el caso anterior. Tenga en cuenta que si la tabla tiene un índice activo, el orden de los registros vendrá determinado por el valor de los campos que forman parte de ese índice, por lo que dará igual el punto en donde lo insertemos o añadamos. También podemos, encontrándonos en el modo de edición eliminar registros de la tabla. Para ello lo primero que deberemos hacer es situamos sobre el registro a borrar, pulsando a continuación la combinación de teclas <Ctrol+Supr>. A diferencia de otras operaciones, como la modificación de un campo, el borrado de un registro no puede deshacerse posteriormente con el comando Undo del menú Edit. A la hora de modificar un campo de un registro ya existente, podemos optar por introducir un nuevo valor, eliminando el anterior, o bien por modificar el dato que se muestra. Por defecto la ventana de edición se encuentra en un modo que permite que las teclas de cursor sean usadas para desplazarse al campo anterior o siguiente. En este modo, la introducción de un valor en un campo elimina el contenido anterior. Podemos pasar al modo de edición de campo pulsando la tecla <F2>, o el penúltimo botón de la barra, de tal forma que las teclas de cursor permitan el desplazamiento por los caracteres del campo, facilitando así la modificación de un valor.

Page 200: DELPHI 5

10.5.2. Columnas no accesibles Ciertos tipos de campo no pueden ser editados desde el Database Desktop y tan sólo podremos introducir y modificar sus valores desde un gestor de bases de datos, como puede ser Paradox, o bien desde una aplicación que nosotros hayamos desarrollado a tal efecto. Los campos de tipo MEMO, que almacenan textos extensos; de tipo OLE, que almacenan objetos, o de tipo GRAPHIC, que contienen gráficos, son algunos de los que no podemos editar en el Database Desktop. En las posiciones de estos campos simplemente se mostrará una cadena indicando el tipo al que pertenecen.

10.6. Consultas La selección de una tabla con la única finalidad de ver su contenido tiene como consecuencia la visualización de todos sus campos y todos sus registros, es decir, se muestra toda la información que contiene la tabla. A veces, sin embargo, podemos necesitar recuperar sólo algunos de los datos con el fin, por ejemplo, de generar un cierto listado o informe. En estos casos lo que se hace es diseñar y ejecutar una consulta. Database Desktop soporta dos tipos de consultas diferentes, a las que se denomina QBE (Query By Example) y SQL (Structured Query Language). La primera de ellas, más simple e intuitiva, se basa en la composición de la consulta a partir de unos datos facilitados como ejemplo. La segunda, mucho más conocida, se basa en el uso del lenguaje de consulta de bases de datos SQL, un estándar prácticamente general.

10.6.1. Construcción de una consulta QBE Para crear una consulta QBE tendremos que seleccionar la opción New del menú File, eligiendo la opción QBE Query del submenú adjunto. Esto nos llevará a una ventana en la que tendremos que seleccionar la tabla sobre la que se va a realizar la consulta, para lo cual podemos partir por especificar el alias, pulsando seguidamente sobre la tabla deseada. Hecho esto aparecerá una ventana, como la mostrada en la figura 10.10, en la cual podremos componer la consulta.

Page 201: DELPHI 5

Figura 10.10. Creación de una consulta QBE Como podrá ver, a la izquierda de cada uno de los campos existe una casilla, similar a un control TCheckBox, que nos servirá para indicar si deseamos que el campo adjunto aparezca o no en la tabla resultante de la consulta. En caso de que deseemos marcar todos los campos, lo podemos hacer de forma inmediata pulsando en la casilla que hay en la izquierda de la ventana, justo debajo del nombre de la tabla. A continuación tendremos que facilitar unos datos de ejemplo, que pueden ir acompañados de operadores relacionales, mediante los cuales seleccionaremos los registros a obtener. En la figura 10.10 se buscan todos aquellos alumnos que pertenezcan a la Provincia Ciudad de la Habana que tengan menos de 21 años de edad. Una vez que tenemos compuesta la consulta, bastará con pulsar la tecla <F8> para obtener la tabla resultante, que estará compuesta de los registros que cumplan las condiciones que hemos impuesto, cada uno de los cuales, contará a su vez sólo con los campos que hayamos marcado. Esta tabla resultado es una tabla temporal, que se almacena en un directorio privado, pero que podemos guardar en caso de que nos interese conservarla. Las consultas QBE pueden ser almacenadas en disco, de tal forma que posteriormente estén disponibles para volver a ejecutarse en cualquier momento.

10.6.2. Construcción de una consulta SQL El proceso de creación de una consulta utilizando el lenguaje SQL se inicia de forma similar a una consulta QBE, seleccionando la opción New del menú File, y eligiendo en este caso la opción SQL File. La

Page 202: DELPHI 5

ejecución de esta opción abrirá una ventana de texto, en la que podremos introducir el comando SQL para realizar la consulta. En la figura 10.11 puede ver la ventana y una sentencia SQL de ejemplo, en la que se seleccionan de la tabla de alumnos los mismos registros obtenidos con la consulta QBE del punto anterior.

Figura 10.11. Creación de una consulta SQL Para obtener la tabla resultante de la consulta SQL, actuaremos de igual forma que se ha descrito en el punto anterior, es decir, bastará con pulsar la tecla <F8>. Las consultas SQL que son archivos de texto, también pueden ser guardadas en disco para su posterior uso.

Page 203: DELPHI 5

11. Componentes enlazados a datos

11.1. Introducción Habiendo diseñado la estructura de la base de datos que necesitamos para nuestra aplicación y definido las tablas que la vayan a conformar, usando el Database Desktop, estaremos ya preparados para usar dichas tablas en nuestros programas, para lo cual necesitaremos conocer los componentes enlazados a datos de Delphi. En la Paleta de componentes de Delphi existe una página, llamada Data Access, que contiene componentes no visuales de acceso a bases de datos. Estos componentes nos servirán, por ejemplo, para crear un enlace entre una tabla de la base de datos y nuestro programa, o para crear y ejecutar una consulta. A la derecha de esa página existe otra, llamada Data Controls, en la que encontraremos los controles enlazados a datos, una serie de componentes visuales cuya finalidad es mostrar y permitir la modificación de los datos que se almacenan en cada campo.

11.2. Edición de una tabla Vamos a partir viendo con qué facilidad podemos crear un formulario para editar el contenido de una tabla, pasando en puntos posteriores a estudiar con más detenimiento el funcionamiento de los componentes de bases de datos más habituales. Suponga que desea construir un formulario en el que sea posible visualizar y editar el contenido de la tabla de libros que se diseñó como ejemplo en el capítulo anterior, incorporando funciones que le permitan desplazarse de un registro a otro, modificar, borrar y añadir.

11.2.1. Establecer un enlace con la tabla El primer paso que daremos para poder acceder a los datos de una tabla será crear un enlace entre ella y nuestro programa, para lo cual usaremos el componente TTable. Este es un componente no visual que dispone de la funcionalidad suficiente como para abrir una tabla y gestionar su contenido. Para ello deberemos facilitarle el nombre de la base de datos y, obviamente, el nombre de la tabla. Inserte un componente de este tipo en el formulario, editando a continuación las propiedades DatabaseName y TableName. La primera de ellas contendrá el nombre de la base de datos, que puede ser tanto un alias que hayamos creado para la base como el camino del directorio en el que se encuentran las tablas de ésta. Puesto que en el capítulo anterior se definió un alias para la base en la que se encuentra la tabla de libros, bastará con asignar a esta propiedad el identificador de ese alias.

Page 204: DELPHI 5

También puede desplegarla lista adjunta a la propiedad Database Name, que contiene todos los alias definidos en el sistema. Como puede suponer, la propiedad TableName almacena el nombre de la tabla a la que se desea acceder. Este dato no tenemos por qué teclearlo directamente, ya que al haber facilitado al componente TTable el nombre de la base de datos podremos desplegar la lista adjunta a la propiedad TableName para poder ver todas las tablas existentes, seleccionando directamente la que deseemos. Una vez haya asignado los valores adecuados a las dos propiedades anteriores, dé el valor True a la propiedad Active. Si todo es correcto no pasará nada, pero el control TTable habrá accedido a la tabla, abriéndola y recuperando datos acerca de ella.

11.2.2. El editor de campos de TTable Una tabla está compuesta de una serie de campos, y mediante el componente TTable nosotros podemos seleccionar qué campos son los que deseamos manipular desde nuestro programa. Para ello utilizaremos el Editor de campos, una ventana a la que podemos acceder haciendo doble clic sobre el componente. Inicialmente esta ventana estará vacía, indicando que no ha seleccionado campo alguno. En este estado la lista de campos a la que es posible acceder desde nuestro programa se crea dinámicamente, es decir, durante la ejecución. En la mayoría de los casos, sin embargo, es más útil y rápido definir en la fase de diseño qué campos son los que necesitamos, creándolos estáticamente. Pulse la combinación <Control+A> en el Editor de campos, o bien pulse el botón derecho del ratón, para hacer aparecer el menú emergente, y seleccione la opción Add fields. Esto hará aparecer otra ventana con una lista de los campos existentes en la tabla, estando todos ellos seleccionados en principio. Bastará con que pulsemos el botón OK para añadir campos al Editor de campos, que mostrará el aspecto que puede ver en la figura 11.1. Los botones existentes en la parte superior nos permitirán el desplazamiento entre los registros que haya en la tabla, como podremos ver en un momento.

Page 205: DELPHI 5

Figura 11.1. El Editor de campos con la tabla de alumnos

11.2.3. Inserción de los controles de edición El siguiente paso que daremos será insertar en el formulario los controles que permitan, en ejecución, ver y editar el contenido cada registro de la tabla. Estos controles, cuyo funcionamiento conoceremos dentro de un momento, serán realmente insertados automáticamente por el propio Delphi. Teniendo abierta la ventana de la figura 11.1, seleccione los elementos de la lista, los cuatro campos, marcándolos con el puntero del ratón mientras mantiene pulsada la tecla <Mayús>, como haría en cualquier otra lista. A continuación pulse el botón izquierdo del ratón estando sobre los campos marcados y, sin soltarlo, arrástrelos hasta situarse en la parte superior izquierda del formulario, momento en el cual soltará el botón del ratón. Podrá ver cómo automáticamente se insertan en el formulario una serie de controles TLabel, TDBEdit y un componente TDataSource. Aunque aún estamos en fase de diseño, podrá comprobar que ya está viendo datos reales de la tabla. Ahora puede utilizar los botones que hay en la parte superior del Editor de campos con el fin de desplazarse de un registro a otro, comprobando en modo de diseño los datos existentes en la tabla.

11.2.4. Navegación por los datos Durante la ejecución, la ventana del Editor de campos no estará disponible, por lo cual no será posible utilizar sus botones para navegar por los datos de la tabla. Este es un elemento que está construido para su uso sólo en la fase de diseño, para permitir una funcionalidad similar durante la ejecución necesitaremos utilizar un control adicional.

Page 206: DELPHI 5

En la página Data Controls existe un componente, llamado TDBNavigator, que cuenta con una serie de botones que facilitan las operaciones de desplazamiento por los registros de la tabla, además de contar con otros adicionales para permitir el borrado o inserción de datos. Inserte en el formulario un control TDBNavigator dando el valor alBottom a la propiedad Align, con el fín de mantenerlo alineado en la parte inferior del formulario. A continuación edite la propiedad DataSource, seleccionando de la lista adjunta el único elemento disponible. Con esto estamos enlazando el control TDBNavigator con los datos sobre los que va a trabajar. El aspecto definitivo del formulario es el que se muestra en la figura 11.2, con los componentes TTable y TDataSource, el control TDBNavigator y los controles TLabel y TDBEdit. No necesita hacer nada más, ni siquiera escribir una línea de código, para poder ejecutar el programa y trabajar sobre la tabla de alumnos.

Figura 11.2. Formulario para editar la tabla de alumnos

11.3.Tablas y consultas Acabamos de ver lo fácil que resulta crear en Delphi un programa para manipular los datos de una tabla, sin necesidad de escribir nada de código y tan sólo con unas pocas operaciones de ratón, ya que prácticamente tampoco hemos tenido que edificar propiedades. Esta simplicidad, sin embargo, no está reñida con la flexibilidad y la potencia, y los componentes de bases de datos de Delphi cuentan con ambas características.

11.3.1. El componente TTabIe Desde un programa Delphi podemos acceder a los datos de una sóla tabla, a todos sus datos, o bien a datos parciales de una o más

Page 207: DELPHI 5

tablas. En el primer caso el componente apropiado es TTable, mientras que en el segundo deberemos usar un componente TQuery. El componente TTable, al igual que TQuery, es un puente entre los datos físicos, que se encuentran en la base de datos, y nuestro programa. Son estos componentes los que realizan todas las llamadas necesarias al BDE, recuperando y actualizando información. Los controles enlazados a datos, que trataremos en un punto posterior, son componentes visuales que cuentan con los elementos necesarios para poder recuperar la información de un campo y actualizarlo. Estos controles no pueden ser enlazados directamente con un componente TTable o TQuery, siendo necesario usar un intermediario que es el componente TDataSource.

11.3.2. El componente TQuery Suponga que desea seleccionar sólo algunos campos o registros de la tabla de alumnos o bien que quiere recuperar datos de dos tablas, estableciendo la relación necesaria entre ellas. En casos, como éstos, el componente apropiado para acceder a la base de datos no es TTable sino TQuery. El funcionamiento de TQuery es muy similar al descrito anteriormente para TTable. Contamos con una propiedad, DatabaseName, en la que facilitaremos el alias o camino en el que se encuentra la base de datos, y con una propiedad. Active, a la que daremos el valor True para activar el acceso a los datos. TQuery no dispone de una propiedad TableName y, en su lugar, encontramos otra llamada SQL. Esta propiedad, que es una lista de cadenas de texto, nos servirá para facilitar la sentencia SQL de selección de los datos que se desean obtener. Bastará con que haga doble clic sobre esta propiedad, en el Inspector de objetos, para abrir la ventana de edición. También puede facilitar la sentencia SQL en ejecución, permitiendo incluso que sea el usuario del programa el que determine los datos que desea obtener. Para realizar una prueba inserte un componente TQuery en el formulario e introduzca la sentencia SQL que se muestra en la figura 11.3. A continuación dé el valor True a la propiedad Active y asocie el componente TDataSource que ya hay en el formulario con el TQuery, modificando el valor de la propiedad DataSet. De forma inmediata podrá ver en el formulario los datos seleccionados y utilizando los botones del Editor de campos, que podrá abrir haciendo doble dic sobre el componente TQuery, comprobar si se han seleccionado los registros deseados.

Page 208: DELPHI 5

Figura 11.3. Sentencia SQL de selección de datos en el TQuery

11.3.3. El componente TDataSource Entre los componentes que utilizan las funciones del IDAPI para acceder al BDE y manipular los datos, que son los dos anteriores, y los controles de bases de datos que nos permiten mostrar y editar esa información, que veremos en el apartado siguiente, existe un intérprete intermedio que es el componente TDataSource. Este componente es una interfaz común para los controles de bases de datos, de tal forma que éstos pueden acceder a los datos independientemente de que se hayan recuperado mediante un TTable o un TQuery. La propiedad DataSet del componente TDataSource hará referencia al componente TTable o TQuery con el que se va a asociar, mientras que la propiedad DataSource de cada uno de los controles de bases de datos contendrán el nombre del componente TDataSource, creando así la cadena necesaria.

11.4. Controles enlazados a datos Todos los controles de bases de datos cuentan con una propiedad llamada DataSource que nos servirá, como acaba de explicarse, para asociarlos con el componente TDataSource que nos permitirá obtener los datos. Además, la mayoría de ellos también cuentan con una propiedad DataField, en la cual indicaremos qué campo de la tabla o consulta es el que se mostrará o editará en el control. Prácticamente existen tantos controles de bases de datos como controles estándar y la única diferencia entre unos y otros es gue los de bases de datos recuperan y almacenan la información en las tablas correspondientes. También hay controles de bases de datos que no

Page 209: DELPHI 5

tienen correspondencia con ningún control normal, como es TDBNavigator o TDBControlGrid.

11.4.1.Mostrar y editar datos Si queremos usar un control de base de datos tan sólo para mostrar el contenido de un Campo, sin facilitar ninguna operación de edición, podemos usar el control TDBText. Este control, que en apariencia es idéntico a un TLabel, cuenta con las citadas propiedades DataSource y DataField, de tal forma que es posible asociarlo con el campo cuyo contenido queremos mostrar. El control de base de datos más usado es TDBEdit, un derivado de TEdit que permite la visualización y edición del contenido de un campo. La asociación entre el campo y el control se establece mediante las propiedades DataSource y DataField, como en el caso anterior. Por lo demás, este control es prácticamente idéntico a TEdit, contando con las mismas propiedades.

11.4.2. Datos lógicos y botones de radio En una base de datos pueden existir campos de tipo lógico o boolean, así como campos que pueden tomar sólo una serie de valores predeterminados. A nuestra tabla de libros, por ejemplo, podríamos añadir un campo de tipo lógico que nos permitiese indicar si el libro viene acompañado de software. También sería posible añadir un campo que nos permitiese clasificar el interés de los libros, con valores como interesante, bueno, normal o pobre, por poner un ejemplo. Los campos de tipo lógico pueden ser gestionados en un formulario mediante el control TDBCheckBox, que es a todos los efectos un control TCheckBox que toma su valor de la base de datos a la cual está enlazado por las propiedades DataSource y DataField. En caso de que tengamos un campo que sólo puede contener una serie de valores limitado, podemos mostrarlo en el formulario como un conjunto de botones de radio, mediante el control TDBRadioGroup. Este control es prácticamente igual a TRadioGroup y cuenta con una propiedad, Items, que nos servirá para facilitar el título de cada uno de los valores posibles. Además de las propiedades DataSource y DataField, este control cuenta también con una propiedad llamada Values, que nos servirá para indicar los valores que puede tomar el campo facilitándolos en el mismo orden en que se han facilitado los títulos en la propiedad Items. De esta forma, en nuestra tabla de libros el interés podría ser un campo de un carácter, con los valores I, B, N o P, que serían los que asignaríamos a la propiedad Values, mientras en el formulario estos valores aparecerían como Interesantes, Bueno, Normal o Pobre, que serían las cadenas a asignar a la propiedad

Page 210: DELPHI 5

Items. En ejecución podemos saber en cualquier momento qué valor es el que se está representando en el control, gracias a que éste dispone de una propiedad Value que lo contiene.

11.4.3. Textos extensos e imágenes En el capítulo anterior, al tratar el Database Desktop vimos que éste no permitía la visualización ni edición detos tipos de campo, como eran los campos MEMO y los que contenían gráficos. Por tanto, para poder ver y editar el contenido de estos campos tendremos que diseñar un formulario en el que insertaremos los controles TDBMemo y TDBImage. El control TDBMemo es como un TMemo que cuenta con las propiedades DataSource y DataField que le permiten obtener y asignar su contenido a un campo de la tabla que indiquemos. De igual forma, el control TDBImage es a todos los efectos un TImage que cuenta también con las propiedades mencionadas, lo que permite establecer un enlace entre él y un campo de la base de datos. Durante la ejecución el contenido de un TDBMemo puede editarse directamente, como si fuese un TMemo. El contenido del TDBImage también puede ser modificado con simples operaciones de copiar y pegar. Esto nos permite obtener las imagenes de cualquier programa, sin importar el formato en que se encuentren, e insertarlas en nuestra base de datos.

11.4.4. Listas y listas combinadas De forma similar a como podemos utilizar un control DBRadioGroup, para dar a elegir o mostrar un número de valores predeterminado y muy limitado, también podemos usar con este fin los controles TDBListBox y TDBComboBox. Estos dos controles permiten, sin embargo, que el número de posibles valores sea más amplio, al facilitar una visualización parcial mediante una lista desplegable o unas barras de desplazamiento. El funcionamiento de TDBListBox y TDBComboBox es prácticamente igual, si exceptuamos las diferencias visuales existentes entre ambos controles. Una vez que hayamos asociado el control con un campo de la base de datos, para lo cual nos serviremos de las propiedades DataSource y DataField, asignaremos los valores posibles a la propiedad Items. De esta forma, durante la ejecución el usuario puede seleccionar el contenido de ese campo entre los valores mostrados en la lista, en lugar de tener que introducirlo manualmente. Podríamos usar un control TDBListBox para, por ejemplo, crear una lista de editoriales posibles, de tal forma que el usuario del programa no tiene por qué teclear su nombre, sino que lo seleccionará directamente de la lista, evitando además que unas veces se introduzca el nombre de una forma y otras veces de otra diferente.

Page 211: DELPHI 5

11.4.5. Rejillas de datos Hasta ahora hemos conocido controles que nos permiten la visualización y edición de un solo campo de un único registro de la tabla, siendo necesaria la inclusión en el formulario de un control TDBNavigator para permitir que el usuario pueda moverse por los datos. Existen, sin embargo, otros controles que nos permiten gestionar de forma simultánea múltiples campos de todos los registros seleccionados, mostrando los datos en forma de tabla. El control TDBGrid es uno de los controles más completos y complejos de Delphi, aunque su uso es bastante simple. Tan sólo cuenta con la propiedad DataSource y no con DataField, ya que este control no se asocia con un campo de la base de datos. Al insertar este control en el formulario y asociarlo al control TDataSource, que a su vez estará asociado con un TTable ó TQuery, podrá ver que de forma inmediata se muestran todos los campos y todos los registros disponibles. Incluso en modo de diseño puede actuar sobre la tabla, desplazándola según le interese, horizontal o verticalmente, y modificando el ancho de las columnas y filas. Como podrá ver, inicialmente el título de las columnas en el control TDBGrid es el propio nombre de los campos. Éste y otros aspectos del funcionamiento de este control son configurables y la mejor forma de hacerlo es usar la ventana del Editor de columnas, a la que accederemos haciendo doble clic sobre el control o bien abriendo el menú emergente y seleccionando la opción Columns Editor. El Editor de columnas En la figura 11.4 puede ver el aspecto del Editor de columnas del control TDBGrid, en este caso con una lista que contiene los cuatro campos de la tabla de alumnos que estamos utilizando como ejemplo, inicialmente esta lista estará vacía, pero bastará con que pulse el botón Add All Fields, tercero contando de izquierda a derecha, para añadir todos los campos de la tabla o consulta. Previamente deberá haber asignado a la propiedad DataSource el valor adecuado, ya que de lo contrario el control TDBGrid no podrá acceder a los datos. Si no deseamos que en el controlTDBGrid aparezcan todos los campos, sino sólo algunos que a nosotros nos interesen, podemos a continuación seleccionar en la lista los campos a eliminar, pulsando a continuación el botón Delete o la tecla <Supr>. También podemos añadir columnas individuales a la tabla, usando para ello el botón New o la tecla <Insert>.

Page 212: DELPHI 5

Figura 11.4. El Editor de columnas del control TDBGrid Al seleccionar cualquiera de las columnas en el Inspector de Objetos, que en la figura 11.4 se muestra a la derecha, se encuentran las propiedades que afectan a los datos que se visualizarán y los títulos de cada una de esas columnas. Con ellas podemos, por ejemplo, establecer el título que nos interese, fijar la alineación de los datos de cada columna, así como el ancho de ésta. También podemos seleccionar el color de fondo y tipo de letra, que puede ser diferente para cada una de las columnas, y también para los títulos de columna. Si queremos que una cierta columna sea usada sólo para mostrar una información, evitando la edición de su contenido, no tenemos más que dar el valor True a la propiedad ReadOnly de esa columna.

11.4.6. Rejillas de controles El control TDBGrid es una de las alternativas que tenemos al uso del control TDBNavigator, con el fin de permitir que el usuario pueda acceder a los múltiples registros de datos, viendo de forma simultánea varios de estos registros. Sin embargo, este control presenta los datos de una forma, como si fuese una tabla, que en ocasiones puede no interesamos. En este caso seguramente nos será útil el control TDBCtrlGrid, que encontraremos más a la derecha en la página Data Controls de la Paleta de componentes. Al insertar un control TDBCtrIGrid en el formulario podrá apreciar que cuenta con tres paneles, uno debajo de otro, dos de los cuales aparecen con una trama de líneas inclinadas. El número de paneles y

Page 213: DELPHI 5

sus dimensiones podemos configurarlos a nuestro gusto. Mediante la propiedad ColCount indicaremos el número de columnas o paneles en sentido horizontal, que inicialmente es uno. De forma análoga, la propiedad RowCount nos permite especificar el número de filas, o paneles en sentido vertical, que por defecto es tres. Mediante las propiedades PanelHeight y PanelWidth fijaremos el alto y ancho, respectivamente, de los paneles, que tendrán todos idénticas dimensiones. La propiedad PanelBorder, por último, nos permite activar o desactivar un borde entre los paneles, que por defecto está activado. Como podrá observar, este control tan sólo cuenta con la propiedad DataSource, al igual queTDBGrid, ya que mediante él podremos mostrar múltiples campos y múltiples registros. Una vez que haya insertado en el formulario un control TDBCtrIGrid, y asignado apropiadamente su propiedad DataSource, inserte en el panel superior, el que no está lleno de líneas inclinadas, controles de datos como los que hemos conocido hasta ahora. Puede insertar, por ejemplo, un control TLabel y un control TDBEdit para cada uno de los campos de la tabla de alumnos, disponiéndolos como se observa en la figura 11.5. También puede usar otros controles, como TDBCheckBox o TDBComboBox.

Figura 11.5. Aspecto del formulario con el control TDBCtrlGrid El aspecto más interesante de este control no lo percibiremos hasta que no ejecutemos el programa, en ese momento podemos ver que los

Page 214: DELPHI 5

controles que nosotros hemos insertado en el primer panel son repetidos en todos los demás paneles existentes en el TDBCtrlGrid, permitiendo así la visualización y edición de múltiples registros, pero de una forma más personalizada que la vista anteriormente mediante el control TDBGrid. En la figura 11.6 puede ver el resultado obtenido tenido en ejecución del diseño anterior.

Figura 11.6. Edición de múltiples registros con TDBCtrlGrid

11.5. Acceso programático a los datos Hasta ahora nos hemos centrado en el trabajo visual, el conocimiento de una serie de controles y componentes que, sin escribir una sola línea de código, facilitan la creación de complejas plantillas de entradas de datos. Sin embargo, en ocasiones desearemos acceder a los datos de las tablas desde el código de nuestro programa, con el fin de realizar cualquier tipo de cálculo, generar una lista, etc. El componente TTable que hemos tratado ya anteriormente cuenta con una serie de métodos que nos permiten el desplazamiento por los registros, la búsqueda, modificación de datos, etc. Además de este componente, también necesitaremos conocer las propiedades y funcionamiento del objeto TField, con el que se representa a cada uno de los campos de la base de datos.

Page 215: DELPHI 5

11.5.1. El objeto TField Aunque en realidad cada uno de los campos de una tabla puede ser de diferente tipo, Delphi necesita contar con una interfaz común que le permita gestionar todos los campos de una forma genérica, y esa interfaz es la que establece el tipo TField. Realmente este tipo sirve como base para otros, que serán los que realmente se utilicen, actuando como una plantilla para todos los tipos derivados. En la tabla 11.1 se enumeran los diferentes tipos derivados de TField, indicándose el tipo de dato que almacena cada uno de ellos.

Tipo Dato que almacena TIntegerField Números enteros en el rango de Integer. TSmallIntField Números enteros en el rango de SmallInt.

TWordField Números enteros en el rango de Word. TStringField Cadenas de hasta 255 caracteres. TFloatField Números reales. TBCDField Números decimales en punto fijo.

TBooleanField Valores de tipo Boolean. TDateTimeField Valores de tipo TdateTime (fechas y horas).

TDateField Fechas. TTimeField Horas.

TBlobField y TBytesField Datos arbitrarios sin un tamaño predeterminado.

TVarBytesField Datos arbitrarios con un máximo de 64 Kbytes. TMemoField Texto de longitud indeterminada.

TGraphicField Gráfico de longitud indeterminada.

Tabla 11.1. Tipos derivados de TField En la práctica nosotros nunca crearemos un objeto TField, ni de ninguno de sus tipos derivados/ya que estos objetos se crean dinámicamente, durante la ejecución, o bien estáticamente, en la fase de diseño, en caso de que mediante el Editor de campos del componente TTable oTQuery se hayan indicado los campos que se desean manipular. Acceso a un TField Desde el código de nuestro programa, en ejecución, podemos acceder a cada uno de los objetos TField básicamente de dos formas, dependiendo de que éstos se hayan creado de forma dinámica o estática. Tanto el componente TTable como TQuery cuentan con una propiedad llamada Fields, que es una matriz de objetos TField. Cada uno de los elementos de esta matriz contiene una referencia a un objeto derivado de TField, representando a cada uno de los campos de la tabla o de la

Page 216: DELPHI 5

consulta. Aunque es posible acceder a un campo mediante su índice en esta matriz, en caso de que lo conozcamos, es mucho más seguro utilizar el método FieldByName(), al que pasando como parámetro el nombre del campo,nos devolverá una referenda al objeto TField correspondiente. Usando este método evitaremos que nuestro código tenga problemas cuando, por ejemplo, el orden de los campos en la tabla sea alterado. En el siguiente fragmento de cógido, en el que se asume la existencia de un componente TTable llamado Table1, puede ver cómo se asigna un valor al campo título del registro actual de la tabla. Table1.FieldByName('Titulo').AsString := 'programación con Java'; Esta forma de acceder a los campos de una tabla es cómoda, pero también lenta e insegura, ya que el programa puede encontrarse en ejecución con que no encuentra un determinado campo. La alternativa a este método consiste en definir, durante el diseño, los campos que estarán disponibles durante la ejecución simplemente abriendo el Editor de campos del TTable o TQuery y añadiendo los campos que necesitemos. Esto conllevará la creación estática de los objetos TField que correspondan, a los que podremos acceder directamente desde el código de nuestro programa concatenando el nombre del componente Ttable o TQuery con el nombre del campo. Esta referencia directa, sin necesidad de realizar búsquedas en una matriz ni nada parecido, es mucho más rápida, además de ser más segura, ya que durante la compilación se comprueba si el objeto Tfield al que deseamos acceder existe o no. En la sentencia siguiente puede ver cómo se realiza la misma asignación del ejemplo anterior, pero en este caso accediendo directamente al campo. Table1Titulo.Value := 'Programación con Java'; Acceso al valor de un campo En las dos sentencias que se han puesto como ejemplo en el punto anterior, se usan dos propiedades diferentes para acceder al mismo valor. La propiedad AsString, propia de TField y heredada por los tipos derivados de la tabla 11.1, realiza una conversión del valor a cadena. Esto es necesario porque el método FieldByName() devuelve una referencia a un TField y este tipo de campo no sabe si el valor es un número, una cadena o una imagen. Por ello es necesario realizar una conversión previa a la asignación. La segunda asignación, en la que accedemos directamente al campo que se ha creado estáticamente, utiliza la propiedad Value para manipular el valor del campo, sin necesidad de indicar que se trata de una cadena. Esto es así porque el objeto Table1Titulo no es de tipo TField, sino de tipo TStringField. Por regla general, cuando deseemos acceder al valor de un campo utilizando un objeto TField deberemos llevar a cabo la conversión que

Page 217: DELPHI 5

corresponda, utilizando para ello los métodos AsString, AsBoolean, Asinteger, AsFloat o AsDateTime. Sin embargo, cuando para acceder al valor de un campo estemos utilizando uno de los tipos derivados de Tfield, cualquiera de los que se enumeraron en la tabla 11.1, podremos usar la propiedad Value sin ningún problema, ya que ésta será del tipo apropiado. Ciertos tipos de campo, como TMemoField o TgraphicField, representan valores que no pueden ser fácilmente manipulados mediante código, como son gráficos o textos de longitud indeterminada. Estos tipos de objeto cuentan con métodos adicionales, como LoadPromFile() y SaveToFile(), que facilitan la asignación y obtención del valor que almacenan. Todos los tipos de campo, tanto TField como sus derivados, cuentan también con los métodos SetData() y GetData(). Mediante estos métodos es posible la asignación y obtención del contenido de cualquier campo, para lo cual deberemos facilitar la dirección de un bloque de memoria en el que se almacenará la información.

11.5.2. Métodos de TTable Sin bien ya sabemos cómo acceder a los datos de cada campo, según los pasos dados en el punto anterior, para poder manipular los datos de una tabla o consulta necesitaremos además desplazamos entre los registros existentes. Para ello usaremos una serie de métodos del componente TTable que vamos a conocer seguidamente. Antes de poder realizar cualquier operación con los datos de una tabla o consulta, ésta deberá estar abierta. Si hemos dado el valor True a la propiedad Active durante el diseño no tendremos por qué preocupamos, pero en caso contrario deberemos realizar una llamada al método Open() o bien dirrectamente asignar el valor True a la citada propiedad. Desplazamiento por los registros Cuando se abre una tabla o consulta, el registro actual es siempre el primero. Por tanto, cualquier acceso a los objetos TField estará accediendo a los datos de ese primer registro. Podemos situamos en el primer registro, en caso de que se hayan realizado otras operaciones previas, con una simple llamada al método First(). De igual forma, podemos situamos en el último registro existente realizando una llamada al método Last(). Para realizar un recorrido secuencial de los registros contenidos en la tabla o consulta, usaremos los métodos Next() y Prior(), que nos llevarán al registro siguiente y anterior, respectivamente, siempre

Page 218: DELPHI 5

que no estemos ya en el primer o último registro. Podemos saber si estamos ya en el primero de los registros comprobando el valor de la propiedad BOF, en caso de ser True ello indicará que no hay registros anteriores al actual. De igual forma, podemos saber si estamos ya en el último registro mediante la propiedad EOF. También podemos avanzar o retroceder un determinado número de posiciones a partir del registro actual utilizando el método MoveBy(). Este método toma como parámetro un número entero, positivo o negativo, indicando el número de registros a avanzar o retroceder, respectivamente. Tareas de edición Una vez que nos hemos situado en el registro apropiado podemos facilitar la edición de su contenido, a partir de los controles de bases de datos que ya conocemos, con una simple llamada al método Edit(). Previamente debemos comprobar si el registro puede ser modificado o no, consultando el valor de la propiedad CanModify. Si esta propiedad contiene el valor False, indicando que el registro no puede ser modificado, no debemos realizar una llamada a Edit(), ya que ello causaría una excepción. Las modificaciones que se efectúen en un determinado registro no serán escritas en la tabla o tablas correspondientes hasta en tanto no se realice una llamada al método Post(), ya sea directa o indirectamente. Cuando tras haber realizado una modificación se hace una llamada a ciertos métodos, como los que hemos visto antes para desplazamos por los registros, automáticamente se realiza una llamada al método Post(), de tal forma que los datos son guardados antes de cambiar de registro. Si no deseamos que los cambios efectuados a un registro sean guardados, en lugar de utilizar el método Post() haremos una llamada al método Cancel(), lo que devolverá los valores originales a los campos y cancelará el modo de edición. Tenga en cuenta que si no desea guardar unos cambios de edición, no le basta con abandonar el registro desplazándose a otro, ya que esa operación implica una llamada indirecta a Post(). Por tanto, siempre que desee cancelar una operación de edición deberá hacerlo de forma explícita, llamando a Cancel(). Además de modificar un registro que ya existe, también podemos añadir o insertar nuevos registros mediante los métodos Append() e Insert(). En el primer caso el registro se añade al final de la tabla, mientras que en el segundo el registro se inserta en la posición actual, desplazando a los registros posteriores. En ambos casos se pasa automáticamente al modo de edición, permitiendo así la entrada de nuevos, valores, que serán escritos una vez que se realice la llamada al método Post(). La operación de añadir o insertar un nuevo registro

Page 219: DELPHI 5

también puede ser cancelada mediante el método Cancel() visto anteriormente. También es posible, lógicamente, eliminar información de una base de datos, borrando los registros que nos interese. Para ello, y una vez que nos hayamos posicionado en el registro a borrar, bastará con realizar una llamada al método Delete(). Trabajo con índices Asociados a una tabla pueden existir uno o más índices, cuya finalidad principal es establecer el orden de los registro y agilizar los procesos de búsqueda. El nombre del índice activo está contenido en la propiedad IndexName del componente TTable, propiedad cuyo valor podemos alterar durante la ejecución para activar el índice que nos interese. El orden de los registros de una tabla está determinado por los valores de los campos que actúan como índice principal o primario. En el mismo momento en que se cambia de índice, asignando el valor apropiado a la propiedad IndexName, el orden de los registros se recalcula, de acorde con los valores de los campos que correspondan. Como se ha dicho antes, un índice también es útil a la hora de buscar registros, principalmente agilizando este proceso que, de otra manera, habría que realizarlo leyendo todos los datos de la tabla. Para buscar el primer registro que contiene en el campo o campos clave un determinado valor, no tenemos más que realizar una llamada al método FindKey(), pasando como parámetro un conjunto con los valores buscados. Este método devolverá True en caso de que se encuentre la clave dada, posicionándose además en ese registro. Un ejemplo Veamos en la práctica cómo podemos utilizar alguno de los métodos que hemos visto, por ejemplo para diseñar un programa que nos permita listar todos los alumnos correspondientes a una cierta provincia. En este programa el usuario introducirá el nombre de la provincia, en un control TEdit, y el código que nosotros vamos a escribir buscará todos los alumnos existentes, mostrándolos en un TListBox. Comience por abrir la tabla en el Database Desktop y añadir un índice secundario, que estará basado exclusivamente en el campo Editorial. Una vez dado este paso, tendremos que diseñar el formulario que puede ver en la figura 11.7, en la que hemos insertado un TEdit y TListBox, ambos encabezados con un TLabel, un TButton y un componente TTable, que es el único componente de base de datos.

Page 220: DELPHI 5

Figura 11.7. Aspecto del formulario a diseñar Seguidamente deberemos establecer las propiedades oportunas para poder acceder mediante el componente TTable a la tabla de alumnos que estamos utilizando en los ejemplos. Debe asignar, por tanto, el valor adecuado a las propiedades DatabaseName y TableName. Despliegue la lista adjunta a la propiedad IndexName seleccionando el índice por provincia que hemos definido. Por último asigne el valor True a la propiedad Active, con el fin de que la tabla esté ya abierta al ejecutar el programa. A continuación haga doble clic sobre el TButton, con el fin de abrir el Editor de código creando el método asociado al evento OnClick de este control, e introduzca el código siguiente. procedure TForm1.Button1Click(Sender: TObject); begin With Table1 Do Begin { Eliminamos títulos de búsquedas anteriores } ListBox1.Items.Clear; First; // Nos ponemos al principio de la tabla { Si encontramos la provincia buscada } If FindKey([Edit1.Text]) Then Repeat { Añadimos el alumno a la lista } ListBox1.Items.Add(FieldByName('Nombre').AsString); Next; //y pasamos al registro siguiente { repitiendo mientras la provincia coincida y no lleguemos al final de la tabla } Until (FieldByName('Provincia').AsString <> Edit1.Text) Or EOF; End;

Page 221: DELPHI 5

end; Lo primero que hacemos es eliminar cualquier contenido de la lista, realizando una llamada al método Clear() de la propiedad Items. Esto evitará que en la lista se acumulen resultados de varias búsquedas. A continuación nos desplazamos hasta el primer registro de la tabla y usamos el método FindKey() para buscar la provincia introducida en el TEdit. En caso de que se encuentre una coincidencia, añadimos el nombre del registro encontrado a la lista, avanzando al registro siguiente con una simple llamada al método Next(). Estas dos últimas sentencias se repetirán continuamente, hasta en tanto no se encuentre una editorial diferente o se llegue al último registro de la tabla.

12. Creación de Informes

12.1. Introducción En Delphi 5 el diseño de informes se basa en el uso de los componentes QuickReport, que encontraremos en la página QReport de la Paleta de componentes. Mediante estos componentes es posible diseñar un informe fácilmente, utilizando un formulario para la composición visual. La ventaja de QuickReport frente a otras herramientas de diseño de informes se trata de un conjunto de componentes VCL y, como tal su código pasa a formar parte de nuestro propio programa sin necesidad así de tener que distribuir aplicaciones separadas. Además, la generación de un informe con QuickReport es más rápida, ya que no es necesaria la ejecución de un programa independiente. Á lo largo de este capítulo vamos a aprender a usar los componentes QuickReport para generar informes simples. Los datos que utilizaremos serán los de la tabla de libros que se creó como ejemplo en el capítulo 9 y se ha usado en diversos ejemplos del 10.

12.2. Funcionamiento general de QuickReport Antes de entrar en los detalles propios de cada componente, vamos a adquirir una idea general sobre su funcionamiento,lo que nos será útil para tener una visión global y conocer ya de antemano los pasos fundamentales que habremos de dar al diseñar un informe. El formato de un informe QuickReport se diseña en un formulario, lo que hace innecesario el uso de una ventana separada con este fin. Dicho formulario se transforma automáticamente en un informe QuickReport en el momento en que insertamos en ella un componente TQuickReport. Este componente nos servirá para relacionar el informe con los datos a utilizar en el mismo, además de para establecer algunos aspectos generales. Por regla general, todo informe se divide en múltiples secciones, como pueden ser la cabecera, cuerpo, pie, divisiones de grupo, etc. Cada una de estas secciones está representada mediante un componente

Page 222: DELPHI 5

TQRBand. Este componente aparece visualmente, al insertarlo en el formulario, como una franja de izquierda a derecha y actúa como un contenedor, de tal forma que es posible insertar en él los componentes necesarios para mostrar la información adecuada en cada sección. Entre estos componentes encontramos TQRDBText, TQRMemo y TQRLabel, que facilitan la impresión de un texto, un campo MEMO o un título, respectivamente. Mediante el componente TQRGroup es posible agrupar los datos de un informe basándose en uno o más campos, esta agrupación puede producirse simplemente a partir del orden de los registros o bien para generar un informe maestro/detalle, para lo cual deberemos usar además el componente TQRSubDetail. El aspecto del informe puede ser realzado incluyendo en él diversos elementos gráficos, usando el componente TQRShape. Este componente, al igual que otros, habrá dé incluirse en el TQRBand correspondiente a la Sección en la que Se desea hacer aparecer el elemento gráfico. Además de los elementos de realce, los títulos y los propios datos, en un informe también pueden aparecer otras informaciones, como campos calculados e información de sistema. En el primer caso usaremos el componente TQRExpr, facilitando el cálculo a realizar, mientras que en el segundo usaremos un TQRSysData, mediante el cual podremos obtener la fecha actual, el número de página y datos similares. Una vez que el informe está diseñado, en ejecución éste puede ser visualizado en pantalla, haciendo una visualización previa, o bien directamente impreso. También es posible exportarlo a un archivo en diversos formatos. .Para ello el componente TQuickReport dispone de los métodos adecuados.

12.3. El componente TQuickReport Este es el componente central de todo informe QuickReport. Su inserción en el formulario provoca que ésta se comporte como una ventana de diseño de informe y no como un formulario normal. El primer cambio que podremos apreciar, en el mismo momento en que insertemos el TQuickReport, es que en el formulario aparece una barra de desplazamiento horizontal y otra vertical, cuya finalidad es facilitar el desplazamiento por las secciones del informe, que pueden ocupar bastante más espacio del disponible físicamente en el formulario. Utilizando las opciones Zoom in y Zoom out del menú emergente, podremos ver Todd el informe e ir ampliando la sección que nos interese. Para iniciar el diseño de un informe usted puede partir de cero, con un formulario vacío, o bien de un informe predefinido. En el Depósito de objetos existen varios modelos de informe prediseñados que podemos añadir a nuestro proyecto según nos interese.

Page 223: DELPHI 5

12.3.1. Selección de los datos a imprimir Los datos a imprimir en el informe vendrán, en la mayoría de los casos, del contenido de una base de datos, aunque también es posible facilitar los datos a imprimir desde otras fuentes gracias a la existencia de un evento, OnNeedData, que se produce cada vez que se necesita un registro de datos a imprimir. Al igual que el componente TDataSource, un TquickReport cuenta con una propiedad DataSet mediante la cual relacionamos el informe con un TTable o TQuery. En cualquier caso, el componente TQuickReport se encargará de ir avanzando de un registro a otro, recuperando los datos para el informe.

12.3.2. Formato del informe Mediante el componente TQuickReport podemos prefijar el tamaño de papel, los márgenes, el número de columnas o el espaciado entre éstas. Todos estos elementos forman parte de la propiedad Page, un objeto TQRPage que cuenta, entre otras, con las propiedades PaperSize, LeftMargin, RightMargin, Orientation y Columns. Los valores de muchas de estas propiedades vienen expresados por defecto en milímetros, unidad de medida que podemos cambiar alterando la propiedad Units. Además de los títulos que podamos disponer en las diferentes secciones del informe, y que nos pueden servir para encabezar diversos apartados, columnas, etc., en el informe existe un título general que es el que se almacena en la propiedad ReportTitle del componente TQuickReport. Muchos de los parámetros de impresión, como el número de copias, la bandeja de papel a utilizar o el rango de páginas a imprimir, los estableceremos con la propiedad PrinterSettings, que al igual que Page contiene una serie de propiedades como FirstPage, LastPage, Copies u OutputBin. Con el fin de facilitar la alineación de los componentes de datos, en el interior del TQuickReport existen una serie de líneas de división, una cuadrícula, numerada en la unidad de medida que hayamos seleccionado. La propiedad Page dispone de una propiedad, llamada Ruler, mediante la cual podremos mostrar u ocultar esa cuadrícula.

12.3.3. Información durante la ejecución Una vez que el informe ha sido preparado, durante la ejecución podemos obtener alguna información acerca de él partiendo de ciertas propiedades. La preparación del informe se realiza automáticamente, antes de mostrarlo en pantalla o imprimirlo, aunque también es posible

Page 224: DELPHI 5

realizar la preparación, sin más, con una simple llamada al método Prepare(). Mediante la propiedad RecordCount podemos saber el número de registros que van a formar parte del informe, mientras que la propiedad RecorNumber nos facilita el número de registro que se está procesando actualmente. Podemos también conocer el número página actual con la propiedad PageNumber.

12.3.4. Visualización e impresión del informe Estando el informe ya diseñado, podemos optar por visualizarlo o bien imprimirlo directamente. Para ello disponemos de los métodos Preview() y Print(). Lo primero que ocurre cuando se llama a cualquiera de estos métodos, es que se prepara el informe, proceso en el cual se puede invertir más o menos tiempo dependiendo de la cantidad de datos a manipular y la relación que pueda existir entre ellos. Mientras dura este proceso, opcionalmente se puede mostrar una ventana en cual se indica el tanto por ciento que se lleva realizado. Si deseamos que esta ventana aparezca no tenemos que hacer nada, ya que por defecto la propiedad ShowProgress tiene el valor True, indicando así que sí debe mostrarse dicha ventana. También durante el diseño es posible realizar una visualización previa del informe, lo que nos permite ir viendo si éste queda tal y como nosotros deseamos. Puede insertar un control TQuickReport en un formulario y, a continuación, hacer doble clic sobre él para hacer aparecer la ventana de parámetros generales del informe que se muestra en la figura 12.1.

Page 225: DELPHI 5

Figura 12.1. Parámetros generales del informe Pulsando el botón Preview se hará una vista previa del informe, como se observa en la figura 12.2, vacía puesto que aún no se han insertado datos en el informe, aunque ya contamos con una serie de botones que nos permiten ajustar la visualización e imprimir el informe.

Page 226: DELPHI 5

Figura 12.2. Ventana de visualización previa en tiempo de diseño

12.4. El componente TQRBand Cada página del informe debe tener, al menos, una sección en la que se imprimen datos, ya que de lo contrario el informe aparecerá vacío. Esta sección estará representada por un componente TQRBand. En la práctica, existirán varios componentes de este tipo en el formulario en la que se diseña el informe, con el fin de definir una cabecera, un cuerpo de datos, un pie, etc. Mediante el mismo tipo de componente, TQRBand, podemos definir, por tanto, secciones que actúan de forma diferente. Una cabecera de página, por ejemplo, se imprimirá sólo una vez y al principio de cada página, mientras que la sección que compone el cuerpo se repite múltiples veces en cada página. Este funcionamiento del componente TQRBand viene determinado, en parte, por el valor que asignemos a la

Page 227: DELPHI 5

propiedad BandType, que será uno de los que se enumera en la tabla 12.1.

Constante Tipo de sección

rbTitle Título del informe. rbPageHeader Cabecera de página.

rbDetail Cuerpo del informe. rbChild Detalle de un registro maestro.

rbPageFooter Pie de página. rbSummary Pie de informe.

rbGroupHeader Cabecera de grupo. rbGroupFooter Pie de grupo.

rbColumnHeader Cabecera de columna. rbOverlay Sección superpuesta.

Tabla 12.1. Valores posibles para la propiedad BandType

Por regla general, en un informe contaremos al menos con una sección rbDetail, que usaremos para imprimir los datos del informe. También es habitual usar una sección rbTitle, para imprimir el título del informe, una sección rbPageHeader y otra rbPageFooter, para disponer elementos que aparecerán como cabecera y pie de página, respectivamente. La sección de tipo rbSummary es útil, por ejemplo, para imprimir unos totales finales que resuman el informe. Mediante el tipo rbOveriay podemos insertar en el informe una sección que actuará como fondo, sobre la cual se imprimirán las demás. Esto nos permite, por ejemplo, la inserción en el informe de una imagen o dibujo de fondo. En lugar de insertar componentes TQRBand seleccionándolos de la Paleta de componentes y colocándolos en el informe, podemos abrir la ventana de parámetros generales del informe, mostrada en la figura 12.1, y seleccionar en la parte inferior las secciones que deseamos que existan en el informe. De esta forma los componentes serán insertados automáticamente.

12.4.1. Aspecto de la sección en el informe Los datos que se incluyan en la sección que representa el componente TQRBand tomarán por defecto el tipo de letra que nosotros hayamos fijado en la propiedad Font. Si deseamos que la apariencia del informe sea similar en pantalla y en el papel, el tipo de letra deberá ser del tipo wysiwyg, por ejemplo un tipo TrueType. El color de fondo de la sección también es confígurable, disponiendo para ello el componente TQRBand de una propiedad Color. Esta

Page 228: DELPHI 5

propiedad, al igual que todas las de su tipo, puede tomar una constante, representando a un color.

12.4.2. El componente TQRExpr En un informe, además de títulos y datos almacenados en la base de datos, también es posible incluir datos que se calculan en ejecución. Estos datos se representarán mediante componentes TQRExpr, en cuya propiedad Expression se facilitará la expresión a evaluar. En ella podremos componer la expresión a partir de una lista de funciones, operadores y campos de las tablas según nos interese. Habitualmente este componente se utiliza para imprimir resúmenes al final del informe, en los pies de grupo o pies de página.

12.4.3. El componente TQRSysData Mediante este componente podemos añadir otros datos al informe, como puede ser la fecha de impresión, el número de página o el número de registros. El funcionamiento de este componente es similar a TQRLabel si exceptuamos la existencia de la propiedad Data, mediante la cual podemos elegir la información a imprimir. Esta propiedad tomará uno de los valores que se muestran en la tabla 12.2. Como es lógico, este componente no cuenta con la propiedad Caption, que se ve sustituida en cierta forma por la propiedad Data.

Constante Información a mostrar

qrsTime Hora de impresión del informe. qrsDate Fecha de impresión del informe.

qrsDateTime Fecha y hora de impresión del informe. qrsPageNumber Número de página actual. qrsReportTitle Título del informe. qrsDetailCount Número total de registros en el informe.

qrsDetailNo Número de registro actual.

Tabla 12.2. Valores posibles para la propiedad Data El título del informe, que representa el valor qrsReportTitle, es el que previamente nosotros habremos facilitado en la propiedad ReportTitle del componente TQuickReport, que por defecto se imprime en una sección con el tipo rbTitle.

12.4.4. El componente TQRShape Además del borde alrededor de cada sección, cuya existencia y apariencia podemos controlar mediante la propiedad Frame del componente TQRBand, en el informe podemos incluir otros elementos de realce. Para ello usaremos el componente TQRShape que, en cierta

Page 229: DELPHI 5

forma, es el equivalente al control TShape que conocimos en un capítulo anterior. Según el valor que asignemos a la propiedad Shape, que será uno de los que se enumeran en la tabla 12.3, aparecerá en el informe una figura u otra. El borde e interior de la figura puede tener un color u otro y un tipo de relleno u otro, dependiendo de las propiedades Brush y Pen, cuyo funcionamiento también conocemos.

Constante Figura a dibujar

qrsRectangle Rectángulo qrsCircle Círculo.

qrsVertLine Una línea vertical. qrsHorLine Una línea horizontal.

qrsTopAndBottom Líneas horizontales arriba y abajo. qrsRightAndLeft Líneas verticales a la izquierda y derecha.

Tabla 12.3. Valores posibles para la propiedad Shape

12.4.5. Imágenes en el informe Como podrá comprobar, en la página QReport de la Paleta de componentes existen dos componentes muy similares cuya finalidad es la inserción de una imagen en el informe. El componente TQRImage nos será útil para insertar cualquier imagen, mientras que TQRDBImage será el adecuado en caso de que las imágenes se encuentren almacenadas en tablas de una base de datos. Podemos incluir una imagen en una sección de cabecera, por ejemplo para insertar un logotipo. También será útil en el caso de que cada registro de la base de datos contenga alguna imagen, como podría ser, por ejemplo, la portada de cada libro. Otro uso habitual de las imágenes en un informe, en combinación con una sección de tipo rbOverlay, es dibujar un fondo en el papel, sobre el que se mostrarán los datos propiamente dichos. Este fondo suele ser claro y no demasiado complejo, con el fin de no impedir la correcta visión de los datos, que es lo que realmente importa del informe.

12.4.6. Un ejemplo Para ver en la práctica cómo podemos utilizar los diversos componentes que hemos conocido hasta ahora, vamos a diseñar un informe que nos permita obtener una lista de los libros que tenemos almacenados en nuestra base de datos. Partiremos insertando en el formulario un componente TTable, asignando a las propiedades DatabaseName y TableName los valores adecuados para

Page 230: DELPHI 5

acceder a la tabla de libros. No olvide dar el valor True a la propiedad Active del TTable, ya que ello le permitirá trabajar con datos reales mientras diseña el informe, facilitándole así la colocación y ajuste de los controles. Inserte ahora un componente TQuickReport, asignando a su propiedad DataSet el nombre del TTable que hemos insertado antes. Asigne a la propiedad ReportTitle el valor 'Lista de libros'. A continuación inserte en el formulario cinco componentes TQRBand, uno debajo de otro, y asigne a la propiedad BandType de cada uno los valores rbTitle, rbPageHeader, rbDetail, rbPageFooter y rbSummary, respectivamente. De esta forma contamos con una sección para mostrar un título, otra que actúa como encabezado de página, una tercera que es en la que se mostrarán los datos, una cuarta que será el pie de página y por último un resumen de informe. Veamos ahora qué controles insertaremos en cada una de estas secciones. La sección de título del informe En la primera sección del informe tan sólo vamos a insertar un componente TQRSysData, que nos servirá para mostrar el título que previamente hemos asignado a la propiedad ReportTitle. Seleccione de la lista adjunta a la propiedad Data el valor qrsReportTitle. Al ser el título del informe, éste deberá aparecer en un tipo de letra algo mayor que el resto. Haga doble clic sobre la propiedad Font y fije el tamaño y estilo de letra para el título. Por último, asigne el valor taCentera la propiedad Alignment, con el fin de que el título aparezca centrado horizontalmente en la página. Esta alineación se producirá en el momento en que se prepare el informe, tomando el título que se haya asignado. La cabecera de página El siguiente apartado que tenemos es la cabecera de página, una sección que se repetirá múltiples veces, una por cada página con que cuente el informe. En esta sección deberá insertar tres controles TQRLabel, con los títulos Título', 'Autor' y 'Editorial'. Colóquelos uno a la derecha de otro, con la separación suficiente como para poder mostrar estos datos de la tabla. Inserte debajo un componente TQRShape en forma de línea horizontal, que le servirá como separación visual entre la cabecera y el cuerpo del informe. El cuerpo La tercera sección del informe es la que va a contener los datos propiamente dichos, componiendo el cuerpo del informe. En esta sección insertaremos tres componentes TQRDBText, relacionando cada uno de

Page 231: DELPHI 5

ellos con las columnas apropiadas de la tabla de libros. Coloque cada componente alineado debajo del título que ha insertado anteriormente en la cabecera de página. Con el fin de evitar un espacio demasiado amplio en el informe, que llevaría a que éste contase con más páginas de las necesarias, ajuste la altura de la sección para que sea la justa que ocupa el texto de un campo. El pie de página La sección siguiente, al igual que la cabecera de página, se repetirá una vez por cada página, imprimiéndose en la parte inferior de ésta. Vamos a utilizar esta sección para mostrar la fecha de impresión y el número de página, por lo que tendremos que incluir dos componentes TQRSysData, uno a la izquierda y otro a la derecha, asignando a la propiedad Data los valores qrsDate y qrsPageNumber, respectivamente. Puede preceder el número de página con un título, insertando a su izquierda con un componente TQRLabel y asignando a la propiedad Caption la cadena 'Página'. La sección de resumen Por último nos encontramos con la secdón de resumen del informe, en la que simplemente vamos a indicar el número de títulos que se han incluido en éste. Para ello insertaremos un componente QRLabel, con la cadena 'Número de títulos' y un componente QRExpr, cuya expresión a evaluar será simplemente COUNT. Puede separar este resumen del cuerpo del informe mediante una línea horizontal, insertando un componente QRShape al igual que hicimos anteriormente en la cabecera de página. Con todo, el aspecto del informe en modo de diseño es el que se muestra en la figura 12.4.

Page 232: DELPHI 5

Figura 12.4. Aspecto del informe en tiempo de diseño Ejecución del informe El formulario en el que hemos diseñado el informe, que al ser el único del proyecto es el principal, no cuenta con ningún elemento visual con el que el usuario pueda interactuar. En la práctica este formulario nunca debería aparecer en pantalla, ya que su finalidad es contener todos los componentes del informe. Para poder ver el resultado del informe, por tanto, deberá añadir otro formulario al proyecto, seleccionándolo además como formulario principal. A continuación utilice la opción Use Unit del menú File para hacer referencia al primer módulo desde el segundo. Inserte en el nuevo formulario un botón, y escriba la siguiente línea de código en el método correspondiente al evento OnClick. Form1.QuickReport1.Preview; Ya puede ejecutar el programa, que inicialmente no tendrá funcionalidad alguna más que la de poder pulsar ese botón, mostrándose el informe que puede ver parcialmente en la figura 12.5. También puede llegar a este mismo punto simplemente seleccionando la opción Preview del menú emergente del TQuickReport durante el diseño.

Page 233: DELPHI 5

Fig 12.5. Resultado obtenido del informe

13. Componentes avanzados

13.1. Introducción Una de las características de Delphi 5 es su total soporte para los controles avanzados de Windows 95/98 y NT, que básicamente realzan la interfaz y facilitan su uso. Estos componentes los encontramos en la página Win32 de la Paleta de componentes y vamos a conocer los más importantes a lo largo de este capítulo. y Además de en Windows 95/98, muchos de estos componentes también podrán utilizarse en Windows NT 3.51 si tiene instalada la nueva interfaz, así como en la versión 4.0, que utiliza ya el nuevo aspecto creado por Microsoft en Windows 95.

13.2. Valores discretos y rangos En el capítulo 6 de esta guía, dedicado a los componentes más habituales de Delphi, conocimos algunos controles que nos permitían solicitar datos al usuario de diferentes formas. El control TTrackBar representa una manera alternativa de solicitar un dato, en este caso numérico, que se encuentre entre unos ciertos límites. En cierta forma

Page 234: DELPHI 5

el funcionamiento de este componente es similar a TScrollBar, pero no tiene por qué ir necesariamente asociado al desplazamiento de algún elemento. El rango de valores entre los cuales es posible moverse vendrá determinado por las propiedades Min y Max, mientras que la posición actual, indicada por un puntero, dependerá del valor que contenga en cada momento la propiedad Position. Al igual que ocurre con un control TScrollBar, el control TTrackBar puede ser manipulado por el usuario, en ejecución, tanto con el ratón como con el teclado, causando cambios en la posición actual. Mediante las propiedades LineSize y PageSize podremos fijar el factor, de incremento o decremento, que se utilizará según se desee desplazar una línea o una página, respectivamente. Podemos utilizar un control TTrackBar disponiéndolo en la ventana en sentido horizontal o vertical, según nos interese, simplemente dando el valor apropiado a la propiedad Orientation, que será trHorizontal o trVertical, respectivamente.

13.2.1. Marcas de posición A diferencia de una barra de desplazamiento normal, el control TTrackBar puede mostrar en su interior unas marcas mediante las cuales se puede indicar la posición de cada punto. La existencia o no de estas marcas, así como su disposición, dependerá del valor que se asigne a la propiedad TickStyle, que será uno de los que se muestra en la tabla 13.1, siendo el valor tsAuto el que se toma por defecto. Si elegimos el estilo tsAuto, las marcas se dispondrán automáticamente a lo largo o ancho del control con la frecuencia que nosotros mismos indiquemos en la propiedad Frequency. En caso de que optemos por el estilo tsManual inicialmente tan sólo existirán dos marcas, una al inicio y otra al final, pudiendo nosotros disponer cualquier otra mediante el método SetT¡ck(), al que pasaremos como parámetro un entero indicando la posición en la que se ha de situar la marca.

Constante Tipo de marca tsNone Ninguna. tsAuto Automática.

tsManual Manual.

Tabla 13.1. Valores posibles para la propiedad TickStyle La posición en la que se dispondrán las marcas en el control dependerá del valor que contenga la propiedad TickMarks, que habrá de ser necesariamente uno de los enumerados en la tabla 13.2. El valor tomado

Page 235: DELPHI 5

por defecto es tmBottomRight, por lo que inicialmente las marcas aparecen en la parte inferior o derecha del control.

Constante Disposición de las marcas

tmBottomRight Abajo o a la derecha, según la orientación. tmTopLeft Arriba o a la izquierda, según la orientación.

tmBoth A ambos lados del control.

Tabla 13.2. Valores posibles para la propiedad TickMarks

13.2.2. Selección de rangos Además de reflejar una posición, el control TtrackBar también puede mostrar la selección de un rango o franja de los valores que representa. Para ello tendremos que usar las propiedades SelStart y SelEnd, a las que asignaremos el valor de inicio y fin, respectivamente, de la franja a mostrar como seleccionada. A diferencia de la posición, que es un factor modificable durante la ejecución por la actuación directa del usuario, la selección de un rango en un control TTrackBar tan sólo es programable mediante código, utilizando las dos propiedades citadas.

13.2.3. En la práctica Veamos en la práctica cómo podemos usar el control TTrackBar, en este ejemplo para seleccionar los componentes de rojo, verde y azul de un color. Este programa nos puede servir para conocer la magnitud de cada componente, que podemos usar posteriormente en nuestros programas mediante la función RGB(), para obtener el color deseado. Comenzaremos, como es habitual, insertando en el formulario los componentes precisos para crear la interfaz del programa, que será similar a la que se muestra en la figura 13.1. Como puede ver se han insertado tres controles TTrackBar, encabezados por tres controles TLabel que sirven como títulos y otros tres controles TLabel que nos servirán para mostrar el valor de cada componente del color. La parte derecha del formulario está ocupada por un control TPanel, cuyo fondo nos servirá para mostrar el color seleccionado en cada momento.

Page 236: DELPHI 5

Figura 13.1. Aspecto del formulario con los controles TTrackBar Seleccione los tres controles TTrackBar con el fin de facilitar la modificación conjunta de sus propiedades. Asigne a la propiedad Max el valor 255, ya que cada uno de los componentes de un color puede estar comprendido entre cero, que es el valor mínimo por defecto, y 255. Al realizar la modificación anterior podrá apreciar que las marcas, que están en la parte inferior del control, tienen tanta densidad que forman una línea continua. Esto se debe a que por defecto la frecuencia de las marcas es 1, valor que nosotros vamos a modificar utilizando en su lugar el 16. Vamos ahora con las propiedades del control TPanel, cuyo color de fondo inicialmente será el negro. Por tanto, tendremos que modificar la propiedad Color, seleccionando el color clBIack. Por último modificaremos las propiedades de los tres controles TLabel en los cuales vamos a mostrar los componentes de rojo, verde y azul. Dé el valor False a la propiedad AutoSize de estas propiedades, asigne el valor taRightJustify a la propiedad Alignment y establezca como valor inicial de la propiedad Caption la cadena "0". El último paso que habremos de dar será escribir el código necesario para que cuando se modifique la posición en uno de los controles TTrackBar, momento en el que éste generará un evento OnChange, se actualice apropiadamente el color del TPanel, así como los valores mostrados en los tres TLabel. Puesto que esta actuación será la misma indistintamente de que se modifique la posición de un componente u otro, escribiremos un solo método que asedaremos al evento OnChange de los tres controles. El código necesario es el que se muestra seguidamente. { Al modificar la posición de cualquiera de los controles TtrackBar }

Page 237: DELPHI 5

Procedure TForm1.TrackBar2Change(Sender: Tobject); begin // Establecemos el nuevo color del TPanel Panel1.Color := RGB(TrackBar1.Position, TrackBar2.Position, TrackBar3.Position); //y mostramos los componentes del color Label4.Caption:= IntToStr(TrackBar1.Position); Label5.Caption:= IntToStr(TrackBar2.Position); Label6.Caption:= IntToStr(TrackBar3.Position); end; Ahora ya puede ejecutar el programa, seleccionando el factor de rojo, verde y azul para ir viendo los colores hasta encontrar el que le interese, momento en el que podrá anotar la magnitud de cada componente. El aspecto del programa, en ejecución, será similar al mostrado en la figura 13.2.

Figura 13.2. Aspecto del programa en ejecución

13.3. Curso de un proceso El componente TProgressBar se utiliza, principalmente, para indicar el punto en el que se encuentra un proceso en curso. En el capítulo anterior vimos cómo, por ejemplo, el componente TQuickReport utilizaba una barra de progreso para indicar que estaba en marcha el proceso de preparación del informe. La forma de utilizar este control es bastante simple, ya que su funcionamiento se basa prácticamente en tres propiedades que ya conocemos, como son Min, Max y Position. Mediante las dos primeras indicaremos los valores de inicio y fin del proceso, mientras que la última determinará la posición actual de ese proceso. Por defecto el rango de valores está comprendido entre cero y cien, ya que por regla general el estado de un proceso en curso se mide en tanto por ciento.

Page 238: DELPHI 5

En lugar de asignar un valor directamente a la propiedad Position, con el fin de incrementar la indicación de estado del proceso, podemos asignar inicialmente un valor de incremento a la propiedad Step, de tal forma que una simple llamada al método StepItQ realizará el incremento de forma automática.

13.3.1. En la práctica Un simple ejemplo nos servirá para conocer mejor el funcionamiento de este nuevo control. La finalidad de este programa será ir incrementando un estado de proceso que durará un determinado número de segundos, que nosotros mismos determinaremos de antemano. El formulario del programa es el que se muestra en la figura 13.3. En ella hemos insertado un control TLabel con un TEdit, para solicitar el número de segundos que ha de durar el proceso; un control TButton, que será el que pondrá en marcha el proceso; un TProgressBar, como es lógico, ya que es éste el control que deseamos probar, y un componente TTimer, que nos servirá para controlar el tiempo.

Figura 13.3. Aspecto del formulario con el control TProgressBar Asigne el valor True a la propiedad Default del TButton, de tal forma que baste con pulsar la tecla <Intro> para activarlo, sin necesidad de abandonar el control TEdit, que será el que tome el foco de entrada inicialmente. Dé el valor False a la propiedad Enabled del componente TTimer, ya que inicialmente el proceso no estará en marcha. Por último asigne el valor 1 a la propiedad Step del TProgressBar, indicando que el incremento que se realizará al llamar a Steplt() será de una unidad. Al pulsar el botón tendremos que asignar a la propiedad Max del TProgressBar el valor que se haya introducido en el TEdit, fijando así el valor máximo del proceso, que se iniciará en el momento en que demos el valor True a la propiedad Enabled del TTimer. También desactivaremos el botón, con el fin de que no sea posible pulsarlo hasta en tanto el proceso no haya finalizado. El código del método asociado al evento OnClick del botón será el siguiente.

Page 239: DELPHI 5

{ Al pulsar el TButton } procedure TForm1.Button1Click(Sender: TObject); begin // Fijamos el valor máximo ProgressBar1.Max := StrToInt(Edit1.Text); // Activamos el TTimer y desactivamos el TButton Timer1.Enabled := True; Button1.Enabled := False; end; Para evitar que se puedan introducir valores no numéricos, lo cual causaría un error de conversión al utilizar la función StrTolnt(), controlaremos cada pulsación de tecla que se produzca en el TEdit, aprovechando para ello el evento OnKeyPress. En el método asociado a este evento comprobaremos si el carácter pulsado es un dígito numérico, ignorándolo en caso contrario. { Con cada pulsación en el TEdit } procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin // Ignorar cualquier pulsación que no sea un dígito numérico o // la tecla de borrado If Not (Key In ['0'..'9', #8]) Then Key := #0; end; Una vez que el proceso está puesto en marcha, cada vez •que el TTimer genere un evento deberemos incrementar el indicador de estado del proceso, para lo cual bastará una llamada al método StepIt(). A continuación comprobaremos si hemos llegado al final del proceso, caso éste en el que desactivaremos el TTimer, activaremos de nuevo el TButton y daremos el valor cero a la propiedad Position del TProgressBar, como puede ver en el código siguiente. { Con cada evento del TTimer } procedure Tform1.Timer1Timer(Sender: TObject); begin With ProgressBar1 Do Begin StepIt; // Incrementamos el proceso If Position = Max Then // Si ya se ha completado Begin Position := 0; // Ponemos a cero la posición // Desactivamos el TTimer y reactivamos el TButton Timer1.Enabled := False; Button1.Enabled := True; End; End; end;

Page 240: DELPHI 5

Al ejecutar el programa deberá introducir un valor indicando el número de segundos de duración del proceso y pulsar el botón para ponerlo en marcha. Inicialmente pruebe con un valor pequeño y observe cómo a medida que van pasando los segundos se va completando la franja de color del TProgressBar, tal y como puede ver en la figura 13.4.

Figura 13.4. Aspecto del programa en funcionamiento

13.4. Incrementar y decrementar valores Seguramente habrá visto en algún programa Windows controles TEdit con unas flechas adjuntas, que permiten incrementar o decrementar el valor que contiene dicho control. En realidad estas flechas, accesibles mediante el control TUpDown, pueden asociarse también a otros controles y no sólo a un TEdit. Un control TUpDown es como una pareja de dos Tbutton que directamente incrementan o decrementan un valor, que puede representarse en un control adjunto. El citado valor se encontrará en un determinado margen que definiremos mediante las propiedades Min y Max, como en otros casos que acabamos de ver. La posición actual se almacenará en la propiedad Position del propio control TUpDown, aunque también puede quedar reflejada en el control adjunto, cuyo nombre habremos de asignar a la propiedad Associate.

13.4.1. Otras propiedades de TUpDown El control TUpDown puede colocarse a la izquierda o derecha del control asociado, según el valor que asignemos a la propiedad AlignButton, que será udLeft o udRight. Si lo deseamos, el control TUpDown puede interceptar las pulsaciones de tecla recibidas por el control adjunto, de tal forma que al usarse las teclas del cursor se incremente o decremente adecuadamente el valor. Aunque la orientación más habitual y tomada por defecto de este control es vertical, de tal forma que una flecha aparece hacia arriba y otra hacia abajo, podemos también modificar este aspecto mediante la

Page 241: DELPHI 5

propiedad Orientation. El valor por defecto es udVertical, existiendo también la posibilidad de orientar el control horizontalmente, asignando a esa propiedad el valor udHorizontal. Cuando al incrementar o decrementar el valor que representa el control TUpDown se llega a uno de los límites, el inferior o el superior, no es posible seguir avanzando a no ser que demos el valor True a la propiedad Wrap. En este caso, al llegar a un límite se saltará automáticamente al opuesto, es decir, si estamos en el valor mínimo y pulsamos el botón de decrementar, el valor que se tomará será el máximo.

13.4.2. En la práctica El funcionamiento del control TUpDown es extremadamente simple y tan sólo ha de insertarlo en el formulario y asociarlo con algún otro control, como puede ser un TEdit o un TButton, para poder comprobar su funcionamiento en la práctica.

13.5. Cabeceras redimensionables Algunas aplicaciones visualizan listas de datos con unas cabeceras que permiten que el usuario establezca el ancho de cada columna, adaptándolo a sus preferencias o necesidades. La cabecera propiamente dicha, que contiene los títulos y los límites de cada columna, es en realidad un control, que en Delphi recibe el nombre de THeaderControl. Un control THeaderControl es una colección de objetos THeaderSection, cada uno de los cuales mantiene la información de una sección de la cabecera, como puede ser el título o el ancho actual. Podemos acceder a esta matriz de objetos a través de la propiedad Sections del THeaderControl. Aparte de esta propiedad, prácticamente no existe ninguna otra que no conozcamos, pudiendo configurarse el color, la alineación, etcétera.

13.5.1. Propiedades de un objeto THeaderSection Como se acaba de decir, cada una de las secciones existentes en un THeaderControl es un objeto THeaderSection, que puede ser definido tanto en fase de diseño como durante la ejecución. Al igual que otros objetos, éste dispone de sus propiedades y métodos que nos servirán, por ejemplo, para establecer el título de la sección, su ancho, alineación, etc. El título que se muestra en una sección de la cabecera se almacena en la propiedad Text del objeto TheaderSection correspondiente. De igual forma, la alineación de dicho título se define mediante la propiedad Alignment, el ancho de la sección con la propiedad Width y su anchura mínima con la propiedad MinWidth. Esta última propiedad tiene por defecto el valor cero, lo que significa que en ejecución el usuario puede reducir la sección hasta hacerla desaparecer.

Page 242: DELPHI 5

Desde el código de nuestro programa tan sólo podemos hacer referencia a los objetos THeaderSection existentes a través de la propiedad Sections. Podemos añadir nuevas secciones mediante el método Add(), de igual forma que utilizamos ese mismo método para añadir líneas a la propiedad Lines de un control TMemo, por ejemplo.

13.5.2. Edición de la propiedad Sections Durante el diseño, para modificar el valor de la propiedad Sections tendremos que utilizar una ventana específica, que haremos aparecer haciendo doble clic sobre dicha propiedad en el Inspector de objetos. En esta ventana, cuyo aspecto puede ver en la figura 13.5, disponemos de dos botones que nos permiten añadir y eliminar secciones. A su derecha otros dos botones permiten cambiar la posición de la sección elegida. Por cada una de las secciones existentes, cuyos títulos se muestran en una lista, podemos definir el propio título, el ancho actual, mínimo y máximo y la alineación.

Figura 13.5. El editor específico para la propiedad Sections Durante la ejecución del programa, desde el código escrito por nosotros, podemos tanto acceder a las secciones existentes como crear otras nuevas. En el fragmento siguiente puede ver cómo se añade una nueva sección y se fija su título. With HeaderControl1.Sections.Add Do Text := 'Nueva sección';

13.5.3. Eventos de THeaderControl Las secciones de una cabecera pueden ser redimensionadas, además de pulsadas como si de botones se tratase. Cada vez que se modifica el tamaño de una sección se genera un evento OnSectionResize, que nosotros podemos aprovechar para adecuar la información que se está mostrando, ajustándola según el nuevo ancho de la columna. En caso de que la propiedad AllowClick del objeto THeaderSection correspondiente a una sección tenga el valor True, indicando que la sección puede ser pulsada como si fuese un botón, al producirse dicha pulsación el control genera un evento OnSectionClick.

Page 243: DELPHI 5

13.5.4. En la práctica Con el fin de ver en la práctica cómo podemos usar un control THeaderControl en nuestros programas, vamos a desarrollar un ejemplo en el cual usaremos la superficie del formulario como área de trabajo, listando en ella el contenido de la tabla de alumnos que utilizábamos en los ejemplos de los dos capítulos anteriores. Definiremos una cabecera con tres secciones, en las cuales mostraremos el Nombre, sexo y provincia de cada alumno. Estas tres secciones se podrán redimensionar durante la ejecución, permitiendo que el usuario adecué su anchura según desee. Puesto que vamos a usar el formulario como si de un papel se tratase, imprimiendo datos en su superficie, vamos a cambiar el color de fondo haciendo que éste sea blanco. Por tanto, deberá asignar el valor clWhite a la propiedad Color. Inserte en el formulario un componente TTable, asignando los valores adecuados a las propiedades DatabaseName y TableName para poder acceder a la tabla de alumnos. Dé también el valor True a la propiedad Active. Puesto que no vamos a usar ningún control de base de datos, no necesitaremos insertar un componente TDataSource. Con el fin de poder acceder directamente a los objetos TField que representan a cada campo, haga doble clic sobre el TTable, para abrir el Editor de campos, pulse la combinación <Control+A> y añada todos los campos a la lista. Tan sólo existirá un componente más en el formulario, un control THeaderControl. Edite la propiedad Sections definiendo las tres secciones citadas antes, de tal forma que el formulario quede aproximadamente como se muestra en la figura 13.6.

Figura 13.6. Aspecto del formulario con el THeaderControl

Page 244: DELPHI 5

Para mostrar los datos en el formulario no vamos a usar control alguno, sino que los dibujaremos directamente en la superficie, a la cual accederemos mediante la propiedad Canvas. El proceso de dibujo, por tanto, deberá llevarse a cabo en el método correspondiente al evento OnPaint, que es generado por el formulario cada vez que es necesario dibujar su contenido. Por consiguiente, será en ese método en el que recorramos el contenido de la tabla de alumnos, leyendo todos sus registros y mostrando el nombre, sexo y provincia almacenados en cada uno de ellos. La longitud de cada uno de estos datos deberá adecuarse al ancho de la sección correspondiente, por lo cual antes de imprimir la cadena, mediante el método TextOut(), será necesario comprobar si ésta excede del ancho de la sección. Para realizar esta comprobación nos serviremos del método TextWidth(), que nos devuelve el ancho en puntos de la cadena que pasemos como parámetro. En caso de que ese ancho sea superior al ancho de la sección, reduciremos la longitud de la cadena en un caracter, utilizando para ello el procedimiento SetLength(). Este proceso se repetirá hasta que la cadena sea lo suficientemente corta, momento en el cual se imprimirá. El código completo de este método es el que se muestra seguidamente. { Cada vez que sea necesario dibujar el contenido del formulario } procedure TForm1.FormPaint(Sender: TObject); Var X, Y: Integer; Cadena: String; begin // Tomamos la coordenada Y inicial Y := HeaderControl1.Height; { Asumimos la referencia al componente TTable } With Table1 Do Begin First; // Nos movemos hasta el primer registro Repeat // Inicio del bucle X:=0; // Cada registro comienza en la coordenada X cero // Tomamos el nombre del alumno Cadena := Table1Nombre.Value; { Y ajustamos su longitus hasta que no exceda del ancho de la sección } While Canvas.TextWidth(Cadena) > HeaderControl1.Sections[0].Width Do SetLength(Cadena, Length(Cadena)-1); // Imprimiéndolo a continuación Canvas.TextOut(X, Y, Cadena); // Incrementamos la coordenada X Inc(X, HeaderControl1.Sections[0].Width); { Repetimos el mismo proceso, pero en este caso con el sexo del alumno } Cadena := Table1Sexo.value; While Canvas.TextWidth(Cadena) > HeaderControl1.Sections[1].Width Do

Page 245: DELPHI 5

SetLength(Cadena, Length(Cadena)-1); Canvas.TextOut(X, Y, Cadena); Inc(X, HeaderControl1.Sections[1].Width); { volvemos a repetir, en este caso con la provincia } Cadena := Table1Provincia.Value; While Canvas.TextWidth(Cadena) > HeaderControl1.Sections[2].Width Do SetLength(Cadena, Length(Cadena)-1); Canvas.TextOut(X, Y, Cadena); Next; // Pasamos al registro siguiente // e incrementamos la coordenada vertical Inc(Y, Canvas.TextHeight(Cadena)); Until EOF; // Repetir hasta mostrar todos los registros End; end; Cada vez que modifiquemos durante la ejecución el ancho de una de las secciones o bien las dimensiones del formulario, el contenido de ésta deberá ser dibujado de nuevo. Para forzar esta acción realizaremos una llamada al método Invalidate() del formulario siempre que se produzcan los evento OnResize y OnSectionResize, como puede ver a continuación. { Al modificar el ancho de una sección ) procedure TForm1.HeaderControl1SectionResize (HeaderControl: THeaderControl;Section: THeaderSection); begin Invalidate; // Invalidamos el contenido del formulario end; { Al modificar él tamaño del formulario } procedure Tform1.Form1Resize(Sender: TObject); begin Invalidate; // También invalidamos su contenido end; Al ejecutar el programa los datos se mostrarán con el ancho prefijado en la fase de diseño, pero bastará con que tome uno de los extremos de cada sección para poder ampliarla o reducirla. En la figura 13.7 puede ver el aspecto que muestra el programa tras algunos ajustes durante la ejecución.

Page 246: DELPHI 5

Figura 13.7. El programa en ejecución

13.6. Barras de estado Uno de los componentes más habituales en los programas Windows es la línea de estado, un área en la parte inferior de la ventana principal, normalmente diferenciada con otro color, en la que se muestran mensajes indicativos, estados diversos e informaciones como la hora. Nosotros también podemos añadir a nuestro programa líneas de estado con este estilo, gracias al control TStatusBar. Este control es similar al que hemos conocido en el punto anterior, en el sentido de que también cuenta con una colección de objetos, en este caso de tipo TStatusPanel, cada uno de los cuales representa una sección o panel de la línea de estado. Podemos acceder a esta matriz mediante la propiedad Panels, una propiedad que cuenta con un método Add() que facilita la creación de paneles durante la ejecución del programa.

13.6.1. El objeto TStatusPanel Cada uno de los paneles de una línea de estado se corresponde con un objeto TStatusPanel que cuenta con las propiedades necesarias para poder establecer el estilo del panel, sus elementos de realce, el texto que se muestra, etc. El estilo de un panel queda determinado por el valor de la propiedad Style del objeto TStatusPanel correspondiente, que puede contener el valor psText, en cuyo caso el panel contiene un texto, o bien el valor psOwnerDraw, caso éste en que el panel genera un evento cuando es

Page 247: DELPHI 5

necesario dibujar su contenido, debiendo nosotros escribir el código preciso para hacerlo. En caso de que el panel vaya a contener un texto, éste se asignará a la propiedad Text y se alineará según el valor asignado a la propiedad Alignment. Mediante la propiedad Bevel del tipo TStatusPanel podemos también añadir un elemento de realce al panel, haciendo que éste aparezca resaltado o hundido en la línea de estado. Esta propiedad podrá tomar cualquiera de los valores mostrados en la tabla 13.3.

Constante Aspecto del panel en la línea de estado pbNone Normal.

pbLowered Hundido. pbRaised Resaltado.

Tabla 13.3. Valores posibles para la propiedad Bevel

13.6.2. Otras propiedades de TStatusBar En caso de que deseemos utilizar la línea de estado como una simple línea para mostrar una cadena de texto, sin tener que preocupamos de paneles, su definición y posterior gestión, no tenemos más que dar el valor True a la propiedad SimplePanel, que por defecto es False. De esta forma en la línea de estado no existirán paneles, y tan sólo se mostrará el texto que asignemos a la propiedad SimpleText. Si la línea de estado se inserta en la parte inferior de una ventana que puede ser redimensionada, podemos hacer que la esquina inferior derecha del TStatusBar muestre un control indicativo de que es posible modificar el tamaño de la ventana. Para ello habremos de dar el valor True a la propiedad SizeGrip.

13.6.3. Eventos de TStatusBar Además de los acostumbrados eventos de ratón, arrastrar y soltar y pulsación, este control cuenta con dos eventos adicionales. El evento OnResize se produce cada vez que el tamaño de la línea de estado cambia, lo que ocurre cuando se modifican las dimensiones de la ventana, ya que la línea de estado suele ajustarse siempre a la parte inferior. El segundo evento existente es OnDrawPanel, que se produce cuando en necesario dibujar el contenido de un panel de la línea de estado. Este evento tan sólo nos será útil si además de un texto deseamos incluir en un panel algún elemento más, como puede ser un icono indicativo.

Page 248: DELPHI 5

13.6.4. Edición de la propiedad Panels Al igual que ocurría con el control THeaderControl, la propiedad Paneis del control TStatusBar cuenta con un editor específico, mediante el cual es posible definir los diferentes paneles de la línea de estado, a los cuales podemos acceder posteriormente, durante la ejecución, con el fin de asignar el texto a mostrar en cada momento. Pulsando sobre la propiedad Panels accederemos a una ventana, como la que se muestra en la figura 13.8, que cuenta con dos botones, que nos permiten definir nuevos paneles y eliminar alguno de los existentes. En la lista se muestra el texto de cada panel, mientras que en el Inspector de objetos es posible establecer su estilo, anchura, texto y alineación.

Figura 13.8. El editor específico para la propiedad Panels Durante la ejecución accederemos a la propiedad Panel como si de una matriz se tratase, haciendo referencia a un objeto TStatusPanel individual con el fin de modificar alguna de sus propiedades. En la sentencia siguiente puede ver cómo se cambia el texto mostrado en el primer panel de la línea de estado. StatusBar1.Panels[0].Text := 'SOB';

13.6.5. En la práctica Realmente el trabajo con este control se lleva a cabo, casi en su totalidad, en la fase de diseño, definiendo los paneles y fijando las propiedades de cada uno de ellos. Durante la ejecución básicamente nos limitaremos a alterar el contenido de la propiedad Text de cada panel, reflejando el estado actual de la aplicación.

13.7. Ventanas multipágina Otro de los elementos que desde la aparición de Windows 95 se utiliza con más frecuencia, ofrece la posibilidad de disponer de varias páginas en una misma ventana, cambiando de una a otra mediante unos marcadores o pestañas, que tienen un título indicativo del contenido de cada página. Cada una de las páginas no tiene por qué ser igual a

Page 249: DELPHI 5

las otras, es decir, los controles existentes en ellas pueden ser diferentes. Para poder construir ventanas con múltiples páginas tendremos que usar el control TPageControl. Este control es en realidad un contenedor en el que podemos incluir uno o más objetos TTabSheet, cada uno de los cuales representa una página. Al insertar en el formulario un TPageControl éste está vacío, no tiene página alguna. Al igual que ocurre con los controles THeaderControl y TStatusBar, los objetos que representan a cada sección, panel o página en este caso, están accesibles mediante una matriz a la que tenemos acceso a través de la propiedad Pages. Sin embargo, y a diferencia de los dos controles anteriores, esta propiedad tan sólo está disponible en ejecución.

13.7.1. Gestión de las páginas en la etapa de diseño Como se acaba de decir, al insertar en el formulario un TPageControl éste no tienen ninguna página definida y, además, no tenemos acceso directo a la propiedad Pages, por lo que no existe ningún editor específico que nos asista en la tarea de definición de las páginas. Existen, sin embargo, unas opciones en el menú emergente del control TpageControl cuya finalidad es permitimos añadir nuevas páginas, así como desplazamos por las ya existentes. Si inserta en el formulario un TPageControl y a continuación pulsa el botón derecho del ratón sobre él, verá que la primera opción del menú emergente es New Page. Si la selecciona, se añadirá al control una página, un objeto TTabSheet, proceso que puede repetir tantas veces como páginas desee insertar. Para hacer activa una u otra, bastará con pulsar sobre la pestaña adecuada, aunque también podemos utilizar las opciones Next Page y Previous Page del menú emergente. La propiedad ActivePage del TPageControl almacena el nombre de la página activa, modificando el valor de esta propiedad también podremos cambiar dicha página activa.

13.7.2. Propiedades de TPageControl El control TPageControl cuenta con una serie de propiedades generales que afectan a todas las páginas que haya definidas en cada momento. Ya se ha mencionado la propiedad ActivePage, que almacena el nombre de la página que está activa en cada momento y que podemos modificar tanto en la fase de diseño como durante la ejecución. Por defecto, todas las pestañas correspondientes a las páginas que haya definidas se muestran en una sola línea, por lo que el ancho de cada pestaña se irá adecuando al espacio disponible, según la anchura del propio TPageControl y el número de pestañas existentes. En caso de que el espacio no sea suficiente para poder visualizar todas las

Page 250: DELPHI 5

pestañas, en el extremo derecho aparecerá una pequeña barra de desplazamiento, que nos permitirá ir moviéndonos de unas a otras. Podemos, sin embargo, indicar al control que distribuya las pestañas en varias líneas, simplemente dando el valor True a la propiedad MultiLine. De esta forma todas las pestañas se mostrarán de forma simultánea. Las dimensiones de cada una de las pestañas por defecto se ajustan al alto del tipo de letra y el ancho del título, pero son dos factores que también podemos modificar, aunque de forma conjunta, para todas las pestañas de forma simultánea. El control TPageControl dispone de dos propiedades, llamadas TabHeight y TabWidth, mediante las cuales es posible establecer el alto y ancho, respectivamente, de todas las pestañas.

13.7.3. Propiedades de TTabSheet Aunque algunas características de las páginas, como son las dimensiones de sus pestañas, se establecen de forma general mediante propiedades del control TPageControl. Otros aspectos, como el título o el tipo de letra del texto en la pestaña, son configurables mediante las propiedades de cada página individual, representada por un objeto TTabSheet. Además de las propiedades Caption, Font, Color y otras que ya conocemos, un objeto TTabSheet cuenta con una propiedad llamada Pageindex, mediante la cual podemos saber el índice de la página en la matriz a la que hace referencia la propiedad Pages del TPageControl. Podemos mostrar u ocultar páginas individuales gracias a la propiedad TabVisible, que es como la propiedad Visible de cualquier control pero aplicada al objeto TTabSheet. Por tanto, dando el valor False a esta propiedad conseguiremos ocultar la página y dándole el valor True la haremos visible.

13.7.4. Gestión de las páginas durante la ejecución Durante la ejecución podemos saber cuántas páginas existen en el TPageControl consultando el valor de la propiedad PageCount, dato que nos servirá para poder acceder a la matriz referenciada por la propiedad Pages. Contando con el índice de una cierta página, podemos alterar su título modificando el contenido de la propiedad Caption, así como hacerla visible o no mediante la propiedad TabVisible. En caso de que una cierta página no esté visible, el contenido de la propiedad Tabindex será -1, mientras que en otro caso contendrá el índice de la página en el TPageControl.

Page 251: DELPHI 5

Podemos avanzar a la siguiente página, o retroceder a la anterior, con una simple llamada al método SelectNextPage(), sin tener que preocupamos de índices ni nada parecido. Este método toma un sólo parámetro, indicando si se desea avanzar, True, o retroceder, False.

13.7.5. En la práctica. De forma similar a lo que ocurre con un control TStatusPanel, el trabajo con este nuevo control se centra prácticamente en el diseño, ya que una vez que han sido definidas las páginas e insertado en ellas los componentes que correspondan, en ejecución el usuario podrá cambiar de una página a otra sin que prácticamente nuestro programa tenga que hacer nada adicional.

13.8. Almacenes de imágenes TImageList es el único componente no visual que existe en la página Win32. Su finalidad es facilitar el almacenamiento de imágenes, como pueden ser iconos, que posteriormente pueden utilizarse desde el programa sin necesidad de tener que acceder a archivo alguno. El verdadero uso de este componente, su aplicación más útil, la conoceremos en los puntos siguientes, cuando tratemos controles como TTreeView y TListView. Las imágenes que se van a almacenar en un TImageList han de tener todas el mismo tamaño, siendo las dimensiones por defecto de 16 por 16 puntos. En caso de que necesitemos disponer de imágenes en diferentes tamaños, tendremos que usar varios controles TImageList.

13.8.1. Asignación de imágenes durante el diseño Al igual que otros componentes de los que hemos visto a lo largo de este capítulo, éste también cuenta con un editor específico, que en este caso haremos aparecer haciendo doble clic sobre el componente o bien seleccionando la opción ImageList Editor del menú emergente. La ventana de este editor específico, cuyo aspecto puede observar en la figura 13.9, cuenta con un botón Add, mediante el cual podemos recuperar una imagen de un archivo en disco, almacenándola en el TImageList. Esta imagen, que puede en realidad tener cualquier tamaño, puede ser cortada a las dimensiones fijadas por el componente, 16 por 16 como se dijo antes, puede ser redimensionada, reduciéndose o ampliándose, o bien puede aparecer centrada. Mediante el botón Delete podemos eliminar la imagen que se encuentra seleccionada en ese momento, que se muestra en la parte superior, mientras que una pulsación del botón Clear causará que el TImageList quede vacío, eliminando todo su contenido.

Page 252: DELPHI 5

13.8.2. Asignación de imágenes mediante código Independientemente de que el componente TImageList se haya preparado previamente con imágenes, durante el diseño, desde el código de nuestro programa podemos añadir en cualquier momento nuevas imágenes, disponiendo para ello de una serie de métodos.

Figura 13.9. El editor de imágenes del componente TImageList Básicamente podemos añadir dos tipos diferentes de imágenes al componente: iconos y mapas de bits. Los primeros se diferencian de los segundos por contar en realidad con dos imágenes, una que es el propio icono y otra que actúa como máscara, marcando las zonas que son transparentes y las que son visibles. Para añadir un icono deberemos previamente recuperarlo del archivo en que se encuentra, para lo cual deberemos crear un objeto TIcon y usar el método LoadFromFile(), al que pasaremos como parámetro el nombre del archivo, extensión incluida. A continuación llamaremos al método AddIcon() del TImageList pasando como parámetro el objeto TIcon creado. Si la imagen a añadir se encuentra almacenada en un archivo BMP, tendremos que crear un objeto TBitmap y utilizar el método LoadFromFile(), usando a continuación el método AddMasked() para añadir la imagen al TImageList. Este método además de la referencia alTBitmap, como primer parámetro, necesita también un color, mediante el cual se establecerá la parte transparente de la imagen, dato este que no es posible obtener directamente como en el caso de los iconos. En lugar de recuperar las imágenes de disco también podemos crear objetos TBitmap dibujando directamente en ellos, mediante la propiedad Canvas, añadiendo finalmente la imagen al TImageList. Esto nos permitiría generar las imágenes dinámicamente, durante la ejecución,

Page 253: DELPHI 5

en lugar de tener que guardarlas en la fase de diseño y recuperarlas posteriormente.

13.8.3. Obtener imágenes de un TImageList Obviamente, la utilidad de este componente sería nula si no fuese posible recuperar las imágenes que almacena, bien dibujándolas directamente en la superficie de un determinado control o simplemente devolviendo una referencia a ellas. Podemos saber el número de imágenes existentes en cada momento en el TImageList mediante la propiedad Count. El índice de la primera imagen será el cero, mientras que la del último será el valor devuelto por Count menos uno. Para recuperar una referencia a una imagen, bien en forma de icono o de mapa de bits, usaremos los métodos GetIcon() o GetBitmap(), respectivamente, a los que pasaremos como primer parámetro el índice de la imagen a recuperar y como segundo una referencia a un TIcon o un TBitmap, en los que se devolverá la imagen. También podemos dibujar la imagen directamente, por ejemplo en la superficie del formulario o en un control TImage, usando para ello el método Draw(). Este método toma como primer parámetro una referencia al objeto TCanvas en el que se va a dibujar, referencia que podremos obtener de la propiedad Canvas de la que disponen muchos controles. Los dos siguientes parámetros, de tipo entero, nos servirán para indicar la posición en que se ha de dibujar la imagen, cuyo índice se facilita en último lugar.

13.8.4. En la práctica Como se ha comentado anteriormente, el uso principal de este componente es servir como contenedor de imágenes para los componentes que vamos a conocer en los puntos siguientes. En la mayoría de las ocasiones las imágenes del componente TImageList se definen en la fase de diseño, y al asociarse con un control TTreeView o TListView no es necesario usar ninguno de los métodos con que cuenta TImageList, ya que son esos dos controles los que se encargan de obtener las imágenes que necesitan, dibujándolas en el lugar apropiado.

13.9. Listas jerárquicas Si ha trabajado con el Explorador de Windows ya conoce las bases del funcionamiento del control TtreeView. Cada nodo del árbol en un control TtreeView consiste de una etiqueta y un número opcional de imágenes de mapa de bits. Cada nodo puede tener una lista de subnodos asociados. Haciendo clic en un nodo, el usuario puede expandir o contraer la lista asociada de subnodos.

Page 254: DELPHI 5

13.9.1. Definir elementos en la fase de diseño Al hacer clic en la propiedad items del TtreeView aparece un nuevo formulario. En la parte derecha de el existen cuatro datos que hacen referencia a cuatro propiedades del objeto TTreeNode correspondiente al elemento de la lista seleccionado en cada momento. Mediante estas cuatro propiedades podemos establecer el título del elemento y asociarle una serie de imágenes que podrán mostrarse de acuerdo a su estado. Para crear un nuevo elemento simplemente hacemos clic en el botón New Item y damos los datos. Luego para añadirle subelementos a ese elemento, estando seleccionado, presionamos New SubItem. Los campos Image Index, Selected Index y State Index de este formulario hacen referencia a los indices de un componente TimageList que contiene la lista de imágenes que queremos utilizar.

13.9.2. Definir elementos durante la ejecución Con el fin de permitir la inserción de nuevos elementos en la lista, el objeto al que hace referencia la propiedad Items cuenta con varios métodos, entre los cuales los dos más habituales son Add() y AddChild(). El primero de ellos añade un nuevo elemento al mismo nivel que otro dado de referencia, mientras que el segundo crea un nuevo nivel debajo del elemento que se facilita como referencia. En ambos casos se toman los mismos parámetros: la referencia al TTreeNode de referencia y el título a asignar al nuevo elemento. El valor devuelto por estos métodos es una referencia a un TTreeNode, que nos permite acceder al elemento que se acaba de añadir con el fin de establecer sus propiedades. Para eliminar un determinado elemento de la lista lo único que hemos de hacer es llamar al método Delete(), facilitando como único parámetro una referencia al elemento a borrar. En caso de que deseemos dejar completamente vacía la lista bastará con realizar una llamada al método Clear().

13.9.3. Propiedades de TTreeView Además de las propiedades lógicas de un control como TTreeView, cuyo funcionamiento general ya conocemos, este control cuenta, además, con algunas específicas que nos permiten, por ejemplo, modificar la forma en que se visualizan los elementos en la lista, así como asociar uno o más componentes TImageList, con el fin de asociar imágenes a los elementos. Los diferentes elementos que conforman la lista tienen una relación de padre-hijo, cuyo enlace puede ser resaltado mediante el uso de líneas de conexión. Para hacer que estas líneas sean visibles, la propiedad ShowLines deberá tener el valor True. Cada uno de los elementos de la lista puede ser hijo del anterior, a la vez que padre de los siguientes. Para facilitar la visualización y

Page 255: DELPHI 5

operación del usuario en ejecución, a la izquierda de los elementos que son padres de otros es posible mostrar unos botones que, además de indicar si la rama se encuentra o no desplegada, servirán para abrir o cerrar dicha rama. La existencia o no de estos botones viene determinada por el valor de la propiedad ShowButtons. Podemos hacer aparecer estos botones también en la raíz del árbol, dando el valor True a la propiedad ShowRoot. Con el fin de reflejar aún más claramente la relación padres/hijos existente entre los elementos de una lista, es posible aplicar una identación, de tal forma que cada hijo se dibuje unos puntos más a la derecha que su padre. Este número de puntos lo podemos fijar, tanto en la etapa de diseño como de ejecución, asignándolo a la propiedad Indent. Como se comentó anteriormente, cada uno de los elementos de un control TTreeView puede estar precedido por una imagen, que será obtenida de un componente TImageList. Para asociar un componente con otro, el primero dispone de las propiedades Images y StateImages, capaces de contener una referencia a un TImageList cada una. El componente TImageList al que hace referencia la propiedad Images contendrá las imágenes normales a usar con los elementos de la lista, mientras que el referenciado por la propiedad StateImages almacenará imágenes de estado, unas imágenes que pueden mostrarse adicionalmente a la imagen normal. Como podremos ver posteriormente en un ejemplo, el título o texto que contiene cada uno de los elementos puede ser modificado directamente por el usuario del programa durante la ejecución, lo que facilita enormemente la tarea del programador ya que no tiene necesidad alguna de escribir código para ofrecer esta posibilidad. No obstante, en caso de que no nos interese que el usuario pueda modificar el texto de los elementos de la lista, lo único que hemos de hacer es dar el valor True a la propiedad ReadOnly.

13.9.4. Propiedades de TTreeNode Las propiedades que acabamos de ver afectan al control TTreeView en su conjunto, pero a veces necesitaremos hacer referencia a las propiedades de un elemento individual, para lo cual usaremos el objeto TTreeNode que representa a cada uno de ellos. Como se ha indicado anteriormente, podemos acceder a estos objetos mediante la propiedad Items, que cuenta con una propiedad Count, mediante la cual podemos saber el número de elementos existentes, y unos métodos que permiten añadir y eliminar elementos de la lista. Además de la propiedad Text, mediante la cual podemos establecer el texto mostrado en un elemento, un TtreeNode cuenta con otras propiedades adicionales, de las cuales vamos a conocer algunas seguidamente.

Page 256: DELPHI 5

Cada elemento de la lista además de contar con un título, almacenado en la propiedad Text, también puede tener asociado un dato, para lo cual se usará la propiedad Data. Esta propiedad es de tipo Pointer, permitiendo almacenar la dirección de cualquier bloque de datos que necesitemos enlazar con el elemento. En una lista normal, almacenada por ejemplo en un TListBox, los elementos tienen un índice que les identifica de forma única. Los elementos de un TTreeView cuentan con dos índices, uno relativo a la posición que ocupan en su rama, cuyo valor se almacena en la propiedad Index del TTreeNode, y otro que es absoluto, siendo único en toda la lista, que se almacena en la propiedad Absoluteindex. Como se ha dicho antes, un elemento de la lista puede contener a su vez otros elementos, actuando como padre. Podemos usar la propiedad HasChildren de un TTreeNode para saber si éste tiene hijos, obteniendo el número de hijos existentes de la propiedad Count, y accediendo a ellos mediante la propiedad Items, que es a su vez una matriz de objetos TTreeNode. También se comentó anteriormente el hecho de que cada elemento de la lista tenía un nivel, según el cual era hijo o padre de otros. Este nivel se almacena en la propiedad Level, que será cero para el nivel más alto, la raíz, y se irá incrementando progresivamente para elementos de ramas inferiores. En el punto anterior hemos visto que el control TTreeView puede estar asociado con dos componentes TImageList, que servirán para facilitar imágenes a adjuntar a los elementos. Para determinar qué imagen aparecerá a la izquierda de cada elemento, el objeto TTreeNode cuenta con las propiedades ImageIndex, SelectedIndex y StateIndex. Cada una de ellas almacena el índice de una imagen, que puede ser -1 en caso de que no se desee utilizar. La propiedad ImageIndex hace referencia a la imagen que se mostrará junto al elemento cuando éste se encuentre en estado normal, mientras que SelectedIndex contendrá el índice de la imagen a mostrar cuando el elemento esté seleccionado. La tercera de las propiedades, StateIndex, nos servirá para adjuntar una imagen adicional, que servirá como indicación de estado.

13.9.5. Métodos de TTreeView De igual forma que otros controles, cuando se pulsa sobre el área de un control TTreeView con el cursor del ratón se genera un evento OnClick. También dispone este control de los eventos de ratón y teclado habituales en otros componentes. Cuando se detecta una pulsación de ratón en un cierto punto del control, podemos saber qué es lo que hay en ese punto realizando una llamada al método GetHitTestInfoAt(), pasando como parámetros las coordenadas del citado punto. Este método devolverá uno de los valores que se enumeran en la tabla 13.4, indicando sobre qué elemento se ha pulsado. Si lo único que nos interesa es obtener una referencia al TTreeNode sobre el que

Page 257: DELPHI 5

se ha pulsado, en lugar de GetHitTestInfoAt() podemos usar el método GetNodeAt(), al que pasando los dos mismos parámetros nos devolverá una referencia al elemento pulsado, o bien el valor N¡1 en caso de que la pulsación se haya efectuado en un punto en el que no existía elemento alguno.

Constante El punto sobre el que se ha pulsado está ...

HtAbove Encima del área que ocupa el control. HtBelow Debajo del área que ocupa el control. htToLeft A la izquierda del área que ocupa el control.

htToRight A la derecha del área que ocupa el control. htNoWhere Debajo del último elemento existente en la lista. htOnItem En el título o imagen de un elemento.

htOnButton En el botón de un elemento. htOnIcon En la imagen de un elemento.

htOnIndent En el espacio de identación. htOnLabel En el título del elemento. htOnRight A la derecha de un elemento.

htOnStateIcon Sobre el icono de estado de un elemento.

Tabla 13.4. Valores que puede devolver el método GetHitTestInfoAt El contenido de un control TTreeView, el texto de sus elementos para ser más concretos, puede ser guardado en un archivo en disco y posteriormente recuperado. Para ello disponemos de los habituales métodos SaveToFile() y LoadFromFile() que, como siempre, tomarán como parámetro el nombre del archivo en el que se desea guardar o del que se ha de recuperar la información del control. Durante la ejecución, el usuario puede cerrar y abrir las ramas de un TTreeView pulsando sobre los elementos apropiados. Como veremos en un momento, nosotros también podemos abrir y cerrar ramas programádamente, mediante los métodos del objeto TTreeNode. El control TTreeView dispone de dos métodos, llamados FullExpand() y FullCollapse(), mediante los cuales podemos expandir totalmente el contenido del control, expandiendo todas las ramas, o bien cerrarlo dejando un sólo elemento, el raíz.

13.9.6. Métodos de TTreeNode También el objeto TTreeNode cuenta con algunos métodos útiles que nos permitirán manipular de forma individual los elementos contenidos en la lista, actuando complementariamente a las propiedades que conocimos en un punto anterior.

Page 258: DELPHI 5

En caso de que un cierto elemento tenga hijos, caso éste en que la propiedad HasChildren será True, podemos desplegar o cerrar la rama mediante los métodos Expand() y Collapse(), respectivamente. Ambos métodos toman un parámetro de tipo Boolean, mediante el cual indicaremos si deseamos que se abran o cierren de forma recursiva todas las ramas que puedan existir en niveles inferiores. Anteriormente decíamos que el texto de un elemento de la lista podía ser editado directamente por el usuario, simplemente pulsando sobre él, haciendo aparecer el cursor de edición. Esto ocurre, por ejemplo, en el Explorador de Windows cuando se renombra un archivo o directorio. Nosotros también podemos iniciar la edición de un elemento, desde el código de nuestro programa, realizando una simple llamada al método EditText(). La acción de edición puede ser cancelada en cualquier momento, con una llamada al método CancelEdit(). La eliminación de un elemento de la lista puede realizarse de diferentes formas, y una de ellas consiste en utilizar el método Delete() del objeto TreeNode correspondiente. Si deseamos eliminar sólo los elementos hijo que tiene un determinado elemento, podemos usar el método DeleteChildren().

13.9.7. En la práctica Vamos a ver en la práctica cómo podemos utilizar un control TTreeView, mediante un ejemplo que nos permitirá almacenar en un archivo índices de libros, conservando una cierta jerarquía en la que el primer nivel estará ocupado por los títulos de los libros, el segundo por los nombres de cada capítulo y el tercero por los títulos de los apartados. Para comenzar vamos a insertar en una fícha los componentes que puede ver en la figura 13.11. La mayor parte del formulario está ocupado por el control TTreeView, mientras que a la derecha podemos ver seis TButton y un componente TImageList.

Figura 13.11. Aspecto del formulario a diseñar

Page 259: DELPHI 5

A continuación tendrá que utilizar un editor de imágenes, Delphi incorpora uno al que puede acceder mediante la opción Image Editor del menú Tools, para crear cinco pequeños iconos, de 16 por 16 puntos, que seguidamente recuperaremos en el componente TImageList, en el orden que se muestra en la figura 13.12. Como puede ver, estos iconos representan un libro cerrado en dos colores diferentes, como puede ser rojo y amarillo, un libro abierto en esos dos mismos colores y una página o documento. El color transparente de estas cinco imágenes será el blanco, ya que es éste el color que tienen como fondo. Como puede ver, en el formulario no existe ningún componente TEdit o similar, ya que la edición de los títulos de los elementos se realizará directamente en el control TTreeView, lo que es mucho más intuitivo y simple para el usuario.

Figura 13.12. Carga de las imágenes en el componente TImageList

Creación de un nuevo libro Al pulsar el botón Nuevo libro tendremos que añadir un nuevo elemento a la lista, que estará en el primer nivel, sin tener relación alguna de padre/hijo con el resto de los elementos de la lista. Por ello pasaremos al método Add() como primer parámetro el valor Mil, en lugar de una referencia a un objeto TTreeNode, consiguiendo así que el nuevo elemento se añada al nivel de raíz al final de la lista. Inicialmente asignaremos a este elemento un título por defecto, pero de inmediato invitaremos al usuario a que introduzca el verdadero título del libro, para lo cual realizamos una llamada al método EditText() del TTreeNode recién añadido. También establecemos los índices correspondientes a las imágenes a usar junto al título del libro, que serán los del libro en color rojo. Por último, antes de dar por terminado el proceso, hacemos que el nuevo elemento se convierta en el elemento actual de la lista, para lo

Page 260: DELPHI 5

cual basta con asignar el nuevo TtreeNode a la propiedad Selected del control TTreeView, como puede ver en el código que se muestra a continuación. { Al pulsar el botón NuevoLibro } procedure TForm1.NuevoLibroClick(Sender: TObject); Var NuevoNodo: TTreeNode; begin { Añadimos un nuevo nodo al primer nivel } NuevoNodo := Indices.Items.Add(Nil, 'Nuevo libro'); { Establecemos las imágenes } with NuevoNodo Do Begin ImageIndex := 0; SelectedIndex := 0; EditText; { y editamos el texto } End; { Seleccionando el nuevo nodo como actual } Indices.Selected := NuevoNodo; End; Inserción de un nuevo capítulo El proceso por el cual se añade un nuevo capítulo es similar al que acabamos de ver, aunque existen algunas diferencias. Lo primero que hemos de tener en cuenta es que para añadir un capítulo debe haber seleccionado un libro en la lista, es decir, si la lista está vacía o no hay un elemento seleccionado, no sabremos dónde hay que añadir el capítulo. Por ello lo primero que haremos será comprobar el valor de la propiedad Selected, de tal forma que si contiene Nil avisamos mediante un mensaje al usuario, indicándole que debe seleccionar un libro. Suponiendo que hay un elemento seleccionado en la lista, éste no tiene por qué ser necesariamente el título del libro, ya que puede estar seleccionado cualquier otro elemento, como puede ser un capítulo o un apartado, casos en los cuales la propiedad Selected no sería Nil. Puesto que el capítulo se ha de añadir como un hijo del elemento que contiene el título del libro, tendremos que obtener una referencia a ese elemento. Para ello comenzamos por obtener el índice absoluto del elemento que hay seleccionado actualmente, recorriendo a continuación la lista en sentido inverso hasta encontrar un elemento cuyo nivel, que se almacena en la propiedad Level, sea cero. En ese momento estamos seguros de que hemos encontrado el título del libro, cuya referencia usamos a continuación en la llamada al método AddChild(), tras lo cual establecemos los iconos, expandimos el libro al que se ha añadido el capítulo y entramos en el modo de edición, con una llamada a EditText(), a fin de que el usuario pueda directamente introducir el nombre del capítulo, sin más.

Page 261: DELPHI 5

{ Al pulsar el botón NuevoCapitulo } procedure TForm1.NuevoCapituloClick(Sender: TObject); Var N: Integer; NuevoNodo: TTreeNode; begin { Si no hay un nodo seleccionado actualmente } If Indices.Selected = Nil Then { no podemos saber a qué libro se desea añadir } ShowMessage('Seleccione antes el libro al que desea añadir el capitulo.') Else Begin { Obtenemos el índice absoluto del nodo seleccionado } N := Indices.Selected.AbsoluteIndex; { y vamos subiendo por el árbol hasta encontrar el nodo correspondiente al libro } While Indices.Items[N].Level <> 0 Do Dec(N); { Añadimos un nuevo nodo estableciendo el titulo y sus imágenes } NuevoNodo := Indices.Items.AddChild(Indices.Items[N], 'Nuevo capítulo'); NuevoNodo.ImageIndex := 2; NuevoNodo.SelectedIndex := 2; {Expandimos el nodo al que se acaba de añadir el capítulo) Indices.Items[N].Expand(False); { Seleccionamos el nuevo nodo como el actual } Indices.Selected := NuevoNodo; NuevoNodo.EditText; { y editamos el título } End; end; Inserción de un nuevo apartado La inserción de un nuevo apartado en un capítulo, se llevará a cabo de forma similar a la descrita hace un momento, aunque en este caso debemos tener en cuenta que debe haber seleccionado un capítulo al que añadir el apartado, ya que si el elemento que hay elegido en ese momento es el título de un libro, no será posible saber a qué capítulo añadirlo. Tras esta comprobación se obtiene una referencia al capítulo, recorriendo los elementos en sentido inverso tal y como hemos hecho antes, y llamando seguidamente a AddChild() para añadir el nuevo elemento. { Al pulsar el botón NuevoApartado } procedure TForm1.NuevoApartadoClick(Sender: TObject); Var N: Integer; NuevoNodo: TTreeNode; begin

Page 262: DELPHI 5

{ Si no hay un nodo seleccionado o si el actual es un libro, no sabemos a qué capítulo añadir } If (Indices.Selected=Nil) Or (Indices.Selected.Level=0) Then ShowMessage('Seleccione el capítulo al que desea añadir el apartado.') Else Begin { Obtenemos el índice absoluto del nodo actual } N := Indices.Selected.AbsoluteIndex; { y subimos por el árbol hasta encontrar el nodo correspondiente al capitulo } While Indices.Items[N].Level <> 1 Do Dec(N); { Añadimos un nuevo nodo y establecemos su título e imágenes } NuevoNodo := Indices.Items .AddChild(Indices.Items[N], 'Nuevo apartado'); NuevoNodo.ImageIndex := 4; NuevoNodo.SelectedIndex := 4; { Expandimos el nodo al que se ha añadido } Indices.Items[N].Expand(False); NuevoNodo.EditText; { y editamos el título } End; end; Eliminación de un elemento A diferencia de la inserción, para eliminar un determinado elemento de la lista no es necesario diferenciar entre si es un libro, un capítulo o un apartado. Tan sólo basta con comprobar que hay un elemento seleccionado, mediante la propiedad Selected, realizando a continuación una llamada al método Delete(). En caso de que el elemento borrado tuviese hijos, éstos también serán eliminados. { Al pulsar el botón Eliminar } procedure TForm1.EliminarClick(Sender: TObject); begin With Indices Do { Si hay un nodo seleccionado } If Selected <> Nil Then Items.Delete(Selected); { lo eliminamos } end; Guardar y recuperar la información Como se dijo anteriormente, el método SaveToFile() tan sólo guarda en disco el texto de los elementos, dentándolo adecuadamente para que al recuperarlo, mediante LoadFromFile(), se pueda deducir fácilmente el nivel de cada elemento y, por tanto, la relación existente entre cada uno de ellos. Sin embargo, en ese archivo no se almacena el contenido de otras propiedades, como Imageindex o Selectedindex, por lo que al recuperar la información del archivo en disco es necesario recorrer

Page 263: DELPHI 5

todos los elementos, asignando el icono que corresponda según el nivel, como puede ver en el código siguiente. { Al pulsar el botón Guardar } procedure TForm1.GuardarClick(Sender: TObject); begin { Grabamos el contenido en un archivo } Indices.SaveToFile('INDICES.DAT'); end; { Al pulsar el botón Recuperar } procedure TForm1.RecuperarClick(Sender: TObject); Var N: Integer; begin With Indices Do Begin { Cargamos el contenido del archivo } LoadFromFile('ÍNDICES.DAT'); { y restablecemos las imágenes según el nivel } For N := 0 To Items.Count - 1 Do Begin Items[N].ImageIndex := Items[N].Level * 2; Items[N].SelectedIndex := Items[N].Level * 2; End; End; end; Cierre y apertura de ramas En el componente TImageList que insertamos en el formulario existían cinco imágenes, de las cuales hasta ahora sólo hemos usado tres. Las imágenes que muestran los libros abiertos se usarán en el momento en que una rama correspondiente a un libro o capítulo sea abierta, volviéndose a la imagen original cuando se cierre. Cada vez que se va a abrir una rama se genera un evento OnExpanding, en el que se reciben como parámetros una referencia al elemento que se va a abrir y una variable, de tipo Boolean, mediante la cual podemos evitar la operación, simplemente asignándole el valor False. De forma análoga, cuando se va a cerrarla rama se genera un evento OnCollapsing, que recibe los mismos parámetros mencionados. Será, por tanto, en los métodos asociados a los eventos OnExpanding y OnCollapsing donde modifiquemos el valor de las propiedades Imageindex y SelectedIndex del elemento que se abre o se cierra, según se muestra a continuación. { Al cerrar un nodo } procedure TForm1.IndicesCollapsing(Sender: TObject;

Page 264: DELPHI 5

Node: TTreeNode; Var AllowCollapse: Boolean); begin { Restauramos las imágenes originales } Node.ImageIndex := Node.Level * 2; Node.SelectedIndex := Node.Level * 2; end; { Al expandir un nodo } procedure TForm1.IndicesExpanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); begin { Si no estamos en el último nivel } If Node.Level <> 2 Then Begin { Usamos las imágenes de los libros abiertos } Node.Imageindex := Node.Level * 2 + 1; Node.SelectedIndex := Node.Level * 2 + 1; End; end; Al ejecutar el programa la lista aparecerá inicialmente vacía, y la única opción factible será la de añadir un nuevo libro. A continuación podrá insertar capítulos y apartados, construyendo jerarquías como la que se muestra en la figura 13.13, correspondiente al programa que acabamos de construir.

Page 265: DELPHI 5

Figura 13.13. El programa en funcionamiento con el índice de un libro

13.10. El control TListView Este control facilita la visualización de datos de una forma distinta. La mayor diferencia que existe entre TListView y TTreeView es que en el primero los elementos de la lista no tienen ninguna relación jerárquica entre sí, es decir, unos elementos no son hijos o padres de otros, como sí ocurre en el control que hemos conocido en el punto anterior. Por tanto, este control es apropiado para presentar listas de datos o elementos independientes, cada uno de los cuales puede tener asociado un icono y unos datos adicionales, que pueden estar o no visibles dependiendo del modo de visualización que esté seleccionado en un momento determinado. Al igual que ocurre con todas las listas, los elementos de un TListView están accesibles mediante una propiedad llamada Items, que es en realidad una matriz, en este caso de objetos TListItem. Cada uno de estos objetos mantiene los datos relativos a cada elemento, como es su título, datos asociados, icono que se adjunta al mostrarlo en el control, etc.

13.10.1. Propiedades de TListView Además de la citada propiedad Items, el control TlistView cuenta con unas cuantas propiedades más, mediante las cuales podemos asociar a este control uno o varios componentes TImageList, con las imágenes o iconos a usar con los elementos, establecer el modo de visualización o definir las columnas que existirán en el modo de detalle. Las propiedades LargeImages, SmallImages y StateImages son las tres del mismo tipo, pudiendo almacenar cada una de ellas una referencia a un componente TImageList. La primera de ellas hace referencia a un

Page 266: DELPHI 5

almacén con iconos grandes, la segunda a un conjunto de iconos pequeños y la tercera a unas imágenes adicionales de estado. Este control es capaz de presentar los elementos que contiene de cuatro formas diferentes, pudiendo seleccionar una u otra mediante la propiedad ViewStyle. Esta propiedad puede tomar cualquiera de los valores enumerados en la tabla 13.5, y en caso de modificarse el valor en ejecución, con una simple asignación, la visualización de los datos se actualizará de forma inmediata.

Constante Modo de visualización

vsIcon Con iconos grandes. vsSmallIcon Con iconos pequeños.

vsList En forma de lista. vsReport Con detalles o en modo informe.

Tabla 13.5. Valores posibles para la propiedad ViewStyle

En el primer modo de visualización los elementos serán dispuestos en la ventana junto con un icono, que será tomado del TImageList al que hace referencia la propiedad LargeImages. El segundo modo dispone los elementos de igual forma, pero usando los iconos referenciados por SmallImages. En el tercer modo los elementos aparecen en forma de lista, de arriba a abajo y de izquierda a derecha, junto con el icono pequeño a su izquierda. El modo vsReport, por último, además del título muestra unos datos adicionales, que se almacenan en una propiedad de cada objeto TListItem. Al igual que ocurre con el TTreeView, los títulos asociados a los elementos pueden ser editados por el usuario durante la ejecución, a no ser que demos el valor True a la propiedad ReadOnly.

13.10.2. Propiedades de TListItem Cada uno de los elementos de un TListView cuenta con un título, que se almacena en la propiedad Caption; una referencia a un icono o imagen normal, en la propiedad ImageIndex, y otra a una imagen de estado, en la propiedad Stateindex. Además, cada objeto TListItem cuenta con una propiedad llamada Subitems, que es un objeto TStrings en el que podemos almacenar una serie de cadenas con datos adicionales asociados a cada uno de los elementos. Al igual que los elementos de un TTreeView, los de un TListView también pueden tener asociados otros datos, cuya dirección se almacena en forma de Pointer en la propiedad Data. Esto nos permite asociar registros de información o incluso objetos a cada uno de los elementos de la lista.

Page 267: DELPHI 5

13.10.3. Métodos de TListView Uno de los métodos más útiles de TListView es el método Arrange(), que nos permite reorganizar los elementos mostrados en el control siempre que el modo de visualización actual sea vsIcon o vsSmallIcon. Esta reorganización es necesaria, sobre todo, cuando se han añadido o eliminado elementos durante la ejecución. El método Arrange() toma como parámetro una de las constantes que se muestran en la tabla 13.6, indicando el orden en que se han de disponer los elementos.

Constante Disposición de los elementos

ArDefault Según el estilo de alineación del control. ArAlignBottom A lo largo del límite inferior del control.

ArAlignLeft A lo largo del límite izquierdo del control. ArAlignRight A lo largo del límite derecho del control. ArAlignTop A lo largo del límite superior del control.

Tabla 13.6. Posibles parámetros para el método Arrange

El control TListView también cuenta con un método GetItemAt(), que nos permite obtener una referencia al elemento que se encuentra en una determinada posición, cuyas coordenadas pasamos como parámetro al llamar al método. Si el valor devuelto es Nill significará que en esa posición no hay elemento alguno.

13.10.4. Métodos de TListItem Aparte de los métodos Add() y Delete(), mediante los cuales podemos añadir o eliminar elementos de la lista, cada TListItem cuenta con un método EditCaption(), que activa la edición del título del elemento, y un método CancelEdit(), que cancela dicha operación. En cualquier momento podemos hacer visible un determinado elemento de la lista, realizando una llamada al método MakeVisible() del TListItem correspondiente. Este método toma como parámetro un valor de tipo Boolean, mediante el cual indicaremos si es necesario mostrar totalmente el elemento o si por el contrario es factible una visualización parcial.

13.10.5. Definición de columnas Cuando el modo de visualización seleccionado en el control, mediante la propiedad ViewStyle, es vsReport, los elementos son mostrados como una lista dividida en columnas, que habremos de definir mediante la propiedad Columns. Esta propiedad es una matriz de objetos TListColum, cada uno de los cuales cuenta con las propiedades Caption, Alignment y Width, mediante las que podemos establecer el título, alineación y anchura de cada columna.

Page 268: DELPHI 5

En la etapa de diseño podemos definir las columnas de un control TListView haciendo doble clic sobre la propiedad Columns, lo que nos permitirá acceder al editor específico que se muestra en la figura 13.14, en el cual disponemos de los botones necesarios para añadir y eliminar columnas. En el Inspector de objetos podremos establecer además el título, alineación y ancho de cada una de ellas.

Figura 13.14. El editor específico para la propiedad Columns Durante la ejecución del programa podemos tanto modificar las columnas definidas en la fase de diseño, accediendo a ellas mediante la propiedad Columns, como añadir nuevas columnas y eliminar otras existentes, ya que disponemos de los habituales métodos Add() y Delete(). Generalmente será necesario definir tantas columnas como datos se hayan añadido a la propiedad SubItems de cada TListItem más una, en la cual se mostrará el título del elemento. Las columnas son redimensionables en tiempo de ejecución, por lo que el usuario puede adecuarlas a sus preferencias.

13.10.6. En la práctica Veamos en la práctica cómo podemos utilizar un control TListView para permitir la visualización de unos datos de diferentes formas, en este caso para mostrar los registros de la tabla de alumnos, la cual usaremos por última vez en este ejemplo. Inserte en un formulario un control TListView, un componente TImageList, un TTable y un TPopupMenu. Asigne a la propiedad Align del TListView el valor alClient, de tal forma que él aspecto del formulario quedará inicialmente como se muestra en la figura 13.15, con el TListView ocupando todo el fondo del formulario. Los otros tres componentes no son visuales, por lo que no importa su disposición. Recupere en el componente TImageList una de las imágenes que representaban un libro cerrado de las usadas anteriormente, y asigne a las propiedades Largelmages y Smalllmages del TListView el nombre del mismo TImageList. Por tanto, vamos a utilizar una misma imagen para la visualización en los dos modos de iconos, grandes y pequeños. En la

Page 269: DELPHI 5

práctica se crearían dos componentes TImageList, uno con las imágenes pequeñas y otro con las grandes.

Figura 13.15. Aspecto del formulario con el control TlistView A continuación haga doble clic sobre la propiedad Columns del TListView, para hacer aparecer el editor de columnas, y defina las cuatro columnas que se mostraron anteriormente en la figura 13.14. Seleccione en la opción de anchura el elemento ítem Width, consiguiendo así que el ancho de cada columna se ajuste inicialmente al necesario para mostrar el dato más largo. Tomemos ahora el componente TTable, asignando a las propiedades DatabaseName y TableName los valores necesarios para poder acceder a la tabla de libros y dando el valor True a la propiedad Active. A continuación abra el editor de campos, haciendo doble clic sobre el componente, y añada todos los campos disponibles. Por último, para finalizar el diseño de la interfaz, haga doble clic sobre el componente TPopupMenu, con el fin de abrir el diseñador de menús, y componga una lista de opciones como la que se muestra en la figura 13.16. Estas opciones nos permitirán, durante la ejecución, alternar entre los diferentes modos de visualización posibles. Asocie este menú al control TListView, desplegando la lista adjunta a la propiedad PopupMenu de este control y seleccionando el único valor disponible. Ahora tendremos que escribir el código necesario para conseguir que nuestro programa funcione, comenzando por recuperar los datos de la tabla de alumnos y definir los elementos correspondientes en el control TListView. El título de cada uno de los elementos será el

Page 270: DELPHI 5

nombre del alumno, mientras que los demás datos, sexo, provincia y edad, serán almacenados en la propiedad SubItems de cada TListItem. De esta forma tendremos tantos elementos como alumnos existan, teniendo cada uno de ellos asociada información adicional.

Figura 13.16. Opciones del menú emergente El proceso de lectura de los datos y creación de los elementos lo vamos a llevar a cabo al crear el formulario, en el método asociado al evento OnCreate. Como puede ver en el código siguiente, lo único que hacemos es leer la tabla hasta el final, creando un nuevo elemento por cada registro. { Al crear el formulario } procedure TForm1.FormCreate(Sender: TObject); begin With Table1 Do // Accedemos a la tabla While Not EOF Do // mientras no lleguemos al final // Añadimos un nuevo elemento a la lista With ListView1.Items.Add Do Begin { Asignando el titulo y los datos adicionales en SubItems } Caption := Table1Nombre.value; SubItems.Add(Table1Sexo.Value); SubItems.Add(Table1Provincia.Value); SubItems.Add(Table1Edad.AsString); Next; // Pasamos al registro siguiente End; end; A continuación tendremos que escribir el código correspondiente al evento OnClick de cada una de las opciones del menú emergente, que serán las que nos permitan cambiar de un modo de visualización a otro. Desde el editor de menús haga doble clic sobre cada opción, y escriba las sentencias que se muestran a continuación, que como puede ver se limitan a la asignación del valor adecuado a la propiedad ViewStyle. { Según la opción que se seleccione en el menú emergente, activamos un modo de visualización u otro }

Page 271: DELPHI 5

procedure TForm1.Iconograndes1ClicktSender: TObject); begin ListView1.ViewStyle := vsIcon; end; procedure TForm1.Iconospequeos1ClicktSender: TObject); begin ListView1.ViewStyle := vsSmallIcon; end; procedure TForm1.Lista1Click(Sender: TObject); begin ListView1.ViewStyle := vsList; end; procedure TForm1.Detalles1Click(Sender: TObject); begin ListView1.ViewStyle := vsReport; end; Ya puede ejecutar el programa, en el que inicialmente se mostrarán sólo los nombres de los alumnos con el icono adjunto. Pulse el botón derecho del ratón y seleccione otro modo de visualización, para poder comparar las diferencias existentes entre unos y otros. En la figura 13.17 puede ver el modo de visualización vsReport en el que, además del icono y el nombre, se muestran también los datos adicionales almacenados en la propiedad SubItems de cada TListItem, de acorde con las columnas definidas en la propiedad Columns.

Figura 13.17. Aspecto del programa con la lista de elementos

13.11. Texto con formato El control TRichEdit es seguramente uno de los más útiles, ya que permite que el usuario introduzca en nuestros programas texto con

Page 272: DELPHI 5

formato. Un control TRichEdit es como un control TMemo y dispone de muchas de las propiedades que ya conocemos de aquel control contando, además, con una serie de propiedades mediante las cuales es posible definir los atributos del texto y de los párrafos. El texto con formato manipulado en un TRichEdit puede ser posteriormente guardado y recuperado en archivos con formato RTF, que también pueden ser leídos por otros editores, como Word para Windows.

13.11.1. Atributos por defecto y de selección A medida que se introduce texto en un TRichEdit, éste va tomando unos atributos por defecto cuyas características se establecen mediante la propiedad DefAttributes. Esta propiedad hace referencia a un objeto TTextAttributes, que cuenta con una serie de propiedades mediante las cuales podemos elegir la familia de letra. Name; su tamaño, Size o Height; su color, Color; el estilo, Style, y si está o no protegido, Protected. Las primeras cinco propiedades ya las conocemos, porque son iguales que las disponibles en los objetos TFont. La propiedad Protected es de tipo Boolean, indicando si el texto está o no protegido. Un texto protegido es de sólo lectura, por lo que no puede modificarse ni borrarse. Modificando el valor de la propiedad DefAttributes estaremos modificando los atributos del texto que se va a escribir a continuación, pero no los del texto que ya hay escrito. Estos atributos están accesibles mediante la propiedad SelAttributes que, al igual que la anterior, es un objeto TTextAttributes, por lo que cuenta con las mismas propiedades comentadas antes. Gracias a la propiedad SelAttributes podemos obtener los atributos de un texto que haya seleccionado en el control TRichEdit, así como modificar alguno de sus apartados.

13.11.2. Atributos de párrafo Además de los atributos propios del texto, que acaban de comentarse, un TRichEdit también es capaz de mantener unos atributos adicionales asociados a cada párrafo de texto. Estos atributos permiten, por ejemplo, establecer una sangría o una alineación diferente para cada párrafo. La propiedad Paragraph de un TRichEdit es un objeto de tipo TParaAttributes, objeto que cuenta con las propiedades siguientes: • Alignment: Nos permite establecer la alineación del párrafo, cuyas líneas pueden estar ajustadas a la izquierda, derecha o centradas. • FirstIndent: Identación en puntos de la primera línea de un párrafo.

Page 273: DELPHI 5

• LeftIndent: Identación general del párrafo, expresada también en puntos desde el margen izquierdo del control. • Numbering: Esta propiedad puede tomar los valores nsNone o nsBullet. En este segundo caso el párrafo irá precedido de un bolo o marca de inicio de párrafo. • Tab: Matriz de enteros mediante la cual es posible establecer la posición de cada uno de los puntos de parada de tabulación. Existirán tantos puntos como indique la propiedad TabCount.

13.11.3. Métodos de TRichEdit Las líneas de texto de un TRichEdit, al igual que las de un TMemo, están accesibles mediante la propiedad Unes, que como sabemos es un objeto de tipo TStrings. Este objeto cuenta con los métodos SaveToFile() y LoadFromFile(), que nos permiten guardar y recuperar el texto, incluyendo su formato. El control TRichEdit también dispone de un método Print(), que nos permite imprimir el texto contenido en el control conservando todo su formato. Al llamar a este método lo único que habremos de facilitar será un título para el documento a imprimir.

13.11.4. En la práctica Para ver en funcionamiento el control TRichEdit vamos a servimos de un pequeño programa, que nos permitirá introducir texto estableciendo atributos de negrita y subrayada, además de poder disponer bolos al inicio de cada párrafo. Este programa es simplemente una demostración breve de las posibilidades de este control, que usted puede completar permitiendo seleccionar el tipo y tamaño de letra, ajuste de cada uno de los párrafos, etc. Comencemos insertando en un formulario un control TRichEdit y tres controles TCheckBox, según la disposición que se muestra en la figura 13.18. Observe que en el título de cada TCheckBox se ha dispuesto una tecla de acceso rápido, de tal forma que podamos activar o desactivar cada opción sin necesidad de abandonar el control TRichEdit, con una simple combinación de la tecla <Alt> y la letra que aparece subrayada. A continuación tendremos que escribir el código correspondiente a la pulsación de los tres TCheckBox, que básicamente lo que hará será comprobar si la opción asociada está o no activa, mediante la propiedad Checked, activando o desactivando en consecuencia el atributo que proceda. Seguidamente puede ver el código que tiene que escribir para cada uno de los métodos del evento OnClick de cada TCheckBox.

Page 274: DELPHI 5

Figura 13.18. Aspecto del formulario con el control TRichEdit { Al pulsar sobre el primer CheckBox } procedure TForm1.CheckBox1Click(Sender: TObject); begin With RichEdit1.SelAttributes Do If CheckBox1.Checked Then // Si está marcado // Activamos la negrita Style := Style + [fsBold] Else //en caso contrario la desactivamos Style := Style - [fsBold]; // Devolvemos el foco al TRichEdit RichEdit1.SetFocus; end; { Al pulsar sobre el segundo CheckBox } procedure TForm1.CheckBox2Click(Sender: TObject); begin With RichEdit1.SelAttributes Do If CheckBox2.Checked Then // Si está marcado // activamos la itálica Style := Style + [fsitalic] Else // si no la desactivamos Style := Style - [fsitalic]; RichEdit1.SetFocus; End; { Al pulsar el tercer CheckBox } procedure TForm1.CheckBox3Click(Sender: TObject); begin With RichEdit1.Paragraph Do If CheckBox3.Checked Then // Si está marcado // activamos los bolos Numbering := nsBullet Else //en caso contrario los desactivamos Numbering := nsNone;

Page 275: DELPHI 5

RichEdit1.SetFocus; end; Al ejecutar el programa podrá utilizar un atributo normal, con letra negrita, con letra cursiva o con una combinación de ambas. Observe que la activación o desactivación de estos atributos sólo afecta al texto que se escriba a continuación, o bien al que haya seleccionado en ese momento.

Figura 13.19. Aspecto del programa en funcionamiento

13.12. Barras de botones Hasta ahora, todas las opciones de los ejemplos propuestos en éste y en capítulos anteriores se han dispuesto, principalmente, en forma de menús o bien usando controles TButton independientes. Cada vez es más habitual, sin embargo, el uso de barras de botones que permiten el acceso a las mismas opciones son un simple movimiento de ratón. Un ejemplo claro lo puede encontrar en las propias barras de botones de Delphi. Para crear una barra de botones hay que usar el componente TToolBar, que encontraremos en la página Win32 a la derecha del control TStatusBar. Al insertar una barra de botones en el formulario, podrá ver que automáticamente se ajusta a la parte superior, ocupándola de izquierda a derecha. Lo que en realidad se obtiene es un contenedor con un borde en la parte superior, sin ningún botón en su interior.

13.12.1. Inserción de botones Una vez tenemos el control TToolBar en el formulario, la siguiente acción que desearemos llevar a cabo será la inserción de los botones. Para ello bastará con desplegar el menú emergente, pulsando el botón derecho del ratón sobre la barra de botones, y seleccionar la primera opción: New Button. De forma inmediata aparecerá un botón cuadrado en la parte izquierda de la barra de botones. Obviamente podemos insertar

Page 276: DELPHI 5

tantos botones como necesitemos. Observe también que la segunda opción del mismo menú, New Separator, permite insertar un espacio separador entre dos botones consecutivos. Cada uno de los botones contenidos en el control TtoolBar es un componente TToolButton, que guarda muchas similitudes con el TButton que ya conocemos. Por defecto, los TToolButton son botones simples que pueden tener un título y un icono y que al ser pulsados generan un evento OnClick. Para acceder durante la ejecución a los botones de un control TToolBar se usa la propiedad Buttons, que es una matriz de objetos TToolButton. Es posible saber cuántos botones existen en la barra consultando el valor de la propiedad ButtonCount.

13.12.2. Propiedades de TToolBar Las propiedades del componente TToolBar establecen el funcionamiento general de la barra de botones, que afecta a todos los botones contenidos en ella. El comportamiento concreto de cada TToolButton se fija con las propiedades particulares de ese objeto, que veremos en un punto posterior. Todos los botones de una barra de botones, a excepción de los separadores y divisores, tienen las mismas dimensiones. Éstas se establecen con las propiedades ButtonHeight y ButtonWidth. Dichos botones pueden estar demarcados con unos bordes que se fijarán de forma independiente para cada margen con la propiedad EdgeBorders. Inicialmente tan sólo se muestra el bode superior. Lo normal es que cada uno de los botones de la barra muestre una pequeña imagen en su interior, aunque de forma adicional también pueden contar con un título. Las propiedades Images, DisabledImages y HotImages permiten asociar el control TToolBar con tres componentes TImageList: una con las imágenes por defecto, otro con las imágenes a usar cuando el botón esté desactivado y otro conteniendo aquellas que se usarán cuando el cursor del ratón pase sobre el botón. Por defecto los títulos de los botones no se muestran, dado que la propiedad ShowCaptions del TToolBar es inicialmente False. Bastará con dar el valor True a dicha propiedad para hacer que los títulos aparezcan. Si queremos que la barra de botones tenga un aspecto plano, tan de moda en las últimas aplicaciones aparecidas, tan sólo tenemos que dar el valor True a la propiedad Flat. De esta forma los botones no resaltarán en la barra hasta en tanto no se sitúe el cursor del ratón sobre ellos, momento en el que, además, mostrarán la imagen del TImageUst apuntado por la propiedad HotImages.

Page 277: DELPHI 5

13.12.3. Propiedades de TToolButton Un botón de este tipo puede actuar de cinco formas diferentes, según el valor que se asigne a la propiedad Style. Las posibilidades son: tbsButton, tbsCheck, tbsDivider, tbsDropDown y tbsSeparator. El primer tipo, que es el tomado por defecto, corresponde a un botón normal que, al ser pulsado, genera un evento OnClick y vuelve a su estado original. El estilo tbsCheck hace que el botón pueda estar en dos estados: pulsado o no pulsado. Al hacer clic sobre un botón de este tipo se dará el valor True a la propiedad Down, lo que provoca que el botón quede visualmente hundido. Una nueva pulsación devuelve el valor False a la propiedad Down y el aspecto original al botón. Los estilos tbsDivider y tbsSeparator son similares, ya que ambos insertan un separador entre dos botones consecutivos, si bien el primero hace que ese espacio aparezca como una barra vertical, mientras que el segundo no incluye elemento visual alguno. Por último tenemos el estilo tbsDropDown, que hace que el botón tenga asociado un menú desplegable, al estilo de los botones Open o Run de Delphi. Si usamos un botón con el estilo tbsDropDown, tendremos que asociar un menú con las opciones que se desplegarán. Este menú será un TPopupMenu, que se asociará con el TToolButton mediante la propiedad DropdownMenu de éste último. Este tipo de botones tienen una opción por defecto, que es la que se ejecuta al pulsar el botón sin desplegar el menú. Para establecer dicha opción se usa la propiedad MenuItem, que cuenta con una lista en la que es posible elegir la opción que corresponda. Para indicar qué imagen es la que hay que mostrar en el interior del botón, tomándola de los componentes TImageList asociados al TToolBar, se usa la propiedad ImageIndex, asignándole el índice de la imagen que corresponda.

13.12.4. Uso de una barra de botones Prácticamente todo el trabajo de creación de una barra de botones se efectúa en la fase de diseño, insertando los botones, asociando imágenes, estableciendo el estilo de cada uno de ellos, etc. Una vez que la barra de botones está creada, basta con hacer doble clic sobre cada botón y escribir el código que corresponda, igual que haríamos con una opción de menú o un control TButton cualquiera. Por lo demás, el trabajo con una barra de botones no supone ninguna complejidad adicional. Durante la ejecución es posible eliminar botones de los existentes, simplemente dando el valor False a la propiedad Visible del componente TToolButton correspondiente. También podemos alterar otros elementos, como la imagen asociada, el título, etc., aunque no es lo habitual.

Page 278: DELPHI 5

14. Reutilización del trabajo

14.1. Introducción En ocasiones, al diseñar las interfaces de una aplicación y escribir el código asociado, se repiten algunas operaciones comunes en el desarrollo. Los cuadros de diálogo suelen incluir algunos conjuntos de botones comunes, ciertas acciones están accesibles desde diferentes puntos del programa, etc. Delphi 5 pone a nuestra disposición diversos métodos para reutilizar el trabajo, evitando así la repetición de procesos con la consecuente pérdida de tiempo en ellos.

14.2. Listas de acciones Hasta ahora conocemos al menos dos elementos mediante los cuales el usuario puede poner en marcha una determinada acción: las opciones de un menú y los botones. No es extraño que una misma opción esté accesible mediante un botón existente en una barra de botones, una opción del menú principal y, además, otra opción en un menú emergente. Es algo que podemos ver en el propio Delphi. Suponga que está creando un programa que, entre otras, dispone de operaciones para copiar, cortar y pegar información, operaciones que desea poner al alcance del usuario mediante el típico submenú Edición que existe en casi todas las aplicaciones, una serie de botones existentes en un TToolBar y un menú emergente. Suponiendo que cada opción tenga asociado un título, un corto mensaje de ayuda, una pequeña imagen asociada y una tecla de acceso rápido, se verá forzado a establecer todas estas opciones para cada uno de los botones del TToolBar, cada uno de los elementos del TMainMenu y los elementos del TPopupMenu. Un trabajo no complejo pero realmente repetitivo. Además, los eventos OnClick generados por los botones y las opciones del menú deberán unificarse, ya que la opción a ejecutar es la misma en todos los casos.

14.2.1. El componente TActionList El último componente de la página Standard, cuyo icono parece la unión de un botón, un cursor de ratón y un menú, es el que nos permitirá unificar y simplificar la programación de las acciones que se han expuesto en el punto anterior, de tal forma que nos ahorremos gran parte del trabajo. Este componente cuenta con un editor específico, que haremos aparecer con un doble clic sobre el TActionList, mediante el cual es posible definir las acciones disponibles en la aplicación. Cada una de estas acciones es un objeto TAction. Como puede apreciarse en la figura 14.1, el editor dispone de los típicos botones que facilitan la creación, eliminación y disposición de los elementos. En la parte izquierda puede ver las propiedades del TAction seleccionado en ese momento.

Page 279: DELPHI 5

Las distintas acciones pueden agruparse en categorías, aunque en la figura 14.20 no existe ninguna. Además de los botones y las teclas <Insert> y <Supr>, podemos usar las distintas opciones del menú emergente del componente TActionList para manipular las acciones existentes o insertar otras nuevas.

Figura 14.1. Editor del componente TActionList El componente TActionList dispone de una propiedad Images a la que podemos asociar un componente TImageList, del que se extraerán las imágenes que necesiten las acciones.

14.2.2. Propiedades del objeto TAction Como se ha apuntado hace un momento, cada una de las acciones se representa mediante un objeto TAction que, como puede ver en la figura 14.1, cuenta con sus propiedades particulares. Básicamente podemos establecer el título de la acción, con la propiedad Caption; el mensaje de ayuda asociado, con la propiedad Hint; la tecla de acceso rápido, propiedad ShortCut, y la imagen asociada, propiedad Imageindex. Mediante las propiedades Enabled y Visible podemos hacer que la acción esté o no disponible o visible. La propiedad Checked permite mostrar una marca junto al título de la acción. Tenga en cuenta que estas propiedades afectan a todos aquellos puntos en que aparezca la acción. Si desactivamos una acción, dando el valor False a la propiedad Enabled, estaremos desactivándola en el menú principal, menú emergente, barra de botones y cualquier otro punto en que la estemos usando.

Page 280: DELPHI 5

Cuando una acción ha de ser ejecutada, porque se haya pulsado el botón o seleccionado la opción a la que está asociada, se generará un evento OnExecute. En el método correspondiente a este evento introduciremos el código de la acción, de forma única, sin tener que compartir métodos entre eventos de botones, opciones de menú y otros controles.

14.2.3. La propiedad Action Disponen de esta propiedad componentes como TToolButton, TMenuItem o TButton. Mediante ella podemos asociar una cierta acción con el componente que nos interese, de tal forma que éste tome su título, mensaje de ayuda, icono asociado, etc., del objeto TAction que corresponda. Además, la actuación del usuario sobre el componente provocará el evento OnExecute citado hace un momento. Al desplegar la lista de la propiedad Action de cualquier control, verá aparecer las acciones disponibles en ese momento. Seleccionando una de ellas podrá apreciar de inmediato los cambios en propiedades como Caption, Hint o ShortCut.

14.2.4. Uso de una lista de acciones Veamos un ejemplo muy simple de uso de una lista de acciones, basándonos en el ejemplo citado antes de una aplicación que disponga de las opciones de copiar, cortar y pegar. Comenzaremos insertando un componente TImageList con las tres imágenes que van a representar las opciones, tal y como puede ver en la figura 14.2. A continuación insertaremos el componente TActionList y crearemos las tres acciones vistas en la figura 14.1, fijando el título, mensaje de ayuda, tecla de acceso rápido e índice del icono asociado.

Figura 14.2. Componente TImageList con las imágenes

Page 281: DELPHI 5

A continuación inserte en el formulario un componente TMainMenu y haga doble clic sobre él. Inserte una opción, llamándola Edición, que actuará como submenú. Las opciones de este menú serán, como puede apreciarse en la figura 14.3, las tres acciones disponibles. No hace falta qué establezca propiedad alguna de las opciones, basta con que despliegue la lista de la propiedad Action y elija la que corresponda, sin más. Repita la operación que acabamos de efectuar pero creando un menú emergente, insertando un control TPopupMenu, haciendo doble clic sobre él y eligiendo directamente el valor adecuado para la propiedad Action de cada opción.

Figura 14.3. Menú principal con las opciones de edición Por último, inserte un componente TToolBar y use su menú emergente para insertar tres botones. Elija individualmente cada uno de ellos y despliegue la lista asociada a la propiedad Action, para elegir la acción que corresponda. Dé el valor True a la propiedad ShowHint. Puesto que lo que nos interesa es ver cómo utilizar una lista de acciones, no la finalidad en sí de las propias acciones, haga doble clic sobre cada elemento del TActionList insertando cualquier código, por ejemplo una llamada al método ShowMessage() para indicar que se ha seleccionado la opción. procedure TForm1.CopiarExecute(Sender: TObject); begin ShowMessage('Copiar'); end; procedure TForm1.CortarExecute(Sender: TObject); begin ShowMessage('Cortar'); end; procedure TForm1.PegarExecute(Sender: TObject); begin ShowMessage('Pegar');

Page 282: DELPHI 5

end; Llegados a este punto, ya puede ejecutar el programa y ver el resultado. Observe que las opciones tanto del menú principal como del menú emergente muestran a su izquierda los mismos iconos que hay en la barra de botones. Los botones de la barra, por su parte, muestran como mensaje de ayuda el título de las opciones del menú. Todas estas opciones y botones comparten el mismo código, ya que están asociadas a las mismas acciones.

Figura 14.4. El programa en funcionamiento

14.3. Componentes compuestos Si un cierto formulario, que ya tenemos diseñado, es necesario varias veces, ya sea en la misma aplicación o en otras diferentes, la forma más fácil de reutilizarlo consiste en añadirlo al Depósito de objetos. Esto es tan fácil como pulsar el botón secundario del ratón sobre el formulario, seleccionando a continuación la opción Add to Repository. En la ventana que aparece, similar a la de la figura 14.5, podrá introducirse un título y una descripción del formulario, así como el nombre del autor e, incluso, un icono que le identifique.

Page 283: DELPHI 5

Figura 14.5. Ventana para añadir un formulario al Depósito de objetos Esta posibilidad, ya existente en versiones previas de Delphi, no soluciona, sin embargo, otro problema: reutilizar ciertos grupos o conjuntos de componentes junto con su código asociado. Con este fin, en Delphi 5 se han añadido los frames o marcos. Un marco es, en cierta forma, muy parecido a un formulario. Su finalidad es servir como contenedor de componentes, alojando también el código asociado a los eventos de éstos. Lo interesante, sin embargo, es que el marco puede, posteriormente, ser usado como un componente único, insertándose en un formulario siempre que se precise.

14.3.1. Creación de un nuevo marco Puede crear un nuevo marco eligiendo la opción New Frame del menú File, o bien seleccionando la opción New de ese mismo menú, para abrir el Depósito de objetos, escogiendo después el objeto Frame. En cualquier caso, verá aparecer un recuadro muy similar a un formulario. Existen, no obstante, algunas diferencias notables. Observe que el marco no dispone de propiedades como Caption o BorderIcons, propias de un formulario. En el interior del marco puede insertar otros componentes. En la figura 14.6, por ejemplo, puede ver cómo se han insertado dos etiquetas de texto y una lista combinada.

Page 284: DELPHI 5

Figura 14.6. Un marco con algunos componentes en su interior

Si tras insertar los componentes deseados en el marco, asigna el valor True a su propiedad AutoSize, podrá ver cómo las dimensiones se ajustan automáticamente, de tal manera que el tamaño del marco es justo el preciso para contener los componentes alojados en él. También puede darse un valor a la propiedad Align, de tal forma que, posteriormente, el marco se ajuste directamente a un margen de su contenedor.

14.3.2. Reutilizar el marco en el mismo proyecto Un marco no es útil de por sí, de igual manera que no lo es un botón de forma aislada. Los marcos están diseñados para incluirse en un contenedor, ya sea éste un formulario, un TGroupBox o incluso otro marco. En cualquier caso, para insertar un marco en su contenedor hay que seleccionar el primer objeto, en el margen izquierdo, de la página Standard de la Paleta de componentes. Al pulsar sobre el contenedor con el botón principal del ratón, verá aparecer una ventana (véase la figura 14.7) en la que podrá elegir entre los marcos disponibles.

Figura 14.7. Ventana para seleccionar un marco del proyecto Al seleccionar uno de los marcos, éste se insertará en su contenedor como si de cualquier otro componente se tratase. En caso de que se diese algún valor a la propiedad Align, el marco se ajustará automáticamente al margen que proceda.

Page 285: DELPHI 5

Es posible incluir el mismo marco múltiples veces en un mismo formulario o contenedor, de igual manera que puede insertar varios botones.

14.3.3. Reutilizar el marco en otros proyectos En caso de que un mismo marco pueda ser útil en diferentes proyectos, no sólo en aquel donde se ha diseñado, existe un método más simple para facilitar su reutilización. Este consiste en añadir el marco a la Paleta de componentes, como si de un componente más se tratase. ¿Cómo hacer esto? Tan fácil como pulsar el botón secundario del ratón sobre el marco y, a continuación, elegir la primera opción disponible: Add To Palette. Tras añadir el marco, podrá ver que en la Paleta de componentes existe otra página. Esta, llamada Templales, contendrá todos los marcos que añadamos, como puede verse en la figura 14.8. Ahora basta con seleccionar el marco e incluirlo en el formulario, como si de cualquier otro control se tratase.

Figura 14.8. La Paleta de componentes tiene una página con los marcos Tenga en cuenta que al reutilizar un marco no sólo consigue incluir un componente formado de otros, sino también todo el código asociado que haya podido escribir. Es decir, no se trata tan soló de componer una plantilla de controles para después insertarlos con mayor facilidad, sino de reutilizar una funcionalidad completa.

14.3.4. Un ejemplo Utilizar los marcos es algo bastante sencillo. Para comprobarlo, inicie un nuevo proyecto y, a continuación, cree un nuevo marco insertando en él dos etiquetas de texto y una lista combinada, como se mostró anteriormente. Asigne a la propiedad Items de la lista combinada los nombres de los meses. Abra la página de eventos en el Inspector de objetos y, teniendo seleccionada la lista combinada, haga doble clic sobre el evento OnClick, introduciendo el código siguiente: procedure TfrmSeleccionMes.cbMesesClick(Sender: TObject); Const DiasMes: Array[0..11] of Smallint = (31,28,31,30,31,30,31,31,30,31,30,31); begin LbDias.Caption:=IntToStr(DiasMes[cbMeses.ItemIndex])+' días'; end;

Page 286: DELPHI 5

Como puede ver, partiendo de la selección del mes se obtiene el número de días que éste tendrá, sin tener en cuenta los años bisiestos, y se asigna a la etiqueta que hay en la parte inferior. Dé el valor alTop a la propiedad Align del marco y el valor True a su propiedad AutoSize. Por último, abra el menú contextual del marco y añádalo a la Paleta de componentes. Ahora abra el formulario que había inicialmente en el proyecto que, en este momento, estará vacío. Inserte desde la Paleta de componentes el marco recién creado, verá que de manera inmediata se sitúa en la parte superior del formulario. Disponga más abajo un control TPanel y, en su interior, inserte otra copia del marco. También añadiremos un botón, de tal forma que la ventana quede como puede verse en la figura 14.9. Al pulsar el botón se ejecutará el código siguiente: procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(SelectorMes.cbMeses.Text); end; El identificador SelectorMes es el nombre que hemos dado a uno de los marcos añadidos al formulario. Observe que dicho identificador se utiliza como un componente, de tal forma que mediante el operador . es posible acceder a su contenido, en este caso la lista combinada cbMeses.

Figura 14.9. El formulario con dos copias del mismo marco Al ejecutar el programa podrá seleccionar un mes de cualquiera de las dos listas combinadas disponibles, viendo aparecer debajo el número de días del mes elegido. Observe que el funcionamiento de los dos marcos es independiente, están creados a partir del mismo molde, pero son dos componentes separados.

Page 287: DELPHI 5

15. Creación de componentes

15.1 Introducción Desde hace ya algún tiempo el hombre con el afán de facilitarse las cosas ha construido usando componentes o pequeñas partes que se ensamblan para formar un todo. Esas partes son por lo general más maniobrables, más entendibles, en una palabra, más sencillas. La creación de software no ha escapado a la máxima “Mantenlo bien simple” (Keep it so simple) y ha abordado los problemas de complejidad de la misma manera. La posibilidad que brinda Delphi de crear nuevos componentes es una de las más interesantes. Con la creación de componentes los programadores pueden crear soluciones propias y compartirlas con los demás enriqueciendo las posibilidades del entorno. Un componente no es más que un objeto descendiente de la clase TComponent que es el ancestro común a todos ellos e implementa las características básicas que le permiten ser mostrado en la paleta de componentes e insertarse en los formularios en tiempo de diseño.

15.2. Niveles de acceso a los componentes Delphi define cuatro niveles de acceso a los campos, métodos y propiedades de un componente que en definitiva no es más con un objeto descendiente de la clase Tcomponent. Los nivelos son los siguientes: Private: Las propiedades y los métodos que se encierren bajo esta cláusula son únicamente accesibles desde la unit donde se encuentran declarados ocultando también la implementación a objetos que se encuentren fuera de esta. Protected: La cláusula protected hace que las declaraciones en su interior sean, al igual que en el caso de private inaccesibles desde fuera de la unit. La única diferencia entre ellas es que en este caso los descendientes tendrán acceso. Public: Todas las declaraciones en este caso serán accesibles desde dentro o fuera de la unit. En este caso se define lo que se conoce como interface del objeto, es decir la parte visible para el usuario. Published: Hace lo que public y además lo declarado en ella se muestra en tiempo de diseño en el inspector de objetos. Por tanto debe usarse published solo para las propiedades pues al usarla para métodos solo conseguiremos que estos sean públicos.

15.3 Creando un componente. Delphi nos da un pequeño asistente para ayudarnos a lograr la estructura mínima de una Unit para la creación de un nuevo componente.

Page 288: DELPHI 5

Seleccione la opción File del menú y luego New.

Figura 15.1

Dentro de la formulario New Items elija Component y aparecerá un nuevo formulario en el que se mostrarán los siguientes campos:

• Class Name: Aquí debemos especificar el nombre del nuevo componente.

• Ancestor type: Introduciremos aquí el ascendiente a partir del cuál derivaremos nuestro componente.

• Palette Page: Indicaremos aquí la página de la paleta en la cuál queremos que aparezca el nuevo componente.

Al aceptar el contenido del formulario se generará automáticamente la unit correspondiente al componente así como la declaración de la clase de nuestro componente en código similar al siguiente: unit RealEdit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TRealEdit = class(TEdit)

Page 289: DELPHI 5

private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TRealEdit]); end; end. Observe que delphi incluye automáticamente el procedimiento RegisterComponents que registrará el componente en el entorno permitiendo su visualización en la paleta de componentes y su edición con el Object Inspector. Una vez aquí podemos comenzar a añadir las propiedades y los métodos que formarán parte del nuevo componente o modificar comportamientos heredados de la superclase. En ocasiones nos preguntaremos cuál será un buen ancestro para nuestro nuevo componente con el objetivo de sacar mejor partido a funcionalidades ya existentes. He aquí varias normas:

• TComponent - El punto de partida para los componentes no visuales.

• TWinControl - El punto de partida si es necesario que el componente disponga de handles.

• TGraphicControl - Un buen punto de partida para componentes visuales que no sea necesario que dispongan de handles, es decir, que no reciban el foco. Esta clase dispone del método Paint y de Canvas.

• TCustomControl - El punto de partida más común. Esta clase dispone de window handle, eventos y propiedades comúnes y, principalmente, canvas con el método Paint.

De lo anterior se puede inferir que la condición de si nuestro componente es visual o no lo define la clase usada como base. Así si heredamos de Tcomponent estaremos creando un componente no visual y

Page 290: DELPHI 5

por otra parte si heredamos de TcustomControl por ejemplo estaremos creando un componente visual.

15.4 Creando un componente visual. La elección del nombre del nuevo componente que aparece en el código anterior a sido intencional pues en lo adelante ilustraremos un ejemplo de implementación de un componente para la validación de la entrada de un número real en un rango determinado. Para ello añadiremos dos propiedades una MinVal que representa el valor mínimo y MaxVal el máximo. Chequearemos que el usuario haya introducido un número real y que este se encuentra en el rango permitido mostrando un mensaje de error en caso contrario. La definición de la clase quedaría de esta manera: type TRealEdit = class(TEdit) protected FMinVal: real; FMaxVal: real; procedure SetMinVal( AMinVal: real ); procedure SetMaxVal( AMaxVal: real ); procedure DoExit; override; published property MinVal: real read FMinVal write SetMinVal; property MaxVal: real read FMaxVal write SetMaxVal; property OnExit; end; Al declarar las propiedades MinVal y MaxVal bajo la cláusula published aseguraremos que estas aparezcan en el inspector de objetos y de esa manera el programador podrá cambiar sus valores en el Object Inspector. Es importante especificar para ellas los procedimientos y funciones que bajo las cláusulas read y write servirán para acceder a los datos reales de las propiedades que se almacenan en FMinVal y FmaxVal en este caso definimos los métodos SetMinVal y SetMaxVal para cuando se establecen los valores y en el caso de la lectura lo hacemos directamente situando tan solo los nombres de los campos. ... Implementation procedure TRealEdit.SetMinVal( AMinVal: real ); begin FMinVal := AMinVal; end; procedure TRealEdit.SetMaxVal( AMaxVal: real ); begin

Page 291: DELPHI 5

FMaxVal := AMaxVal; end; procedure TRealEdit.DoExit; var Value: real; Code: integer; begin Val( Text, Value, Code ); if (code = 0) and ( Value>=FMinVal ) and (Value<=FMaxVal) then inherited DoExit else begin application.messagebox('El valor del campo es incorrecto', 'Error', MB_OK); SetFocus; end; end; procedure Register; begin RegisterComponents('Samples', [TRealEdit]); end; ... Lo interesante de la implementación es la reimplementación del procedimiento DoExit. Por definición el procedimiento DoExit heredado se invoca cuando se produce un evento del tipo OnExit. Este último se produce siempre que un componente visual pierde el foco es decir deja de estar activo. El nuevo DoExit de nuestro componente permite que una vez que el mismo pierde el foco, se compruebe que se ha editado realmente un número real y que este se encuentra en el rango entre el valor de MinVal y MaxVal. De cumplirse la condición anterior se ejecuta el DoExit heredado causando un comportamiento normal, de lo contrario se muestra un cuadro de mensaje indicando el error y se le regresa el foco al componente en cuestión.

15.5 Creando un componente no visual. Los componentes del tipo no visual realizan una función determinada pero no son visibles en tiempo de ejecución. Es posible sin embargo insertarlos en una forma en tiempo de diseño y editar incluso sus propiedades a través del Object Inspector. Como ejemplo en este caso implementaremos un componente cuya función será determinar el equivalente en grados celcius de un valor dado en Farenheit. La unit queda de esta manera: unit FaC;

Page 292: DELPHI 5

interface uses Classes; type TFaC = class(TComponent) private FFValue: real; function GetValueInCelcius: real; protected public property Celcius: real read GetValueInCelcius; published property Farenheit: real read FFValue write FFValue; end; procedure Register; implementation function TFaC.GetValueInCelcius: real; begin Result:=(5*(FFValue-32))/9; end; procedure register; begin registercomponents('Samples', [TFaC]); end; end. Observe que en el ejemplo anterior hemos heredado de TComponent por tratarse de un componente no visual.

15.6 Creando un bitmap para el componente.

Cada componente necesita un bitmap para representarlo en la paleta de componentes. Si no se especifica uno, Delphi utilizará uno por defecto.

El bitmap no se incluye en el código fuente del componente, sino que debe incluirse en un archivo aparte con la extensión .DCR (dynamic component resource). Este fichero puede crearse con el propio editor de imágenes que incorpora Delphi.

El nombre del archivo .DCR debe coincidir con el nombre con que se ha salvado la unidad que contiene el componente. El nombre de la imagen bitmap (que debe estar en mayúsculas) debe coincidir con el nombre del

Page 293: DELPHI 5

componente. Los dos ficheros (el de la unidad *.pas y el de el bitmap *.dcr) deben residir en el mismo directorio.

En nuestro componente visual, si hemos salvado la unidad con el nombre realedit.pas nuestro archivo de recursos deberá tener el nombre realedit.dcr. Dentro de este archivo se encontrará el bitmap, al que pondremos el nombre TRealEdit. El tamaño del bitmap debe ser de 24x24 pixels.

15.7 Instalando componentes creados. Instalar un componente en la paleta de componentes es muy sencillo. Basta con seguir los siguientes pasos:

• Elegir la opción Options y luego Install Components del menú de Delphi.

• Pulsar sobre el botón Add para seleccionar la unidad que contiene el código fuente del componente y luego hacer clic sobre OK.

• Delphi compilará la unidad y reconstruirá el archivo COMPLIB.DCL. Si se encuentran errores al compilar, se nos informará de ello. Basta con corregir el error, salvar la unidad y repetir el proceso anterior.

• Si la compilación tiene éxito, nuestro componente entrará a formar parte de la paleta de componentes.

Nota: Es una buena idea antes de comenzar hacer una copia de seguridad del archivo COMPLIB.DCL para evitar posibles problemas derivados de fallos al compilar.

15.8 Introducción a los editores de propiedades.

Hasta ahora nos hemos centrado en la creación de componentes sin preocuparnos de cómo se introducían los valores de las distintas propiedades definidas en ellos. Ha llegado el momento de centrarnos en este aspecto. Como todos sabemos para introducir y leer el valor de las propiedades en tiempo de diseño utilizamos el inspector de objetos. El inspector de objetos nos muestra en una primera columna el nombre de la propiedad y en una segunda el valor de dicha propiedad. Para introducir un nuevo valor lo tecleamos y listo. Pero esto es tan sólo apariencia. Los componentes interactúan con el inspector de objetos a través de los editores de propiedades. Desde el punto de vista del usuario, es el inspector de objetos el responsable de editar las propiedades, pero detrás de la escena hay una serie de objetos, los editores de propiedades, que se encargan de definir las capacidades de edición de las diversas propiedades de un componente.

En tiempo de diseño, cuando se selecciona un componente, el inspector de objetos crea instancias de los editores de propiedades necesarios para editar las propiedades definidas en el componente seleccionado.

Page 294: DELPHI 5

Cuando termina la edición, el mismo inspector de objetos destruye los editores de propiedades creados.

Delphi incluye una serie de editores de propiedades por defecto que son suficientes para la mayoría de las propiedades con las que trabajamos habitualmente. La potencia de Delphi permite que creemos nuestros propios editores y que incluso reemplacemos los que una determinada propiedad tiene por defecto.

15.9 Responsabilidades de un editor de propiedades.

Un editor de propiedades debe dar respuesta a dos aspectos principales:

• Definir cómo debe ser editada la propiedad. Aquí hay dos posibilidades: el valor de la propiedad puede ser modificado sobre el propio inspector de objetos (bien sea introduciendo un nuevo valor o seleccionándolo de una lista de valores), o se puede utilizar un cuadro de diálogo para dotar de mayor flexibilidad a la edición (Por ejemplo la propiedad color)

• Convertir el valor de la propiedad a un valor de tipo string. El inspector de objetos siempre trabaja con strings. Aunque la propiedad sea de tipo integer o float o sea un método, el inspector de objetos sólo trata con representaciones de tipo string de dichas propiedades. Es el editor de propiedades el que debe suministrar al inspector de objetos de dicha representación un string de la propiedad. Esta "traducción" puede ir desde lo más sencillo (utilizar la función IntToStr) hasta lo más complejo (programar una rutina para la traducción). Todo dependerá del tipo de propiedad con la que estemos tratando.

15.10 Pasos necesarios para escribir un editor de propiedades.

Los pasos que es necesario seguir para escribir un editor de propiedades son los siguientes:

• Crear una nueva unit en la que definiremos el editor de propiedades. Más adelante hablaremos detalladamente sobre este punto, ya que no es tan trivial como puede parecer en principio.

• Añadir la unidad dsgnintf a la cláusula uses del editor de propiedades. En esta unidad están definidos los editores de propiedades por defecto que utiliza Delphi, además de la importantísima clase TPropertyEditor, la cuál es la clase base de todos los editores de propiedades.

• Crear una nueva clase que descienda de TPropertyEditor o de alguno de sus descendientes. Por convención, el nombre de los editores de propiedades finaliza con la palabra Property. Por

Page 295: DELPHI 5

ejemplo TIntegerProperty, TStringProperty... A continuación se muestran los principales editores de propiedades por defecto que incorpora Delphi.

Editor de propiedades Tipo

TPropertyEditor Clase base para todos los editores de propiedades

TIntegerProperty Byte, word, integer, Longint

TCharProperty Char

TEnumProperty Tipos enumerados

TSetProperty Sets

TFloatProperty Single, Double, Extended, Comp, Currency

TStringProperty Strings

TClassProperty Cualquier objeto

TMethodProperty Cualquier método (eventos)

TComponentProperty Para propiedades que hacen referencia a componentes

• Implementar los métodos necesarios para dotar al editor de propiedades con las funcionalidades deseadas.

• Registrar el editor de propiedades en la Visual Component Library(VCL)

Más adelante profundizaremos en todos aspectos según vayamos desarrollando distintos editores, pero ahora ha llegado del momento de crear nuestro primer editor de propiedades.

15.11 Un editor de propiedades para números binarios.

Vamos a desarrollar nuestro primer editor de propiedades, que será editable sobre el propio inspector de objetos. Para ello pongámonos en la siguiente situación: imaginemos que hemos creado un componente con una propiedad de tipo integer que guarda un valor que debe introducirse y visualizarse en base binaria y no en base decimal. Si no creáramos un editor de propiedades y dejáramos que Delphi utilizase el editor por defecto (TIntegerProperty en este caso) nos encontraríamos que tanto la introducción como la visualización del valor de la propiedad se realizaría en base decimal, que no es lo que queremos. Este es el típico caso en el que desarrollando un simple editor de propiedades ahorramos trabajo al futuro usuario de nuestro componente, evitando que tenga que hacer la conversión entre decimal y binario.

Como ya hemos comentado, tenemos que decidir de que clase vamos a derivar nuestro editor. En nuestro caso es fácil, ya que la propiedad

Page 296: DELPHI 5

almacenará un valor de tipo integer, así que de seguro te darás cuenta que ha de ser TintegerProperty.

Muy bien, nuestra propiedad almacena un entero, pero no queremos que el inspector de objetos nos muestre su valor decimal directamente, queremos efectuar una conversión de dicho valor decimal a base binaria y que sólo entonces el inspector de objetos nos muestre el valor. Para lograr esto debemos implementar (override) la función GetValue. Esta función, definida en TPropertyEditor, es llamada por el inspector de objetos para obtener la representación del valor de la propiedad en forma de string. Dentro de esta función debemos convertir el valor de la propiedad de decimal a binario y, a continuación, transformar este valor en string, ya que como hemos mencionado el inspector de objetos siempre muestra strings. De este modo la función GetValue queda así:

unit BinaryPropEd; interface uses DsgnIntf; type TBinIntegerProperty = class(TIntegerProperty) public function GetValue : string; override; procedure SetValue(const Value : String); override; end; procedure Register; implementation Const Bits16 : Array [1..16] of Integer = (32768,16384,8192,4096,2048,1024,512,256,128,64,32,16,8,4,2,1); function TBinIntegerProperty.GetValue : string; Var Num, i : integer; begin Num:=GetOrdValue; Result := '0000000000000000'; for i := 1 to 16 Do if ( Num >= Bits16[i] ) Then begin Num := Num - Bits16[i]; Result[i] := '1'; end; if ( Num > 0 ) Then raise EPropertyError.Create('Error converting '+IntToStr(GetOrdValue) + ' to binary');

Page 297: DELPHI 5

Insert('B',Result,1); end; ... end.

De la implementación de esta función la parte más importante es la primera línea:

Num:=GetOrdValue

Necesitamos obtener el valor que tiene en ese momento la propiedad para trabajar sobre él. Para ello utilizamos el método GetOrdValue, definido de nuevo en TPropertyEditor, el cuál se encarga de devolver el valor de la propiedad en forma de ordinal (integer). De forma análoga, existen los métodos GetFloatValue, GetMethodValue, GetVarValue, etc. para utilizar con el tipo de propiedad correspondiente.

Una vez almacenado el valor de la propiedad en la variable Num, comienza la conversión del valor de decimal a binario, la cuál es fácil de entender. Es bueno notar que el número máximo de digitos binarios soportados es 16, margen más que suficiente para la mayoría de aplicaciones. Por último tenemos que devolver un valor de tipo string como resultado de la función. Para ello vamos almacenando en la variable Result el string a devolver. Para finalizar, anteponemos la letra 'B' a la cadena para indicar que se trata de base binaria. Y eso es todo en lo que a esta función respecta. De este modo, cuando el inspector de objetos deba mostrar el valor de la propiedad, llamará a el método GetValue el cuál le devolverá el string correspondiente. Pero nos queda la otra mitad: estaría bien que pudiéramos introducir el valor de la propiedad tanto en decimal como en binario según nos interesase pues vamos a ello.

Para conseguir esta funcionalidad debemos implementar (override) el método SetValue, definido de nuevo en la clase TPropertyEditor. Cuando el usuario entra un nuevo valor usando el inspector de objetos, este llama al método SetValue, el cuál debe efectuar la traducción inversa a la efectuada por el método GetValue. Es decir, debe convertir el string que contiene el nuevo valor de la propiedad al tipo de datos de dicha propiedad. En nuestro caso, el string vendrá en base decimal o binaria (en este último caso, la primera letra de la cadena será una 'B') y habrá que convertirlo a base decimal. Para ello implementaremos el método SetValue de la siguiente forma:

... type TBinIntegerProperty = class(TIntegerProperty) public function GetValue : string; override; procedure SetValue(const Value : String); override; end;

Page 298: DELPHI 5

procedure Register; implementation ... procedure TBinIntegerProperty.SetValue(const Value : String); Var i, Total, Longitud : integer; NumText : string; begin if UpperCase(Value[1])='B' then begin NumText:=Copy(Trim(Value),2,Length(Trim(Value))-1); NumText:=Copy('0000000000000000',1,16-Length(NumText)) + NumText; Total:=0; for i:=1 to Length(NumText) do begin if not (NumText[i] in ['0','1']) then raise EPropertyError.Create(NumText[i] + ' is not a valid binary digit') else if NumText[i]='1' then Total:=Total+Bits16[i]; end; SetOrdValue(Total); end else SetOrdValue(StrToInt(Value)); end; ... end.

En la implementación de este método primero comprobamos si el usuario ha introducido el nuevo valor de la propiedad en base decimal o en base binaria. En el primer caso, tan sólo hay que convertir el string a integer mediante la función StrToInt(Value) y, a continuación, utilizar el método SetOrdValue para almacenar el valor correspondiente. De forma análoga, según el tipo de la propiedad, existen los métodos SetFloatValue, etc.

En el caso de que la primera letra de la cadena sea una 'B', se convierte la cadena con el valor binario a base decimal y se vuelve a utilizar el método SetOrdValue para almacenar el valor en la propiedad.

Una vez implementados estos dos métodos (GetValue y SetValue) ya tenemos nuestro editor de propiedades terminado; tan sólo nos queda un pequeño, pero indispensable paso, registrarlo en la VCL.

15.12 Registro de un editor de propiedades.

De igual manera que debemos registrar en la VCL los componentes, con los editores de propiedades debemos hacer lo mismo. Para ello

Page 299: DELPHI 5

disponemos del método RegisterPropertyEditor (definido en la unidad DsgnIntf) que tiene la siguiente declaración:

procedure RegisterPropertyEditor(PropertyType:PTypeInfo; ComponentClass : TClass; const PropertyName : string; EditorClass : TPropertyEditorClass);

PropertyType hace referencia al tipo de la propiedad al que se aplicará el editor de propiedades. Para suministrar un valor a este parámetro normalmente utilizaremos la función TypeInfo, por ejemplo, TypeInfo(integer).

ComponentClass permite restringir el uso del editor de propiedades a la clase específica. Un valor de NIL registra el editor para todos los componentes.

PropertyName especifica el nombre de la propiedad. Un valor distinto de NIL registra el editor sólo para la propiedad especificada, mientras que un valor '' lo registra para todas las propiedades

EditorClass especifica el editor de propiedades que se registra (la clase).

Jugando con estos parámetros, tenemos a nuestra disposición un amplio abanico de posibilidades para registrar nuestro editor de propiedades. Vemos algunos ejemplos con nuestro recién creado editor:

RegisterPropertyEditor(TypeInfo(integer), NIL, '', TBinIntegerProperty)

Registra el editor de propiedades para todos los componentes que tengan una propiedad de tipo entero. Está es la forma más global de registrar un componente y afecta a todos los componentes registrados en la VCL. Si registramos nuestro editor así, veremos que todas las propiedades de tipo integer nos aperecen ¡en binario! Además podemos introducir un nuevo valor en decimal o en binario (anteponiendo la letra B). ¡Estamos sustituyendo el editor de propiedades que Delphi utiliza para enteros por el nuestro! :) Esta es la forma más global de registrar un editor de propiedades.

RegisterPropertyEditor(TypeInfo(integer),TMiComponente, 'PropiedadBinaria',TBinIntegerProperty) Registra el editor sólo y exclusivamente para la propiedad 'PropiedadBinaria' del componente 'TMiComponente'. Esta es la forma más restringida de registrar un editor de propiedades.

Un buen consejo sería registrar el editor de la forma más global posible para que experimentes con él.

También se puede crear un componente de "mentirita" y registrar sólo el editor para el mismo. Algo así:

...

Page 300: DELPHI 5

Type TMiComponente = class(TComponent) ... property PropiedadBinaria : integer read FPropBin write FPropBin; ... end; ...

15.13 Ubicación de un editor de propiedades.

Como ya mencionamos al mostrar los pasos que se deben seguir para crear un editor de propiedades, el primero de ellos es crear una unidad donde situar el editor. En ese momento dijimos que no era una elección tan trivial como pudiera parecer en un principio. ¿En que unit debemos situar el editor? ¿en la misma unit donde está el componente que tiene la propiedad que será editada?, ¿en una unit aparte? Tenemos tres posibles ubicaciones:

• La primera opción es situar el editor de propiedades en la misma unidad en que reside el componente que tiene la propiedad que utiliza el editor de propiedades. Esta es la opción más intuitiva; sin embargo no es la más recomendable, sobre todo si el editor utiliza un cuadro de diálogo.

El motivo es que Delphi sólo utiliza el editor de propiedades en tiempo de diseño. De hecho, el form que contiene el cuadro de diálogo no es enlazado con la aplicación, ni tampoco la clase del editor de propiedades. Sin embargo, si se enlazan los recursos asociados al cuadro de diálogo, recursos que en tiempo de ejecución lo único que hacen es ocupar espacio, ya que no se utilizarán para nada. Por tanto, estamos incrementando el tamaño del ejecutable a lo tonto :( Por otra parte, si el editor de propiedades no utiliza cuadros de diálogo, el efecto no es tan pernicioso pero sigue sin ser recomendable.

• La segunda opción es situar el editor de propiedades (si utiliza un formulario cómo cuadro de diálogo) en la misma unidad del cuadro de diálogo. De este modo, la aplicación que usa el componente no enlaza el editor de propiedades ni sus recursos asociados.

• La tercera opción es situar el editor de propiedades en una unidad de registro. Una unidad de registro es una unidad normal y corriente que agrupa varias sentencias de registro correspondientes a distintos componentes y editores de propiedades que residen en distintas unidades. Esta es la opción más recomendable si el editor de propiedades es utilizado por varios componentes.

Por tanto, las dos últimas opciones son las más recomendables dependiendo la elección de una u otra del uso que se vaya a dar al editor de propiedades. De todas formas, no olvides añadir a la

Page 301: DELPHI 5

cláusula uses las unidades correspondientes según donde sitúes el editor.

unit BinaryPropEd; interface uses DsgnIntf; type TBinIntegerProperty = class(TIntegerProperty) public function GetValue : string; override; procedure SetValue(const Value : String); override; end; procedure Register; implementation Const Bits16 : Array [1..16] of Integer = (32768,16384,8192,4096,2048,1024,512,256,128,64,32,16,8,4,2,1); function TBinIntegerProperty.GetValue : string; Var Num, i : integer; begin Num:=GetOrdValue; Result := '0000000000000000'; for i := 1 to 16 Do if ( Num >= Bits16[i] ) Then begin Num := Num - Bits16[i]; Result[i] := '1'; end; if ( Num > 0 ) Then raise EPropertyError.Create('Error converting '+IntToStr(GetOrdValue) + ' to binary'); Insert('B',Result,1); end; procedure TBinIntegerProperty.SetValue(const Value : String); Var i, Total, Longitud : integer; NumText : string; begin if UpperCase(Value[1])='B' then begin NumText:=Copy(Trim(Value),2,Length(Trim(Value))-1); NumText:=Copy('0000000000000000',1,16-Length(NumText)) + NumText; Total:=0;

Page 302: DELPHI 5

for i:=1 to Length(NumText) do begin if not (NumText[i] in ['0','1']) then raise EPropertyError.Create(NumText[i] + ' is not a valid binary digit') else if NumText[i]='1' then Total:=Total+Bits16[i]; end; SetOrdValue(Total); end else SetOrdValue(StrToInt(Value)); end; procedure Register; begin RegisterPropertyEditor(TypeInfo(Integer),nil,'',TBinIntegerProperty); end; end. Direcciones de interés en Internet Existen en Internet numerosas páginas en las que podremos encontrar información de Delphi. Basta con teclear Delphi en el campo de búsqueda de cualquier navegador y aparecerán un buen número de páginas. Aveces sin embargo es difícil distinguir aquello que es importante de lo que no lo es, por ello te brindamos un listado con algunas direcciones de Internet que tratan sobre Delphi. Club Delphi http://www.clubdelphi.com Sin lugar a dudas, el centro más importante de Delphi en Español. En esta sitio encontrará interesantes artículos, cursos, componentes, una extensa sección de enlaces, ofertas de trabajo, un apartado de respuestas a preguntas frecuentes, etc. El Club Delphi cuenta, aparte de con esta web, con sus propios foros de discusión, a los que podrá suscribirse desde la dirección indicada más arriba. Comunidad Borland http://community.borland.com Además de la información sobre Delphi que puede encontrarse en http://www.borland.com, Borland cuenta con una sitio específico para desarrolladores. En el podrá encontrar una comunidad centrada en Delphi, con artículos de los especialistas más conocidos, noticias y todo tipo de recursos útiles.