0% encontró este documento útil (0 votos)
709 vistas502 páginas

ES Guia Delphi Marco Cantu Desarrollo

Guia Delphi Marco Cantu Desarrollo

Cargado por

cova5609
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
709 vistas502 páginas

ES Guia Delphi Marco Cantu Desarrollo

Guia Delphi Marco Cantu Desarrollo

Cargado por

cova5609
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 502

Marco Cantù

La guía de Delphi
Libro para Desarrollo sistemas integrados control, SA

© 2009
Versión en castellano
2 - Indice

DERECHOS RESERVADOS
El contenido de esta publicación tiene todos los derechos reservados, por lo que no se
puede reproducir, transcribir, transmitir, almacenar en un sistema de recuperación o
traducir a otro idioma de ninguna forma o por ningún medio mecánico, manual,
electrónico, magnético, químico, óptico, o de otro modo. La persecución de una
reproducción no autorizada tiene como consecuencia la cárcel y/o multas.

LIMITACIÓN DE LA RESPONSABILIDAD
Tantos el autor como en Danysoft hemos revisado el texto para evitar cualquier tipo de
error, pero no podemos prometerle que el libro esté siempre libre de errores. Por ello le
rogamos nos remita por e-mail a sus comentarios sobre el libro en
attcliente@danysoft.com

DESCUENTOS ESPECIALES
Recuerde que Danysoft ofrece descuentos especiales a centros de formación y en
adquisiciones por volumen. Para más detalles, consulte con Danysoft.

MARCAS REGISTRADAS
Todos los productos y marcas se mencionan únicamente con fines de identificación y están
registrados por sus respectivas compañias.

Autor: Marco Cantù | www.marcocantu.com


Publicado en castellano por Danysoft
Avda. de la Industria, 4 Edif. 1 3º
28108 Alcobendas, Madrid. España.
902 123146 | www.danysoft.com
Libro para Desarrollo sistemas integrados control, SA .
Si no es ese usuario rogamos nos lo comente a attcliente @ danysoft.com.

IMPRESO EN ESPAÑA
ISBN : 978-84-932720-9-8
© | Madrid, Noviembre-2009 | versión en castellano.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 3

Prólogo

A mi mujer Lella, con amor, ánimo, pasión, dedicación y pacien-


cia

Este libro trata sobre Delphi 2009 y versiones superiores.


No encontrarás en él una introducción a la programación en Del-
phi, su lenguaje Object Pascal o su Visual Component Library en él.
Ya que este libro puedes leer sobre nuevas funcionalidades de Del-
phi 2009 para Win32 en cada una de estas áreas.
El libro cubre el soporte Unicode de Delphi 2009, las nuevas fun-
cionalidades del lenguaje (como son los genéricos y los métodos
anónimos), las mejoras del IDE, las nuevas clases de la Run Time
Library, los nuevos componentes de la VCL (incluyendo el control
Ribbon), y la extensión para la arquitectura de bases de datos y la
tecnología de múltiples capas de DataSnap.
Como es habitual en mis libros, cubro la teoría pero también te
muestro docenas de ejemplos, que puedes descargar y utilizar en tu
ordenador. Si aún no tienes Delphi 2009, puedes descargar una
versión de prueba y también ver el programa verdadero en acción
en una serie de videos enlazados desde la página web:
http://www.danysoft.com/embarcadero

Este libro es una secuela de Delphi 2007 Handbook, así que no re-
pito su contenido en absoluto. Si estás interesado en las nuevas
funcionalidades de Delphi 2009 desde Delphi 7 (o una vieja versión

La guía de Delphi por Marco Cantù


4 - Indice

similar), también puedes contactar con tu comercial Danysoft para


adquirir las dos ediciones.
Si estás buscando una introducción a Delphi, sin embargo, puedes
dirigirte a mi “Essential Pascal” para los fundamentos del lenguaje
y a los libros de la serie “Mastering Delphi” (en particular tanto
“Mastering Delphi 7” como “Mastering Delphi 2005”).
Puedes encontrar más detalles sobre todos mis libros en mi sitio
web personal:
http://www.marcocantu.com

Como es habitual, escribir este libro supuso un esfuerzo, y tengo


que dar las gracias a muchos desarrolladores de la comunidad de
Delphi que me apoyaron de diferentes maneras, comenzando por
los revisores técnicos y directores de producto Delphi y los miem-
bros del equipo de I+D. Un gran agradecimiento va para mi mujer
e hijos por su paciencia y ánimo.
Espero que disfrutes del resultado, como yo he disfrutado escri-
biéndolo. Y espero que te guste Delphi 2009, una de las mejores
versiones de Delphi que ha habido, como yo lo he hecho.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 5

Tabla de
Contenidos

Prólogo ........................................................................................................ 3

Tabla de Contenidos ..................................................................................... 5

Introducción .............................................................................................. 13
Delphi ............................................................................................................................ 14
Por Qué Importa Win32.......................................................................................... 14
Este Libro ...................................................................................................................... 16
El Autor ..........................................................................................................................17
Información de Contacto ..........................................................................................17

Apartado I:
Unicode..................................................................................................... 19

Capítulo 1: ¿Qué es Unicode?...................................................................... 21


Caracteres del pasado: desde ASCII a la codificación ISO ........................................... 22
Unicode: Un alfabeto para todo el mundo .................................................................... 24
Desde Puntos de Código a Bytes .............................................................................. 27
Puntos de Código Unicode y Diagramas ................................................................. 28
Transformación de Formatos en Unicode (UTF) ................................................... 29
Estudiando UTF-16 ................................................................................................. 30
Descripciones de Puntos de Código Unicode .......................................................... 33
Byte Order Mark ...................................................................................................... 35
Unicode en Win32 ......................................................................................................... 35
Velocidad de Llamada a la API Unicode.................................................................. 38
Parámetros UnicodeString en llamadas al API .......................................................40
Unicode y Fuentes y APIs ........................................................................................40
Unicode Antes de Delphi 2009 ................................................................................ 42
A continuación............................................................................................................... 43

Capítulo 2: El Tipo Unicode String ............................................................. 45


De AnsiChar a WideChar .............................................................................................. 46
Char como un Tipo Ordinal ..................................................................................... 46
Conversiones con Chr .............................................................................................. 48

La guía de Delphi por Marco Cantù


6 - Indice

Caracteres de 32-bit ................................................................................................. 48


La Nueva Unidad Character .................................................................................... 49
Sobre String y UnicodeString........................................................................................ 51
La estructura interna de los Strings......................................................................... 52
UnicodeString y Unicode ......................................................................................... 55
El Tipo UCS4String .................................................................................................. 57
Los múltiples tipos de cadenas ..................................................................................... 57
El Nuevo Tipo AnsiString ........................................................................................ 58
Creando una cadena de tipo personalizado ............................................................. 59
Gestión de Cadenas UTF-8 ...................................................................................... 63
Conversión de Cadenas ................................................................................................. 65
Conversiones Ralentizando Nuestro Código ........................................................... 66
Las Llamadas Aseguradas ........................................................................................ 68
Tenga cuidado con Literales en Concatenación ...................................................... 70
Usando RawByteString ............................................................................................ 70
Nuevas Funciones de Conversión UTF-8 ................................................................ 74
Cadenas y Caracteres Literales ..................................................................................... 75
Streams y Codificaciones .............................................................................................. 77
Streaming Listas de Cadenas ................................................................................... 78
Definiendo una codificación personalizada............................................................. 81
Unicode y la VCL ........................................................................................................... 84
¿Un núcleo creciente en la RTL? ............................................................................. 85
Unicode en archivos DFM........................................................................................ 86
Localizando la VCL .................................................................................................. 87
A continuación............................................................................................................... 87

Capítulo 3: Exportando a Unicode .............................................................. 89


Operaciones Char Erróneas ..........................................................................................90
Cuidado con Set of Char...........................................................................................90
Evite FillChar para Caracteres ................................................................................. 92
Operaciones de Cadena Que Fallan o Ralentizan ......................................................... 94
Atendiendo a Todas las Alertas de Conversión de Cadenas .................................... 94
No Mueva Datos String ............................................................................................ 96
Leyendo y Escribiendo Búfers ................................................................................. 97
Añadiendo y Concatenando Cadenas ...................................................................... 99
Strings son... Strings (no Bookmarks) ................................................................... 100
Molestas “Importaciones” Actuales ....................................................................... 100
InliningTest que usaba AnsiString .........................................................................101
Utilizando Funciones Ansi-predeterminadas........................................................ 102
Cadenas Unicode y Win32 .......................................................................................... 105
Aplicaciones de consola Win32............................................................................. 106
PChar y el Puntero Matemático .................................................................................. 107
El Problema Con PChar ......................................................................................... 107
De PChar a PByte ................................................................................................... 109
PInteger y la Directiva POINTERMATH ............................................................... 109
No utilice PChar para Pointer Math .......................................................................110
Parámetros Variants y Open Arrays ............................................................................ 111
A continuación.............................................................................................................. 111

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 7

Apartado II:
Delphi 2009 y Su Compilador ................................................................... 113

Capítulo 4: Nuevas Características del IDE................................................ 115


Instalación y Ejecución ................................................................................................ 116
No Es Necesario el SDK .NET ................................................................................. 116
Instalar el limpiador de Windows .......................................................................... 117
El Flag -idecaption ..................................................................................................118
Gestión de Proyectos en Delphi ...................................................................................118
Actualizando Ficheros de Configuración de Proyecto ............................................ 119
Rediseñado Diálogo De Opciones De Proyecto ...................................................... 121
Nuevas Opciones de Proyecto para el compilador ................................................ 122
Otras Nuevas Opciones de Proyecto ...................................................................... 125
Ubicación Predeterminada de Proyectos............................................................... 125
El Project Manager ...................................................................................................... 126
Vistas del Project Manager .................................................................................... 127
Construcción de Configuraciones y Opciones de Configuración .......................... 129
Administrador de Configuración de Proyecto ....................................................... 132
Gestión de Recursos en el IDE .................................................................................... 133
Un "Nuevo" Compilador de Recursos.................................................................... 136
La Clase Delphi Explorer............................................................................................. 137
Otras nuevas características ........................................................................................ 140
Paleta de Herramientas del cuadro de búsqueda .................................................. 140
Actualización de Asistentes de Componentes ........................................................ 141
¿Algo nuevo en el Editor?............................................................................................ 143
Depurador ................................................................................................................... 144
Depuración y Nuevas Características del Lenguaje .............................................. 145
A continuación............................................................................................................. 145

Capítulo 5 : Genéricos .............................................................................. 147


Genéricos de Pares Clave-Valor .................................................................................. 148
Tipo de Normas sobre Genéricos............................................................................ 151
Genéricos en Delphi .................................................................................................... 152
Normas de Compatibilidad Para Los Tipos Genéricos ......................................... 154
Funciones Genéricas Globales (Bueno, Casi) ........................................................ 155
Instanciación del Tipo Genérico ............................................................................ 156
Funciones de Tipo Genérico .................................................................................. 158
Restricciones Genéricas ............................................................................................... 161
Restricciones de clase ............................................................................................ 162
Restricciones Específicas de Clase ......................................................................... 164
Restricciones de interfaz ........................................................................................ 164
Referencias de Interfaz vs. Restricciones de Interfaz Genéricas .......................... 167
Restricción del Constructor por Defecto ............................................................... 168
Resumen de las Restricciones Genéricas y Su Combinación ................................ 170
Contenedores Genéricos Predefinidos ......................................................................... 171
Usando TList<T> ................................................................................................... 172
Ordenando un TList<T> ........................................................................................ 173
Ordenando con un Método Anónimo .....................................................................175
Contenedores de Objetos ........................................................................................ 177
Usando un Diccionario Genérico ............................................................................ 177
Interfaces Genéricas .....................................................................................................181

La guía de Delphi por Marco Cantù


8 - Indice

Interfaces Genéricos Predefinidos ........................................................................ 184


Punteros inteligentes en Delphi .................................................................................. 185
A continuación ........................................................................................................ 191

Capítulo 6: Métodos Anónimos ................................................................ 193


Sintaxis y Semántica de los Métodos Anónimos ........................................................ 195
Una Variable de Método Anónimo ........................................................................ 195
Un Parámetro de Método Anónimo ...................................................................... 196
Usando Variables Locales ........................................................................................... 197
Extendiendo la Vida de las Variables Locales ....................................................... 198
Más sobre los Métodos Anónimos ............................................................................. 200
El (Potencialmente) Paréntesis Perdido ............................................................... 200
Detrás de los Métodos Anónimos ..........................................................................202
Tipos de Referencia Listos Para Usar ....................................................................203
Métodos Anónimos en el Mundo Real ........................................................................204
Gestores De Eventos Anónimos.............................................................................204
Cronometraje de Métodos Anónimos .................................................................... 207
Sincronización de Procesos con la VCL .................................................................209
Bucle For Paralelo .................................................................................................. 213
AJAX en Delphi ...................................................................................................... 217
Debatiendo la Demo AJAX .................................................................................... 221
A continuación............................................................................................................. 223

Capítulo 7: Más Cambios del Lenguaje y de la RTL ................................... 225


Otras Nuevas Características del Lenguaje................................................................. 226
Versión del compilador .......................................................................................... 226
Una Comentada Directiva Deprecated .................................................................. 227
Salidas con un Valor...............................................................................................228
Configuración de Propiedades por Referencia ...................................................... 229
Cambios en Overloading .......................................................................................230
Código que Desencadena un Error de Compilación.............................................. 231
Código que Llama a un Método Diferente ............................................................. 232
Nuevos y Alias de Tipos Enteros............................................................................ 233
Nuevos Métodos de TObject ....................................................................................... 234
El Método ToString ................................................................................................ 234
El Método Equals .................................................................................................. 235
El Método GetHashCode ....................................................................................... 236
El Método UnitName ............................................................................................. 236
Importando un Ejemplo de .NET .......................................................................... 236
Resumen de la Clase TObject................................................................................. 239
Unicode y Nombres de Clase .................................................................................240
Cambios en el Soporte Threading ............................................................................... 241
Construyendo Cadenas................................................................................................ 245
Métodos de Encadenamiento en StringBuilder .................................................... 247
La Velocidad en la Construcción de Cadenas ........................................................248
Migrando un Ejemplo Delphi .NET ....................................................................... 250
Usando Readers y Writers........................................................................................... 251
Mejoras Excepción(ales) ............................................................................................. 256
El Mecanismo InnerException .............................................................................. 256
Preprocesamiento de Excepciones ........................................................................260
Nuevas Clases de Excepción .................................................................................. 262

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 9

Resumen de las Nuevas Unidades y Las Nuevas Clases RTL ..................................... 262
Más y Menos FastCode .......................................................................................... 264
A continuación............................................................................................................. 264

Apartado III: La VCL y Las Bases de Datos ............................................... 265

Capítulo 8: Mejoras En la VCL .................................................................. 267


Mejoras Fundamentales de la VCL .............................................................................268
Avisos y Globos Personalizados ............................................................................. 269
Mejoras para Componentes Estándares ..................................................................... 271
Los Botones Adquieren Nuevas Características .................................................... 272
Etiquetas Brillantes y de Enlaces ........................................................................... 275
Envoltura de Texto en RadioGroup ...................................................................... 276
Edits Consigue Muchas Características Nuevas .................................................... 277
ComboBoxes y Hints ............................................................................................. 280
Nuevo Control ButtonedEdit ................................................................................ 281
Actualizaciones de Controles Comunes ......................................................................284
Agrupaciones en un ListView ................................................................................284
Marquesina y más acerca de los Controles ProgressBar ....................................... 287
Casillas de Verificación en una Cabecera ............................................................. 288
RichEdit 2.0 ...........................................................................................................289
Componentes Nativos de la VCL ................................................................................. 291
Los Componentes Action Manager ........................................................................ 291
Sobre Paneles ......................................................................................................... 292
El Nuevo Control CategoryPanelGroup ................................................................. 292
Actualización de TrayIcon..................................................................................... 296
Fuentes por defecto para la Aplicación y Objetos Globales en Pantalla ............... 297
Mejoras en Soporte de Gráficos ............................................................................. 299
El Portapapeles y Unicode .....................................................................................303
Soporte de Vista Ampliado..........................................................................................304
A continuación............................................................................................................. 305

Capítulo 9: Soporte COM En Delphi 2009 ................................................ 307


IDL, Librerías Tipo, y RIDL ....................................................................................... 308
Una RIDL Textual ..................................................................................................309
El Formato RIDL (Servidores COM) .......................................................................... 310
Registrando y Llamando al Servidor ..................................................................... 314
El nuevo panel de registro de las “Type Libraries” ..................................................... 315
COM y Unicode ........................................................................................................... 318
Características que Vuelven: Active Forms ................................................................ 319
A continuación............................................................................................................. 322

Capítulo 10: Ribbon ................................................................................. 323


Presentando el “Fluent User Interface” ...................................................................... 324
El lado legal de la “Ribbon” ................................................................................... 325
Una “Ribbon” simple ............................................................................................. 326
Acciones y la “Ribbon” ................................................................................................ 329
De eventos a Acciones ............................................................................................ 329
“ActionList” y “ActionManager” ........................................................................... 331
Acciones y Ribbon en la práctica ........................................................................... 332
Grupos y comandos................................................................................................ 333

La guía de Delphi por Marco Cantù


10 - Indice

El Menú de aplicación ............................................................................................ 335


La barra de herramientas de acceso rápido ........................................................... 337
El soporte de los Key Tips ...................................................................................... 338
Los componentes Ribbon ............................................................................................ 339
“Ribbons” en aplicaciones de bases de datos.............................................................. 344
Utilizar Screen Tips ..................................................................................................... 348
“Screen Tips” sin “Ribbon” .................................................................................... 348
ScreenTipsManager y las acciones ........................................................................ 350
A continuación............................................................................................................. 353

Capítulo 11: Datasets Y dbExpress ............................................................ 355


Un ClientDataSet Unicode ......................................................................................... 356
Unicode en Datasets, Toma 2...................................................................................... 358
Listas de Cadenas Unicode .................................................................................... 359
Bookmarks .............................................................................................................360
Tipos de Campos y Cadenas................................................................................... 362
Otras Mejoras del Dataset ........................................................................................... 362
Nuevos Tipos de Campos ....................................................................................... 363
Un Dataset Más Virtual.......................................................................................... 363
Extensiones de Campos .........................................................................................368
Campos BLOB Considerados ANSI ...................................................................... 369
Extensiones de Parámetros.................................................................................... 370
DataSets Internos ........................................................................................................ 371
Portando un (simple) dataset personalizado......................................................... 372
dbExpress en Delphi 2009 .......................................................................................... 373
Ajustes de Conexión y Cadenas de Conexión ....................................................... 374
Configuración de propiedades del controlador y Controladores Delegado .......... 376
Despliegue y Archivos INI ..................................................................................... 377
Drivers en el Ejecutable ......................................................................................... 379
Soporte de Metadatos Extendidos ........................................................................ 380
Bombeo de Datos para dbExpress ......................................................................... 383
Controles Data-Aware ................................................................................................. 385
Desde DBImage hasta el Viejo y Pobre DBGrid ....................................................386
A continuación............................................................................................................. 387

Capitulo 12: DataSnap 2009 ..................................................................... 389


Construyendo la primera demostración de DataSnap 2009 ......................................390
Construyendo el servidor .......................................................................................390
El primer cliente..................................................................................................... 392
De DataSnap a DataSnap 2009 ............................................................................. 393
Añadiendo métodos al servidor ............................................................................. 395
Sesiones e hilos con un servidor DataSnap no dirigido a base de datos ....................398
Ciclo de vida de los objetos de servidor .................................................................402
Un cliente que arranca un servidor y abre múltiples conexiones .........................403
Gestión de memoria ...............................................................................................406
Gestión de hilos ...................................................................................................... 407
Portando un ejemplo anterior de DataSnap ............................................................... 410
Portar el servidor ................................................................................................... 410
Actualizando el cliente ............................................................................................ 411
Características avanzadas de ThinPlus2009 ......................................................... 413
El interfaz administrativo de DataSnap...................................................................... 416

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 11

Conclusión ...................................................................................................................420

Indice ...................................................................................................... 421

La guía de Delphi por Marco Cantù


12 - Indice

Introducción

Presentado por primera vez por Borland el 14 de Febrero de


1995, Delphi tiene una larga y gloriosa historia de éxitos en las
áreas del desarrollo Windows y cliente/servidor. Con millones
de aplicaciones escritas en su lenguaje Object Pascal, Delphi
ha generado un completo ecosistema de componentes, herra-
mientas, revistas, libros, y (por supuesto) sitios web y recur-
sos online.
Delphi está ahora en su 12ª versión, la 20ª si cuentas todas
incluyendo las de su predecesor Turbo Pascal1, el cual se lanzó
por primera vez hace 25 años. ¡Lo que es nuevo en esta ver-
sión de Delphi es la compañía propietaria del producto!

1La versión actual del compilador de Delphi, de hecho, es la 20.00. Esto es desta-
cado por el valor que la VER200 define, mencionado en la sección “Versión
del Compilador” al comienzo del Capítulo 7.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 13

Con la adquisición de la división CodeGear de Borland el 1 de


Julio de 2008, Delphi se convierte en subsidiario de Embar-
cadero Technologies. Este cambio de propiedad ocurre bas-
tante tarde en el ciclo de desarrollo de Delphi 2009, por lo
que el único efecto práctico de los cambios es la inclusión de
ER/Studio en la versión Architect del producto. Desde el co-
mienzo de la división CodeGear dentro de Borland, sin em-
bargo, ha habido un interés renovado (e inversión en térmi-
nos de I+D, QA y Documentación) en Delphi, específicamente
en sus versiones Win32. Por esto es que es relevante fijarse
por un segundo en los temas políticos de más alto nivel.

Delphi
Como ya he mencionado, la creación de la división CodeGear
y luego la adquisición de esta división por Embarcadero Tech-
nologies está proporcionando unos nuevos cimientos para
Delphi, y nuevas inversiones en el producto. Incluso si no
tiene un marketing agresivo, y queda fuera del radar de la ma-
yoría de publicaciones, aún así Delphi tiene millones de usua-
rios activos, tanto en el sector de ISV (donde su simplicidad
en el despliegue gana sobre las versiones basadas en platafor-
mas) como en los entornos cliente/servidor, donde la estabili-
dad de una inversión es más valorada que el estar en la onda
de una plataforma.
Es verdad que la comunidad de Delphi es más pequeña de lo
que fue hace unos pocos años, y que parte de ella aguanta con
versiones más viejas del producto, pero sigue siendo el líder
en muchos países y realmente ha vuelto a estar de buen hu-
mor en el último año.

La guía de Delphi por Marco Cantù


14 - Indice

Por Qué Importa Win32


Si lees la mayoría de las revistas de TI, sigues los blog, o asis-
tes a conferencias, parece que solo las últimas tecnologías (y
las últimas manías) se valoran para trabajar y todo lo demás
está muerto o muriendo. Este es un comentario muy lejano de
la realidad.
Desde el desarrollo en COBOL a mainframes, desde ordena-
dores AS/400 a bases de datos DBF, hay toneladas de tecno-
logía heredada que no solo se mantiene sino en las que se ven
nuevas inversiones. Puede ser por razones de compatibilidad,
pero también porque las empresas prefieren tener tecnología
probada y fiable para el núcleo de su negocio, que arriesgar
sus negocios en la tecnología más recientemente promocio-
nada.
Esto no significa, por supuesto, que seguir las tendencias, pro-
porcionar mayor calidad, y potenciar a los usuarios no sea im-
portante. Justo lo opuesto. Si te puedes mantener entregando
valor adicional sobre fundamentos sólidos, tienes una ventaja
doble. Mirándolo desde el punto de vista de Windows, por
ejemplo, Microsoft ha incrementado valor con su creciente
conjunto de librerías y arquitecturas basadas en la plataforma
.NET. Por otro lado es verdad que, a pesar de la robustez y es-
tabilidad del núcleo, apuntar a las últimas y mejores tecnolo-
gías .NET es como fijarse en un objetivo que se mueve rápido,
lo que no es exactamente lo mejor cuando necesitas construir
tu aplicación cliente/servidor que llevará un par de años crear
y esperas no tocarla en los próximos diez años o así.
En el otro extremo están los Micro ISV, pequeños fabricantes
de herramientas, desarrolladores de productos shareware, y
constructores de utilidades para Internet. Ellos están en una
situación de productos que abarcan una vida corta y podrían
beneficiarse ciertamente de estar a la última… pero incluso
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 15

ellos no pueden depender de una plataforma grande y cam-


biante para desplegar sus aplicaciones. Necesitan algo que
funcione en todas y cada una de las versiones de Windows.
Esta es una situación en la que Delphi brilla en comparación
con la mayoría de soluciones. La única excepción real es Vi-
sual C++, pero (si nunca has intentado hacerlo) desarrollar en
él no es una experiencia RAD y POO como lo son desarrollar
en .NET y VCL.
La biblioteca MFC de Visual C++ es solo una fina capa sobre
el API de Windows, mientras que Delphi proporciona lo que
se llama un plataforma, con gestión de memoria y servicios de
ejecución, una biblioteca de clases bastante grande con mu-
chas ideas en lo referido a la creación de interfaces de usuario,
soporte de Internet, y conectividad a base de datos, por nom-
brar solo las áreas más notables del producto.
Delphi hace tan buenos trabajos produciendo aplicaciones
nativas con aspecto Windows, como Skype, que es raro que
haya algún signo visible de que una aplicación ha sido desa-
rrollada con Delphi.

Este Libro
Habiendo introducido la situación de Delphi, es el momento
de hablar sobre este libro. Como mi reciente “Delphi 2007

La guía de Delphi por Marco Cantù


16 - Indice

Handbook” este no es un manual que abarque todas las fun-


cionalidades de Delphi, lo que requeriría probablemente cerca
de 4.000 páginas2.
A pesar del tamaño, el foco de este libro se centra en las nue-
vas funcionalidades encontradas en Delphi 2009, o al menos
añadidas a Delphi desde que se lanzó Delphi 2007 (como Bla-
ckFish SQL y algunas extensiones de metadatos del dbExpress
que fueron incluidas en las actualizaciones de Delphi 2007).
No necesita decirse que da un papel central a Unicode y a los
cambios en el núcleo del lenguaje (como los genéricos y los
métodos anónimos), pero hay también material sobre las ac-
tualizaciones para la RTL y la VCL, las mejoras en el soporte
para Vista, los últimos controles de interfaz de usuario, y un
análisis en profundidad de dbExpress y el nuevo DataSnap
2009 con capacidades multi-hilo del producto.
Como en mis libros anteriores hay mucha teoría y material de
introducción mezclado con incontables ejemplos, disponibles
online en:
http://www.marcocantu.com/dh2009

Como mencioné en el “Prologo”, también he creado unos vi-


deos cortos de demostración (cubriendo cómo funciona el
programa, no cómo se escribió) para la mayoría de los ejem-
plos del libro, disponibles online en:
http://www.marcocantu.com/dh2009/videos.html

Al publicar este libro, le he dado la forma que más me gusta,


contando con la ayuda de editores y revisores en los que con-
fío, y (espero) que este valor se refleje en el resultado. Cuando

2Esta suposición (4.000 páginas) es mi estimación de la cantidad de material


que he escrito sobre Delphi en los últimos 13 años. Esto es, sin considerar los
capítulos que fueron incluidos en las ediciones subsiguientes de mí serie
Mastering Delphi.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 17

publiqué “Delphi 2007 Handbook”, fue mi primera experien-


cia de libros electrónicos. Ahora he aprendido de los errores,
afinando algunas operaciones, y he reducido alguno de los do-
lores de cabeza de la publicación para centrarme completa-
mente en escribir durante bastante tiempo. ¡Espero que en-
cuentres que este esfuerzo ha tenido valor!

El Autor
Para aquellos que tienen en sus manos por primera vez uno
de mis libros, y para aquellos que no han leído recientemente
uno, mi nombre es Marco Cantù, y he estado en el negocio de
“escritor de libros de Delphi” desde la primera versión del
producto, cuando lancé el “Mastering Delphi” original (un
corpulento tomo de 1.500 páginas). Esta no fue mi primera
experiencia escribiendo, ya que antes había escrito trabajos
sobre Borland C++ y la Object Windows Library.
En los últimos años, junto a mi continuo compromiso con la
comunidad de Delphi, también he dedicado mucho tiempo a
las tecnologías relacionadas con XML, y a XSLT, con servicios
web (incluyendo las implementaciones SOAP y REST), Ja-
vaScript y AJAX, y otras tecnologías Web 2.0. Después de un
descanso, he vuelto a escribir para auto-publicar mis libros, y
no solo sobre Delphi, ya que he finalizado también un volu-
men sobre redes sociales.
Junto a la escritura, me mantengo ocupado con consultorías
(la mayoría sobre arquitectura de aplicaciones), ayudando a
vender Delphi en Italia, haciendo revisiones de código, ense-
ñando Delphi, y realizando consultorías generales para desa-
rrolladores.

La guía de Delphi por Marco Cantù


18 - Indice

Con frecuencia también soy ponente en conferencias sobre


Delphi y desarrollo en general, incluyendo las nuevas confe-
rencias online de Embarcadero. Si estás interesado en invi-
tarme a hablar en un evento público o a dar una sesión de for-
mación (sobre Delphi o cualquier tema avanzado) en tu em-
presa, siéntete libre de contactarme vía

Información de Contacto
Finalmente, aquí hay alguna información de contacto:
http://blog.marcocantu.com
http://www.marcocantu.com

Mi sitio web personal hospeda una página específica para el


libro, incluyendo actualizaciones, descarga de código fuente, y
otras informaciones:
http://www.marcocantu.com/dh2009

Tengo una lista de mailing online basada en un grupo de Goo-


gle donde puedes apuntarte desde mi sitio web. También llevo
una newsgroup online con una sección dedicada a discutir mis
libros y sus contenidos, disponible en la web (en la sección lla-
mada “marco cantu”) en:
http://delphi.newswhat.com

Finalmente, en Danysoft traductores y editores de la versión


en castellano del libro, han creado una página sobre el mismo
en:
http://www.danysoft.com/bol/dh2009

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 19

Apartado I:
Unicode

La primera parte de este libro se centra en Unicode, el carác-


ter de codificación internacional estándar que soporta por pri-
mera vez Delphi 2009. Los tres capítulos de este apartado in-
troducen este tema, describen la implementación actual, y le
guiarán en las tareas de importación y compatibilidad, respec-
tivamente.

 Capítulo 1: ¿Qué es Unicode?


 Capítulo 2: El tipo de cadena Unicode
 Capítulo 3: Actualización a Unicode

La guía de Delphi por Marco Cantù


20 - Indice

Capítulo 1: ¿Qué
es Unicode?

Unicode es el nombre del conjunto de caracteres internacio-


nales que engloba los símbolos de todos los alfabetos escritos
del mundo, de hoy y del pasado, y algo más3. El estándar Uni-
code (formalmente denominado "ISO/IEC 10646") está defi-
nido y documentado por el Consorcio de Unicode, y contiene
más de 100.000 caracteres. Su sitio web principal es:
http://www.unicode.org

Como la adopción de Unicode es un elemento central de Del-


phi 2009 y son muchas las cuestiones a abordar, este capítulo

3Unicode incluye también símbolos técnicos, signos de puntuación, y muchos


otros caracteres utilizados en la escritura de texto, aunque no formen parte
de ningún alfabeto.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 21

se centra sólo en la teoría entre Unicode y las otras codifica-


ciones de caracteres, mientras que el siguiente se centrará en
los elementos clave de su aplicación en Delphi.

Caracteres del pasado:


desde ASCII a la
codificación ISO
El Estándar Americano de Código para Intercambio de Infor-
mación (ASCII) se desarrolló a principios de los años 60 como
un estándar de codificación de caracteres, abarca las 26 letras
del alfabeto Inglés, tanto en minúsculas como en mayúsculas,
los números, símbolos comunes de puntuación, y numerosos
caracteres4 de control.
ASCII utiliza un sistema de codificación de 7 bits para repre-
sentar 128 caracteres diferentes. Sólo los caracteres entre el #
32 (espacio) y el # 126 (Tilde) tienen una representación vi-
sual, tal y como se muestra en la siguiente tabla:

4Si bien la mayoría de los caracteres de control han perdido su sentido (como el
separador de archivo o el tabulador vertical), algunos como el retorno de ca-
rro (# 13), salto de línea (# 10), Tab (# 9), y Retroceso (# 8) se usan aún coti-
dianamente.

La guía de Delphi por Marco Cantù


22 - Indice

Aunque ASCII fue sin duda el principio (con su conjunto bá-


sico de 128 caracteres que siguen siendo parte de la base de
Unicode), pronto fue sustituido por versiones extendidas que
utilizan notación de 8 bits para añadir nuevos conjuntos de
128 caracteres al conjunto.
Ahora, el problema es que con tantos idiomas en todo el
mundo, no hay una forma simple de averiguar qué otros ca-
racteres debemos incluir en el conjunto (a veces mencionado
como ASCII-8). Abreviando esta historia, Windows adopta un
conjunto diferente de caracteres, llamado code page (página
de código), cuyo conjunto de caracteres depende de su confi-
guración local y de la versión de Windows. Además de las pá-
ginas de código de Windows hay muchos otros convenios ba-
sados en enfoques similares de paginación.
El más relevante es el estándar ISO 8859, que define conjun-
tos de caracteres regionales. El más usado (bueno, el más
usado en los países occidentales, para ser más preciso) es el
conjunto Latino, denominado ISO 8859-1. Aunque parcial-
mente es similar, la página de códigos Windows 1252 no se
ajusta plenamente a la serie ISO 8859-1. Windows agrega ca-
racteres extra como el símbolo €, tal como veremos más ade-
lante.
Si sigo la impresión de todos los caracteres de 8-bits, en mi
equipo (que utiliza Windows 1252 como página de códigos

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 23

por defecto) obtengo la siguiente salida (la suya puede ser di-
ferente)5:

¿Cómo he conseguido tanto esta como la anterior imagen?


Utilizando un sencillo programa en Delphi 2009 (llamado
FromAsciiToUnicode) que muestra los caracteres en un com-
ponente StringGrid, inicialmente con el número de columna y
línea pintados en las correspondientes cabeceras. El pro-
grama fuerza algunas conversiones tipo del tipo6 AnsiChar
para poder gestionar caracteres tradicionales de 8-bits (am-
pliaremos esta información en el siguiente capítulo):
procedure TForm30.btnAscii8Click(Sender: TObject);

5Si el sistema por defecto utilizase una página de códigos multi-byte, el código de
este programa se convierte en un sin sentido, porque la mayoría de los carac-
teres entre #$80 y #$FF son bytes perdidos, que no se puede mostrar por si
mismos.
6Como veremos en detalle en el próximo capítulo, en Delphi 2009 el tipo 'Char'
ha cambiado y el antiguo tipo Char, desde Delphi 1 a Delphi 2007, se llama
ahora AnsiChar.

La guía de Delphi por Marco Cantù


24 - Indice

var
I: Integer;
begin
ClearGrid;
for I := 32 to 255 do
begin
StringGrid1.Cells [I mod 16 + 1,
I div 16 + 1] := AnsiChar (I);
end;
end;

En las versiones anteriores de Delphi usted podría obtener el


mismo resultado escribiendo la siguiente versión más simple
(que utiliza Char en vez de AnsiChar para la conversión):
for I := 32 to 255 do
begin
StringGrid1.Cells [I mod 16 + 1,
I div 16 + 1] := Char (I);
end;

Pienso que no es realmente necesario que le diga lo confuso


de esta situación con las diferentes codificaciones ISO 8859
(hay 16 de ellas, y todavía no pueden cubrir los alfabetos más
complejos), la página de códigos de Windows, de caracteres
multi byte soporta el chino y otros idiomas. Con Unicode,
todo esto queda desfasado, a pesar de que la nueva norma
tiene su propia complejidad y problemas potenciales.

Unicode: Un alfabeto para


todo el mundo
Como ya he mencionado, todo esto cambió con la introduc-
ción de Unicode. La idea que hay detrás de Unicode (que es lo
que hace que sea simple) es que todos y cada uno de los carac-
teres tiene su propio número único (o punto de código, para
usar el término correcto de Unicode). No quiero ahondar en la
teoría completa de Unicode (aunque si usted quiere, puede
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 25

consultar el libro Unicode (Unicode book) con la norma7 com-


pleta), pero sí poner de relieve sus puntos clave.
En cualquier caso, voy a empezar por ampliar el programa
FromAsciiToUnicode, que tiene un tercer botón que muestra
esos mismos 256 caracteres (256 menos los 32 caracteres de
control iniciales y el carácter de espacio). Esto es lo que ob-
tendrá (y esto no depende de su localización o de la página de
código de Windows):

Puede esperar ver exactamente la misma secuencia de carac-


teres, ya que como todo el mundo sabe, la porción inicial del
conjunto de caracteres Unicode mapea la secuencia ASCII,
¿verdad? De hecho, esto es ¡totalmente erróneo! Sólo el con-
junto original ASCII-7 tiene su equivalente perfecto en Uni-
code, y la mayoría del resto de caracteres extendidos también
coinciden, pero no todos ellos. De hecho, el tramo entre 128 y
160 es diferente (aunque para ser más precisos, es diferente

7Másinformación del libro “The Unicode Standard” que puede usted encontrar
en: http://www.unicode.org/book/aboutbook.html.

La guía de Delphi por Marco Cantù


26 - Indice

desde la propia interpretación de Microsoft del código de la


página Latín 1). Si usted observa en la imagen8 anterior, podrá
comprobar una colección de símbolos utilizados rara vez ...
pero hay uno que (por lo menos en mi zona del mundo) es
muy importante, el símbolo de moneda €. Para seguir el pro-
cedimiento generado, he añadido el código siguiente al mismo
programa, de nuevo utilizando dos diferentes tipos de carac-
teres, AnsiChar y Char:
procedure TForm30.btnEuroClick(Sender: TObject);
var
aChar: AnsiChar;
uChar: Char;
begin
aChar := '€';
uChar := '€';
ShowMessage ('€ for AnsiChar is ' +
IntToStr (Ord (aChar)));
ShowMessage ('€ for UnicodeChar is ' +
IntToStr (Ord (uChar)));
end;

Tenga en cuenta que la forma en que este fragmento de có-


digo se compila depende de la opción --codepage del com-
pilador, que (si no se especifica) por defecto se refiere a la
propia página de códigos9 del sistema operativo. Así que si re-
compila el mismo código en un zona diferente del mundo, sin
proporcionar una página de códigos explícita, usted obtendrá
un programa compilado distinto (no sólo una salida dife-
rente).

8Para una demostración de este ejemplo, vea el vídeo de YouTube "Delphi does
Unicode ", que realicé en agosto del 2008, durante el período en que se nos
permitió a los probadores experimentales de "Tiburón" anunciar las nuevas
características del producto. Con estos vídeos quedan cubiertos los otros
ejemplos de este capítulo. El vínculo es el siguiente:
http://www.youtube.com/watch?v=BJMakOY8qbw
9La página de códigos utilizada para compilar el programa sólo afecta a la forma
en que se gestiona el carácter AnsiChar, y no el Char Unicode. De hecho,
los caracteres Unicode y strings ignoran el código de la página por completo
(¡lo que es una gran razón para usarlos!)

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 27

Una vez más, los resultados que obtendrá pueden depender


de su configuración y parecer algo extraño ... pero tendremos
que aprender a vivir con ello en el mundo Unicode. Esto es lo
que yo obtengo [ver imagen en página siguiente]:

Desde Puntos de Código a Bytes


La confusión oculta de Unicode (lo que hace que sea com-
plejo) es que hay muchas formas de representar el mismo
punto de código (o valor numérico de caracteres Unicode) en
términos de almacenamiento real, en bytes físicos. Si la única
manera para representar todos los puntos de código Unicode
de un modo simple y uniforme fue utilizar cuatro bytes por
cada uno de los puntos de código10 la mayoría de los desarro-
lladores percibirían como demasiado caro en términos de me-
moria y procesamiento.

10EnDelphi los Puntos de Código Unicode están representados utilizando el tipo


de datos UCS4Char, que se tratan en la sección " Caracteres de 32-bit" del
capítulo 2.

La guía de Delphi por Marco Cantù


28 - Indice

Una de las opciones es usar pequeñas representaciones con


diferente número de bytes (por lo menos 1 ó 2, pero posible-
mente hasta 4) para los distintos puntos de código de todo el
conjunto Unicode. También se denomina representación de
longitud-variable. Estas codificaciones tienen nombres de los
que probablemente haya oído hablar, como UTF-8 y UTF-16,
y de las que examinaremos los detalles técnicos en la siguiente
sección.
Existe un error común en creer que UTF-16 puede directa-
mente direccionar todos los puntos de código con dos bytes,
pero como Unicode define más de 100.000 puntos de código
usted puede fácilmente adivinar que no van a caber. Es cierto,
que sin embargo, a veces los desarrolladores utilizan sólo un
subconjunto de Unicode, para hacer que encaje en una repre-
sentación de longitud-fija-2-bytes-por-carácter. En sus prime-
ros días, este subconjunto de Unicode se llamó UCS-211 ,
ahora lo puede encontrar a menudo como referencia Basic
Multilingual Plane (BMP). Sin embargo, esto es sólo un sub-
conjunto de Unicode (uno de sus muchos Planos).

Puntos de Código Unicode y


Diagramas
Para ser realmente preciso, debería incluir algún concepto
más allá de los puntos de código. A veces, de hecho, múltiples
puntos de código pueden ser utilizados para representar un
único grafema (un carácter visual). Este no es generalmente
una letra, sino una combinación de letras o letras y símbolos.

11El Conjunto universal de caracteres 2-byte (UCS-2) se considera ahora una co-
dificación de caracteres obsoleta. Sin embargo, ambos UTF-16 y UCS-2, ma-
pean los puntos de código contenidos de la misma forma, dentro del BMP,
excluyendo el subrogado especial punto de código 2048.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 29

Por ejemplo, si usted tiene una secuencia de los puntos de có-


digo que representan la letra latina a (#$0061) seguido por el
punto de código que representa el acento grave (#$0300), esto
se debería mostrar como un único carácter acentuado.
En términos de codificación Delphi, si escribe lo siguiente
(disponible de nuevo en el ejemplo FromAsciiToUnicode):
var
str: String;
begin
str := #$0061 + #$0300;
ShowMessage (str);

El mensaje tendrá un único carácter acentuado:

En este caso tenemos dos Chars, representando dos puntos de


código, pero sólo un grafema. El hecho es que mientras que en
el alfabeto latino puede usar un punto de código Unicode es-
pecífico para representar este grafema (letra con acento
grave), en otros alfabetos la combinación de puntos de código
Unicode es la única manera de obtener un determinado gra-
fema (y su correcta representación).

Transformación de Formatos en
Unicode (UTF)
Pocas personas saben que el término común "UTF" es el acró-
nimo de Unicode Transformation Format. Estos son algorit-
mos de asignación, mapeados, parte del Unicode estándar,
que ubica cada punto de código en su mapa (la representación
numérica absoluta de un carácter) a una secuencia única de

La guía de Delphi por Marco Cantù


30 - Indice

bytes que representan un determinado carácter. Observe que


estas asignaciones pueden ser utilizadas en ambas direccio-
nes, convirtiendo diferentes representaciones como de ida y
vuelta.
La norma define tres de estos formatos, dependiendo de
cuántos bits se utilizan para representar la parte inicial de la
serie (los primeros 128 caracteres): 8, 16, ó 32. Es interesante
observar que las tres formas de codificación necesitan al me-
nos 4 bytes de datos por cada punto de código.
 UTF-8 transforma los caracteres en una longitud variable de
codificación de 1 a 4 bytes. UTF-8 es muy popular para HTML
y protocolos similares, porque es muy compacto y la mayoría
de los caracteres (como en etiquetas HTML) entran en el sub-
conjunto ASCII12.
 UTF-16 es popular en muchos sistemas operativos (inclu-
yendo Windows) y entornos de desarrollo. Es muy ventajoso
ya que la mayoría de caracteres caben en dos bytes, razona-
blemente compacto y rápido de procesar.
 UTF-32 otorga mucho sentido a su proceso (todos los puntos
de código tienen la misma longitud), pero es memoria que se
consume y limita su uso en la práctica.
Un problema relacionado con representaciones multi-byte
(UTF-16 y UTF-32) es ¿cuál de los bytes es lo primero? Según
el standard, todos los modos están permitidos, por lo que

12OriginalmenteUTF-8 se representaba de 1 a 6 bytes, para representar a cual-


quier supuesto futuro punto de código Unicode, pero fue más tarde limitado
a usar únicamente la definición formal Unicode hasta el punto de código
10FFFF. Más información, incluyendo un mapa de las diferentes longitudes
de puntos de código en UTF-8, en http://en.wikipedia.org/wiki/Utf-8.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 31

puede tener un UTF-16 BE (big-endian13) o LE (little-endian),


y lo mismo con UTF-32.

Estudiando UTF-16
¿Cómo crear una tabla de caracteres Unicode como la que he
mostrado anteriormente sólo para los ASCII? Podemos empe-
zar por ver los puntos de código en el plano básico multilingüe
sobre el 32 (los caracteres habituales de control) y excluir los
llamados pares subrogados. No todos los valores numéricos
son verdaderos puntos de código UTF-16, ya que hay algunos
valores numéricos no válidos para caracteres (llamados su-
brogados) utilizados para crear códigos pareados y represen-
tar puntos de código por encima de 65535.
Como mostrar una tabla de 256 * 256 es bastante duro, he
mantenido la rejilla tal como es y añadido un control Tree-
View en el costado que le permiten elegir un bloque arbitrario
de 256 puntos a mostrar. He usado un TreeView, ya que hay
256 secciones (incluidos los subrogados), por lo que he deci-
dido agruparlos en dos niveles:

Cuando el programa se inicia, se rellena el TreeView con 16


grupos de nivel superior, cada uno contiene 16 subgrupos de

13Lacodificación big-endian tiene el byte más significativo en primer lugar


(acaba en grande), la codificación little-endian tiene el byte menos significa-
tivo primero (acaba en pequeño). Como veremos enseguida, estas series de
bytes se marcan a menudo en sus archivos de cabecera con un llamado Byte
Orden Mark, Orden de Marca de Byte, (BOM).

La guía de Delphi por Marco Cantù


32 - Indice

segundo nivel, proporcionando así 256 opciones, cada una de


las cuales puede mostrar una tabla con 256 caracteres, por un
total de 64K de puntos de código (de nuevo, sin tener en
cuenta los excluidos):
procedure TForm30.FormCreate(Sender: TObject);
var
nTag: Integer;
I: Integer;
J: Integer;
topNode: TTreeNode;
begin
for I := 0 to 15 do
begin
nTag := I * 16;
topNode := TreeView1.Items.Add (nil,
GetCharDescr (nTag * 256) + '/' +
GetCharDescr ((nTag + 15)* 256));
for J := nTag to nTag + 15 do
begin
if (J < 216) or (J > 223) then
begin
TreeView1.Items.AddChildObject (
topNode,
GetCharDescr(J*256) + '/' +
GetCharDescr(J*256+255),
Pointer (J));
end
else
begin
TreeView1.Items.AddChildObject (
topNode,
'Puntos de Código Subrogado',
Pointer (J));
end;
end;
end;
end;

// Función de Ayuda
function GetCharDescr (nChar: Integer): string;
begin
if nChar < 32 then
Result := 'Char #' + IntToStr (nChar) + ' [ ]'
else
Result := 'Char #' + IntToStr (nChar) +
' [' + Char (nChar) + ']';
end;

Como puede ver en el código anterior, cada nodo del Tree-


View obtiene un número con su número de página que es
la La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 33

posición inicial de su campo de datos (generalmente un pun-


tero). Este se utiliza cuando se selecciona un segundo nivel en
el elemento TreeView (que es un nodo que tiene un nodo pa-
dre) para calcular el punto inicial de la tabla:
procedure TForm30.TreeView1Click(Sender: TObject);
var
I, nStart: Integer;
begin
if (TreeView1.Selected.Parent <> nil) then
begin
// un segundo nivel de nodo
nCurrentTab := Integer(TreeView1.Selected.Data);
nStart := nCurrentTab * 256;
for I := 0 to 255 do
begin
StringGrid1.Cells [I mod 16 + 1, I div 16 + 1] :=
IfThen (I + nStart >= 32, Char (I + nStart), '');
end;
end;
end;

Compruebe el uso de la función de IfThen para sustituir opcio-


nalmente los 32 caracteres iniciales por una cadena vacía. El
punto de partida del TreeView actual se mantiene en el campo

La guía de Delphi por Marco Cantù


34 - Indice

nCurrentTab del formulario. Esta información es necesaria


para mostrar el punto de código y su valor cuando el usuario
mueve el ratón sobre las celdillas de la tabla:
procedure TForm30.StringGrid1MouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var
gc: TGridCoord;
nChar: Integer;
begin
gc := StringGrid1.MouseCoord(X, Y);
nChar := (gc.Y - 1) * 16 + (gc.X - 1);
StatusBar1.SimpleText :=
GetCharDescr (nCurrentTab * 256 + nChar);
end;

Cuando utilice el programa y navegue por las diferentes


páginas de código de diferentes alfabetos, observará a
menudo caracteres que no se muestran correctamente. Lo
más probable es que se deba a la fuente que esté utilizando, ya
que no todas las fuentes proporcionan una representación
adecuada para todo el conjunto de caracteres Unicode. Esta es
la razón por la que he añadido al programa UnicodeMap la ca-
pacidad de seleccionar una fuente distinta (se logra con un
doble clic sobre la tabla). Puede encontrar más información
acerca de este problema en la sección "Unicode y Fuentes y
APIs" más adelante en este capítulo.

Descripciones de Puntos de
Código Unicode
En el sitio web del Consorcio de Unicode, usted podrá encon-
trar gran cantidad de información, incluso un archivo de texto
con una descripción escrita de un gran número de puntos de
código (la mayoría de ellos con exclusión de los ideogramas
unificados para el chino, japonés, y coreano). He usado este

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 35

archivo para crear una versión ampliada del programa Unico-


deMap, llamado UnicodeData. La interfaz de usuario se basa
en la misma estructura, pero el programa lee y analiza el ar-
chivo UnicodeData.txt14, y añade la descripción de caracteres
disponibles en la barra de estado cuando nos desplacemos so-
bre la rejilla:
Analizar este archivo no es francamente simple, ya que no es-
tán todos los símbolos Unicode representados. He recurrido a
la creación de un StringList con información en formato char-
number=description extraída del archivo. El archivo original uti-
liza un punto-y-coma para separar los campos y un carácter
de retorno de carro (solo, sin combinarlo con salto de línea)
para cada registro. Después de cargar el archivo entero en una
cadena, utilizo el código siguiente para analizar y mover las
dos descripciones a la sección de información (ya que a veces
sólo una u otra descripción es relevante):
nPos := 1;
// ahora analizamos el archivo Unicode
while nPos < Length (strData) - 2 do
begin
strSingleLine := ReadToNewLine (strData, nPos);
nLinePos := 1;
strNumber := ReadToSemicolon (
strSingleLine, nLinePos);
strDescr1 := ReadToSemicolon (
strSingleLine, nLinePos);
Skip8Semi (strSingleLine, nLinePos);-
strDescr2 := ReadToSemicolon (
strSingleLine, nLinePos);

sUnicodeDescr.Add(strNumber + '=' +
strDescr1 + ' ' + strDescr2);
end;

14LaURL de este archivo es: http://unicode.org/Public/UNIDATA/Unicode-


Data.txt. Existe un segundo archivo mucho mayor (que no he utilizado en la
demo) para los ideogramas unificados disponible en: http://www.uni-
code.org/Public/UNIDATA/Unihan.zip.

La guía de Delphi por Marco Cantù


36 - Indice

Este código puede ser ejecutado en el mensaje de un maneja-


dor de mensajes wm_user colocado en el evento OnCreate del
formulario principal, para permitir al sistema iniciar el for-
mulario principal antes de realizar esta larga operación. La
barra de estado se actualiza en el bucle para informar a los
usuarios del progreso actual. El bucle tiene otras sentencias
de finalización, para saltarse los caracteres por encima de
$FFFF.
La información almacenada en la lista de cadenas se extrae
cuando tiene que mostrar la descripción de un carácter, con
este código adicional del método StringGrid1MouseMove:
if Assigned (sUnicodeDescr) then
begin
strChar := IntToHex (nChar, 4);
nIndex := sUnicodeDescr.IndexOfName(strChar);
if nIndex >= 0 then
StatusBar1.SimpleText := StatusBar1.SimpleText +
' -- ' + sUnicodeDescr.ValueFromIndex [nIndex];
end;

Obteniendo la información acerca de los puntos de código, el


programa puede también crear un elemento más lógico en el
árbol. Esto no es demasiado difícil para la mayoría de los alfa-
betos, ya que la mayoría de sus símbolos tienen un nombre
genérico sin indicar que pertenecen a un grupo determinado.
Una vez hayamos obtenido nuestra propia agrupación de to-
dos los puntos de código Unicode, será posible la lectura de
distintos documentos15, sin tener que analizar de nuevo el ar-
chivo UnicodeData.txt.

15Como veremos en el próximo capítulo, la nueva unidad de código Characters


incluye métodos para el reconocimiento de un punto de código Unicode, de-
pendiendo si es un símbolo, una marca de puntuación, un espacio ...

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 37

Byte Order Mark


Los archivos de almacenamiento de caracteres Unicode a me-
nudo utilizan una cabecera de inicio, llamada Byte Orden
Mark (BOM) como una marca que indica el formato Unicode
utilizado y la ordenación de sus bytes (BE o LE). La siguiente
tabla proporciona un resumen de los distintos BOM, que pue-
den ser de 2, 3, ó 4 bytes de longitud:
00 00 FE FF UTF-32, big-endian
FF FE 00 00 UTF-32, little-endian
FE FF UTF-16, big-endian
FF FE UTF-16, little-endian
EF BB BF UTF-8

Veremos en el próximo capítulo cómo Delphi gestiona el BOM


dentro de sus clases streaming. El BOM aparece en el co-
mienzo de un archivo con los datos Unicode inmediatamente
después de este. Por lo tanto, un archivo UTF-8 con el conte-
nido AB contiene cinco valores hexadecimales (3 para el
BOM, 2 para las letras):
EF BB BF 41 42

Unicode en Win32
Desde sus primeros días, la API de Win32 (que se remonta a
Windows NT) ha incluido el soporte de los caracteres Uni-
code. La mayoría de funciones de la API de Windows tienen

La guía de Delphi por Marco Cantù


38 - Indice

dos versiones disponibles, una versión Ansi marcada con la


letra A y una versión WideString marcada con la letra W.
Como ejemplo, vea las siguientes líneas de código de Win-
dows.pas en Delphi 2007:
function GetWindowText(hWnd: HWND; lpString: PChar;
nMaxCount: Integer): Integer; stdcall;
function GetWindowTextA(hWnd: HWND; lpString: PAnsiChar;
nMaxCount: Integer): Integer; stdcall;
function GetWindowTextW(hWnd: HWND; lpString: PWideChar;
nMaxCount: Integer): Integer; stdcall;

function GetWindowText; external user32


name 'GetWindowTextA';
function GetWindowTextA; external user32
name 'GetWindowTextA';
function GetWindowTextW; external user32
name 'GetWindowTextW';

PAnsiChar o PWi-
Las declaraciones son idénticas, pero ambas utilizan
deChar para el paso del string. Observe que la versión simple
sin indicación de formato de cadena es sólo un marcador de
posición para uno de ellos, invariablemente utiliza la 'A' en la
versiones anteriores de Delphi (de dónde se ha tomado el có-
digo), mientras que en Delphi 2009 (como veremos) la opción
por defecto se convierte en la versión "W". Básicamente, cada
función del API que tiene parámetros strings tiene dos versio-
nes separadas, mientras que todas las funciones que no utili-
zan strings tienen una sola, por supuesto.
Windows 95 (y las siguientes versiones de Windows 98 y ME)
aplicaban las funciones A y proveían las funciones W como
alias que convertían Wide en Ansi. Esto significa que estas
funciones generalmente no soportan Unicode, con algunas ex-
cepciones como TextOutW (que se implementa como una ver-
dadera función Unicode en Windows 95/98/ME). Por otra
parte, Windows NT y las siguientes versiones basadas en él
(Windows 2000, XP y Vista) aplican funciones W, y proveen
las funciones A como un alias de conversión de Ansi a Wide (a
veces, ralentizando las operaciones).

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 39

Incluso en versiones anteriores de Delphi usted podría pasar


un valor WideString a una API "W", llamándola explícita-
mente. Por ejemplo, el programa UnicodeWinApi (que puede
ser compilado tanto en Delphi2007 como en Delphi 2009), el
código es:

La primera llamada la versión ANSI de MessageBox muestra


un mensaje con una secuencia de símbolos de interrogación,
mientras que la segunda (que se muestra aquí) tiene la salida
correcta16:

Observe el uso de caracteres Unicode en cadenas del código


fuente y también en el nombre de una variable local. Esto se
soportaba ya en Delphi 2006 y 2007 permitiéndole además
guardar el archivo de código fuente en formato Unicode
(UTF-16 o UTF-8). Una nueva llamada dibuja un texto en un
raro alfabeto sobre el formulario, con la salida mostrada a

16Lafrase está escrita en bengalí y significa "¿Qué es Unicode?", por lo menos se-
gún http://www.unicode.org/standard/translations/bangla.html. Para el
nombre de la variable he utilizado parte de la frase ... que probablemente (o
afortunadamente) ¡no signifique nada!

La guía de Delphi por Marco Cantù


40 - Indice

continuación. Este es el código fuente, tomado del editor de


texto:

Hay dos hechos importantes más a tener en cuenta sobre las


cadenas en el API de Win32. El primero es que algunos de los
antiguos sistemas operativos (como Windows 95) ofrecen sólo
una aplicación parcial de las funciones del API. El segundo es
que COM utiliza un enfoque diferente, con cadenas en el for-
mato BSTR, asignadas en Delphi al tipo17 non-reference-coun-
ted WideString.

Velocidad de Llamada a la API

17Másinformación sobre el tipo WideString y su relación con COM está disponi-


ble en la sección “Unicode Antes de Delphi 2009” al final de este capítulo. El
soporte COM todavía se basa en el tipo WideString Delphi en 2009, muy
poco ha cambiado en esa área del producto

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 41

Unicode
Usted puede preguntarse si utilizar Unicode llamando a la
API de Windows es más lento o más rápido que utilizando la
sencilla API "A". Huelga decir que he tenido la misma duda,
como Unicode causa algunas cargas extras en memoria, me
preguntaba si esta iniciativa realmente tiene sentido para
programas que no necesiten Unicode.
En teoría, como la única implementación de la API de Win-
dows Vista y XP es la basada en Unicode, deberíamos esperar
una velocidad mayor de ejecución, así como que el código
salte alguna conversión de cadena durante su llamada. De
modo que lo he intentado con el siguiente código, que es parte
del ejemplo UniApiSpeed:
procedure TForm30.btnUserNameClick(Sender: TObject);
var
I:Integer;
nSize: DWORD;
t1: TDateTime;
str: string;
pch: PChar;
begin
nSize := 100;
SetLength (str, nSize);
pch := PChar (str);
t1 := Now;
for I := 1 to 10000 do
begin
GetUserName (pch, nSize);
end;
t1 := Now - t1;
Memo1.Lines.Add ((Sender as TButton).Caption + ' ' +
FormatDateTime ('ss.zzz', t1));

He compilado el mismo programa en Delphi 7 y en Delphi


2009 y observé que los resultados fueron casi idénticos. He
intentado un bucle similar basado en la llamada a la API Set-
WindowText y en este caso he notado un efecto muy extraño.
Si ejecuto la aplicación bajo el depurador, tarda un 15% me-

La guía de Delphi por Marco Cantù


42 - Indice

nos tiempo que su contrapartida en Delphi 7, si se ejecuta in-


dependientemente, lo hace de forma mucho más lenta. El pro-
blema, sin embargo, es que el programa consume un tiempo
dibujando, una y otra vez, el título, por lo que estos resultados
están totalmente alterados.
Estas dos pruebas puede que no sean muy pertinentes. Debe-
ría haber intentado con muchas otras llamadas a la API para
poder tener una conclusión definitiva, pero esto demuestra
que pasando a Unicode, puede tener una velocidad similar o
ligeramente mejorada en llamadas a la API. Usted no ganará
demasiado (al menos mientras no utilice caracteres Unicode),
pero no incurrirá en ningún gasto extra por lo general18.

Parámetros UnicodeString en
llamadas al API
Aunque la mayoría de funciones de la API de Windows que
tienen como parámetro una cadena están declaradas en la
unidad Windows con un parámetro PChar, hay algunas excep-
ciones a esta regla.
Las declaraciones de la API GetTextExtentPoint32, ExtTextOut,
LoadKeyboardLayout, DrawText, SetWindowText, LoadCursor,

18Como veremos en el próximo capítulo, en efecto, hay importantes gastos gene-


rales potenciales relacionados con las conversiones implícitas de cadena rea-
lizada por el compilador de Delphi. Este es uno de los temas cubiertos en la
sección “Conversiones lentas dentro del Código” del capítulo 2. También no-
tará que algunas de las funciones ANSI de cadena relacionadas se llevaron a
cabo en Delphi 2007 llamando a rutinas del API de Windows que requieren
de conversiones y, a continuación volver a Unicode. Las llamadas a estas ru-
tinas en Delphi 2009 deberían ser mucho más rápidas, como ha detallado
Jan Goyvaerts en este blog: http://www.micro-isv.asia/2008/09/speed-be-
nefits-of-using-the-native-win32-string-type.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 43

WinHelp, y HtmlHelp tienen versiones overloaded (e inlined) to-


mando un parámetro UnicodeString. Supongo que esto po-
dría ayudar a aplicar la conversión correcta en caso que usted
esté pasando una cadena, cualquier cadena, a estas funciones
(usted podrá comprenderlo mejor después de leer sobre los
diferentes tipos de cadenas en Delphi 2009, en el próximo ca-
pítulo).
No está claro por qué estas funciones tengan este trato espe-
cial comparadas con las docenas de otras funciones de la API
de Windows que tienen parámetros PChar. Una de las razo-
nes podría ser el aumento de la compatibilidad entre la VCL
para Win32 y la VCL para. NET si el tipo de cadena que se uti-
liza preferiblemente es un Pchar.

Unicode y Fuentes y APIs


Otro hecho importante a tener en cuenta es que mientras
Windows soporta realmente Unicode, esto lo hace de formas
diferentes en sus distintas versiones. Las series Windows 9x
(una sigla que quiere decir 95, 98, o ME – Mileninium Edi-
tion) tienen un soporte limitado de Unicode. Si usted tiene el
Windows 2000, Windows XP, o algunos de los sistemas ope-
rativos para versiones servidor, puede usted tener ventaja por
el suplemento de soporte a idiomas. Este puede ser instalado
en el Idioma regional y opciones de idiomas adicionales del
panel de control. Este apoyo extra proviene principalmente
del modo fuentes extendidas (o Unicode-habilitado). Vista ha
ampliado el soporte a Unicode por defecto.
Cuando Windows XP y Vista necesitan mostrar un punto de
código Unicode, y este punto de código no está disponible en
la fuente, a veces llevan a cabo una “sustitución de fuentes”, es

La guía de Delphi por Marco Cantù


44 - Indice

decir, muestran el punto de código en una fuente distinta19.


Esto depende de la API que se llame para la visualización de
texto (DrawText, TextOut, ExtTextOut se comportan dis-
tinto), sobre la fuente que esté usando, y en el punto de código
dado. Esta es la razón por la que utilizar una fuente completa
Unicode es una buena idea20.
Si está usted interesado en más detalles, puedes echar un vis-
tazo al ejemplo UniFont - Subst, que, básicamente dibuja un
conjunto de cadenas con diferentes fuentes y diferentes lla-
madas a la API en el formulario. El programa utiliza tres tipos
de letra estándar (Times New Roman, Tahoma, y Arial) que
se muestran de arriba a abajo, y la tres llamadas a la API men-
cionadas anteriormente que se muestran de izquierda a dere-
cha. Esta es una de las tres partes (hay una para cada fuente),
del código que las dibuja:
Canvas.Font.Name := 'Times New Roman';
aRect := Rect(10, 60, 250, 100);
DrawText(Canvas.Handle, PChar (str1), Length (str1),
aRect, DT_LEFT or DT_SINGLELINE or DT_EXPANDTABS
or DT_NOPREFIX);
TextOut (Canvas.Handle, 260, 60, PChar (str1),
Length (str1));
aRect := Rect(510, 60, 750, 100);
ExtTextOut (Canvas.Handle, 510, 60, 0, aRect,
PChar (str1), Length (str1), nil);

La cadena se define mediante una secuencia de puntos de có-


digo Unicode consecutivos a partir de una posición al azar:
var
str1: string;

19Puede encontrar gran cantidad de información detallada acerca de la sustitu-


ción de fuentes realizada por diferentes llamadas a la API de Microsoft en
este artículo: http://www.microsoft.com/global-
dev/getwr/steps/wrg_font.mspx.
20Paraobtener información y disponibilidad de fuentes Unicode para Windows,
puede referirse a Recursos Unicode de Alan Wood(data de unos pocos años
atrás, pero se mantiene hasta la fecha) en: http://www.alanwood.net/uni-
code/fonts.html

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 45

nPoint: Word;
I: Integer;
begin
nPoint := 32 + random (1024*64 - 32 - numberOfChars);
if (nPoint >= $D800 - numberOfChars) and
(nPoint <= $DFFF) then
begin
// reintentar y saltar
PaintFonts;
Exit;
end;

str1 := ConvertFromUtf32(UCS4Char (nPoint));


for I := 1 to numberOfChars do
str1 := str1 + ConvertFromUtf32(
UCS4Char (nPoint + I));

En este programa se puede pintar el formulario de una vez o


activar un temporizador automático para consecutivas pro-
yecciones. En la mayoría de los casos, verá la misma cadena
mostradas 9 veces. Muy a menudo, sin embargo, verá que el
texto no se muestra correctamente cuando se utilizan deter-
minados tipos de letra (Tahoma, más comúnmente), sustitu-
yéndose por bloques cuadrados. En otros casos, verá que al-
gunas de las llamadas a la API (en DrawText en particular)
puede mostrar los puntos de código de cualquier forma, pero
reemplazando la fuente con otra distinta. A continuación se
muestra una captura de pantalla en la que se puede ver ambos
casos a la vez:

La guía de Delphi por Marco Cantù


46 - Indice

Unicode Antes de Delphi 2009


Si Unicode se soporta en el núcleo de nuestro lenguaje (para
el tipo string) e igualmente las bibliotecas runtime y las bi-
bliotecas de componentes visuales (VCL) es ciertamente, una
nueva característica de Delphi 2009, el soporte parcial de
Unicode ha sido parte de Delphi desde hace muchos años.
Desde anteriores versiones, en su mayoría para apoyar COM,
Delphi tuvo un tipo de datos WideChar (caracteres 16-bits) y
un tipo de datos WideString (strings de caracteres WideChar).
Sin embargo, el tipo WideString no era (y sigue sin serlo) una
referencia óptima y es mucho menos eficiente que las cadenas
habituales de Delphi. Ello no es más que una encapsulación
del tipo21 de dato COM BSTR.
También hay varias unidades y clases con un apoyo específico
para WideStrings, incluida la unidad WideStrUtils de caracte-
rísticas extras (que también incluye una buena cantidad de
funciones relacionadas con UTF-8), la clase TWideStringList, y
WideString ampliamente soportadas en las clases TDataSet y
Tfield.

A continuación
No quiero ahondar en los detalles de las características que
cambian considerablemente en Delphi 2009 y que tendremos
tiempo para cubrir posteriormente en este libro. Ahora es
hora de empezar a buscar la aplicación efectiva de las cadenas

21WideString es un BSTR COM que usa UTF-16 en Windows 2000 y superiores,


si bien es sobre la base de UCS-2 en Win9x y NT. Algunos detalles más están
disponibles en: http://msdn.microsoft.com/en-us/library/ms221069.aspx

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 47

Unicode, el nuevo tipo UnicodeString. Hay muchos cambios


en el uso de cadenas en Delphi 2009, esta es la razón por la
que el próximo capítulo es uno de los más largos en el libro.

La guía de Delphi por Marco Cantù


48 - Indice

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 49

Capítulo 2: El
Tipo Unicode
String

Como usted seguramente sabe, una de las nuevas, y mejores


características de Delphi 2009, es la introducción de un nuevo
tipo de cadena UnicodeString, que es también el formato li-
gado al tipo básico string. Cada vez que escribe "string" en su
código, se está refiriendo de hecho a UnicodeString, mientras
que en versiones anteriores de Delphi (con excepción de Del-
phi 1) se referían a AnsiString.
Junto con Char que se convierte en un alias de WideChar, este
es un cambio muy significativo, que afecta a todo su código
base. Es por eso que no será suficiente un solo capítulo para
explicar todo lo que necesita saber. Voy a tratar la totalidad
del nuevo tipo string, pero son muchos e inevitables los incon-
venientes que surgen al importar código Delphi existente a la
nueva versión del compilador Embarcadero, habilitada para
Unicode, que trataré en el próximo capítulo.

La guía de Delphi por Marco Cantù


50 - Indice

De AnsiChar a WideChar
Durante algún tiempo, Delphi ha incluido dos tipos de datos
para representar caracteres:
 AnsiChar, con una representación de 8 bits (con un total de
256 símbolos diferentes), interpretados en función de su có-
digo de página;
 WideChar, con 16 bits de representación (con un total de 64K
de símbolos diferentes)22.
En este sentido, nada ha cambiado en Delphi 2009. Lo que es
diferente es que el tipo Char antes era un alias de AnsiChar y
ahora es un alias de WideChar. Cada vez que el compilador
detecta Char en su código, lee realmente WideChar. Tenga en
cuenta que no hay forma de cambiar esta nueva compilación
por defecto23.
Este es un cambio, que afecta a una gran cantidad de código
fuente y con muchas ramificaciones. Por ejemplo, el puntero
PChar ahora un alias de PWideChar, en lugar de PAnsiChar,
como solía ser. Veremos cómo esto afecta a las llamadas a las

22WideChar es simplemente un entero de 16-bits sin signo sin ningún tipo de co-
dificación de caracteres específica adjunta. Cuando se utiliza un UnicodeS-
tring, sin embargo, un WideChar puede interpretarse como un subrogado, de
modo que dos WideChar pueden ser enlazados para representar a un único
punto de código Unicode. Amplía esta información en la sección “UnicodeS-
tring y Unicode”.
23Como con el tipo string, el tipo Char está específicamente mapeado a un tipo
fijo de datos por código. Los desarrolladores solicitaron una directiva de
compilación para cambiarlo, pero sería una pesadilla en terminos de control
de calidad, soporte o compatibilidad de paquetes entre otras cosas. De todas
formas, todavía tiene la opciónde convertir su código para que use un tipo
específico como el AnsiChar.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 51

funciones de la API de Windows en una sección posterior de


este capítulo.

Char como un Tipo Ordinal


El nuevo tipo Char de gran tamaño sigue siendo un tipo ordi-
nal, de modo que puede utilizar Inc y Dec sobre él, para escri-
bir bucles con un contador Char, y cosas por el estilo.
var
ch: Char;
begin
ch := 'a';
Inc (ch, 100);
...
for ch := #32 to High(Char) do
str := str + ch;

Estos fragmentos de código son parte de los diferentes méto-


dos del ejemplo TestChar. El único asunto que podría ocasio-
narle algún problema (limitado) es cuando declare un con-
junto basado en el tipo Char:
var
CharSet = set of Char;
begin
charSet := ['a', 'b', 'c'];
if 'a' in charSet then
...

En este caso, el compilador asume que usted importa código


existente a Delphi 2009, y decide considerar la posibilidad de
que este Char sea un AnsiChar (ya que como un set sólo puede
tener 256 elementos como máximo24) y emitir un mensaje de
advertencia:
W1050 WideChar reduced to byte char in set expressions.

24Si usted intenta declarar un amplio conjunto de, digamos, una serie de núme-
ros enteros (como lo hice en algunas líneas comentadas de código del ejem-
plo CharTest) obtendrá el error: E2028 Sets may have at most 256 ele-
ments.

La guía de Delphi por Marco Cantù


52 - Indice

Consider using 'CharInSet' function in 'SysUtils' unit.

El código probablemente funcione como se espera, pero no


todo el código recuperado se transformará fácilmente, ya que
no es posible obtener un conjunto de todos los caracteres
nunca más. Si esto es lo que usted necesita, tendrá que cam-
biar su algoritmo (posiblemente siguiendo lo que nos sugiere
la advertencia, tocaré este tema ampliándolo en el siguiente
capítulo, en la sección “Cuidado con Set of Char”, centrado en
importar código existente a Delphi 2009).
Si lo que está buscando, en cambio, es suprimir las adverten-
cias (la compilación de las cinco líneas de código anterior cau-
san dos de estas) puede escribir:

var
charSet: set of AnsiChar; // suprime la alerta
begin
charSet := ['a', 'b', 'c'];
if AnsiChar('a') in charSet then // suprime la alerta
...

Conversiones con Chr


Valore también que usted no sólo puede convertir un valor
numérico a un carácter usando la conversión al tipo AnsiChar
o WideChar, sino también sobre la base de la clásica técnica
Pascal, el uso de la función mágica del compilador Chr (que
puede ser considerada como la contraria de Ord). Esta función
mágica se ha ampliado para pasar una palabra como paráme-
tro, en lugar de un byte.
Fíjese, sin embargo, que, a diferencia de caracteres literales
(cubiertos en la sección “Cadenas y Caracteres Literales” más
adelante en este mismo capítulo), las llamadas a Chr estarán
ahora siempre interpretadas en el ámbito Unicode. Por lo
tanto, si su antiguo código declara:
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 53

Chr (128)

desde Delphi 2007 a Delphi 2009 puede tener alguna sor-


presa. Si utiliza #128, en su lugar, usted podría obtener un re-
sultado diferente, o no, dependiendo de su código de página.

Caracteres de 32-bit
A pesar de que el tipo por defecto Char está ahora asignada a
WideChar, vale la pena comentar que Delphi define también
un tipo de carácter de 4-bytes, UCS4Char, que se define en la
unidad System como:
type
UCS4Char = type LongWord;

Si bien este tipo de definición y el correspondiente para


UCS4String (definido como un array de UCS4Char) ya se encon-
traban en Delphi 2007, la relevancia del tipo de datos
UCS4Char en Delphi 2009 viene del hecho de que es ahora
cuando se utilizan significativamente en varias rutinas de la
RTL, incluidas las de la nueva unidad Character que tratare-
mos próximamente.

La Nueva Unidad Character


Para un mejor soporte de los nuevos caracteres Unicode (y
también de las cadenas Unicode, por supuesto) Delphi 2009
introduce una nueva unidad en la RTL, denominada Charac-
ter25. Esta unidad define la clase sellada TCharacter que es

25El "Character" como nombre de unidad parece un poco fuera de sincronización


con la nomenclatura general de normas adoptadas por la RTL de Delphi,
donde "Utils" es a menudo el final de la colección de las funciones. Depende
más del hecho de que, en efecto, hay una clase dentro de la unidad, llamada
TCharacter, aunque es una clase muy extraña.

La guía de Delphi por Marco Cantù


54 - Indice

básicamente una colección de funciones26, estáticas de clase,


además de una serie de rutinas globales asignadas a las fun-
ciones de clase como públicas (y algunas como privadas).
La unidad también define dos tipos enumerados interesantes.
El primero se llama TUnicodeCategory y mapea distintos ca-
racteres en amplias categorías tales como control, espacio,
mayúsculas o minúsculas, número decimal, puntuación, sím-
bolos matemáticos, y muchos más. El segundo enumerado se
llama TUnicodeBreak y define la familia de los diversos espa-
cios, guión, y retornos.
La clase sellada TCharacter tiene más de 40 métodos que o
bien trabajan en un carácter independiente o bien dentro de
una cadena:
 Obtener la representación numérica del carácter (GetNume-
ricValue).

 Solicitar la categoría (GetUnicodeCategory) o clasificarla contra


una de las distintas categorías (IsLetterOrDigit, IsLetter, Is-
Digit, IsNumber, IsControl, IsWhiteSpace, IsPunctuation, IsSym-
bol, y IsSeparator)

 Comprobar si es minúscula o mayúscula (IsLower y IsUpper) o


la conversión de estas (ToLower y ToUpper)
 Verificar si es parte de un par subrogado a UTF-16 (IsSurroga-
tePair, IsSurrogate, IsLowSurrogate, e IsHighSurrogate)

 Convertirlo desde y hacia UTF32 (ConvertFromUtf32 y Con-


vertToUtf32)

Las funciones globales son casi una coincidencia exacta de es-


tos métodos estáticos de clase, algunos de los cuales corres-
ponden a las funciones de la RTL de Delphi aunque en general

26Parala definición de métodos de clase estáticos y clases selladas ver, entre


otras fuentes, mi Manual de Delphi 2007.

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 55

con diferentes nombres. Hay sobrecarga de algunas de las


funciones RTL básicas de trabajo con caracteres, con versio-
nes ampliadas que requieren el código Unicode-habilitado.
Por ejemplo, en el programa CharTest he añadido los siguien-
tes fragmentos al tratar de convertir a una letra acentuada en
mayúsculas:
var
ch1: Char;
ch2: AnsiChar;
begin
ch1 := 'ù';
Memo1.Lines.Add ('WideChar');
Memo1.Lines.Add ('UpCase ù: ' + UpCase(ch1));
Memo1.Lines.Add ('ToUpper ù: ' + ToUpper (ch1));

ch2 := 'ù';
Memo1.Lines.Add ('AnsiChar');
Memo1.Lines.Add ('UpCase ù: ' + UpCase(ch2));
Memo1.Lines.Add ('ToUpper ù: ' + ToUpper (ch2));

El código tradicional de Delphi (la función UpCase sobre la ver-


sión AnsiChar) se ocupa solamente de caracteres ASCII, por
lo que no convertirá este carácter27. Lo mismo también es
aplicable (probablemente por razones de compatibilidad ha-
cia atrás) si usted pasa un WideChar a la misma. La función
ToUpper es correcta (termina llamando a la función CharUpper
de la API de Windows):
WideChar
UpCase ù: ù
ToUpper ù: Ù
AnsiChar
UpCase ù: ù
ToUpper ù: Ù

Observe que puede mantener su actual código de Delphi, con


la llamada a UpCase sobre un Char, y que se mantendrá la
norma de comportamiento de Delphi.

27Lomismo también es válido para la función UpperCase, que gestiona sólo AS-
CII, mientras que AnsiUpperCase gestiona todo en Unicode, a pesar de su
nombre.

La guía de Delphi por Marco Cantù


56 - Indice

Para una mejor demostración de las características menciona-


das en Unicode introducidas por la unidad Characters, puede
ver el método btnUTF16Click del ejemplo CharTest que define
una cadena como28:
var
str1: string;
begin
str1 := '1.' + #9 + ConvertFromUtf32 (128) +
ConvertFromUtf32($1D11E);

El programa a continuación, hace las siguientes pruebas (to-


das retornando True) sobre los diversos caracteres de la ca-
dena:
TCharacter.IsNumber(str1, 1)
TCharacter.IsPunctuation (str1, 2)
TCharacter.IsWhiteSpace (str1, 3)
TCharacter.IsControl(str1, 4)
TCharacter.IsSurrogate(str1, 5)

Por último, compruebe cómo la función IsLeadChar de SysU-


tils se ha modificado para manejar pares subrogados Unicode,
así como otras funciones relacionadas con las utilizadas para
pasar al siguiente carácter de una cadena y similares. Voy a
utilizar algunas de estas funciones de las que trabajan con una
cadena con un par subrogado en la sección ”UnicodeString y
Unicode”.

Sobre String y

28Punto de código Unicode $1D11E es el símbolo musical clave de

Sol. La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 57

UnicodeString
El cambio en la definición del tipo Char es importante porque
está ligado con el cambio en la definición del tipo String. A di-
ferencia de characters, strings se asigna a un nuevo tipo de
datos que antes no existía, llamado UnicodeString. Como ve-
remos, su representación interna es también, muy diferente
de la del tipo classic AnsiString29.
Como ya había un tipo WideString en el lenguaje, represen-
tando cadenas basadas sobre el tipo WideChar, ¿por qué mo-
lestarse con la definición de un nuevo tipo de datos? WideS-
tring no tenía (y sigue sin tener) un recuento de referencia y
es extremadamente pobre en términos de rendimiento y flexi-
bilidad (por ejemplo, utiliza el asignador de memoria global
de Windows, antes que los nativos FastMM4).
Al igual que AnsiString, UnicodeString tiene recuento de refe-
rencia, utiliza semánticas copy-on-write y se resuelve bas-
tante bien. A diferencia de AnsiString, UnicodeString utiliza
dos bytes por carácter30 y se basa en UTF-16.
El tipo string está ahora asignado al tipo UnicodeString en un
código duro tal como lo está el tipo Char por las mismas razo-
nes. No hay ninguna directiva del compilador u otro truco
para cambiar esto. Si tiene código que debe seguir utilizando

29Estoyusando el término específico del tipo classic AnsiString, para referirme a


la cadena con el tipo utilizado para trabajar a partir de Delphi 2 hasta Delphi
2007. El tipo AnsiString sigue siendo parte de Delphi 2009, pero tiene un
comportamiento modificado, de modo que al referirse a su estructura ante-
rior usaré el término classic AnsiString.
30Enrealidad UTF-16 es una codificación de longitud variable, y en ocasiones
UnicodeString utiliza dos elementos WideChar subrogados (es decir, cuatro
bytes) para representar a un único punto de código Unicode

La guía de Delphi por Marco Cantù


58 - Indice

el antiguo tipo de cadenas, basta con sustituir su declaración


por una explícita del tipo AnsiString.

La estructura interna de los


Strings
Uno de los principales cambios relacionados con el nuevo tipo
UnicodeString es su representación interior. Esta nueva re-
presentación, sin embargo, es compartida por todos los tipos
strings con recuento de referencias UnicodeString y AnsiS-
tring, pero no por los tipos de cadenas de referencia no con-
tada, incluidos los tipos ShortString31 y WideString.
La representación del tipo classic AnsiString fue la siguiente:

-8 -4 Dirección de referencia de la cadena

Ref count length Primer carácter de la cadena

El primer elemento (contando hacia atrás desde el comienzo


de la misma cadena) es la longitud de la cadena Pascal, el se-
gundo elemento es el recuento de referencia. En Delphi 2009,
la representación de cadenas de referencia-contada se con-
vierte en:

-12 -10 -8 -4 Dirección de referencia de la


cadena

31ShortString es el nombre de la tradicional cadena tipo Pascal, una cadena de


AnsiChar limitada de 255 caracteres, ya que utiliza una longitud de byte
como primer elemento. El tipo ShortString era la definición original de la ca-
dena en Delphi 1. Desde que Delphi 2 introdujo referencias largas de cadenas
contadas, el uso de ShortString ha disminuido, pero hay casos específicos en
los que son agradables de usar y se ejecutan mejor.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 59

Code page Elem size Ref count length Primer carácter de la cadena

Al lado de la longitud y la cuenta de referencia, los nuevos


campos representan el tamaño de cada elemento y la página
de código. Si bien el tamaño del elemento se utiliza para dis-
criminar entre AnsiString y UnicodeString, el código de pá-
gina tiene sentido en particular para el tipo AnsiString (ya que
funciona en Delphi 2009), mientras que el tipo UnicodeString
tiene el código de página fijo 1200.
El soporte correspondiente a la estructura de datos se declara
en la sección implementation32 de la unidad System como:
type
PStrRec = ^StrRec;
StrRec = packed record
codePage: Word;
elemSize: Word;
refCnt: Longint;
length: Longint;
end;

Con la sobrecarga de una cadena pasando de 8 bytes a 12 by-


tes, uno podría preguntarse si una representación más com-
pacta no sería más eficaz, aunque los nuevos campos son más
compactos que los tradicionales (que podrían cambiarse sólo
a expensas de la compatibilidad).
Este es un clásico estira-y-afloja entre la memoria y la veloci-
dad: mediante el almacenamiento de datos en diferentes zo-
nas de la memoria (y no utilizando partes de una única ubica-
ción) debemos ganar un extra en velocidad de ejecución, aun-
que represente un costo extra de memoria por cada cadena
que creemos.

32Como la sección de implementación no se puede utilizar en nuestro código, lo


cual es comprensible para el mantenimiento de la estructura interna de da-
tos, cuya implementación es susceptible a futuros cambios. Hay funciones de
ayuda para acceder a la información que generalmente necesitamos usar.

La guía de Delphi por Marco Cantù


60 - Indice

Si bien en el pasado se tenían que utilizar punteros basados


en código de bajo nivel para el acceso a la cuenta de referen-
cia, la RTL de Delphi 2009 añade algunas funciones a mano
para acceder a las distintas cadenas de metadatos:
function StringElementSize(const S: UnicodeString): Word;
function StringCodePage(const S: UnicodeString): Word;
function StringRefCount(const S: UnicodeString): Longint;

Hay también una nueva función de ayuda en la unidad SysU-


tils, llamada ByteLength, que devuelve el tamaño de un Uni-
codeString en bytes haciendo caso omiso de los atributos
StringElementSize (así, curiosamente, no va a trabajar con ca-
denas de tipos distintos de UnicodeString).
Como ejemplo, puede crear una cadena y pedir alguna infor-
mación acerca de esta, como ya hice en el ejemplo StringTest:
var
str1: string;
begin
str1 := 'foo';
Memo1.Lines.Add ('SizeOf: ' + IntToStr (SizeOf (str1)));
Memo1.Lines.Add ('Length: ' + IntToStr (Length (str1)));
Memo1.Lines.Add ('StringElementSize: ' +
IntToStr (StringElementSize (str1)));
Memo1.Lines.Add ('StringRefCount: ' +
IntToStr (StringRefCount (str1)));
Memo1.Lines.Add ('StringCodePage: ' +
IntToStr (StringCodePage (str1)));
if StringCodePage (str1) = DefaultUnicodeCodePage then
Memo1.Lines.Add ('Is Unicode');
Memo1.Lines.Add ('Size in bytes: ' +
IntToStr (Length (str1) * StringElementSize (str1)));
Memo1.Lines.Add ('ByteLength: ' +
IntToStr (ByteLength (str1)));

Este programa produce un resultado similar al siguiente:


SizeOf: 4
Length: 3
StringElementSize: 2
StringRefCount: -1
StringCodePage: 1200
Is Unicode
Size in bytes: 6
ByteLength: 6

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 61

El código de página devuelto por un UnicodeString es 1200,


este número se almacena en la variable global DefaultUnicode-
CodePage. En el código anterior (y en su salida) se puede ob-
servar claramente que no hay una llamada directa a fin de de-
terminar la longitud de una cadena de bytes, dónde Length de-
vuelve el número de caracteres.
Por supuesto, usted puede (en general) multiplicar este por el
tamaño en bytes de cada carácter, mediante la siguiente fór-
mula:
Length (str1) * StringElementSize (str1)

No sólo puede preguntar por la información de una cadena,


pues también puede cambiar algo de la misma. Una manera
de convertir una cadena de bajo-nivel es llamar al procedi-
miento SetCodePage (una operación que sólo se pueden apli-
car a un tipo RawByteString type, como veremos), que sólo
puede ajustar el código a la página real o realizar una conver-
sión completa de la cadena. Voy a utilizar este procedimiento
en la sección "Conversión de Cadenas".

UnicodeString y Unicode
Huelga decir que el nuevo tipo string (o el nuevo tipo Unico-
deString, para ser más preciso) utiliza el conjunto de caracte-
res Unicode. Si ha leído el anterior capítulo, la pregunta sería,
“¿Qué sabor de Unicode?”
No debería de sorprendernos conocer que los nuevos tipos de
cadena usan, como ya he mencionado, UTF-1633. Esto tiene
mucho sentido por muchas razones, la más importante es que

33Másprecisamente, el tipo de UnicodeString almacenado en la memoria como


un UTF-16 con una cadena de representación little endian, o UTF-16 LE.

La guía de Delphi por Marco Cantù


62 - Indice

este es el tipo nativo de cadena gestionado por la API de Win-


dows en las recientes versiones de este sistema operativo.
Como hemos visto en la sección correspondiente al tipo Wide-
Char en Delphi 2009, la nueva clase de apoyo TCharacter (no
sólo se utiliza para WideChar, sino también para procesar
UnicodeString) tiene pleno soporte de UTF-16 y de pares su-
brogados. Lo que no mencioné en esa sección es que esto
tiene el efecto secundario sensible de hacer el número de ele-
mentos de una cadena WideChar diferente del número de
puntos de código Unicode que esta contiene, ya que un único
punto de código Unicode puede ser representado por un par
subrogado (es decir dos WideChar).
Una forma de crear una cadena con pares subrogados es utili-
zar la función ConvertFromUtf3234 que devuelve una cadena
con el sustituto par (dos WideChar) en las circunstancias pro-
pias, como las siguientes:
var
str1: string;
begin
str1 := 'Surr. ' + ConvertFromUtf32($1D11E);

Si pregunta ahora por la longitud de la cadena, obtendrá 8,


que es el número de WideChar, pero no el número lógico de
puntos de código Unicode en la cadena. Si imprime la cadena
consigue el efecto adecuado (bueno, al menos Windows en ge-
neral, muestra un bloque cuadrado como sustituto de la pa-
reja, en lugar de dos).
Como se ha demostrado con el método de btnSurrogateClick
del formulario principal del ejemplo StringTest, calcular el

34Enel código de ConvertFromUtf32 (o más precisamente en el método de clase


ConvertFromUtf32 que llama a la clase Tcharacter) se puede ver el algo-
ritmo actual usado para asignar puntos de código Unicode en pares subroga-
dos. Interesante lectura si está interesado en los detalles.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 63

número lógico de puntos de código Unicode podría ser más


complejo de lo que usted esperaba:
n := CharToByteLen (str1, Length (str1) - 1 );
CountChars (str1, Length (str1), cChar, cByte);
n := cChar – 1;

Una cuestión relacionada es lo que ocurre cuando se repro-


duce un bucle para cada carácter de la cadena. Lo normal de
un bucle con ciclo for-in es que tan sólo le permita trabajar en
cada elemento WideChar de la cadena, no en cada punto de
código lógico Unicode. Entonces debería usar el bucle while
basado en la función NextCharIndex o adaptar el bucle para
que compruebe los subrogados:
if TCharacter.IsHighSurrogate (str1 [I]) then
Memo1.Lines.Add (str1 [I] + str1 [I+1])

La lista completa para ambos casos está disponible en el


mismo ejemplo StringTest, y que no listo aquí, como en la
mayoría de los casos, se puede asumir que trabaja con el BMP
(Basic Multilingual Plane) que trata a cada WideChar de la ca-
dena Unicode como un único punto de código35.

El Tipo UCS4String
Existe también otro tipo de cadena que puede utilizar para
manejar una serie de puntos de código Unicode, el tipo
UCS4String. Este tipo de datos representa una matriz diná-
mica de caracteres de 4-bytes (del tipo UCS4Char). Como tal,
no tiene ningún recuento de referencia o apoyo copy-on-
write, y muy poco soporte en la RTL.

35El hecho de que dos puntos de código Unicode pueda ser visualizado como un
único grafema (véase la sección de "puntos de código Unicode y Graphemes"
en el Capítulo 1) hace que sea aún más difícil mapear el número de Wide-
Char en una cadena Unicode con el número de caracteres de la pantalla.

La guía de Delphi por Marco Cantù


64 - Indice

Aunque este tipo de datos (que ya estaba disponible en Delphi


2007) puede ser utilizado en situaciones específicas, no es es-
pecialmente adecuado para las circunstancias generales. Sin
duda puede ser un residuo en la memoria, ya que no es sólo
cadenas utilizando 4 bytes por carácter, sino que además
puede terminar con múltiples copias en la memoria.

Los múltiples tipos de


cadenas
Junto con la introducción del nuevo tipo UnicodeString, la ac-
tualización en la representación interna compartida por todos
los tipos de cadenas (incluido el tipo AnsiString) deja espacio
para algunas mejoras adicionales en la gestión del string. El
equipo I+D de Delphi tomó ventaja de esta nueva representa-
ción interna (y todo el trabajo se realizó a nivel del compila-
dor para mejorar la gestión de la cadena) para podernos pro-
porcionar actualmente múltiples tipos de datos e incluso una
nueva definición del mecanismo de tipo string.
La tipos predefinidos de cadenas con recuento de referencia36,
además de UnicodeString, son los siguientes:
 AnsiString un tipo cadena de único-byte-por-carácter basado
en la actual página de códigos del sistema operativo, sigue
muy de cerca del AnsiString clásico de versiones anteriores de
Delphi;

36Estoexcluye los tipos de cadenas que no son por referencia, entre los que se in-
cluyen los tipos ShortString, WideString, y UCS4String.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 65

 UTF8String es una cadena basada en la variable de caracteres


de formato UTF-8;
 es una matriz de caracteres sin página de códigos estableci-
dos, sobre la que la conversión de caracteres no se logra por el
sistema (por lo tanto, en parte se asemeja a la clásica AnsiS-
tring, cuando se utiliza como una matriz pura de caracteres)
.37
La definición de tipo de mecanismo se revela cuando se ob-
serva en la definición de estos nuevos tipos de cadenas:
type
UTF8String = type AnsiString(65001);
RawByteString = type AnsiString($FFFF);

En esta sección voy a cubrir el AnsiString y los tipos persona-


lizados de cadenas y, a continuación, el tipo UTF8String. Voy
a centrarme en RawByteString en la siguiente sección que
abarca conversiones de cadenas, tal como usted utilizará ge-
neralmente este tipo de cadenas para evitar las conversiones.

El Nuevo Tipo AnsiString


A diferencia del pasado, el nuevo tipo de cadena AnsiString
lleva una más pieza de información, la página de códigos de
los caracteres de la cadena. El DefaultSystemCodePage cuya va-
riable por defecto es CP_ACP, actual código de la página en
Windows, pero puede ser modificado por llamar al procedi-
miento especial SetMultiByteConversionCodePage. Esto se
puede hacer para obligar a todo un programa a trabajar (por
defecto) con caracteres de una determinada página de códigos

37Conmatrices de bytes disponibles (tal como se contempla en el capítulo si-


guiente) usted debe tratar de pasar a la construcción más específica, aunque
RawByteString le permitirá migrar los datos de código con menos esfuerzo.

La guía de Delphi por Marco Cantù


66 - Indice

(que la instalación del sistema operativo debe soportar, por


supuesto).
En general, en cambio, usted se atendría a la página de código
corriente, o la cambiaría para cadenas individuales, llamando
al procedimiento SetCodePage (presentado anteriormente,
mientras he comentado sobre caracteres y páginas de códi-
gos). Este procedimiento puede ser llamado de dos maneras
diferentes. En el primer caso, sólo cambia el código de página
para una cadena (cargada tal vez por un archivo separado o
socket), ya que usted conoce su formato original. En el se-
gundo caso, usted puede llamar la función para convertir una
cadena dada (algo que ocurre automáticamente en la asigna-
ción de una cadena a otra con un códigos de página diferente,
como se explica más adelante).
Aunque puede seguir utilizando el tipo AnsiString para tener
una representación en memoria más compacta de las cadenas,
en la mayoría de los casos usted realmente preferirá convertir
su código para utilizar el nuevo tipo UnicodeString. Esto es,
mantener sus cadenas declaradas con el string de tipo gené-
rico. Sin embargo, hay circunstancias en que es necesario uti-
lizar una cadena con tipo específico. Por ejemplo, cuando esté
cargando o guardando archivos, transfiriendo los datos desde
y hacia una base de datos, utilizando protocolos de Internet
donde el código debe seguir estando basado en un formato de
caracteres de 8 bits. En todos estos casos, convierta su código
para utilizar AnsiString38.

Creando una cadena de tipo

38Más técnicas de importación se tratarán en el Capítulo 3. La

guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 67

personalizado
Además de utilizar el nuevo tipo AnsiString, que está vincu-
lado por defecto a la página de códigos utilizada cuando com-
piló la aplicación, usted puede utilizar el mismo mecanismo
para definir su propio tipo de cadena personalizada. Por
ejemplo, puede definir un tipo de cadena Latin-1 (como lo he
hecho en el ejemplo LatinTest) escribiendo:

type
Latin1String = type AnsiString(28591);

procedure TFormLatinTest.btnNewTypeClick(
Sender: TObject);
var
str1: Latin1String;
begin
str1 := 'a string with an accent: Cantù';
Log ('String: ' + str1);

Puede utilizar este tipo de cadenas como cualquier otro, pero


estará vinculado a un determinado código de la página. Por lo
tanto, si usted usa este tipo de cadenas, al convertir un La-
tin1String en un UnicodeString (por ejemplo, para mostrarla
en la llamada Log anterior), el compilador Delphi añadirá una
conversión más. La última línea del fragmento de código, ha
ocultado un llamamiento a _UStrFromLStr, que termina lla-
mando a más funciones internas de la unidad System, hasta la
operación de conversión real realizada por el MultiByteToWi-
deChar de la API de Windows. Esta es la secuencia de llama-
das39:
procedure _UStrFromLStr(var Dest: UnicodeString;
const Source: AnsiString);

39Como veremos más adelante en la sección "Conversión de Cadenas" estas con-


versiones pueden considerablemente ralentizar las operaciones propias. Es
por eso que el compilador emite advertencias similares en operaciones im-
plícitas de conversión.

La guía de Delphi por Marco Cantù


68 - Indice

procedure InternalUStrFromPCharLen(
var Dest: UnicodeString; Source: PAnsiChar;
Length: Integer; CodePage: Integer);
function WCharFromChar(WCharDest: PWideChar;
DestChars: Integer; const CharSource: PAnsiChar;
SrcBytes: Integer; CodePage: Integer): Integer;
function MultiByteToWideChar(CodePage, Flags: Integer;
MBStr: PAnsiChar; MBCount: Integer;
WCStr: PWideChar; WCCount: Integer): Integer; stdcall;
external kernel name 'MultiByteToWideChar';

La API de Windows puede realizar las conversiones adecua-


das, pero estas son potencialmente conversiones con pérdi-
das, ya que incluso algunos caracteres disponibles en las dis-
tintas páginas de código Windows no pueden estar represen-
tados en Latin1. Un ejemplo de esto sería el símbolo del Euro,
otra simpática anécdota.
El método anterior btnNewTypeClick sigue mostrando algunos
detalles más de la cadena:
Log ('Last char: ' + IntToStr (
Ord (str1[Length(str1)])));
Log('ElemSize: ' + IntToStr (StringElementSize (str1)));
Log('Length: ' + IntToStr (Length (str1)));
Log ('CodePage: ' + IntToStr (StringCodePage (str1)));

Ejecutar este código produce los siguientes resultados:


Last char: 249
ElemSize: 1
Length: 30
CodePage: 28591

Para demostrar que mi nueva cadena personalizada trata de


manera diferente que el tipo estándar AnsiString (por lo me-
nos en mi ordenador y con mi configuración local), he escrito
un método de prueba en el proyecto LatinTest que agrega el
mismo carácter final mayúscula (de #128 a #255) a ambos, una
AnsiString y un Latin1String, mostrándose en grupos en un
Memo:
procedure TFormLatinTest.btnCompareCharSetClick(
Sender: TObject);
var
str1: Latin1String;
str2: AnsiString;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 69

I: Integer;
begin
for I := 128 to 255 do
begin
str1 := str1 + AnsiChar (I);
str2 := str2 + AnsiChar (I);
end;

for I := 0 to 15 do
begin
Log (IntToStr (128 + I*8) + ' - ' +
IntToStr (128 + I*8 + 7));
Log ('Lati: ' + Copy (str1, 1 + i*8, 8));
Log ('Ansi: ' + Copy (str2, 1 + i*8, 8));
end;
end;

La parte inicial del resultado pone de relieve las diferencias


entre los dos conjuntos (de nuevo, el resultado verá como
puede variar en función de su propia ubicación):
128 - 135
Lati: ? ,f".??
Ansi: € ‚ƒ„…†‡
136 - 143
Lati: ^?S<O Z
Ansi: ˆ‰Š‹Œ Ž
144 - 151
Lati: ''"".--
Ansi: ‘’“”•–—
152 - 159
Lati: ~Ts>o zY
Ansi: ˜™š›œ žŸ

Dicho esto, al menos en mi latitud, un ejemplo mucho más in-


teresante sería utilizar el código de página de un alfabeto no
latino, como el Cirílico. Como un ejemplo, se define un se-
gundo tipo de cadena personalizada en el proyecto LatinTest:
type
CyrillicString = type Ansistring(1251);

Puede utilizar esta cadena en un modo muy similar al anterior


fragmento de código, pero la parte interesante es utilizar el
orden de caracteres altos, aquellos con un valor numérico de
más de 127. He elegido unos cuantos con un bucle for:
procedure TFormLatinTest.btnCyrillicClick(
Sender: TObject);
var
La guía de Delphi por Marco Cantù
70 - Indice

str1: CyrillicString;
I: Integer;
begin
str1 := 'a string with an accent: Cantù';
Log ('String: ' + str1);
Log ('Last char: ' + IntToStr (
Ord (str1[Length(str1)])));
Log('ElemSize: ' + IntToStr (StringElementSize (str1)));
Log('Length: ' + IntToStr (Length (str1)));
Log ('CodePage: ' + IntToStr (StringCodePage (str1)));

str1 := '';
for I := 150 to 250 do
str1 := str1 + CyrillicString(AnsiChar (I));
Log ('High end chars: ' + str1);
end;

La salida de este método es la siguiente:


String: a string with an accent: Cantu
Last char: 117
ElemSize: 1
Length: 30
CodePage: 1251
High end chars: –— ™љ›њќћџ ЎўЈ¤Ґ¦§Ё©Є«¬-
®Ї°±Ііґµ¶·ё№є»јЅѕїАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзи
йклмнопрстуфхцчшщъ

Usted podrá comprobar que la letra acentuada se ha conver-


tido en la correspondiente versión no acentuada, ya que el va-
lor original no está disponible40. La cadena constante es una
cadena Unicode y la cesión a str1 realiza una conversión im-
plícita. De hecho, el valor numérico del último carácter es di-
ferente.
También esta vez los caracteres de gama alta son completa-
mente diferentes. Para obtener el efecto deseado, considere
que tiene que escribir el doble moldeado:
CyrillicString(AnsiChar (I))

40LaWideCharToMultiByte tras la conversión trata de fallar graciosamente en


algunas situaciones. Por ejemplo, degradar dobles comillas en comillas rec-
tas en lugar de marcas dudosas y las letras acentuadas, cómo en el código de
ejemplo, pierden su acento.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 71

Si simplemente concatena los caracteres en la cadena para


convertirlos después, serán tratados como caracteres Uni-
code.

Gestión de Cadenas UTF-8


Uno de los efectos secundarios de la nueva estructura interna
de tipos de cadenas, es que ahora también podemos manejar
cadenas en el formato UTF-8 de una forma más nativa. A di-
ferencia del pasado, cuando UTF8String era simplemente un
alias del tipo String, el nuevo tipo ya es plenamente recono-
cido: las conversiones son automáticas y todas las rutinas de
manipulación existentes de cadenas UTF-8 se han importado
para utilizar los nuevos tipos específicos.
Considere este código trivial (parte del ejemplo Utf8Test):
var
str8: Utf8String;
str16: string;
begin
str8 := 'Cantù';
Memo1.Lines.Add('UTF-8');
Memo1.Lines.Add('Length: ' + IntToStr (Length (str8)));
Memo1.Lines.Add('5: ' + IntToStr (Ord (str8[5])));
Memo1.Lines.Add('6: ' + IntToStr (Ord (str8[6])));

str16 := str8;
Memo1.Lines.Add('UTF-16');
Memo1.Lines.Add('Length: ' + IntToStr (Length (str16)));
Memo1.Lines.Add('5: ' + IntToStr (Ord (str16[5])));

Como se podría esperar, la cadena str8 tiene una longitud de


6 (es decir, 6 bytes), mientras que la cadena str16 tiene una
longitud de 5 (lo que, sin embargo, significa 10 bytes). Com-
pruebe cómo invariablemente Length devuelve el número de
elementos de la cadena, que en el caso de representaciones de
longitud variable no coincide con el número de puntos de có-
digo Unicode representados por la cadena. Esta es la salida
del programa:

La guía de Delphi por Marco Cantù


72 - Indice

UTF-8
Length: 6
5: 195
6: 185

UTF-16
Length: 5
5: 249

La razón es que, como vimos en el último capítulo, las cade-


nas UTF-8 utilizan una variable para implementar su longi-
tud, de modo que los caracteres fuera de la espacio inicial de
7-bits ANSI que toman al menos dos caracteres. Este es el
caso de las u acentuadas anteriormente expuestas. Asig-
nando la misma cadena UTF-8 a una variable AnsiString, y
ejecutando un código similar (de nuevo en el ejemplo
Utf8Test), obtenemos lo siguiente:
ANSI
Length: 5
5: 249

Sin embargo esta vez la longitud de la cadena de 5 realmente


significa 5 bytes y no sólo 5 caracteres.
El soporte del formato UTF-8 podría no ser tan completo
como el de UTF-16, la implementación nativa de string en
Delphi 2009, pero se ha mejorado mucho de manera muy sig-
nificativa. Existen rutinas específicas para la manipulación de
UTF-8 en la unidad WideStrUtils, y también todo el apoyo
para el streaming de archivos de texto en este formato41. Lo
que es básico, sin embargo, es el hecho de que, por ejemplo,
puede trabajar en una cadena y mostrarla en cualquier control
sin tener que realizar una conversión explícita (ni tener que
recordar siempre y cuando llevarla a cabo), lo que sin duda es
una gran ayuda.

41Voya cubrir la clase TEncoding y las conversiones de archivos de texto más


adelante en este capítulo, en la sección “Streams y Codificaciones”.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados


control, SA
Indice - 73

Aunque algunas operaciones con cadenas UTF-8 puedan ser


lentas, por las conversiones extras y desde las propias del tipo
UnicodeString, teniendo un determinado tipo de datos en lu-
gar de un alias sin tipo forzado por el compilador, es una gran
diferencia para cualquier desarrollador de Delphi tener que
hacer frente a esta codificación.
Usted también es muy libre de escribir su propia versión so-
brecargada de las rutinas existentes (o unas nuevas), utili-
zando este tipo de cadenas para evitar cualquier conversión
extra.

Conversión de Cadenas
Hemos visto que usted puede asignar un valor UnicodeString
a un AnsiString, o a un UTF8String y la conversión se llevará
siempre a cabo. Del mismo modo, cuando usted asigne un An-
siString, con un determinado código de página, a otro basado
en un código de página diferente, aparece la conversión auto-
máticamente. También puede convertir una cadena asignando
una página de códigos diferente, preguntemos por la conver-
sión a realizar:
type
Latin1String = type AnsiString(28591);

procedure TFormStringConvert.btnLatin1Click(
Sender: TObject);
var
str1: AnsiString;
str2: Latin1String;
rbs: RawByteString;
begin
str1 := 'any string with a €';
str2 := str1;

Memo1.Lines.Add (str1);
Memo1.Lines.Add (IntToStr (Ord (str1[19])));

La guía de Delphi por Marco Cantù


74 - Indice

Memo1.Lines.Add (str2);
Memo1.Lines.Add (IntToStr (Ord (str2[19])));
rbs := str1;
SetCodePage(rbs, 28591, True);
Memo1.Lines.Add (rbs);
Memo1.Lines.Add (IntToStr (Ord (rbs[19])));
end;

En ambos casos anteriores, la conversión es una conversión


con pérdida, ya que el símbolo Euro no puede estar represen-
tado en el código de página Latin-1. Observe la utilización de
la rutina SetCodePage, que sólo puede aplicarse a un paráme-
tro RawByteString, de ahí su asignación. Este es el resultado
que obtendrá:
any string with a €
128
any string with a ?
63
any string with a ?
63

Conversiones Ralentizando
Nuestro Código
Las conversiones automáticas, realizándose en segundo
plano, son muy prácticas, ya que el sistema hace un montón
de trabajo por nosotros, pero si no lo hace con cuidado, consi-
derando lo que está haciendo, podría terminar con un código
extremadamente lento, por las continuas conversiones y ope-
raciones de copia de las cadenas. Considere la posibilidad del
siguiente código (parte del ejemplo StringConvert):
str1 := 'Marco ';
str2 := 'Cantù ';
for I := 1 to 10000 do
str1 := str1 + str2;

Dependiendo del tipo real de cada una de las dos cadenas, el


algoritmo puede ser extremadamente rápido o excesivamente
lento. La demo utiliza string (que es UnicodeString) en una

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 75

primera vuelta y una combinación de AnsiString y


UTF8String (el peor caso posible, ya que tendrán que ser con-
vertidas de ida y vuelta a UnicodeString para cada asignación)
en un segundo. Este es el resultado de 10.000 iteraciones:
plain: 00.001
mixed: 01.717

Sí, usted está leyendo los números correctos, ¡este es el resul-


tado para 1.000 veces o 3 ordenes de magnitud! Por si esto no
fuera lo bastante malo, considere la posibilidad de lo que ocu-
rre con 50.000 concatenaciones:
plain: 00:00.003
mixed: 00:42.879

¡Esto si es un incremento exponencial42! En otras palabras,


una conversión implícita ocasional está muy bien, pero nunca
debe dejar que ocurra dentro de ¡un bucle o rutina recursiva!
Lo que es importante saber, es que puede compilar su pro-
grama con las advertencias de conversión de cadena habilita-
das (que es en realidad la opción por defecto), y ver dónde el
compilador añade código de conversión. En esa sola línea de
código, utilizada para concatenar strings de los diferentes ti-
pos, obtendrá las siguientes advertencias:
W1057 Implicit string cast from 'UTF8String' to 'string'
W1057 Implicit string cast from 'AnsiString' to 'string'
W1058 Implicit string cast with potential data loss from
'string' to 'UTF8String'

El problema “potential data loss” (potencial pérdida de datos)


surge porque no todas las cadenas pueden ser expresadas en

42El aumento exponencial es debido al hecho de que cada vez más las grandes ca-
denas deben volver a ser asignadas en la memoria muchas veces. Lo qué se
ve frenado por el código es parcialmente la conversión, pero sobre todo la
necesidad de crear nuevas cadenas grandes temporales en lugar de seguir
aumentando el tamaño de la actual.

La guía de Delphi por Marco Cantù


76 - Indice

todos los formatos. Por ejemplo, si usted asigna un UnicodeS-


tring a un AnsiString, existen posibilidades de que la opera-
ción no sea posible. Como las operaciones de conversión de
cadenas son bastante comunes, las dos advertencias (‘Explicit
string cast and Implicit string cast with potential data loss’)
están activadas por defecto.
Con estas advertencias habilitadas verá muchas trampas po-
tenciales, pero un programa medio puede tener muchas, e in-
cluso alguna conversión explícita, que no serán eliminadas
cambiando simplemente a un conjunto diferente de adverten-
cias (‘Explicit string cast’ e ‘Implicit string cast’ con potencial
pérdida de datos). ¡Desactive esta opción de advertencias
cuando haya realizado la comprobación!43
Una quinta advertencia similar surge a la hora de asignar una
cadena constante a una cadena, en el caso de que algunos de
los caracteres no puedan ser convertidos. La advertencia en
este caso es ligeramente diferente:
[DCC Warning] StringConvertForm.pas(63): W2455 Narrowing
given wide string constant lost information

Esta es una advertencia de la que usted debería deshacerse, ya


que la operación no tendrá demasiado sentido.
Como otro ejemplo de conversión implícita (y un poco oculta)
frenando la ejecución del programa, considere el siguiente
fragmento de código:
str1 := 'Marco Cantù';
for I := 1 to MaxLoop2 do
str1 := AnsiUpperCase (str1);

43Estas“advertencias diagnósticas”, como el indicador y de seguridad de tipo in-


troducidos para mantener la compatibilidad con .NET, pueden ser desactiva-
das por defecto. Incluso aunque yo sea un defensor de la regla “mantener to-
das las advertencias y tratar de compilar los programas sin insinuaciones y
advertencias " convengo que las “advertencias diagnósticas” deberían ser
tratadas de manera diferente.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 77

En los casos en los que la variable str1 es un UnicodeString


todo va bien, pero en los casos en que se trate de un AnsiS-
tring, esto provocará dos conversiones. Esto no es tan malo
como en el caso anterior (porque aquí la cadena es corta y de
todos modos se requiere una copia de esta), pero muestra un
poco de sobrecarga (para un millón de iteraciones):
AnsiUpperCase (string): 00:00.289
AnsiUpperCase (AnsiString): 00:00.540

Las Llamadas Aseguradas


Otra serie de operaciones “ocultas” añadidas por el compila-
dor son la familia de llamadas Ensure String, que añaden la
comprobación del código de página a un parámetro string, y
eventualmente desencadenar una conversión si no coincide.
La más importante de estas llamadas es EnsureUnicodeS-
tring.
Como es muy difícil lograr una incorrecta situación en Delphi
(excepto una conversión directa al tipo de cadena incorrecto),
usted podría preguntarse por qué se han añadido. La razón es
que cuando un UnicodeString es administrado por el código
C++Builder, las cosas pueden salir mal.
Para asegurar las cosas, el compilador añade estos chequeos
extras en diferentes lugares, sobre todo cuando se trabaja en
una cadena pasada como parámetro. Mientras que este es el
comportamiento predeterminado, usted puede compilar có-
digo específico (y librerías) deshabilitando esta opción, utili-
zando la opción del compilador --string-checks o la directiva
$STRINGCHECKS. Teniendo en cuenta que usted tiene que
utilizar por su propio riesgo, estos modificadores ya que no
están documentados y no tienen soporte oficial (aunque, cu-
riosamente, están directamente disponibles en las opciones de
proyecto). Sin embargo, como los posibles problemas son
La guía de Delphi por Marco Cantù
78 - Indice

muy limitados cuando se utiliza en Delphi (y no C++ Builder),


probablemente pueda desactivarlo, como una técnica para ga-
nar velocidad.
Por ejemplo, si escribe la siguiente línea:
function DoubleLengthUpperOn(
const S: UnicodeString): Integer;
begin
Result := Length(AnsiUppercase(S));
end;

y compila estas en una versión idéntica y con un nombre dife-


rente, con la cadena de controles on y off, y después la llama
dentro de un bucle de 10 millones de iteraciones, verá el si-
guiente cronómetro:
UpperOn: 00:02.202
UpperOff: 00:02.159

Esto parece muy modesto, y bastante irrelevante. Sin em-


bargo, si usted optimiza el código con funciones ardiente-
mente rápidas (por ejemplo, la eliminación de la llamada An-
siUpperCase), los resultados de 10 veces más iteraciones se
convierten en:
On: 00:03.556
Off: 00:00.310

En este caso, la diferencia es significativa (diez veces más) y


parece lineal con el número de iteraciones del bucle. Incluso
si los casos similares son marginales, esta es probablemente
una buena razón para mantener esta opción desactivada.
Para obtener más información sobre estos ajustes y sus efec-
tos en términos de generar código ensamblador, puede con-
sultar el siguiente blog de Jan Goyvaerts:
http://www.micro-isv.asia/2008/10/needless-string-
checks-with-ensureunicodestring

Tenga cuidado con Literales en


La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 79

Concatenación
Hablando de la concatenación de cadenas, usted tiene que es-
tar atento a las concatenaciones que engloben cadenas litera-
les. Por ejemplo, debe considerar las aparentes siguientes lí-
neas triviales de código:
Log ('String: ' + str1);
Log (str1 + ' is a string');

Ahora bien, si str1 es un UnicodeString, no debería haber


ningún problema en absoluto. Si str1 es un AnsiString, en al-
guna de sus variantes, la concatenación con una cadena Uni-
code literal podría forzar diferentes conversiones depen-
diendo de si la cadena literal viene antes o después de la ca-
dena variable. En la mayoría de los casos de cadenas mixtas
con participación de literales, mi sugerencia sería añadir un
tipo explícito emitido por la cadena, como en:
Log ('String: ' + UnicodeString(str1));
Log (UnicodeString(str1) + ' is a string');

Usando RawByteString
¿Qué pasa si usted necesita pasar un AnsiString como pará-
metro a una rutina? Cuándo el parámetro es asignado a una
cadena con un tipo de codificación especifico, esta se conver-
tirá al tipo adecuado, con la posibilidad de pérdida de datos.
Esa es la razón por la que Delphi 2009 introduce un nuevo
tipo de cadena personalizada, llamado RawByteString y que
se define como:
type
RawByteString = type AnsiString($ffff);

Esta es la definición de una cadena sin ningún tipo de codifi-


cación o, para ser más precisos, con el marcador de posición,

La guía de Delphi por Marco Cantù


80 - Indice

indicando $ffff “no encoding”. El RawByteString puede con-


siderarse como una cadena de bytes, que ignora la codifica-
ción adjunta en el caso de una conversión automática a la
hora de asignarse un AnsiString. En otras palabras, cuando
pase un string de 1-byte por carácter como un parámetro
RawByteString, no se realizará conversión alguna, a diferen-
cia de cualquier otro tipo derivado de tipo AnsiString. Usted
puede hacer una conversión llamando a la rutina SetCode-
Page, como se ha demostrado anteriormente en la sección
"Conversión de Cadenas".
Como tal, puede convertirse en un práctico reemplazo del tipo
de string (o AnsiString) en código que utilice strings para el
proceso de datos genérico y personalizado que usted desee
mantener con 1-byte de representación por carácter44.
Declarar variables de tipo RawByteString para almacenar una
cadena real rara vez sucederá45. Teniendo en cuenta el código
definido de la página, esto puede llevar a un comportamiento
indefinido y a una potencial pérdida de datos. Por otra parte,
si su objetivo es el ahorro de datos binarios utilizando una ca-
dena como una asignación de memoria y su representación,
puede utilizar el RawByteString de la misma forma que utilizó
los AnsiString en las ultimas versiones de Delphi. Sustitu-
yendo el código non-string que ha utilizado con el AnsiString
por RawByteString este es un camino interesante de migra-
ción (como se verá en la sección “No Mueva Datos String” del
capítulo 3).

44Nodebe confundirse por este soporte ampliado para 1-byte por carácter de las
cadenas compatibles ANSI: la solución preferida, es de lejos, migrar el pro-
cesamiento de código de su cadena al tipo UnicodeString. No se deje tentar
demasiado por estos nuevos tipos de cadenas adicionales.
45Paraver algunas consideraciones interesantes sobre RawByteString vaya al
blog de Jan Goyvaerts http://www.micro-isv.asia/2008/08/using-rawbytes-
tring-effectively/

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 81

Por ahora, vamos a centrarnos en un ejemplo típico en el que


se puede utilizar como parámetro RawByteString. Si desea ver
información sobre algunas cadenas de 8-bits, puede escribir
cualquiera de las dos siguientes declaraciones (estos son los
métodos del form principal de la demo RawTest):
procedure DisplayStringData (str: AnsiString);
procedure DisplayRawData (str: RawByteString);

El código de los dos métodos es idéntico (He aquí el listado de


uno de los dos):
procedure TFormRawTest.DisplayRawData(
str: RawByteString);
begin
Log ('DisplayRawData(str: RawByteString)');
Log ('String: ' + UnicodeString(str));
Log ('CodePage: ' + IntToStr (StringCodePage (str)));
Log ('Address: ' + IntToStr (Integer (Pointer (str))));
end;

Tenga en cuenta que las conversiones a UnicodeString se uti-


lizan para mostrar el formato propio de la cadena, que son ne-
cesarias para evitar que los datos se traten como un AnsiS-
tring llano debido a la concatenación de una cadena literal
con una cadena cuya página de códigos no esta definida en
tiempo de compilación46.
La razón por la que muestro la dirección de memoria de la ca-
dena (al lado de su contenido y del código de página) es que
esto nos permitirá determinar si la cadena ha sido convertida
(y copiada), o si es exactamente la misma cadena pasada
como parámetro.
Ahora podemos utilizar una variable AnsiString (no asig-
nando simplemente una cadena constante sino haciendo al-
gún proceso, o el resultado sería diferente) y pasarlo como un

46Lautilización directa de Log(str) debería funcionar, ya que no existe concate-


nación implicada.

La guía de Delphi por Marco Cantù


82 - Indice

parámetro para los dos métodos, después de haber registrado


alguna cadena de datos :
procedure TFormRawTest.btnRawAnsiClick(Sender: TObject);
var
strAnsi: AnsiString;
begin
strAnsi := 'Some text ';
strAnsi := strAnsi + AnsiChar (210) + AnsiChar (128);

Log ('String: ' + strAnsi);


Log ('CodePage: ' + IntToStr (
StringCodePage (strAnsi)));
Log ('Address: ' + IntToStr (
Integer (Pointer (strAnsi))));

DisplayStringData (strAnsi);
DisplayRawData (strAnsi);
end;

El resultado será como el esperado, ya que al llamar a Dis-


playStringData y al método DisplayRawData no se llevarán a
cabo las conversiones y todas las operaciones se realizaran en
la misma cadena:
String: Some text Ò€
CodePage: 1252
Address: 28149532

DisplayStringData(str: AnsiString)
String: Some text Ò€
CodePage: 1252
Address: 28149532

DisplayRawData(str: RawByteString)
String: Some text Ò€
CodePage: 1252
Address: 28149532

Si esto parece evidente, tal vez no sea tan claro lo que ocurre
cuando pasamos una UTF8String como parámetro real a los
métodos. La llamada del código es bastante similar, aunque
convierto cada carácter tratándolo como un valor UTF-8 :
var
strUtf8: UTF8String;
nChar: Integer;
begin
strUtf8 := 'Some text ';
nChar := 210;
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 83

strUtf8 := strUtf8 + UTF8String (AnsiChar (nChar));


nChar := 128;
strUtf8 := strUtf8 + UTF8String (AnsiChar (nChar));

Log ('String: ' + strUtf8);


Log ('CodePage: ' + IntToStr (
StringCodePage (strUtf8)));
Log ('Address: ' + IntToStr (
Integer (Pointer (strUtf8))));

DisplayStringData (strUtf8);
DisplayRawData (strUtf8);
end;

En este caso, pasando la cadena como un AnsiString se realiza


una conversión real (que es una conversión perdida, puesto
que los caracteres no pueden ser representados por un Ansi-
Char), mientras la operación RawByteString procesa la ca-
dena original directamente y produce la salida correcta:
UTF-8 string
String: Some text
CodePage: 65001
Address: 28804892

DisplayStringData(str: AnsiString)
String: Some text ?
CodePage: 0
Address: 28804732

DisplayRawData(str: RawByteString)
String: Some text
CodePage: 65001
Address: 28804892

En el programa usted puede ver más pruebas realizadas con


tipos String definidos a medida. Al igual que con el testeo de
UTF8String cada vez que pasa una cadena personalizada
como un AnsiString tiene lugar una conversión, que está po-
tencialmente pérdida, mientras que utilizando como paráme-
tro RawByteString puede mantener la cadena en su valor ori-
ginal y mostrarlo correctamente. Aquí está una selección de
las líneas de salida:
Latin string
String: Some text Ò

La guía de Delphi por Marco Cantù


84 - Indice

DisplayStringData(str: AnsiString)
String: Some text Ò?

DisplayRawData(str: RawByteString)
String: Some text Ò

Cyrillic string
String: Some text ТФ

DisplayStringData(str: AnsiString)
String: Some text ??

DisplayRawData(str: RawByteString)
String: Some text ТФ

Nuevas Funciones de Conversión


UTF-8
Además de muchas transformaciones automáticas de cadena
también hay varias nuevas funciones de conversión directa de
cadena que puede utilizar al respecto. Por ejemplo, hay una
gran número de nuevas funciones de conversión de y para
UTF-8, sobrecargados para los diferentes tipos de cadenas:
function UTF8Encode(...): RawByteString;
function UTF8EncodeToShortString(...): ShortString;
function UTF8ToWideString(...): WideString;
function UTF8ToUnicodeString(...): UnicodeString;
function UTF8ToString(...): string;

Cadenas y Caracteres
Literales
Hemos visto varios ejemplos en los que puede asignar un ca-
rácter literal individual o una cadena literal a cualquiera de
los tipos de cadenas, con la conversión propia que tienen lu-
gar detrás de estas escenas.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 85

Las cadenas literales se consideran invariablemente del tipo


UnicodeString. Tenga en cuenta que esto podría causar pro-
blemas con resoluciones que sobrecargan al pasar un cadena
constante a una función como Pos, que ahora tiene varias ver-
siones. En general, sin embargo, la gestión de las cadenas lite-
rales son bastante directas. Como vimos en el último capítulo,
puede añadir cualquier carácter Unicode a una cadena cons-
tante en el editor, y todo funcionará sin problemas.
Los caracteres literales son la causa de algunas cuestiones
más, particularmente por razones de compatibilidad hacia
atrás. Los caracteres literales simples se convertirán en fun-
ción de su contexto. Es más difícil para el compilador deter-
minar qué hacer con hexadecimales (o decimales) de caracte-
res literales, como en el siguiente código tomado del ejemplo
HighCharTest:
var
str1: string;
begin
str1 := #$80;

Por razones de compatibilidad hacia atrás, todos literales de


cadena de 2 dígitos son analizados como AnsiChar por de-
fecto, por lo que un desarrollador como yo que viva en Europa
(o, más técnicamente, que tenga el mismo código de página
establecido que yo) verá como el símbolo de moneda Euro se
muestra en la cadena. En realidad, por la ejecución de la de-
claración:
Log (str1 + ' - ' + IntToStr (Ord (str1[1])));

Obtengo la siguiente salida:


€ - 8364

En otras palabras, el literal es tratado como un AnsiChar y


convertido al punto de código Unicode correcto. Si desea pa-
sar plenamente a Unicode, puede ser que no le guste este
comportamiento, ya que nunca se sabe cómo un determinado

La guía de Delphi por Marco Cantù


86 - Indice

literal va a ser interpretado. Esa es la razón por la que Delphi


presentó en 2009 una nueva directiva del compilador:
{$HIGHCHARUNICODE <ON|OFF>}

Esta directiva determina cómo los valores literales entre #$80


y #$FF serán tratados por el compilador. Lo que se ha señalado
anteriormente es el efecto de la opción por defecto (OFF). Si
la activa, el mismo programa producirá este resultado:
- 128

El número se interpreta como un punto de código real Uni-


code y su salida contendrá su carácter de control no imprimi-
ble. Otra opción para expresar este punto de código específico
(o de cualquier punto de código Unicode por debajo de
#$FFFF) es utilizar la notación de cuatro dígitos:
str1 := #$0080;

Este no es interpretado como el símbolo de divisa Euro, inde-


pendientemente de la configuración de la directiva
$HIGHCHARUNICODE.

Lo que es agradable es que se puede utilizar la notación de


cuatro dígitos para expresar caracteres del Lejano Oriente, al
igual que los siguientes dos caracteres Japoneses:
str1 := #$3042#$3044;

mostrado47 como (junto con su representación numérica):


あい - 12354 – 12356

También puede utilizar literalmente los elementos #$FFFF que


se convertirán en su propio par subrogado.
Por último, aviso que para los literales de cadena, el código de
la página está tomado de las opciones del compilador, que

47あい se traduce por "reunión", según BabelFish, pero no estoy 100% seguro de
dónde lo encontré inicialmente .

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 87

puede usted modificar para un proyecto específico, y no a par-


tir del sistema de código de la página del equipo en el que us-
ted está compilando o ejecutando el programa.

Streams y Codificaciones
Si en nuestra aplicación movemos todas nuestras cadenas a
Unicode, cuando se trabaja con la RTL y la VCL, y mientras
que la invocación de la API de Windows no sea difícil, las co-
sas pueden ser un poco más complicadas, tal como leer y es-
cribir sus cadenas desde ficheros. ¿Qué sucede, por ejemplo,
con las operaciones de archivos con TStrings?
Delphi 2009 introduce otra nueva clase de base para manejar
archivos codificados, denominada TEncoding y como imi-
tando algo a la clase System.Text.Encoding del .NET Frame-
work. La clase TEncoding, que se define en la unidad SysUtils,
tiene varias subclases que representan las codificaciones auto-
máticamente soportadas por Delphi (estas son codificaciones
estándares a las que puede añadir las suyas propias):
type
TEncoding = class
TMBCSEncoding = class(TEncoding)
TUTF7Encoding = class(TMBCSEncoding)
TUTF8Encoding = class(TUTF7Encoding)
TUnicodeEncoding = class(TEncoding)
TBigEndianUnicodeEncoding = class(TUnicodeEncoding)

La clase TUnicodeEncoding utiliza el mismo formato UTF-16


LE (Little Endian) usado por el tipo UnicodeString. Un objeto
de cada una de estas clases está disponible en la clase TEnco-
ding, como datos de clase, tiene su función getter correspon-
diente y su propiedad de clase (class property):
type
TEncoding = class
...

La guía de Delphi por Marco Cantù


88 - Indice

public
class property ASCII: TEncoding read GetASCII;
class property BigEndianUnicode: TEncoding
read GetBigEndianUnicode;
class property Default: TEncoding read GetDefault;
class property Unicode: TEncoding read GetUnicode;
class property UTF7: TEncoding read GetUTF7;
class property UTF8: TEncoding read GetUTF8;

La clase TEncoding tiene métodos para leer y escribir caracte-


res a byte streams, para llevar a cabo las conversiones, ade-
más de una función especial para manejar el BOM llamado
GetPreamble. Por lo tanto, se puede escribir (en cualquier
parte del código):
TEncoding.UTF8.GetPreamble

Streaming Listas de Cadenas


Los métodos ReadFromFile y WriteToFile de la clase TStrings
pueden ser llamados con una codificación determinada. Si es-
cribe una lista de cadenas de archivo de texto sin proporcio-
nar una codificación específica, esta clase utilizará TEnco-
ding.Default, que utiliza a su vez el DefaultEncoding interno
extraído en la primera aparición de la actual página de códi-
gos de Windows. En otras palabras, si guarda un archivo, ob-
tendrá el mismo archivo ANSI que antes.
Por supuesto, usted también puede fácilmente forzar el fi-
chero a un formato diferente, por ejemplo al formato UTF-16:
Memo1.Lines.SaveToFile('test.txt', TEncoding.Unicode);

Esto guarda el archivo con un BOM o preámbulo Unicode.


Cuando realice la operación correspondiente LoadFromFile si
no especifica una codificación, el método de carga va a termi-
nar llamando a GetBufferEncoding método de la clase TEnco-
ding que determinará la codificación en función de la presen-
cia de un BOM (en el caso de su ausencia, se utilizará la codi-
ficación ANSI por defecto).
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 89

¿Qué pasa si usted especifica una codificación en LoadFrom-


File? La codificación que usted proporcione será utilizada
para leer el fichero, independientemente del BOM que este
contenga, a menudo produciendo un error. Yo preferiría espe-
rar una excepción en caso de una discrepancia como esta,
guardar un fichero con un código de página y forzar su carga
con otro distinto, es sin duda un error del desarrollador. El no
obtener una excepción puede ayudar en el caso que el archivo
codificado se haya guardado sin un BOM, y todavía no se ha
considerado como un archivo ASCII, pero si como un UTF.
Pero vamos a centrarnos en la operación de guardado del fi-
chero. Si no cambia el actual código de Delphi, sus programas
guardarán los archivos como ANSI. Si los programas importa-
dos no manejan datos Unicode, sus programas y sus ficheros
serán completamente compatibles hacia atrás.
Pero y ¿si un programa no maneja datos Unicode? Suponga-
mos que tenemos una StringList con las líneas escritas en di-
ferentes idiomas48, al igual que en el siguiente formulario en
tiempo de diseño del proyecto StreamEncoding:

48Estas
líneas se han extraído de la web “¿Qué es Unicode?” del Consorcio de
Unicode, que tiene su texto traducido en varios idiomas utilizando una varie-
dad de alfabetos.

La guía de Delphi por Marco Cantù


90 - Indice

Si tenemos código Delphi anterior que guarde listas de cade-


nas a un fichero y también lo recargue, es probable que tenga
el siguiente aspecto:
procedure TFormStreamEncoding.btnPlainClick(
Sender: TObject);
var
strFileName: string;
begin
strFileName := 'PlainText.txt';
ListBox1.Items.SaveToFile(strFileName);
ListBox1.Clear;
ListBox1.Items.LoadFromFile(strFileName);
end;

Huelga decir que el efecto sería un desastre total, ya que sólo


una fracción de los caracteres utilizados tienen una represen-
tación ANSI, por lo que terminaría con un montón de signos
de interrogación en el objeto listbox.
Una simple alternativa sería cambiar este código como en el
evento del segundo botón al pie del proyecto:
strFileName := 'Utf8Text.txt';
ListBox1.Items.SaveToFile(strFileName, TEncoding.UTF8);

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 91

De nuevo, no tenemos que especificar una codificación


cuando se cargue la lista de cadenas, ya que Delphi lo tomará
a partir del BOM.
Si prefiere guardar los datos como ANSI a menos que sea ne-
cesario, se puede comprobar el contenido de la lista de cade-
nas para determinar si guardarlos como ASCII ó UTF-8:
procedure TFormStreamEncoding.btnAsNeededClick(
Sender: TObject);
var
strFileName: string;
encoding1: TEncoding;
begin
strFileName := 'AsNeededText.txt';
encoding1 := TEncoding.Default;

if ListBox1.Items.Text <>
UnicodeString (AnsiString(ListBox1.Items.Text)) then
encoding1 := TEncoding.UTF8;

ListBox1.Items.SaveToFile(strFileName, Encoding1);

Este código comprueba si se puede convertir una cadena a un


AnsiString y devolverlo a un UnicodeString sin perder su con-
tenido. Para una cadena muy larga, esta doble conversión y la
comparación sería muy compleja, por lo que podría utilizar la
alternativa de código siguiente (que no es tan precisa, ya que
se basa en un código de página49 específico, pero se acerca):
var
ch: Char;
begin
...
for ch in ListBox1.Items.Text do
if Ord (ch) >= 256 then
begin
encoding1 := TEncoding.UTF8;
break;
end;

49Compruebe como ch >= 256 no funciona si el código de la página por defecto


es distinto que Windows 1252. Por ejemplo, "Cantù" no tiene ningún carác-
ter >= 256, pero no puede ser representado en la página de códigos 1251.

La guía de Delphi por Marco Cantù


92 - Indice

Usando un código similar usted puede decidir qué formato


utilizar, en función de la situación. Aunque, podría ser una
idea mejor, mover todos sus ficheros a codificación Unicode
(UTF-8 ó UTF-16), independientemente de los datos reales.
Usando UTF-16 generará archivos más grandes, pero también
reducirá las conversiones al guardar y cargar.
Sin embargo, dado que no hay forma de especificar una con-
versión por omisión, pasar todos los archivos por la codifica-
ción Unicode significaría la necesidad de cambiar todos y cada
una de las operaciones al guardar estos archivos... a menos
que use un truco, como la modificación de la norma de com-
portamiento por defecto de la clase. Este truco podría concre-
tarse en forma de un ayudante de clase helper50. Considere el
siguiente código:
type
TStringsHelper = class helper for TStrings
procedure SaveToFile (const strFileName: string);
end;

procedure TStringsHelper.SaveToFile(
const strFileName: string);
begin
inherited SaveToFile (strFileName, TEncoding.UTF8);
end;

Observe que la herencia aquí no significa llamar a una clase


base, pero la clase será ayudada por su clase de ayuda. Ahora
sólo tiene que escribir (o mantener su código cómo):
ListBox1.Items.SaveToFile(strFileName);

50Si está interesado en aprender más acerca de los ayudantes de clase, una buena
fuente es mi “Manual Delphi 2007”, pero seguramente puede encontrar
otras referencias buscando en la web. El concepto de clase de ayuda es poco
conocido pero es una característica muy potente de las últimas versiones
Delphi.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 93

para guardarlo como UTF-8 (o cualquier otra codificación de


su elección). Encontrará este código en el ejemplo StreamEn-
coding.

Definiendo una codificación


personalizada
Incluso si Delphi 2009 viene con algunas codificaciones
preestablecidas, podría incluso terminar necesitando alguna
más. Un ejemplo de codificación, no tan común, que puede
necesitar es UTF-32 (little-endian). Definir y utilizar una co-
dificación personalizada es, sin duda, posible, aunque hay al-
gunas asperezas.
En primer lugar, usted tiene que definir una clase que herede
de cualquier TEncoding o de uno de sus descendientes. Dado
que no existen las clases para manejar la codificación de ca-
racteres de 4-bytes, he comenzado por heredarlos de la clase
base:
type
TUTF32Encoding = class (TEncoding)
class var
UTF32Encoding: TUTF32Encoding;
strict protected
function GetByteCount(Chars: PChar;
CharCount: Integer): Integer; override;
function GetBytes(Chars: PChar;
CharCount: Integer; Bytes: PByte;
ByteCount: Integer): Integer; override;
function GetCharCount(Bytes: PByte;
ByteCount: Integer): Integer; override;
function GetChars(Bytes: PByte;
ByteCount: Integer; Chars: PChar;
CharCount: Integer): Integer; override;
public
function GetPreamble: TBytes; override;
class function Encoding: TEncoding;
function GetMaxByteCount(
CharCount: Integer): Integer; override;
function GetMaxCharCount(
ByteCount: Integer): Integer; override;

La guía de Delphi por Marco Cantù


94 - Indice

end;

Aquí hay básicamente dos métodos básicos de conversión


(GetBytes y GetChars), métodos de recuento para cuatro carac-
teres/bytes, un método para definir el BOM o preámbulo, y
una función de clase utilizada para devolver una instancia
única (singleton), guardada en la variable de clase. Sólo los
dos métodos de conversión son complejos, mientras que para
todo lo demás todo lo que tiene que tener en cuenta es que us-
ted toma 4 bytes, que es SizeOf(UCS4Char), para cada carác-
ter. Estos son los métodos, excepto para la conversión, que se
describe más adelante con más detalle:
class function TUTF32Encoding.Encoding: TEncoding;
begin
if not Assigned (UTF32Encoding) then
UTF32Encoding := TUTF32Encoding.Create;
Result := UTF32Encoding;
end;

function TUTF32Encoding.GetByteCount(
Chars: PChar; CharCount: Integer): Integer;
begin
Result := CharCount * SizeOf(UCS4Char);
end;

function TUTF32Encoding.GetCharCount(
Bytes: PByte; ByteCount: Integer): Integer;
begin
Result := ByteCount div SizeOf(UCS4Char);
end;

function TUTF32Encoding.GetMaxByteCount(
CharCount: Integer): Integer;
begin
Result := (CharCount + 1) * 4;
end;

function TUTF32Encoding.GetMaxCharCount(
ByteCount: Integer): Integer;
begin
Result := (ByteCount div 4) + (ByteCount and 1) + 1;
end;

function TUTF32Encoding.GetPreamble: TBytes;


begin
// UTF-32, little-endian
SetLength(Result, 4);

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 95

Result[0] := $FF;
Result[1] := $FE;
Result[2] := $00;
Result[3] := $00;
end;

El código imita un poco las clases de la RTL de Delphi, en


particular, se asemeja a la clase TUnicodeEncoding. Los méto-
dos de conversión son ligeramente más complicados. Dado
que no quiero manejar los caracteres directamente, estoy
usando las funciones la conversión de UnicodeString a
UCS4String previstas por la RTL de Delphi. Extraer los bytes
físicos para almacenarlos en el stream es una consecuencia de
su conversión a UCS4String y mover los datos a bajo nivel:
function TUTF32Encoding.GetBytes(Chars: PChar;
CharCount: Integer; Bytes: PByte;
ByteCount: Integer): Integer;
var
u4String: UCS4String;
begin
Result := CharCount * SizeOf(UCS4Char);
u4String := UnicodeStringToUCS4String (
UnicodeString (Chars));
Move(u4String[0], Bytes^, Result);
end;

Para la conversión contraria necesita mover los datos binarios


al tipo UCS4String, transformarlos, y copiar el resultado en el
buffer de salida:
function TUTF32Encoding.GetChars(Bytes: PByte;
ByteCount: Integer; Chars: PChar;
CharCount: Integer): Integer;
var
u4String: UCS4String;
uString: UnicodeString;
begin
Result := CharCount;
SetLength (u4String, Result);
Move(Bytes^, u4String[0],
CharCount * SizeOf(UCS4Char));
uString := UCS4StringToUnicodeString (u4String);
Move(uString[1], Chars^,
CharCount * SizeOf(Char));
end;

La guía de Delphi por Marco Cantù


96 - Indice

Ahora, con este hábito de codificación disponible, simple-


mente puede escribir código como en el ejemplo CustomEn-
coding:
procedure TFormCustomEncoding.btnTestEncoding2Click(
Sender: TObject);
begin
Memo1.Lines.LoadFromFile ('Utf8Text.txt');
Memo1.Lines.SaveToFile ('Utf32.txt',
TUTF32Encoding.Encoding);
Memo1.Lines.LoadFromFile ('Utf32.txt',
TUTF32Encoding.Encoding);
end;

El único problema potencial es que no podemos simplemente


llamar a LoadFromFile sin el método de codificación y esperar
que la RTL de Delphi lo reconozca, ya que esto, simplemente,
no sucederá51. En Delphi 2009 no hay manera de instalar
nuestras costumbres de codificación en la RTL para que esta
pueda reconocer su preámbulo con su código BOM-detection
en el interior de la función de clase TEncoding.GetBufferEnco-
ding. Esto se demuestra a través del último botón del ejemplo.

Unicode y la VCL
Disponer de soporte para cadenas Unicode en el lenguaje Del-
phi es emocionante, después de haber sido reasignada la API
de Win32 a la versión Wide ofrece así un montón de migracio-
nes fáciles, pero el cambio fundamental es que toda la RTL y
la Visual Component Library (VCL) están ahora plenamente
habilitadas para Unicode. Todos los tipos strings (y las string
lists) gestionados por los componentes están declarados como

51Asimismo, tenga en cuenta que muy pocos editores reconocen el BOM de la


UTF-32 y su codificación.

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 97

string, por lo que ahora coinciden con el nuevo tipo Unico-


deString.
Algunas de las zonas internas de la RTL, de bajo-nivel, se ba-
san sin embargo en diferentes formatos. Por ejemplo, los
nombres de las propiedades se basan en UTF-8, por lo que es
parte del soporte de la RTTI disponible en la unidad de
TypInfo. Además de algunas excepciones muy específicas,
todo lo demás se ha migrado a UnicodeString y a UTF-16.
El soporte para Unicode es un elemento clave, pero no la
única característica que ayuda a mejorar el apoyo para la
construcción de aplicaciones internacionales. Otras caracte-
rísticas se refieren a la utilización de BiDiMode y al soporte
para la Traducción.
En cuanto a los archivos de código fuente, tenga en cuenta
que puede guardarlos en cualquier formato que le guste, pero
es necesario utilizar un formato Unicode si está utilizando
cualquier code point por encima de 255 en su código fuente (
para identificar nombres, cadenas, comentarios, o simple-
mente cualquier otra cosa). El editor le pedirá que use cada
formato cuando sea necesario, aunque puede ir a los archivos
de código fuente Unicode de todos modos.

¿Un núcleo creciente en la RTL?


Con todos los extras de proceso de cadenas y de gestión de có-
digo en tiempo de ejecución, ¿son los archivos ejecutables
Delphi 2009 más grandes que en el pasado? He comparado el
tamaño mínimo de una aplicación VCL compilado con los pa-
quetes en tiempo de ejecución (MiniPack) y lo básico de un

La guía de Delphi por Marco Cantù


98 - Indice

programa52 (MiniSize), compilado en Delphi 2007 y Delphi


2009, obteniendo los siguientes resultados:

Delphi 2007 Delphi


2009

MiniPack 15,872 16,896

MiniSize 19,456 20,992

El "peso extra" es alrededor de 1 KB, una cantidad que au-


menta si usted realiza conversiones de cadenas, pero debería
seguir siendo bastante mínima la comparación con el tamaño
de cualquier aplicación del mundo real.

Unicode en archivos DFM


Acabo de mencionar la forma en que el IDE de Delphi puede
tratar el Unicode habilitado para archivos de código fuente,
pero no hemos visto lo que ocurre con los archivos DFM al
añadir un carácter extendido a una de las propiedades. Un ex-
perimento muy simple será abrir un nuevo programa, colocar
un botón en él, y pegar en el título del botón un carácter Uni-
code, como los caracteres Japoneses de la sección “Literales
de Cadena”.
Visualizando el formulario como texto u observando en el fi-
chero DFM actual verá el texto siguiente:
object Button1: TButton
Left = 176
Top = 104

52Estos
dos programas son parte de “Mastering Delphi 2005” y no he copiado el
código fuente, ya que son bastante simples. Puedes descargarlos al igual que
todo el código fuente del libro desde www.marcocantu.com/md2005

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 99

Width = 75
Height = 25
Caption = #12354#12356
TabOrder = 0
end

Ahora añada otro formulario al programa (como lo hice en el


programa DfmTest o modifique este mismo), y esta vez cam-
bie el nombre del botón de añadir caracteres Unicode, así:

¿Cuál es el efecto sobre el DFM en este caso? Se guardará en


el formato UTF-8 (junto con el archivo de PAS). Ábralo como
texto y verá una diferencia dispar entre el nombre del compo-
nente y su título, que son coincidentes, pero utilizan diferen-
tes representaciones:
object Buttonあい: TButton
Left = 224
Top = 112
Width = 75
Height = 25
Caption = 'Button'#12354#12356
TabOrder = 0
end

En este caso, el archivo DFM no es compatible hacia atrás


con Delphi 2007.

Localizando la VCL
Con el soporte a Unicode, el tradicional soporte de Delphi "bi-
direccional mode ", o BiDiMode, reflejo de los controles en un
formulario, y el Translation Manager, que forma parte del
IDE, es cada vez más relevante.
No voy a cubrir BiDiMode en detalle, ni proveer orientación
para el Internal y External Translation Managers, ya que estas
herramientas no cambian comparadas con las versiones ante-
riores de Delphi. La arquitectura de traducción de la VCL son
La guía de Delphi por Marco Cantù
100 - Indice

herramientas basadas en las que han estado en Delphi desde


anteriores versiones, pero que han sido ciertamente mejora-
das (y algunos de sus errores corregidos), ya que ahora están
en el punto de mira.

A continuación
Ahora que ha visto cómo trata Delphi 2009 las cadenas Uni-
code, podemos centrarnos en la transición existente en el có-
digo fuente Object Pascal del mundo ASCII al mundo Uni-
code. Hay muchas cuestiones relacionadas, como PChar ba-
sado en un puntero matemático, una buena razón por la que
se trata este tema en un capítulo separado.
La cobertura de las nuevas características relacionadas con
cadenas, no termina aquí, en el capítulo 7 cubriré otras nue-
vas características de la RTL como la clase TStringBuilder y
otras clases mejoradas para el tratamiento de texto.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 101

La guía de Delphi por Marco Cantù


102 - Indice

Capítulo 3:
Exportando a
Unicode

Disponer de soporte nativo Unicode en Delphi es un gran


paso adelante, y el hecho de poder continuar utilizando el tipo
de string significa que usted puede importar código existente
simplemente a costa de recompilar. Este cambio es un gran
cambio. Desde las llamadas a la API de Windows empleando
punteros PChar sólo con el soporte matemático del puntero,
hay muchas áreas de Delphi para las que puede esperarse que
nuestra migración no será tan fácil y sencilla. En este capítulo
se profundiza en estos y otros problemas posibles.
Antes de sumergirse en este capítulo, sea consciente de que si
usted necesita mantener la compilación de su código con ver-
siones anteriores de Delphi, podrá emplear favorablemente la
directiva del compilador UNICODE, que está definida por el
compilador de Delphi 2009. Podrá entonces escribir fragmen-
tos de código que no compilen en versiones anteriores de Del-
phi escribiendo:
{$IFDEF UNICODE}
// código espefícico para Delphi 2009

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 103

{$ENDIF}

Operaciones Char
Erróneas
Como acabo de mencionar, la mayor parte de operaciones ba-
sadas en cadenas de caracteres se recompilan y migran sin
problemas. Sin embargo, hay algunas que no y requerirán una
solución en el código.
Una gran cantidad de código Pascal, Turbo Pascal, Object
Pascal, y Objet Pascal de Delphi, asume que el tamaño de un
carácter es un byte. Todo este código puede potencialmente
fallar al pasar a Delphi 2009. Como veremos en la sección
acerca de FillChar a continuación, para obtener el tamaño ac-
tual en bytes de una cadena se deberá siempre multiplicar la
longitud de la cadena por el valor StringElementSize, ya que a
menudo un carácter requiere de dos bytes (pero no siempre).

Cuidado con Set of Char


Ya he mencionado que no se puede declarar un set of Char
nunca más, al menos no con el sentido tenía en versiones an-
teriores de Delphi, que tiene ahora un conjunto que incluye
todos los caracteres posibles. Como se ha cubierto en el ante-
rior capítulo, el compilador asumirá que está portando código
existente para Delphi 2009 y decidirá considerar su declara-
ción set of Char como si fuese set of AnsiChar, emitiendo
una simple advertencia. Es posible que, si tiene código anti-
guo que utilice esta construcción, falle.

La guía de Delphi por Marco Cantù


104 - Indice

Puede ver un ejemplo de esta advertencia (y la solución explí-


cita usada para removerla) en el ejemplo CharTest del capí-
tulo 2. La verdadera cuestión en este caso es que no hay ma-
nera de definir un conjunto de todos los caracteres nunca
más, o de expresar la inclusión de un carácter en un conjunto
con este método de codificación. Realmente, ¡tiene que cam-
biar el código completamente! Considere, por ejemplo, el sen-
cillo código de la demo CharTest que acabo de mencionar:
var
charSet: set of Char;
begin
charSet := ['a', 'b', 'c'];
if 'a' in charSet then ...

El enfoque alternativo es evitar totalmente usar un conjunto


de caracteres y usar un algoritmo diferente, como:
var
charSet: string;
begin
charSet := 'abc';
if Pos ('a', charSet) > 0 then ...

Esto tiene la ventaja de que también funciona cuando los ca-


racteres no son ASCII, mientras que el uso de sets fija los lí-
mites a 256 valores en la comparación. De forma similar, con
los ensayos para la inclusión de un carácter en un rango,
como:
if ch1 in ['0'..'9'] then ...

que compila y sólo funciona gracias a la reducción de la varia-


ble ch1 a un byte char (como se anuncia en la advertencia),
usted debe escribir su código cómo lo debería hacer en la ma-
yoría de lenguajes de programación, como:
if (ch1 >= '0') and (ch1 <= '9') then ...

Todas estas técnicas tienen la ventaja de ser compatibles ha-


cia atrás. Si necesita reemplazar su is character in set pruebe
específicamente Delphi 2009, yo le recomendaría, sin duda,

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 105

que utilice la nueva función específica CharInSet de la unidad


SysUtils:
if CharInSet ('a', charSet) then ...
if CharInSet ('a', ['a', 'b', 'c']) then ...

Este código es casi idéntico a la prueba inicial, y es muy fácil


de reemplazar las antiguas pruebas con las nuevas funciones
cuando portamos el código a Delphi 2009. La función CharIn-
Set se define para ambos valores AnsiChar y WideChar utiliza
como segundo parámetro el tipo de conjunto definido a conti-
nuación:
type
TSysCharSet = set of AnsiChar;

function CharInSet(C: AnsiChar;


const CharSet: TSysCharSet): Boolean; overload; inline;
function CharInSet(C: WideChar;
const CharSet: TSysCharSet): Boolean; overload; inline;

Otro enfoque alternativo que es bastante eficiente y trabaja a


través de distintas versiones, es sustituir estas pruebas con
una declaración como esta:
case Ch of
'a'..'c': ...
end;

Este tiene también la ventaja de ser mucho más rápido que la


función CharInSet. Un último caso específico es comprobar su
inclusión en el conjunto :
if str[i] in LeadBytes then ...

En este caso debe usar la nueva función IsLeadChar para sus-


tituir esta sentencia. En algunos casos, las comprobaciones
Unicode relacionadas están proporcionadas por la unidad
Character que puede serle de ayuda igualmente.

La guía de Delphi por Marco Cantù


106 - Indice

Evite FillChar para Caracteres


Aunque el procedimiento FillChar originalmente se desti-
naba a ser utilizado para el rellenado de una cadena con el
mismo carácter muchas veces, también se (aún más común-
mente) utilizaba para llenar un buffer genérico con datos. El
uso más frecuente es probablemente la reducción a cero de
una estructura de datos, por lo que es imposible cambiar la
definición actual del procedimiento (a pesar de su nombre):
var
rc: TRect;
begin
FillChar (rc, SizeOf (rc), 0);

Con strings cambiando a un tamaño de carácter de dos bytes


surgen los problemas. El primero es porque la cadena es
ahora el doble de grande, mientras que el segundo parámetro
de FillChar se expresa en bytes, y no en el número de caracte-
res lógico. Por ello la primera de las dos operaciones FillChar
en este fragmento de código (a partir del ejemplo CharTrou-
bles) falla, mostrando con la posterior operación en pantalla
una t:

var
str1, str2: string;
begin
str1 := 'here comes a string';
str2 := 'here comes a string';

FillChar (str1[1], Length (str1), 0); // nope!


Memo1.Lines.Add ('15 char is: ' + str1[15]);

FillChar (str2[1],
Length (str2) * StringElementSize (str2), 0); // yes!
Memo1.Lines.Add ('15 char is: ' + str2[15]);

¿Qué es peor?, piense lo que el rellenado con ceros del string


hará, pero el rellenado con un carácter específico causará un
completo desorden. De hecho, si usted rellena la cadena con
la letra A, por ejemplo:
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 107

FillChar (str2[1],
Length (str2) * StringElementSize (str2), 'A');
Memo1.Lines.Add ('15 char is: ' + str2[15]);

lo que termina haciendo es no llenar la cadena con el carácter


$41 pero si con el $4141, por lo que la cadena se convertirá en
una secuencia de caracteres Chinos:
15 char is: 䅁

En otras palabras, puede absolutamente dejar de usar Fill-


Char para rellenar una cadena con copias de un mismo carác-
ter y seguir usando este procedimiento sólo para las estructu-
ras de datos. Para rellenar una cadena puede utilizar en su lu-
gar StringOfChar:
str2 := StringOfChar ('A', 15);
Memo1.Lines.Add (str2);

Si usted necesita un resultado AnsiString, sepa que hay una


versión sobrecargada de StringOfChar, tomando un carácter
AnsiChar. Considere, sin embargo, que si escribe:
var
S: AnsiString;
begin
S := StringOfChar('A', 15);

el compilador utilizará la versión de WideChar de Strin-


gOfChar y convertirá la UnicodeString resultante a un AnsiS-
tring. Para evitar esta conversión puede escribir:
S := StringOfChar(AnsiChar('A'), 15)

La simple razón es que el compilador no puede llamar a una


función sobrecargada basándose en el tipo de resultado, lo
hará sólo sobre la base de los parámetros de entrada.

Operaciones de Cadena

La guía de Delphi por Marco Cantù


108 - Indice

Que Fallan o Ralentizan


Cuando se trabaja con cadenas, y en particular cuando se re-
utiliza código existente de procesamiento de cadena en Delphi
2009, hay dos cuestiones diferentes por las que debe estar
atento y comprobar la salida. En primer lugar, usted tiene que
comprobar si todas las operaciones producen el mismo resul-
tado, ya que a veces no es el caso. En segundo lugar, usted
tiene que estar seguro de que algunas operaciones no se con-
viertan en un proceso terriblemente lento.
La terrible ralentización sucede generalmente a causa de las
conversiones implícitas de cadenas, y, en particular, cuando
se intenta mantener las variables en torno a AnsiString. Si la
declaración AnsiString original en su código fue para diferen-
ciarla del tipo ShortString, y a continuación importamos el có-
digo, posiblemente su mejor opción sea utilizar el tipo string
genérico.

Atendiendo a Todas las Alertas


de Conversión de Cadenas
Si usted necesita mantener distintos tipos de cadenas o está
importando código anterior en general, debería, por lo menos
durante algún tiempo, atender las alertas de conversión implí-
citas de cadena, para tener una mejor comprensión de la
forma en que el compilador interpreta el código y de las ope-
raciones extras (posiblemente innecesarias) que este inyectará
en su código. Como vimos en la sección “Asignando y Convir-
tiendo Cadenas” del capítulo 2, esto puede ralentizar el código
del orden de unas mil veces.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 109

Recuerde que en la actualidad hay varias advertencias de con-


versión de cadenas, algunas de los cuales no están habilitadas
por defecto. Esta es una lista completa de las alertas relativas
a string/character que vale la pena atender, al menos en la
fase de conversión:
 Conversión explicita de cadena
 Conversión explicita de cadena con potencial pérdida de datos
 Conversión implícita de cadena
 Conversión implícita de cadena con potencial pérdida de da-
tos
 Al reducir una constante de cadena wide/Unicode se puede
perder información
 Al reducir una constante WideChar a AnsiChar se puede per-
der información
 WideChar reducido a byte char en una expresión de asigna-
ción
 Al ampliar una constante AnsiChar dada a un WideChar se
puede perder información
 Al ampliar una constante AnsiString dada se puede perder in-
formación

La guía de Delphi por Marco Cantù


110 - Indice

Recuerde que también puede convertir alertas de conversio-


nes de cadenas especificas en errores, al menos temporal-
mente, por lo que será más fácil capturar los mismos en caso

de que su código esté causando demasiadas alertas y conse-


jos53:

No Mueva Datos String


Acceder a datos string a bajo nivel, por ejemplo, con una lla-
mada Move no era una muy buena idea anteriormente, ya que
se podría perder la cuenta por referencia y causar problemas
de sobrecarga de memoria y otros más. Llamar a la función
Move con caracteres es aún peor ahora, ya que tenemos varias

53Puedesampliar la información sobre el nuevo cuadro de diálogo opciones de


proyecto y ajustes en el Capítulo 4, en la sección “Rediseñado el Diálogo de
Opciones de Proyecto”.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 111

representaciones de cadena que son incompatibles en el nivel


binario.
Como ejemplo, considere el siguiente código (tomado de la
demo MoveStrings) que mueve los datos de una cadena a un
buffer y los vuelve a cargar en otra cadena:
procedure TFormMoveStrings.btnMoveFailureClick(
Sender: TObject);
var
str1, str2: string;
buffer: TBytes;
begin
str1 := 'Hello world';

SetLength (buffer, Length (str1));


Move (str1[1], buffer[1], Length (str1));

SetLength (str2, Length (buffer));


Move (buffer[1], str2[1], Length (buffer));

Log (str1 + ' becomes ' + str2);


end;

Como Length devuelve el número de caracteres en dos-bytes


y Move trabaja en bytes, sólo la primera mitad de la cadena se
copia en el buffer y y luego la segunda cadena con la parte
principal de la cadena final rellena de cualquier tipo de dato
que estuviera en la memoria no inicializada:
Hello world becomes Hello圠湩潤we

Si tiene código existente que utiliza cadenas como búfers


(como en el ejemplo anterior) y no quiere cambiar su código,
una solución podría ser cambiar la definición del tipo de ca-
dena a (o, en menor medida, AnsiString). La misma demo
tiene el mismo algoritmo con las cadenas declaradas como:
var
str1, str2: RawByteString;

Esta versión del código producirá la salida correcta, ya que


será mediante el uso de AnsiString. Dependiendo de las cir-
cunstancias (el significado real de los datos que traslade,

La guía de Delphi por Marco Cantù


112 - Indice

string o búfer de datos), usted puede que preferir uno de estos


tipos sobre el otro.
Aún mejor, siempre que sea posible, cambie su código utili-
zando una matriz dinámica de bytes como TBytes, la estruc-
tura de datos que he usado en el fragmento de código de esta
sección para mantener un búfer genérico. El tipo TBytes se de-
fine en la unidad SysUtils como una matriz dinámica de bytes:
type
TBytes = array of Byte;

Leyendo y Escribiendo Búfers


Cuando utilice operaciones puras con strings, su código ante-
rior se importará a Delphi 2009 sin mayores obstáculos.
Cuando usted guarde su cadena de datos a archivos o a búfers
de memoria, es mucho más fácil ver que las cosas van mal54.
El siguiente es un ejemplo de un mal uso de streams en me-
moria, en el que los datos se extraen e insertan utilizando di-
ferentes codificaciones, que no fuerzan una conversión, pero
hace que el sistema considere los caracteres de un tipo dife-
rente de lo que realmente son y se mezclen:
var
memStr: TMemoryStream;
begin
memStr := TMemoryStream.Create;
try
Memo1.Lines.
LoadFromFile ('StreamTroubles_MainForm.pas');
Memo1.Lines.SaveToStream(memStr, TEncoding.UTF8);
memStr.Position := 0; // reset
Memo2.Lines.LoadFromStream(memStr, TEncoding.Unicode);

54Espero que un desajuste del BOM en el archivo, con el preámbulo de codifica-


ción utilizado para su carga, plantee una excepción, ya que esto le daría un
poco más de sentido. Por supuesto, siempre puede leer los bytes correspon-
dientes, en lugar de utilizar strings, en caso de operaciones de bajo nivel que
desee controlar plenamente

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 113

finally
memStr.Free;
end;

La ejecución de este código se traduce en un contenido total-


mente ilegible para Memo2. Ahora bien, si en este fragmento
de código revela este error bastante evidentemente, en la ma-
yoría de situaciones del mundo real, puede caer en el mismo
efecto pero de formas mucho más sutiles.
La recomendación número uno, siempre que tenga que guar-
dar un archivo, es guardar asimismo el BOM, para que quede
claro en qué formato está55. Esto no es difícil de lograr traba-
jando con la memoria, porque aunque usted no recuerde el
formato actual, el código streaming de Delphi agrega el BOM
correcto incluso a un memory stream.
Así, puede fijar el programa anterior llamando al método
LoadFromStream sin codificación ninguna, dejando que el sis-
tema compruebe por si mismo el formato declarado en el pro-
pio stream:
var
memStr: TMemoryStream;
begin
memStr := TMemoryStream.Create;
try
Memo1.Lines.
LoadFromFile ('StreamTroubles_MainForm.pas');
Memo1.Lines.SaveToStream(memStr, TEncoding.UTF8);
memStr.Position := 0; // reset
Memo2.Lines.LoadFromStream(memStr);
finally
memStr.Free;
end;

55Enel capítulo anterior, en la sección "Streams y Codificaciones" hemos visto


cómo definir una clase de ayuda para la clase TStrings para cambiar la co-
dificación predeterminada para el streaming. Vea este ejemplo como una
manera de personalizar el comportamiento de su código existente sin tener
que actualizar en muchos lugares.

La guía de Delphi por Marco Cantù


114 - Indice

Añadiendo y Concatenando
Cadenas
Otro tipo de codificación que debe sustituir siempre que sea
posible, es la cadena de concatenación de código. No es dema-
siado difícil encontrar las apariciones de AppendStr en su có-
digo (también porque utilizándolo provocará una advertencia
por obsoleto: deprecated warning), pero es más complicado el
modo de sustituir la concatenación de cadenas directa reali-
zada con el signo +.
También, la sugerencia es sustituir la concatenación de cade-
nas directa con el uso de la clase TStringBuilder en el caso que
esté realizando una concatenación56 compleja. Para mostrar
un mensaje compuesto de dos cadenas, mantener el signo +
es totalmente correcto.
Una vez más, lo que realmente debería velar es por la concate-
nación que cause conversiones implícitas de cadena, ya que
esto significa que se copien los datos muchas, muchas veces.
En otras palabras, añadir y concatenar cadenas funciona bien
cuando todas las variables de cadena (left-value y right-va-
lues) son del mismo tipo (toda UnicodeString, toda AnsiS-
tring, toda UTF8String, etc.). En ese caso, el compilador uti-
liza el código de la página correcto para los strings literales.
Cuando se mezclen cadenas con diferentes páginas de código,
se harán conversiones explícitas para evitar que una conver-
sión oculta ralentice el código (o cause pérdida de datos).

56A diferencia de lo que se ha escrito, utilizando la clase TStringBuilder no


mejora específicamente el rendimiento, pero hace que el código sea más
claro (en caso de conversiones complejas y concatenaciones) y si lo necesita,
compatibilidad con .NET. La clase TStringBuilder se trata en detalle en
el capítulo 7.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 115

Strings son... Strings (no


Bookmarks)
Aunque usted tiene el nuevo tipo RawByteString disponible
para representar conjuntos de caracteres genéricos o bytes, mi
recomendación es utilizar tipos de strings sólo para la trami-
tación de las cadenas de caracteres. Usted podría pensar esto
es obvio, pero no lo es tanto.
Como ejemplo, la VCL ha utilizado largamente un tipo string
para representar el tipo bookmarks de un dataset. La propie-
dad Bookmark de la clase TDataSet en Delphi 2007 es del tipo
TBookmarkStr, que se define como:
type
TBookmarkStr = string;

La cadena no es realmente lo que parece, es simplemente un


alias a un puntero, pero con una extraña definición que hizo
posible, como referencia de conteo, un mecanismo para mar-
cadores de este tipo sin mayores costes.
Esta definición ha cambiado en Delphi 2009, causando poten-
cialmente algunas incompatibilidades, siendo la propiedad
Bookmark de la clase TDataSet ahora de un nuevo tipo, que se
define como:
type
TBookmark = TBytes;

La antigua propiedad del tipo TBookmarkSt sigue estando defi-


nida como un alias, pero ha quedado obsoleta (aunque no téc-
nicamente marcada con la directiva deprecated), así que ten-
drá que cambiar su código para utilizar el tipo TBookmark en lu-
gar del tipo TBookmarkStr. Cubriré los problemas relacionados
con la importación de código que utiliza bookmarks a Delphi
2009 en el capítulo 12. Mi opinión principal aquí es que, si

La guía de Delphi por Marco Cantù


116 - Indice

nunca hizo nada como esto, reescriba su código: no hay una


forma mejor.

Molestas “Importaciones”
Actuales
Mientras recompilaba cientos de aplicaciones de mis anterio-
res libros, choqué con unos pocos casos concretos en los que
portar a Unicode requeriría algunas actualizaciones reales y
no una simple recompilación. Algunos de estos casos son real-
mente sólo errores triviales, y fáciles de solucionar in situ ob-
servando el código fuente, pero creo que es importante refe-
renciarlos aquí, ya que su código podría utilizar técnicas simi-
lares. Yo podría haber construido interminables ejemplos “a
propósito”, pero decidí utilizar casos reales, aunque posible-
mente sean menos importantes.

InliningTest que usaba


AnsiString
El siguiente fragmento de código proviene de Mastering Del-
phi 2005 y se actualizó posteriormente en Delphi 2007 Hand-
book57. Este demuestra la velocidad extra adquirida por el ali-
neado asignado a la función, frente a una similar que no esté
alineada, escrita de la siguiente forma:
const
LoopCount = 100000000;

var

57El código fuente completo está disponible en la carpeta del proyecto


dh2007_InliningTest

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 117

ssample : string;

function LengthStdcall (const s: AnsiString): Longint;


begin
Result := Integer(S);
if Result <> 0 then
Result := PInteger(Result-4)^;
end;

procedure TForm3.bntLenghtClick(Sender: TObject);


var
ttt: TTimeTest;
I, J: Integer;
begin
ssample:= 'sample string';
J := 0;
ttt := TTimeTest.Create;
try
for I := 0 to LoopCount do
Inc (J, LengthStdcall (ssample));
memo1.Lines.Add ('Length ' +
ttt.Elapsed + '[' + IntToStr (J) + ']');
finally
FreeAndNil (ttt);
end;
end;

El código todavía funciona, pero el tiempo registrado es cier-


tamente sospechoso:
Length 15,188[1300000013]
Inline 203[1300000013]

Los 15 segundos extra son necesarios para convertir el corto


UnicodeString en un AnsiString cien millones de veces. Todo
lo que tiene que hacer para solucionar este código y regresar
al resultado esperado es cambiar la declaración de la función
LengthStdcall a:
function LengthStdcall (const s: string): Longint;

Así vuelve a su tiempo esperado (aproximadamente):


Length 408[1300000013]
Inline 204[1300000013]

Utilizando Funciones Ansi-


La guía de Delphi por Marco Cantù
118 - Indice

predeterminadas
En StrUtils y SysUtils solía haber una gran variedad de fun-
ciones y procedimientos con ANSI en su nombre. ¿Qué sucede
con ellos en el Delphi Unicode? Debemos de eliminarlos,
mantenerlos, ¿o qué?
No hay una respuesta única, pero en general pueden seguir tal
como están implementadas aplicándose a las cadenas de tipo
genérico, pero sería aún mejor renombrarlas para utilizar el
mismo nombre, sin el prefijo Ansi.
En algunos casos, la actualización es automática. La mayor
parte de las funciones de cadenas Ansi de la RTL (con o sin un
prefijo Ansi) se han trasladado a la nueva unidad y la mayoría
de ellas tienen una versión sobrecargada sobre la base del tipo
UnicodeString. Si usted no incluye esta unidad, acabará vin-
culando automáticamente58 a la versión UnicodeString.

De hecho, hay dos situaciones diferentes:


 Algunas de las funciones Ansi ahora trabajan internamente
sólo con el tipo UnicodeString. Al llamarlas con un parámetro
AnsiString, estas lo convertirán automáticamente.
 Algunas de las funciones Ansi tienen sus versiones AnsiString
y UnicodeString sobrecargadas. Dependiendo de la cadena
que pase como parámetro, está llamando a una u otra versión.
Una de las funciones en el primer grupo es AnsiResemblesText.
Puede sustituir una llamada a AnsiResemblesText con una a

58Si lo que desea es seguir usando el tipo AnsiString, y no desea añadir la unidad
AnsiStrings en demasiados lugares, considere la posibilidad de utilizar una
directiva de alias a la unidad, por ejemplo, la redefinición de SysUtils como
SysUtils más AnsiStrings. En una línea de comandos sería:
-AsysUtils=SysUtils;AnsiString

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 119

ResemblesText, ya que ambas ruedan ahora sobre el tipo Uni-


codeString:
function ResemblesText(const AText, AOther: string):
Boolean; overload;
function AnsiResemblesText(const AText, AOther: string):
Boolean; overload;

Por supuesto, si su código necesita usar un AnsiString actual,


incluso llamando a la versión Ansi de esta función no se aho-
rrará la conversión implícita de estas cadenas. La directiva de
sobrecarga overload, en este caso, no se utiliza de manera
efectiva, pero le permite añadir una versión de las funciones
AnsiString en sus propias unidades.
Una de las funciones en el segundo grupo es ReverseString.
En este caso, la versión Ansi de la función toma un parámetro
AnsiString:
function ReverseString(const AText: string):
string; overload;
function AnsiReverseString(const AText: AnsiString):
AnsiString;

En esta situación, usted podría tener el problema opuesto, es


decir si mantiene la llamada Ansi y utiliza el tipo UnicodeS-
tring, el compilador inyectará conversiones extras inútiles y,
posiblemente pérdidas. En esta situación, usted tendría que
actualizar su código para utilizar la versión no-Ansi de la fun-
ción. En la situación opuesta (es decir, si utiliza ReverseString
pasando AnsiString), puede añadir, además, una declaración
para utilizar la unidad de AnsiStrings y así habilitar la tercera
versión disponible de esta función:
function ReverseString(const AText: AnsiString):
AnsiString; overload;

Para hacer las cosas aún más complicadas, hay funciones que
tienen una versión Wide (de nuevo para mantener la compati-
bilidad con la unidad WideStrUtils) y algunas funciones que
sólo tienen una versión Ansi.

La guía de Delphi por Marco Cantù


120 - Indice

En un buen número de casos, sin embargo, las unidades pri-


mordiales (SysUtils y StrUtils) sólo tienen versiones Unico-
deString con las versiones AnsiString movidas ahora a la Uni-
dad de AnsiStrings. Un buen ejemplo podría ser el de Upper-
Case59 y sus funciones relacionadas:
// in SysUtils
function UpperCase(const S: string): string; overload;
function UpperCase(const S: string;
LocaleOptions: TLocaleOptions): string;
overload inline;
function AnsiUpperCase(const S: string):
string; overload;
function WideUpperCase(const S: WideString): WideString;

// in AnsiStrings
function UpperCase(const S: AnsiString):
AnsiString; overload;
function UpperCase(const S: AnsiString;
LocaleOptions: TLocaleOptions): AnsiString;
overload; inline;
function AnsiUpperCase(const S: AnsiString):
AnsiString; overload;

// in WideStrUtils
function UTF8UpperCase(const S: UTF8string): UTF8string;

En general, por lo que veo, se aplican tres normas:


 Embarcadero trata de minimizar sus cambios en el código, de
modo que usted pueda mantener, al menos, alguna llamada
ANSI, ya que son sin perjuicio.
 Usted debe tratar de deshacerse de todas las llamadas ANSI (y
llamadas Wide) en sus programas, aún a coste de emplear al-
gún tiempo extra en la migración.
 En el caso de que quiera seguir usando el tipo AnsiString (lo
que no está recomendado), utilice la nueva unidad AnsiS-
trings.

59Aunque las funciones como UpperCase ahora toman un parámetro Unico-


deString, todavía operan con caracteres ASCII solamente.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 121

Cadenas Unicode y Win32


Como ya se ha mencionado en el capítulo anterior, la API de
Win32 tiene en la mayoría de los casos llamadas separadas
para cadenas ANSI (marcada con A) o cadenas Unicode (mar-
cada con una W de Wide). Para ser más preciso, estas APIs
utilizan cualquiera de los dos tipos PAnsiChar ó PWideChar.
Ya he declarado que el hecho de que la API Win32 esté tan
fuertemente basada en UTF-16 hace de este formato la elec-
ción más obvia para el desarrollo nativo en Windows, con he-
rramientas como Delphi.
En muchos casos, las versiones ANSI de la API de Windows
llaman a la versión Wide realizando una conversión extra. En
otros casos, las APIs Wide son, de hecho, más lentas. El cam-
biar al mismo tiempo el formato string, el alias de tipo PChar y
la versión de la API de Windows mapeada significa que puede
convertir la mayor parte de su código de una forma muy sen-
cilla: ¡Usted no tiene que hacer nada absolutamente! Consi-
dere el siguiente ejemplo:
TextOut (Canvas.Handle, 104, 224,
PChar(str1), Length (str1));

Esto funciona por igual en Delphi 2007 y Delphi 2009, aun-


que la llamada al API de Windows termine siendo diferente.
Esto es cierto para la mayoría de las llamadas a la API de
Win32 con parámetros de cadena, que se han reestructurado
de la versión A (ASCII) a la W (Wide), junto con un cambio en
el compilador a nivel de PChar desde PAnsiChar a PWideChar.
Hay algunas API específicas, sin embargo, que no tienen dos
versiones separadas y siempre exigen un puntero PAnsiChar.
Un ejemplo típico nos lo da la función GetProcAddress, una
DLL exportando símbolos limitados a Ansi. En estos casos,

La guía de Delphi por Marco Cantù


122 - Indice

debe convertir ambos, la cadena y el puntero a otro tipo de ca-


racteres distinto. Por lo tanto, una línea como:
GetProcAddress (hmodule, PChar (strFnName))

Se convertirá en:
GetProcAddress (hmodule, PAnsiChar (
AnsiString(strFnName)));

Otro caso particular es el de la función CreateProcessW. Esta


existe, pero puede modificar el contenido de la cadena con el
nombre del archivo ejecutable y fallaría con una excepción, si
usted pasa una cadena como valor constante.

Aplicaciones de consola Win32


Si está interesado en el desarrollo de aplicaciones de consola,
tenga en cuenta que todos los Input/Output de Consola en
Delphi aún se basan y seguirán basándose en la base ANSI.
Esto es cierto en el caso de las rutinas como Read, Write,
ReadLn, y WriteLn, entre otras. La ventana de consola muestra
los caracteres utilizando código de página ANSI (o incluso un
código de página OEM), y las mismas operaciones, cuando se
redirijan a los archivos, podrían causar el mismo problema.
Verdaderamente, la consola de Windows se puede abrir en
modo Unicode (si ejecuta con el comando /u flag: cmd /u),
pero esto se hace raras veces, ya que sólo funciona si su salida
se envía a un archivo y no a la pantalla. Delphi 2009 no es
compatible con el modo Unicode de la consola.
Si usted desea hacer experimentos, puede comenzar con la
demo UnicodeConsoleTest en el que utilizo un objecto60 TTex-
tWriter con codificación Unicode, conectado con un stream

60La clase TTextWriter se trata en el capítulo 7.

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 123

asociado a su salida estándar. Mostrarlo en la pantalla de la


consola es incorrecto, como esperaba. Al redirigir el resultado
a un archivo se obtiene un archivo UTF-16, pero sin la marca
BOM. Este es el código:
var
aString: string;
textWriter1: TTextWriter;
fileStream1: TFileStream;

begin
aString := 'My ten Euros (10€)';
try
fileStream1 := TFileStream.Create(
GetStdHandle(STD_OUTPUT_HANDLE));
textWriter1 := TStreamWriter.Create (
fileStream1, TEncoding.Unicode);
try
textWriter1.Write (aString);
finally
textWriter1.Free;
fileStream1.Free;
end;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

PChar y el Puntero
Matemático
El tipo PChar en Delphi ha sido tradicionalmente utilizado en
dos escenarios totalmente diferentes. El primero es la gestión
de cadenas de caracteres de un modo compatible con el len-
guaje C y la API de Windows. El segundo es para sustituir un
puntero de tipo genérico, porque PChar era el único tipo de
puntero con soporte a punteros matemáticos. Por ejemplo,
puede moverse al siguiente carácter de una cadena escri-
biendo uno de los siguientes métodos:
La guía de Delphi por Marco Cantù
124 - Indice

var
pCh1: PChar;
begin
...
pCh1 := PChar1 + 1;
Inc (PChar1);

No sólo puede aumentar un PChar, sino que también puede


disminuirlo, compararlo con otro puntero, y hacer muchas
otras operaciones.

El Problema Con PChar


Este uso de PChar fue tan útil que este tipo se utiliza a me-
nudo en lugar de otros punteros, como PInteger en el si-
guiente fragmento de código extraído del ejemplo Pointer-
Math, el cual lee una matriz utilizando un puntero (un pun-
tero PChar) y moviendo el puntero desde un Integer hasta el
siguiente sumándole 4 a este mismo (ya que un Integer es de
cuatro bytes). Aquí tiene el código completo del método:
procedure TFormPointerMath.Button1Click(Sender: TObject);
var
TenIntegers: array [1..10] of Integer;
pOneInteger: PChar;
I: Integer;
begin
// escribir
for I := 1 to 10 do
TenIntegers [I] := I;

// ahora leerlo usando un puntero


pOneInteger := @TenIntegers;
for I := 1 to 10 do
begin
Memo1.Lines.Add(
'Address: ' + IntToHex (Integer(pOneInteger), 8) +
' - Value: ' + IntToStr (PInteger(pOneInteger)^));
pOneInteger := pOneInteger + 4;
end;
end;

Si compila este código en cualquier versión de Delphi desde


Delphi 2 a Delphi 2007, obtendrá el resultado siguiente:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 125

Address: 0012F4A8 - Value: 1


Address: 0012F4AC - Value: 2
Address: 0012F4B0 - Value: 3
Address: 0012F4B4 - Value: 4
Address: 0012F4B8 - Value: 5
Address: 0012F4BC - Value: 6
Address: 0012F4C0 - Value: 7
Address: 0012F4C4 - Value: 8
Address: 0012F4C8 - Value: 9
Address: 0012F4CC - Value: 10

Puede ver como la dirección se aumenta de 4 en 4 cada vez, y


el valor correcto que devuelve. Tengo que subrayar, que si
ahora recompila el mismo código exacto en Delphi 2009, us-
ted obtendrá unos resultados totalmente diferentes:
Address: 0012F4AC - Value: 1
Address: 0012F4B4 - Value: 3
Address: 0012F4BC - Value: 5
Address: 0012F4C4 - Value: 7
Address: 0012F4CC - Value: 9
Address: 0012F4D4 - Value: 29043072
Address: 0012F4DC - Value: 4476177
Address: 0012F4E4 - Value: 4400843
Address: 0012F4EC - Value: 4403501
Address: 0012F4F4 - Value: 4474789

Esto no es lo que el código pretende, por supuesto, pero es lo


que realmente manda este código. Incrementar 4 caracteres el
puntero en Delphi 2009 significa que se desplazan 8 bytes ha-
cia adelante, ya que cada carácter es ahora de dos bytes. No
sólo la salida esta mal, sino pero también estamos haciendo
un acceso ilegal a la memoria, que podría ser muy peligroso
en los casos en que hubiéramos escrito en este área de memo-
ria.

De PChar a PByte
Si este problema es potencialmente problemático, al menos
para una secuencia de código de bajo nivel, la solución está al
alcance de la mano. En el código anterior, usted puede sim-
plemente sustituir la versión-específica del tipo PChar por la

La guía de Delphi por Marco Cantù


126 - Indice

versión-agnóstica tipo PByte61. Un puntero a byte, de hecho,


sigue siendo lo mismo y se comporta igual, independiente-
mente del tamaño de los caracteres. Todo lo que tienes que
hacer en un método similar al anterior es cambiar este pun-
tero a una declaración de variables:
var
pOneInteger: PByte;

Sin cambiar nada en el código, recompile el programa y este


debería funcionar. Lo bueno es que (en Delphi 2009) PByte
soporta el mismo puntero matemático que soporta PChar. En
versiones anteriores de Delphi, PByte no soportaba el puntero
matemático, pero usted todavía puede usar un algoritmo
como el discutido, cambiando a más uno el ciclo de incre-
mento:
Inc (pOneInteger, 4);

El hecho de que Inc y Dec trabajen con más tipos de punteros


es poco conocido entre los usuarios de Delphi. Aunque, dispo-
ner del puntero matemático al completo significa que puede
también comparar punteros, y realizar otras operaciones.

PInteger y la Directiva
POINTERMATH
Sin embargo, como estamos tratando con números enteros,
¿no sería mejor escribir código como este (cambiando al in-
cremento a uno y pasando del modelado del código original)?
procedure TFormPointerMath.btnPIntegerClick (
Sender: TObject);

61Unasolución alternativa, que es más compatible con versiones anteriores de


Delphi, es usar PAnsiChar en lugar de PChar. Sin embargo, el uso PByte se
recomienda generalmente para hacer que su intención sea más clara y legible
que usando PansiChar.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 127

var
TenIntegers: array [1..10] of Integer;
pOneInteger: PInteger;
I: Integer;
begin
// escribir
for I := 1 to 10 do
TenIntegers [I] := I;

// lee mediante un puntero


pOneInteger := @TenIntegers;
for I := 1 to 10 do
begin
Memo1.Lines.Add(
'Address: ' + IntToHex (Integer(pOneInteger), 8) +
' - Value: ' + IntToStr (pOneInteger^));
pOneInteger := POneInteger + 1;
end;
end;

Una vez más, esto ha sido posible usando una llamada Inc in-
cluso en Delphi 2007 (y que el ejemplo PointerMathD2007
pueda abrirse con esta versión del IDE, lo demuestra), pero
en Delphi 2009 usted puede compilar el código anterior aña-
diendo al código fuente la directiva:
{$POINTERMATH ON}

No utilice PChar para Pointer


Math
Para resumir esta sección, deje de utilizar PChar para cual-
quier cosa que no sea un carácter o relacionado con las cade-
nas. Si necesita que su código sea capaz de mantener la com-
pilación con versiones anteriores de Delphi, puede utilizar Inc
y Dec, y hacer algunos cambios en el código anterior. Si todo
lo que necesita es el soporte de Delphi de 2009, convierta el
código a PByte (generalmente es el camino más fácil) o use un
tipo de puntero específico y la nueva directiva POINTERMATH.
En cualquier caso, ¡hacer una búsqueda de PChar en toda su
base de código es generalmente una buena idea!
La guía de Delphi por Marco Cantù
128 - Indice

Parámetros Variants y
Open Arrays
Cuando esté trabajando con variants, la mayoría del código de
conversión de variants a cadenas funcionará tal como cabe es-
perar, ya que hay un nuevo tipo de variant:
varUString = $0102;
{ Unicode string 258 } {not OLE compatible}
Todas las conversiones basadas en variants deberían trabajar
correctamente, sin muchas diferencias con su código variant
relacionado, a menos que tenga que interactuar con COM o
automatización OLE, en cuyo caso todavía tiene que utilizar el
tipo WideString (al igual que antes, esto no cambia).
Cuando trabaje con parámetros variant open array, y otras es-
tructuras de datos sin tipificar, las opciones AnsiString y Uni-
codeString deben ser manipuladas específicamente. Por ejem-
plo, la estructura TVarRec tiene ahora tres entradas distintas
relacionadas con la cadena (solían ser dos), entre muchos
otros tipos que he omitido:
type
TVarRec = record
case Byte of
vtString: (VString: PShortString);
vtAnsiString: (VAnsiString: Pointer);
vtUnicodeString: (VUnicodeString: Pointer);
...

Si procesa un parámetro open array con una sentencia case


que tiene ramas para cadenas específicas, tiene que conside-
rar esta nueva alternativa.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 129

A continuación
Acabamos de finalizar la cobertura al soporte de Unicode en
Delphi 2009. A continuación, en la Parte II voy a tratar en
profundidad los cambios en el IDE, el compilador y la RTL.
En la sección RTL volveremos a estudiar las funciones de ges-
tión de cadenas. Y más adelante, en la parte del libro dedicada
a bases de datos, cubriré la forma en que los cambios introdu-
cidos por el soporte Unicode afectan a la clase TDataSet y sus
clases relacionadas.

La guía de Delphi por Marco Cantù


130 - Indice

Apartado II:
Delphi 2009 y
Su Compilador

Ahora que he cubierto completamente la nueva característica


más importante de Delphi 2009, el soporte Unicode, ya es
hora de centrarnos en todo el entorno de desarrollo, el compi-
lador y las librerías en tiempo de ejecución. Esta sección trata
lo más esencial y las características de bajo nivel, mientras
que las novedades en la interfaz de usuario y la base de datos
se cubrirán en las siguientes partes del libro.
 Capítulo 4: Nuevas Características del IDE
 Capítulo 5: Genéricos
 Capítulo 6: Métodos Anónimos
 Capítulo 7: Más cambios del Lenguaje y de la RTL

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 131

Capítulo 4:
Nuevas
Características
del IDE

La 6ª encarnación62 del IDE Galileo tiene sólo un limitado


conjunto de nuevas capacidades, si usted no tiene en cuenta el
hecho de que todo se ha convertido a Unicode, lo que está
probablemente lejos de ser trivial. Las mejoras más relevantes
se relacionan con las nuevas ampliaciones del Project Mana-
ger y la habilidad de compartir opciones de proyectos entre
l0s diferentes proyectos, utilizando las nuevas opciones de fi-
cheros. Finalmente también se mejora, significativamente en
Delphi 2009, la gestión de los recursos de ventanas.

62Estoycontando de la siguiente manera: 1 fue C#Builder, la 2 fue Delphi 8, la 3


fue Delphi 2005, la 4 fue Delphi 2006 (o Borland Developer Studio), 5 fue
Delphi 2007 (o RAD Studio). El número 6.0, se confirma también por el
nombre de la carpeta utilizada por defecto al instalar el producto.

La guía de Delphi por Marco Cantù


132 - Indice

Instalación y Ejecución
Como en Delphi 2007, la instalación de Delphi 2009 está ba-
sada en InstallAware. Esta vez, sin embargo, la experiencia de
instalacion ha sido mejorada considerablemente, especial-
mente su velocidad. En Delphi 2009 se puede completar la
instalación en 20 minutos en lugar de varias horas. La instala-
ción de la actualización 1 del producto (lanzado a finales de
Octubre de 2009) es igualmente fácil.
Un cambio notable en este sentido, es el hecho de que la
ayuda es ahora una instalación separada, por lo que se puede
actualizar con más frecuencia y por separado del producto
principal (de este modo, no tiene que volver a reinstalar Del-
phi cuando actualice la ayuda, ni tampoco tiene que reinstalar
la ayuda en el caso de que desee volver a instalar el IDE). La
instalación de la ayuda puede tomarse más tiempo que la ins-
talación del producto ya que la imagen de la ayuda es más
grande que la misma del IDE.
Cuando se instala en Windows Vista, tendrá (por defecto) el
producto instalado en las siguientes carpetas:
C:\Program Files\CodeGear\RAD Studio\6.0
C:\Users\Public\Documents\RAD Studio\6.0\Demos\
C:\Program Files\Common Files\CodeGear Shared

No Es Necesario el SDK .NET


Anteriormente, desde Delphi 8 y hasta incluso Delphi 2007,
uno de los requisitos previos para instalar el IDE fue la pre-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 133

sencia del SDK de Microsoft .NET (la versión 1.1, anterior-


mente la versión 2.0 más adelante)63. Esto ya no es necesario
para Delphi 2009. Todavía es necesario instalar el runtime
.NET de Microsoft, el cual puede ya tener instalado como
parte del sistema operativo, pero no es necesario el SDK, que
es mucho mayor y requiere cientos de MB.
El motor de ayuda utilizado por Embarcadero es Document
Explorer de Microsoft o DExplorer. Anteriormente sólo esta-
ban disponibles en el SDK, pero ahora puede desplegarlos
como una instalación, que es lo que Embarcadero está utili-
zando en esta versión de Delphi.
La ayuda de Delphi es muy amplia (esta es la razón por la que
se necesita tanto tiempo para instalarla), ya que incluye tanto
la documentación de Embarcadero como la documentación de
la plataforma de Microsoft. En esta versión, sin embargo, el
equipo fijó un “ranking” de temas concretos estando los de
Delphi siempre antes que los de la plataforma genérica. El
contenido específico de Delphi también ha mejorado mucho.

Instalar el limpiador de Windows


A veces, cuando desinstala Delphi para reemplazarlo por una
versión actualizada, el instalador se queja, se detiene y no fun-
ciona como sería de esperar. En estos casos, Embarcadero re-
comienda la limpieza de todas las carpetas de la aplicación
(incluyendo algunos ocultos de los que depende el sistema
operativo). Una alternativa es utilizar la utilidad propia de

63El SDK .NET se requiere para el Explorador de Documentos, hasta que Micro-
soft tenga disponible un instalador independiente para la ayuda. Otras par-
tes de la arquitectura .NET utilizadas por el IDE de Delphi, incluyendo el
motor de MSBuild, son parte del Standard runtime, no del SDK.

La guía de Delphi por Marco Cantù


134 - Indice

Microsoft Windows Install Clean Up, que se puede encontrar


en:
http://support.microsoft.com/default.aspx?
scid=kb;en-us;290301

Tenga en cuenta que el uso de estas herramientas de bajo ni-


vel pueden obstaculizar su sistema, por lo cual proceda con
precaución (solamente después de leer las instrucciones y
bajo su propio riesgo).

El Flag -idecaption
Usted probablemente sabe (aunque esto era un secreto bien
guardado durante muchos años64) que puede ejecutar varias
instancias del IDE, al mismo tiempo, con diferentes configu-
raciones de registro utilizando el comando en línea –R.
El problema está, en que si usted ejecuta dos versiones dife-
rentes del IDE a la vez, es muy difícil saber cuál es cuál. Otro
parámetro en la línea de comandos para el IDE es -idecap-
tion, que toma un título como valor. Sumando estos dos co-
mandos en línea se pueden ejecutar el IDE con el siguiente
enlace:
"C:\Program Files\CodeGear\RAD Studio\6.0\bin\bds.exe" -
pDelphi -rSmall -idecaption="Small Tiburòn"

64Hedocumentado la instrucción -R en el capítulo 1 del libro "Delphi 2007


Handbook", en la sección "Running the Delphi IDE".

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 135

Este comando ejecuta el IDE de Delphi con la personalidad


única de Delphi Win32, activando la configuración del Regis-
tro "Small", y cambiando el título del IDE “Small Tiburón65”,

como se muestra a continuación:


Si no se especifica desde la línea de comandos, el título del
IDE se recupera del Registro, en la sección de Personalidades,
en los que existe una cadena diferente para cada versión (o
personalidad activa), del IDE.

Gestión de Proyectos en
Delphi
La gestión de los proyectos es una operación muy habitual. Si
Delphi 2007 añadió algunos conceptos totalmente nuevos,
como el soporte de MSBuild los parámetros de construcción
(Depuración y Liberación) y eventos para situaciones de pre-
construcción y post-construcción, la nueva versión hace que
estas características sean más flexibles y mucho más fáciles de
usar, a partir de una importante modernización del Project
Manager en si mismo. Antes de mirar el Project Manager, sin
embargo, tenemos que mirar la mejora de los archivos de pro-
yecto y la renovada caja de diálogo de Opciones de Proyecto.

65Como puede haber oído, Tiburón fue el nombre en clave de Delphi 2009.

La guía de Delphi por Marco Cantù


136 - Indice

Actualizando
Desde los primeros días de Delphi, el fichero de código fuente
del proyecto (con la extensión .DPR) contiene el código Object
Pascal y utiliza una o más configuraciones separadas de pro-
yecto para almacenar otros ajustes. El formato y la extensión
del archivo de configuración de proyecto ha cambiado varias
veces en las últimas versiones, pasando de un archivo INI a
un archivo XML y, a continuación, a un archivo XML de (el
formato de fichero ).
Desde Delphi 2007 a Delphi 2009, el formato general de este
proyecto de configuración de archivo no cambia. Pero su con-
tenido es diferente, y Delphi 2007 no reconoce las opciones
añadidas por la versión más reciente del IDE. Al abrir un pro-
yecto existente de Delphi 2007, el IDE de Delphi 2009 le pe-
dirá el nombre de un archivo de copia de seguridad en donde
pueda copiar la versión del proyecto de archivo de configura-
ción:

El nombre predeterminado del archivo de configuración de


copia de seguridad del proyecto es el nombre del proyecto con
la extensión .dproj.2007. En este caso concreto, por ejemplo,
he rebautizado el nombre de archivo de proyecto como IedMo-
nitor2007.dproj. Después de realizar esta operación, el IDE
sumará al panel de mensajes la línea:
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 137

Upgrading project. Backup


C:\progetti\IedMonitor\IedMonitor2007.dproj created.

Tenga en cuenta sin embargo, que la actualización del archivo


del proyecto de configuración a la versión de Delphi 2009 no
se crea en realidad hasta que usted lo guarde.
La versión de copia de seguridad le permitirá volver a abrirlo
en Delphi 2007. Si usted tiene la necesidad de compatibilizar
hacia atrás, sería mejor guardar la versión de Delphi 2009 del
proyecto con un nombre diferente.
En el nuevo fichero .DPROJ, Delphi 2009 añade una nueva
etiqueta de versión del proyecto:
<ProjectVersion>11.1</ProjectVersion>

La actualización implica cambios en la construcción de la con-


figuración (como se explica más tarde), y en la gestión de los
recursos. Las siguientes secciones son nuevas o están muy
modificadas:
<PropertyGroup Condition="'$(Config)'=='Release' or
'$(Cfg_Release)'!=''">
<Cfg_Release>true</Cfg_Release>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or
'$(Cfg_Debug)'!=''">
<Cfg_Debug>true</Cfg_Debug>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_DependencyCheckOutputName>SimpleApp.exe
</DCC_DependencyCheckOutputName>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="SimpleApp.dpr">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="SimpleAppMainForm.pas">
<Form>Form30</Form>
</DCCReference>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>

La guía de Delphi por Marco Cantù


138 - Indice

<BuildConfiguration Include="Release">
<Key>Cfg_Release</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_Debug</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>

Si intenta reabrir este archivo de proyecto en Delphi 2007


(sólo la versión anterior reconoce este formato), verá el si-
guiente error:

Rediseñado Diálogo De Opciones


De Proyecto
El cuadro de diálogo Opciones del Proyecto es uno de los diá-
logos de Delphi que se tiende a utilizar muy a menudo, y creo
que no soy yo solo. Es por esto que Delphi 2009, en su amplia
reforma, a veces me deja perplejo. El nuevo diseño incluye
unas páginas con opciones que son parte de la configuración
del constructor, y (como veremos más adelante en la sección
“Configuraciones del Constructor y Ajustes de Configura-
ción”), estas páginas de la caja de diálogo se utilizan en la
práctica también en el interior del Administrador de Configu-
ración del Proyecto. Mire por ejemplo las diferencias en las
opciones del compilador de página Delphi entre Delphi 2007
(ver imagen página 122) y Delphi 2009 (ver imagen página
123):

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 139

La diferencia es muy significativa. En el nuevo diseño las casi-

La guía de Delphi por Marco Cantù


140 - Indice

llas de verificación son sustituidas por la de “True/False” y los


botones de radio por desplegables con las distintas opciones.
También hay un área de ayuda en la parte inferior (minimi-
zada en la imagen anterior, ya que quise encajar todas las op-
ciones de la página en el cuadro de diálogo), ofreciendo una
información reducida sobre las distintas alternativas. Un ele-
mento interesante del área “description” es que proporciona
el valor por defecto para la opción.
Se ha realizado un rediseño gráfico al que le tomará un cierto
tiempo acostumbrarse, en parte porque los temas dentro de
cada grupo están ahora en orden alfabético, pero lo están en
un orden distinto al de antes. El directorio de opciones se ha
movido bajo el nodo principal del Compilador Delphi. Pero
junto a los cambios organizativos, ¿hay algo que haya desapa-
recido o algo nuevo?

Nuevas Opciones de Proyecto


para el compilador
En la página “Delphi Compiler/Compiling”, que solía llamarse
compiler, la sección Code Generation tiene las siguientes nue-
vas opciones:
 Code inlining control corresponde a la directiva $INLINE del
compilador y controla como trabaja inlining.
 Emit runtime type information corresponde a la directiva -
$M de comando en línea o la directiva $M, y determina la ge-
neración de información en tiempo de ejecución para una de-
terminada clase (o la totalidad de las clases de un proyecto).
 Minimum enum size corresponde al flag -$Z (o la directiva
$Z) y determina el tamaño mínimo utilizado para los valores

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 141

de los tipos enumerados (un Byte, un Word, un Double


Word, o un Quad Word).
 String format checking, que está habilitado por defecto,
puede ser deshabilitado para evitar algunos chequeos66 de ca-
dena automáticos y corresponde a la directiva $STRINGCHE-
CKS. Esta es una nueva opción del compilador para Delphi
2009 y se supone que sigue estando indocumentado y bas-
tante oculto... por lo que es una sorpresa poder verlo desta-
cado en el cuadro de diálogo de las Opciones de Proyecto.
 Code page ya estaba en versiones anteriores, pero ahora es
mucho más relevante en relación con la forma en que trabaja
el tipo AnsiString, como hemos tratado ya en el capítulo 2.
La sección Debugging tiene una nueva opción Use imported
data references (asignada a $G) que controla la creación de
referencias de datos importados (aumenta la eficiencia de me-
moria pero protegiendo el acceso a variables globales que se
definen en otros paquetes en tiempo de ejecución).
Los errores en tiempo de ejecución y de sintaxis tienen los
mismos elementos (y también por defecto los mismos valores)
que en las versiones anteriores de Delphi. La sección Other
options muestra nuevas opciones, exceptuando la Generate
XML documentation que ya estaba disponible:
 Additional switches to pass to the compiler puede ser usado
para insertar más directamente las opciones del compilador

66Como vimos en el capítulo 2, en muchos lugares dentro de la RTL de Delphi,


existen las llamadas a la función EnsureUnicodeString y otras funciones de
la familia Ensure String. Usted puede pedir al compilador que se salte estas
llamadas extras al desactivar el uso de --string-checks en las opciones
del compilador o la directiva $STRINGCHECKS. Estos interruptores no están
documentados y no tienen apoyo oficial, pero están directamente disponibles
en el Proyect Options del diálogo del IDE, cosa que me parece bastante ex-
traña.

La guía de Delphi por Marco Cantù


142 - Indice

de la línea de comandos que no están especialmente soporta-


das por el IDE, sin embargo esta característica técnica ya dis-
ponible en Delphi 2009 significa que ahora soporta todas y
cada una de las opciones del compilador.
 Allow unsafe code le permitirá compilar código considerado
no apto para un entorno como .NET y que tenga poco sentido
(o no) para el compilador de Win32.
 Look for 8.3 filenames also instruye al compilador para traba-
jar con antiguas versiones de Windows y corresponde con la
opción -P del compilador.
 Output unit dependency information esta opción habilitará el
comando del compilador --depends que al parecer no goza de
mantenimiento, por ahora.

Otras Nuevas Opciones de


Proyecto
La página de consejos y advertencias corresponde a la antigua
página del Compilador de Mensajes. Hay, por supuesto, va-
rios de los nuevos avisos (hints) relacionados con las cadenas
Unicode y otras nuevas características del compilador.
La vinculación de la página, que se llamaba Linker, es ahora
visualmente muy diferente (y mucho más compacta, puesto
que hay unos cuantos botones radio), siendo la única opción
nueva la Set base address for relocatable images.
En el nivel principal de la página del compilador de Delphi
están exactamente las mismas opciones encontradas previa-
mente en Directories/Conditionals. Lo que si puede ser con-
fuso es que hay otra página llamada Directories and Conditio-
nals que es parte de la configuración de recursos del compila-
dor en el marco de la página principal de Resource Compiler.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 143

Estas páginas son completamente nuevas y permiten contro-


lar el compilador de recursos del IDE de Delphi de forma que
no se había experimentado anteriormente. Hay más adelante
en este capitulo una sección específica, "Gestión de Recursos
en el IDE", que abarca este tema.

Ubicación Predeterminada de
Proyectos
Desde Delphi 2005, la ubicación por defecto para todos los
proyectos nuevos es la carpeta de documentos del usuario.
Pocos desarrolladores de Delphi saben que esto puede modifi-
carse estableciendo un valor por defecto para el proyecto me-
diante el cuadro de edición en Environment Options page of
the Tools | Options dialog box.

El Project Manager
Junto con un rediseño del cuadro de diálogo de Opciones de
Proyecto (que todavía no he cubierto totalmente al examinar
las nuevas características de configuración) Delphi 2009
muestra una importante actualización del panel Project Ma-
nager, uno de los paneles más comúnmente utilizado del IDE.
Incluso con una mirada superficial a su ventana se revelan al-
gunas de sus nuevas características:

La guía de Delphi por Marco Cantù


144 - Indice

Usted puede ver que hay un nuevo nodo Build Configurations

con otros subnodos, utilizados para activar la configuración


de una forma mucho más sencilla que en Delphi 2007. Este
tema se trata en la sección posterior “Construcción de Confi-
guraciones y Opciones de Configuración”.
La barra de herramientas del Project Manager cuenta con va-
rios botones nuevos, hasta el punto que, lo que generalmente
usted querrá hacer, será eliminar los textos de las etiquetas,
utilizando el menú contextual de la propia barra de herra-
mientas. El nuevo botón Sync selecciona el fichero actual en el
editor del Project Manager, sólo si el archivo es parte del pro-
yecto, por supuesto. La operación opuesta (es decir, activar la
selección actual de Project Manager en el editor) se puede ha-
cer con un doble clic.
Los botones Expand y Collapse recursivamente, expanden y
contraen todos los nodos bajo el nodo actual. Aplique Expand
a un grupo de proyecto y esto le mostrará un árbol con toda la
configuración y los archivos de todos los nodos del grupo en
este proyecto. Tengo que decir que es muy útil. El nuevo
cuarto botón, Views, se trata en la siguiente sección.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 145

Vistas del Project Manager


Otro nuevo rasgo es la configuración de las vistas del Project
Manager. En la parte derecha de la barra de herramientas, se
puede ver un nuevo botón Views, que le permite cambiar la
forma en que el Project Manager muestra los archivos que
han sido colocados en diferentes carpetas. Hay tres opciones.
He probado estas creando un programa simple de ejemplo
(llamado ProjManagerTest) con dos formularios en la carpeta
principal y dos unidades en una segunda carpeta llamada
Shared y colocada al mismo nivel en la jerarquía del sistema
de archivos:
 Directory (Nested) es la configuración por defecto (y lo
único disponible desde Delphi 8 hasta Delphi 2007) que
muestra los archivos agrupados por directorios y los directo-
rios con una estructura idéntica a la de disco con nodos sepa-
rados que se pueden ampliar, (usted debería entonces expan-
dir varios nodos para moverse y bajar un par de sub-carpe-

tas):
 Directory (Flat) es una nueva visión en la que los archivos
están todavía divididos por directorios pero cada directorio
diferente es parte de un lista plana, independientemente de su

La guía de Delphi por Marco Cantù


146 - Indice

posición en el sistema de archivos. En otras palabras, se ob-


tiene una lista de carpetas, cada una con los archivos, en lugar
de (posiblemente) otras carpetas anidadas:

 List es una nueva visión que corresponde a la tradicional lista


de archivos de Delphi 7 en el Project Manager. Los directorios
son simplemente ignorados y se obtiene una lista alfabética de

archivos:

Construcción de Configuraciones
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 147

y Opciones de Configuración
Como he dicho antes y se puede ver en las imágenes de las pá-
ginas anteriores, el Proyect Manager cuenta con un nuevo
nodo de construcción de configuraciones para cada proyecto
(es decir, para los casos en que usted este trabajando con un
grupo de proyecto con múltiples proyectos activos). Este nodo
sustituye a la engorrosa separación de ventana utilizada para
la gestión de la configuración en Delphi 2007. Usando el nodo
y sus sub-nodos puede cambiar la configuración de la cons-
trucción en la que esté trabajando con un doble clic, y actuali-
zar la construcción directamente en el nodo dado.
Al seleccionar una determinada configuración en el nodo
principal, puede añadir también una nueva configuración. De-
pendiendo del origen del objeto seleccionado cuando realiza
la operación, usted creará una configuración principal o una
sub-configuración. Para ser más preciso, el nodo que elija de-
termina la configuración base, desde incluso las configuracio-
nes básicas predefinidas heredando sus principales ajustes de
la configuración Base (que es el núcleo a partir del cual here-
dan la configuración de Debug y Release). ¿Qué quiero decir
con "heredar opciones" de una configuración? Delphi 2009
dispone de un nuevo sistema de gestión de configuración, en
el que se puede aplicar un ajuste para una configuración espe-
cífica (como de Debug y Release) o configurar una opción
para las dos configuraciones, pero no que no se especifica,
sino que hereda estas de la configuración Base. En una confi-
guración específica puede ver su valor específico y el que he-
reda de la configuración base en dos líneas consecutivas, ver
si realizan su tarea y cambiarlas, ya sea una o la otra (afec-
tando también a la configuración específica). Esto se consigue
al expandir cada opción de configuración seleccionando el
signo más a la izquierda del nodo. Esto es lo que usted puede

La guía de Delphi por Marco Cantù


148 - Indice

conseguir expandiendo las tres líneas de errores en tiempo de


ejecución en la página Compiler/Compiling del compilador de
Delphi:

Modificar la configuración en la configuración Base afectará


también a cualquier otra configuración que herede estas op-
ciones.
En el Project Manager, usted también puede seleccionar una
estructura de configuración y exportar sus opciones a un fi-
chero “option set”. Esto es como salvar una plantilla de confi-
guración o su esqueleto a un archivo externo, y la configura-
ción quedará vinculada a este archivo.
Esto hace que sea más sencillo mover los mismos ajustes a un
proyecto nuevo o a otro ya existente, tal como puede utilizar
en el Project Manager (utilizando la opción del menú emer-
gente Apply Options Set mientras construye una estructura de
configuración) o en el cuadro de diálogo Opciones del Pro-
yecto (usando el botón Apply Options) para importar un con-
junto de opciones de configuración. En ambos casos, Delphi
ejecuta la opción Apply Option Set del cuadro de diálogo, en
el que puede seleccionar un fichero y elegir si desea mantener
la configuración exterior del archivo vinculado (de modo que
un cambio en el archivo se verá reflejado en los proyectos que
lo usen) o simplemente combinar la configuración actual utili-
zando algunas normas de prioridad:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 149

Una vez que haya creado una opción externa en un fichero,


puede modificar cualquier proyecto que se refiera a él, usando
el menú Edit Local del panel Project Manager. Esto abre un
editor con el contenido de un subconjunto de las páginas del
cuadro de diálogo de Opciones de Proyecto, como se muestra
a continuación:

El fichero .OPTSET es un archivo XML con un formato simi-


lar al del .DPROJ, de nuevo sobre la base del formato XML
de MSBUILD, y un tipo de proyecto OptionSet. En el ejemplo

La guía de Delphi por Marco Cantù


150 - Indice

que usted puede encontrar en la carpeta ProjManagerTest, el


ProjManagerTestOptionsSet.optset tiene el siguiente conte-
nido:
<Project xmlns="http://.../msbuild/2003">
<PropertyGroup>
<DCC_RunTimeTypeInfo>true</DCC_RunTimeTypeInfo>
</PropertyGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality
</Borland.Personality>
<Borland.ProjectType>OptionSet</Borland.ProjectType>
<BorlandProject><Delphi.Personality/></BorlandProject>
</ProjectExtensions>
</Project>

Administrador de Configuración
de Proyecto
Con el constructor de opciones disponible directamente en el
panel del Project Manager, usted no tiene porque utilizar el
Configuration Manager para cambiar la actual estructura de
configuración. Sin embargo, este cuadro de diálogo es muy
útil ya que le permite cambiar la estructura de la configura-
ción para muchos proyectos, de un grupo de proyectos, al
mismo tiempo. De hecho, el Configuration Manager también
está disponible en Delphi 2009, y en una versión muy mejo-
rada lo que le permite administrar varias estructuras de confi-
guración diferentes y determinar un conjunto de opciones
para todos los proyectos de un grupo a la vez.
Para invocar el Configuration Manager no utilice el menú
emergente del Project Manager, como en Delphi 2007, sino
que seleccione el elemento correspondiente en el menú Pro-
ject del IDE. Cuando lo haga, obtendrá esta rediseñada inter-
faz de usuario:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 151

En la parte izquierda se puede ver una lista de proyectos con


la configuración activa para cada uno de ellos. En la parte de-
recha puede ver algunos detalles sobre la configuración selec-
cionada en la pestaña de encima de esta, al igual que la lista
de los ajustes no lo son por defecto (la de la imagen es el re-
sumen de la opción set file enumerados en la sección ante-
rior). Usando las pestañas, puede filtrar los proyectos del lado
izquierdo que tienen la opción de configuración dada estable-
cida como activa.
En Delphi 2009, el Administrador de Configuración le per-
mite editar las opciones de proyecto para cada estructura de
configuración, añadir nuevas configuraciones, crear o editar
conjuntos de opciones, modificar la configuración activa... y
realizar la mayoría de las operaciones relatadas en el mismo
lugar, aunque su utilización no sea trivial.
Cuando se trabaja con múltiples proyectos dentro de un grupo
de proyecto, el ConfigurationManager es una clara ventaja
frente a la navegación en el Project Manager para trabajar en
la estructura de configuraciones. Para proyectos individuales,
el Project Manager cuenta ahora con todo lo que usted nece-
sita.

La guía de Delphi por Marco Cantù


152 - Indice

Gestión de Recursos en el
IDE
En las últimas versiones de Delphi, se pueden añadir scripts
de recursos (ficheros.RC) o archivos de recursos precompila-
dos (ficheros .RES) al Project Manager para dejar que se com-
pilen junto con el proyecto y se unan al ejecutable. En Delphi
2009 la gestión de los recursos se ha simplificado con la inclu-
sión de algunas herramientas más.
En primer lugar, ahora puede arrastrar los archivos de recur-
sos hasta el Project Manager para conseguir que los recursos
se incluyan en un proyecto. Puede arrastrar los iconos, mapas
de bits, y mucho más. Delphi generará un archivo de secuen-
cia de comandos de recursos adicionales para estos recursos
de proyecto, y lo compilan directamente junto con su pro-
grama, embebiendo estos recursos en el ejecutable. Puede
cambiar cualquier atributo de estos archivos de recursos (in-
cluyendo su nombre interno) en el Object Inspector:

En segundo lugar, bajo la opción desplegable Project del


menú principal del IDE hay un elemento nuevo en el menú,
Resources. Seleccionando estos elementos aparece el cuadro
de diálogo relativo a los recursos, que puede utilizar para revi-
sar todos los recursos del programa, añadir nuevos archivos
de recursos, cambiar el nombre, cambiar el formato, y así su-
cesivamente:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 153

Mediante la adición de algunos recursos a un proyecto, en


tiempo de compilación Delphi generará para usted un ar-
chivo de recursos idóneo. Para el programa ResourceTest (con
los recursos anteriormente descritos), Delphi 2009 genera
una lista de archivos de comandos de recursos para el pro-
yecto denominado ResourceTestResource.rc:
Icon_Factory Icon "FACTORY.ICO"
Bitmap_Shipping Bitmap "SHIPPING.BMP"

Este fichero de recursos no se agrega al proyecto (si lo hace,


verá como los recursos de advertencias se duplican), pero está
compilado con el. De hecho, si incurrió en un error, como de-
clarar un mapa de bits con un icono, el compilador se deten-
drá con el error siguiente:
[BRCC32 Error] ResourceTestResource.rc(2): resource file
SHIPPING.BMP is not in 3.00 format

y abra el archivo de comandos de recursos en línea. En el


tiempo de compilación, Delphi 2009 genera (o actualiza) el
archivo de comandos de recursos, lo compila, y se une al eje-
cutable. El archivo intermedio es un fichero con extensión
DRES que se incluye en el proyecto con una directiva añadida
automáticamente al fichero de código fuente del proyecto
(junto con el archivo estándar RES incluyendo el icono de
proyecto y los recursos de cadenas):
program ResourceTest;

{$R *.dres}

La guía de Delphi por Marco Cantù


154 - Indice

uses
Forms,
ResourceTest_MainForm in
'ResourceTest_MainForm.pas' {FormResourceTest};
{$R *.res}
begin
Application.Initialize;
...

Puede ver los pasos de la compilación de recursos en la pano-


rámica de la vista Output producida por el compilador desde
Delphi 2007 y que introdujo el soporte a MSBuild. Aquí está
lo relacionado con esta salida que usted verá si mantiene la
opción Verbose activa entre las opciones de recursos del com-
pilador:
c:\program files\codegear\rad studio\6.0\bin\cgrc.exe -
c65001 -v ResourceTestResource.rc -foResourceTest.dres
CodeGear Resource Compiler/Binder Version 1.00 Copyright
(c) 2008 Embarcadero Technologies Inc.
Microsoft (R) Windows (R) Resource Compiler Version
6.0.5724.0
Copyright (C) Microsoft Corporation. All rights reserved.

Creating ResourceTest.dres
Using codepage 65001 as default

ResourceTestResource.rc.

Writing ICON:1, lang:0x409, size 744


Writing GROUP_ICON:ICON_FACTORY, lang:0x409, size 20.
Writing BITMAP:BITMAP_SHIPPING, lang:0x409, size 44264

En caso de que usted nunca haya usado recursos de Windows


directamente, el programa ResourceTest tiene unas cuantas
líneas de código para cargar el icono de la aplicación, el icono
del formulario principal y cargar el mapa de bits en un com-
ponente Image:
procedure TFormResourceTest.btnGifClick(
Sender: TObject);
begin
Image1.Picture.Bitmap.LoadFromResourceName(
hInstance, 'Bitmap_Shipping');
end;

procedure TFormResourceTest.btnIconClick(
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 155

Sender: TObject);
begin
Icon.LoadFromResourceName(hInstance, 'Icon_Factory');
Application.Icon.LoadFromResourceName(
hInstance, 'Icon_Factory');
end;

Un "Nuevo"
Las recientes versiones de Delphi, hasta incluso Delphi 2007,
han utilizado el Compilador de Recursos de Borland
(BRCC32.EXE) un anticuado programa cuyos derechos de autor
muestran en su nota ya histórica:
Copyright (c) 1990, 1999 Inprise Corporation. Reservados
todos los derechos.

Delphi 2009 se suministra con un nuevo compilador de recur-


sos, o (para ser más precisos), un compilador de recursos di-
ferente: el que proviene del SDK de Microsoft Windows. Esto
es, sin duda, beneficioso en términos de ayuda para todos los
nuevos formatos de recursos que Windows maneja, pero
causa algunos problemas debido al hecho de que el compila-
dor de recursos de Borland, desde sus primeros días, extendió
el propio de Microsoft, proporcionando unas características
adicionales que ahora quedan perdidas.
Pero aun puede decidir utilizar el compilador de recursos de
Borland, con la correspondiente opción67 en la página del
compilador de recursos de dialogo de Opciones de Proyecto

67Durante mis ensayos con Delphi 2009, la opción de habilitar el compilador de


recursos de Borland parece ser ignorada por el IDE. Tal vez una solución
este en marcha. Como estos compiladores de recursos son archivos ejecuta-
bles externos, puede cambiarles el nombre después de reorientar las opera-
ciones, pero como la transmisión de los parámetros es diferente, esto puede
llevar a errores.

La guía de Delphi por Marco Cantù


156 - Indice

(que le permite también modificar otros parámetros del com-


pilador de recursos):

El SDK del compilador de recursos de Windows se invoca a


través del nuevo CodeGear Resource Compiler/Binder, que es
simplemente una interfaz para el SDK del compilador. Los
cambios en el compilador de recursos incluyen la falta de ca-
pacidad de manejar la imagen (binaria) de datos en línea, so-
portar el rastreo de comas después de las cadenas en una lista
de cadenas, la manera diferente de manejar cadenas (ahora
tratadas como strings en lenguaje C, forzándole a evitar cual-
quier \ en un nombre del archivo con doble barra invertida),
la diferente forma de gestionar las carpetas para su inclu-
sión...
Ahora, si usted nunca ha usado los archivos de recursos direc-
tamente, probablemente puede ignorar cualquiera de estos
cambios. Nada gestionado directamente por el entorno de
Delphi, desde la incorporación de los archivos DFM como re-
cursos con el uso de la declaración resourcestring, es total-
mente compatible hacia atrás. Si usted usó recursos directa-
mente, debe revisar sus archivos de recursos con cuidado.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 157

La Clase
Un nuevo panel en Delphi 2009 es el nuevo panel Explorador
de Clases (disponible en la opción Delphi Class Explorer del
menú View). El Delphi Class Explorer ofrece una amplia re-
presentación de los símbolos del proyecto, diferente a la op-
ción Structure View68, que muestra una (o algo similar) repre-
sentación gráfica de los elementos de una sola unidad.
En el Explorador de Clase de Delphi en el primer nivel, verá
una lista de nodos alojando la definición global de cada uni-
dad (más el archivo de proyecto), mientras los nodos restan-
tes muestran todas las clases definidas en el proyecto:

Para cada clase se pueden ver sus miembros específicos y su


relación con otras clases. Esto se representa en función de la
selección del primer botón de la barra de herramientas: Base
to derived (mostrado en la ilustración anterior), Derived to
base, o de Container (como se muestra a continuación).

68LaStructure View para el código fuente de un archivo se llamó originalmente


Code Explorer hasta Delphi 7, no debe confundirse con la nueva Delphi Class
Explorer.

La guía de Delphi por Marco Cantù


158 - Indice

En este último caso, las clases (y globales) se separan por su


unidad y no aparece su relación de herencia en la pantalla. El
menú emergente que le permite añadir un nuevo campo a una
clase, o una nueva operación (o un método, incluidos los
constructores y destructores), o una propiedad, como en la si-
guiente imagen:

Añadir una propiedad funciona de la forma habitual en Del-


phi (mucho más cuando use un modelo basado en UML). La
herramienta tiende a mapear los métodos getter y setter aun-
que, si usted lo prefiere, puede ir directamente al mapeado del
campo, añadiendo una propiedad y preguntando por el campo
correspondiente que se va a crear. El Explorador de Clases
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 159

añadirá las siguientes líneas a la clase, como en la imagen an-


terior:
type
TBaseClass = class
strict private
function GetAnotherInteger : Integer;
procedure SetAnotherInteger(val : Integer);
public
property AnotherInteger: Integer
read GetAnotherInteger write SetAnotherInteger;
strict private
var
FAnotherInteger:Integer;
end;

Encuentro que el uso del bloque strict private var es bas-


tante extraño, pero es formalmente correcto y, probable-
mente, añadiendo la palabra clave var hace la generación de
código más fácil y menos arriesgada. Sin embargo, si tuviera
que cambiar el código a mi gusto, me gustaría tener más
tiempo para declarar la propiedad y usaría Class Completion,
que produce un código más limpio y estándar propio de Del-
phi. Para mí, Delphi Class Explorer es principalmente una he-
rramienta para navegar por el código fuente de un proyecto, y
raramente utilizaría el método mostrado al menos que nece-
site diagramas UML.

Otras nuevas
características
La actualización del diálogo de Opciones de Proyecto, las nue-
vas características de Project Manager y la ampliación de la
estructura de configuraciones, el mejor soporte para recursos,
y el Explorador de Clases son probablemente las nuevas ca-
racterísticas más relevantes del IDE de Delphi 2009, si no

La guía de Delphi por Marco Cantù


160 - Indice

consideramos el hecho de que todo el IDE se ha habilitado en


Unicode.
Hay muchas otras características menores que le pueden ayu-
dar en el día a día a trabajar con el entorno de desarrollo de
Delphi, listadas en esta sección. Un notable conjunto de mejo-
ras en el entorno de desarrollo de Delphi, que se describen en
el capítulo específico, en relación con COM. Los cambios en el
IDE relacionados con los grandes cambios en el soporte a
COM, y en el editor Type Library, se tratan en el Capítulo 9.

Paleta de Herramientas del


cuadro de búsqueda
En Delphi 2006, usted podía escribir, estando la paleta de he-
rramientas seleccionada, para filtrar los componentes a partir
de sus letras iniciales (con la excepción de la inicial T). En
Delphi 2007, usted podría hacer lo mismo, pero también se-
leccionando el texto dentro del nombre del componente, por
lo que podría elegir, por ejemplo, decir IdHTTP introduciendo
evidentemente HTTP. En Delphi 2009, la paleta de herra-
mientas tiene el mismo comportamiento que en Delphi 2007,

pero con otra interfaz de usuario que hace que sea más evi-
dente para todos los usuarios que se pueden buscar en la lista
de componentes escribiendo:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 161

Tal como seleccione la paleta (su acceso directo con el teclado


es Ctrl + Alt + P), puede comenzar escribiendo en el cuadro de
búsqueda (en lugar de en el título del panel) y la paleta de
componentes filtra los componentes que muestra:

Hay otro cambio en la paleta de herramientas. Como muchas


personas se quejaban debido al excesivo desplazamiento ne-
cesario para llegar a las categorías de la parte inferior de la
lista, el auto-colapso de las categorías es el comportamiento
por defecto. Otro comportamiento que usted puede ajustar
con precisión es, si desea que la selección actual del cuadro de
búsqueda se mantenga después de seleccionar un compo-
nente o no.

Actualización de Asistentes de
Componentes
Los cuadros de diálogo utilizados para crear un nuevo compo-
nente VCL o para importar un componente (un control Acti-
veX o un ensamblado .NET, para ser utilizados como un con-
trol COM) se han mejorado y se convertido en asistentes con
múltiples-pasos.
La capacidad real de crear el esqueleto de un componente va-
cío o un embalaje de un control externo, no se ha modificado
significativamente. La única característica realmente nueva es

La guía de Delphi por Marco Cantù


162 - Indice

la capacidad (para ambos asistentes) de instalar el compo-


nente en un paquete existente o uno nuevo al que usted debe
darle su nuevo nombre.
El cambio es relevante en la interfaz de usuario de este asis-
tente que puede activar desde el menú Components del IDE.
Por ejemplo, la página inicial del nuevo asistente de compo-
nentes VCL, tiene un cuadro de búsqueda para filtrar el com-

ponente base del que heredará su nueva clase:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 163

Tal cómo proceda, rellenando el nombre de la clase y otros


detalles estándares, usted llegará a la última página, lo que le
permite crear un paquete nuevo o añadir el componente

nuevo a uno ya existente, como se muestra en la siguiente pá-


gina.
En el caso de que tenga un proyecto de paquetes activo, verá
una opción adicional para agregar el nuevo componente al
mismo. Capacidades similares se han añadido al asistente de
Importación de Componentes. En la práctica, veremos algu-
nos de estos asistentes en el capítulo 9, al mismo tiempo que
cubriremos la programación COM y, en particular, la importa-
ción de una biblioteca de tipos en Delphi 2009.

¿Algo nuevo en el Editor?


Si bien las últimas versiones de Delphi han visto mejoras en la
utilidad del editor, con la introducción del Block Completion,
La guía de Delphi por Marco Cantù
164 - Indice

Live Templates, Refactorings, y muchos cambios en el Code


Insight, Delphi 2009 ofrece soporte actualizado a las nuevas
características del lenguaje, pero poco más, en este ámbito.
Lo que es nuevo es la opción de Auto Invoke de Code Comple-
tion. Esta nueva opción está deshabilitada por defecto y puede
ser habilitada en la sección correspondiente de Code Insight
en la página principal del cuadro de diálogo de Opciones del
IDE. ¿Cuál es el efecto de Auto Invoke? El editor debe realizar
un seguimiento de sus últimas selecciones y ofrecerse para re-
petirlas. Cómo funciona esto realmente en la práctica, es difí-
cil de explicar.

Depurador
Como el resto del IDE, el depurador se ha trabajado también
para soportar Unicode plenamente. Este soporte estaba par-
cialmente disponible en versiones anteriores, pero en Delphi
2009 se extiende. Por ejemplo, si usted quiere inspeccionar
una variable de cadena con Run | Inspect (o Debug | Inspect
en el menú local del editor) no sólo va a obtener el valor co-
rrecto de Unicode, además le informará, en la parte inferior,
del tipo verdadero de variable de esta cadena.
A continuación se puede ver, en el Debug Inspector, una com-
paración entre un AnsiString y un UnicodeString (reportado
este como string):

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 165

En este caso Delphi está depurando el evento btnWarningClick


del formulario principal del ejemplo StringConvert del capí-
tulo 2. Las dos ventanas son en realidad la muestra de la
misma cadena, aunque la primera no puede ser convertida co-
rrectamente debido a los caracteres Chinos.
Existen también otras características de la depuración que no
se refieren al soporte Unicode. Un asunto menor es que la
vista CPU soporta las instrucciones SSE3 y SSE4 (menor, por
lo menos, para alguien que usa con poca frecuencia el len-
guaje ensamblador, como yo).
Una manera más interesante, aunque todavía muy a bajo ni-
vel, es la función de apoyo del depurador para la Wait Chain
Traversal69 que figura en Vista (y Windows Server 2008). En
el panel Threads Status hay ahora una columna con informa-
ción acerca de los distintos subprocesos que están contribu-
yendo a un callejón sin salida. Esta información puede ser ex-
tremadamente importante para entender lo que sucede en sus
aplicaciones con procesos multi-hilo.

69Un artículo técnico de MSDN que describe Wait Chain Transversal a nivel sis-
tema operativo está disponible en http://msdn.microsoft.com/en-us/li-
brary/ms681622.aspx. Chris Hesik de Embarcadero trata en su blog esta
nueva característica del depurador en Delphi http://blogs.code-
gear.com/chrishesik/2008/07/21/34833.

La guía de Delphi por Marco Cantù


166 - Indice

Depuración y Nuevas
Características del Lenguaje
Aunque el depurador parezca muy similar a la versión ante-
rior, se ha dedicado gran cantidad de esfuerzo para permitir
que los usuarios puedan depurar aplicaciones que utilizan ge-
néricos y métodos anónimos. Debido a la sofisticada genera-
ción de código que se realiza en segundo plano, el código que
está depurando es bastante diferente del que escribió original-
mente. Aún con algunos problemas, la depuración de las nue-
vas características del lenguaje, en general, funciona bastante
bien.

A continuación
Habiendo examinado las nuevas características del IDE de
Delphi 2009, ahora puedo volver al lenguaje. Ya hemos visto
que hay muchas características nuevas relacionadas con el so-
porte de UnicodeString y AnsiString, pero el compilador tiene
muchas mejoras más.
Con los genéricos y lo métodos anónimos (o closures) que
aparecen por primera vez en el lenguaje, Object Pascal da su
salto más importante hacia adelante desde la aparición de
Delphi. Es por eso que hay dos capítulos completos, y algo
más de información en un tercero, plenamente dedicado a la
compilación y el lenguaje.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 167

Capítulo 5 :
Genéricos

El fuerte control de tipo que provee Delphi es muy útil para


mejorar la exactitud del código, un tema que tiendo a destacar
en mis libros de iniciación. Un fuerte control de tipo, sin em-
bargo, también puede ser una molestia, si usted no dispone de
un procedimiento o una clase que pueda actuar con diferentes
tipos de datos. Esta cuestión esta dirigida por una nueva ca-
racterística del lenguaje Object Pascal, agregado reciente-
mente a lenguajes similares como C# y Java, llamados genéri-
cos. Esto es lo que escribí en 1994 en un libro sobre C++70:
Ahora puede declarar una clase, sin especificar el tipo
de dato de uno o más de sus miembros: esta operación
se puede retrasar hasta que un objeto de esa clase es
realmente declarado. Del mismo modo, puede definir
una función sin especificar el tipo de uno o más de sus
parámetros hasta que la función sea llamada.
Ahora, 14 años después, esta función llega a Object Pascal.
Como puede imaginar estoy emocionado de disponer de gené-
ricos (que se llaman templates en C++) en Delphi, aunque

70El libro es "Borland C++ 4.0 Programación Orientada a Objetos", escrito por
mí y Steve Tendon, con una presentación de Philippe Kahn.

La guía de Delphi por Marco Cantù


168 - Indice

debo decir que he sido testigo de diferentes excesos de esta


función que yo no entendía plenamente en ese momento.
Dudo que los genéricos sean excesivos en Delphi sino más
bien todo lo contrario: existe el riesgo de que una actualiza-
ción tan importante del lenguaje pase casi inadvertida. En
este capítulo se tratará de profundizar en el tema, demostrán-
dole el valor de los genéricos en Delphi y cómo pueden ser
aplicados incluso en la programación visual estándar.

Genéricos de Pares Clave-


Valor
Como un primer ejemplo de una clase genérica, he implemen-
tado una estructura de datos con un par valor. El primer frag-
mento del siguiente código muestra la estructura de datos es-
critos al estilo tradicional, con un objeto utilizado para con-
servar el valor:
type
TKeyValue = class
private
FKey: string;
FValue: TObject;
procedure SetKey(const Value: string);
procedure SetValue(const Value: TObject);
public
property Key: string read FKey write SetKey;
property Value: TObject read FValue write SetValue;
end;

Para usar esta clase puede crear un objeto, fijar su clave y el


valor, y usarla, como en los siguientes fragmentos de los dis-
tintos métodos del formulario principal del ejemplo KeyVa-
lueClassic:
// FormCreate
kv := TKeyValue.Create;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 169

// Button1Click
kv.Key := 'mykey';
kv.Value := Sender;

// Button2Click
kv.Value := self; // the form

// Button3Click
ShowMessage('[' + kv.Key +',' + kv.Value.ClassName + ']');

Los genéricos hacen posible utilizar una definición mucho


más amplia para el valor, pero no para el punto de la clave. Lo
que es totalmente diferente, como podremos ver, es que una
vez haya instanciado la clase genérica key-value, se convierte
en una clase específica, atada al tipo determinado de datos
dado. Esto hace este tipo de código más seguro, pero me estoy
adelantando a mí mismo. Vamos a empezar con la sintaxis
utilizada para definir una clase genérica:
type
TKeyValue<T> = class
private
FKey: string;
FValue: T;
procedure SetKey(const Value: string);
procedure SetValue(const Value: T);
public
property Key: string read FKey write SetKey;
property Value: T read FValue write SetValue;
end;

En esta definición de clase, encontramos un tipo no especifi-


cado, indicado por el marcador de posición T71. T es el símbolo
de uso frecuente, por convenio, pero en lo que respecta al
compilador que le concierne, puede usar cualquier símbolo
que le guste. El uso de T en general hace que el código sea más
legible cuando la clase genérica sólo utiliza un tipo paramé-
trico; en el caso en que la clase necesite múltiples tipos como

71“T” ha sido el nombre estándar o de posición, para un tipo genérico, desde la


época de las plantillas en Lenguaje C++ introducidas a principios de 1990.
Dependiendo de los autores, la “T” significa tanto “tipo” como “plantilla de
tipo”.

La guía de Delphi por Marco Cantù


170 - Indice

parámetros es común nombrarlos de acuerdo a su función


real, en lugar de utilizar una secuencia de letras (T, U, V) tal y
como sucedió en C++ al principio.
La clase genérica TKeyValue<T> utiliza un tipo de clase no espe-
cificado como el tipo de uno de sus dos campos, el valor de la
propiedad, y el parámetro del método setter. Los métodos se
definen como de costumbre, pero advierta que independiente-
mente del hecho de que tengan que ver con el tipo genérico,
su definición contiene el nombre completo de la clase, in-
cluido el tipo genérico:
procedure TKeyValue<T>.SetKey(const Value: string);
begin
FKey := Value;
end;

procedure TKeyValue<T>.SetValue(const Value: T);


begin
FValue := Value;
end;

Para utilizar la clase, en cambio, usted tiene que calificarla


plenamente, proporcionando siempre el valor real para el tipo
genérico. Por ejemplo, ahora puede declarar un par clave-va-
lor [key-value] alojando un objeto botón con valor, escri-
biendo:
kv: TKeyValue<TButton>;

El nombre completo es necesario también cuando se crea una


instancia, porque este es el nombre actual del tipo (mientras
que el genérico, el tipo de nombre sin instanciar, es como un
tipo de mecanismo de construcción).
El uso de un tipo de valor específico del par clave-valor hace
que el código sea mucho más sólido, ya que ahora sólo se
puede añadir un objeto TButton (o derivado) al par clave-valor,
pudiéndose utilizar los distintos métodos extraídos del objeto.
Estos son algunos fragmentos del formulario principal del
ejemplo KeyValueGeneric:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 171

// FormCreate
kv := TKeyValue<TButton>.Create;

// Button1Click
kv.Key := 'mykey';
kv.Value := Sender as TButton;

// Button2Click
kv.Value := Sender as TButton; // was "self"

// Button3Click
ShowMessage ('[' + kv.Key + ',' + kv.Value.Name + ']');

Cuando asignamos un objeto genérico en la versión del código


anterior podría haber añadido un botón o un formulario,
ahora sólo podemos utilizar el botón, como regla forzada por
el compilador. Del mismo modo, en lugar de un genérico en la
salida kv.Value.ClassName se puede utilizar la propiedad Name,
del componente, o cualquier otra propiedad de la clase TBut-
ton.

Por supuesto, también podemos imitar el programa original al


declarar el par key-value como:
kvo: TKeyValue<TObject>;

En esta versión del genérico con esta clase en su key-value,


podemos añadir cualquier objeto como valor. Sin embargo, no
podrá hacer mucho con los objetos extraídos, a menos que
lance un tipo más específico. Para encontrar un buen equili-
brio, tal vez quiera ir a por algo entre los botones concretos y
cualquier otro objeto, requiriendo su valor al ser un compo-
nente:
kvc: TKeyValue<TComponent>;

Usted puede ver los correspondientes fragmentos de código


en el mismo programa de demostración KeyValueGeneric. Por
último, usted también puede crear una instancia con el par
genérico key-value que no almacene los valores de ningún ob-
jeto, sino enteros bastante simples:
var
kvi: TKeyValue<Integer>;

La guía de Delphi por Marco Cantù


172 - Indice

begin
kvi := TKeyValue<Integer>.Create;
try
kvi.Key := 'object';
kvi.Value := 100;
kvi.Value := Left;
ShowMessage ('[' + kvi.Key + ',' +
IntToStr (kvi.Value) + ']');
finally
kvi.Free;
end;

Tipo de Normas sobre Genéricos


Cuando usted declara una instancia de un tipo genérico, a este
tipo se le asigna una versión específica, que es forzada por el
compilador en todas las operaciones subsiguientes. Por lo
tanto, si usted tiene una clase genérica como:
type
TSimpleGeneric<T> = class
Value: T;
end;

en cuanto declare un objeto con un tipo determinado, no


puede asignarle ya otro tipo en su campo Value. Habida
cuenta de los dos objetos siguientes, algunas de las tareas
mostradas a continuación (parte del ejemplo del TypeCom-
pRules) son incorrectas:

var
sg1: TSimpleGeneric<string>;
sg2: TSimpleGeneric<Integer>;
begin
sg1 := TSimpleGeneric<string>.Create;
sg2 := TSimpleGeneric<Integer>.Create;

sg1.Value := 'foo';
sg1.Value := 10; // Error
// E2010 Incompatible types: 'string' and 'Integer'
sg2.Value := 'foo'; // Error
// E2010 Incompatible types: 'Integer' and 'string'
sg2.Value := 10;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 173

Una vez que usted defina un tipo determinado en la declara-


ción genérica, este es forzado por el compilador, tal como us-
ted esperaría de un lenguaje fuertemente tipificado como es
Object Pascal. El chequeo de tipo se propaga también para los
objetos genéricos en su conjunto. Aunque especifique el pará-
metro genérico a un objeto, no se le puede asignar un tipo ge-
nérico similar basado en una instancia diferente e incompati-
ble. Si esto le parece confuso, este ejemplo debería ayudarle a
aclararlo:
sg1 := TSimpleGeneric<Integer>.Create; // Error
// E2010 Incompatible types:
// 'TSimpleGeneric<System.string>'
// and 'TSimpleGeneric<System.Integer>'
Como veremos en la sección “Normas de Compatibilidad Para
Los Tipos Genéricos” en este caso peculiar, el tipo de norma
de compatibilidad es por la estructura y no por el tipo de
nombre. Usted no puede asignar un tipo diferente e incompa-
tible al tipo genérico una vez que este haya sido declarado.

Genéricos en Delphi
En el ejemplo anterior hemos visto como puede definir y utili-
zar una clase genérica en Delphi, una de la ampliaciones de
mayor alcance del lenguaje Object Pascal desde que Delphi 3
introdujo las interfaces. Me decidí a presentar esta caracterís-
tica con un ejemplo, antes de profundizar en los aspectos téc-
nicos, que son bastante complejos y muy importantes al
mismo tiempo. Después de cubrir los genéricos desde la pers-
pectiva del lenguaje, volveremos con más ejemplos, inclu-
yendo el uso y la definición del contenedor genérico de clases,
una de las principales razones por la que estas técnicas se han
añadido al lenguaje.

La guía de Delphi por Marco Cantù


174 - Indice

Hemos visto que cuando se define una clase en Delphi 2009,


ahora puede añadir un "parámetro" extra entre los corchetes
angulares para contener el espacio a un tipo que se proporcio-
nará más tarde:
type
TMyClass<T> = class
...
end;

El tipo genérico puede ser usado como el tipo de un campo


(como lo hice en el anterior ejemplo), como el tipo de una
propiedad, como el tipo de un parámetro o el valor de retorno
de una función, y aún más. Aviso que no es obligatorio utilizar
el tipo para un campo local (o matriz), ya que hay casos en los
que el tipo genérico se utilizará tan sólo como un resultado,
un parámetro, o no se utiliza en la declaración de la clase, sino
sólo en la definición de algunos de sus métodos.
Esta forma extendida, o de declaración de tipo genérica, no
sólo esta disponible para las clases, sino también para los re-
gistros (que, en caso de que no se haya dado cuenta, en las
más recientes versiones de Delphi pueden tener también mé-
todos, propiedades y operadores sobrecargados). No se puede
declarar una función global genérica, no como en C++, pero
puede declarar una clase genérica con un simple método de
clase, que es casi la misma cosa y no desordena su espacio de
nombre global.
Una clase genérica puede tener también varios tipos paráme-
trizados, como en el siguiente caso en el que puede especificar
para un método un parámetro de entrada y un valor de re-
torno de tipo distinto:
type
TPWGeneric<TInput,TReturn> = class
public
function AnyFunction (Value: TInput): TReturn;
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 175

La aplicación de los genéricos en Delphi, como en otros len-


guajes estáticos no se basa en un marco de tiempo de ejecu-
ción. Es manejada por el compilador y el enlazador, dejando
casi nada para el mecanismo de ejecución. A diferencia de la
llamada virtual a las funciones, que están vinculadas en
tiempo de ejecución, la plantilla de métodos se genera una vez
para cada tipo de plantilla que usted inicie, ¡y se generan en el
tiempo de compilación! Vamos a ver los posibles inconvenien-
tes de este enfoque, pero el lado positivo, implica que las cla-
ses genéricas son tan eficaces como las clases sencillas, o in-
cluso más eficaces, ya que reducen el tiempo de la comproba-
ción necesaria. Antes de mirar alguno de los asuntos internos,
sin embargo, permítame centrarme en algunas normas muy
importantes que rompen el tipo de norma tradicional de com-
patibilidad del lenguaje Pascal.

Normas de Compatibilidad Para


Los Tipos Genéricos
En el Pascal tradicional y en el Object Pascal de Delphi, las
normas centrales de compatibilidad se basan en el nombre del
tipo de equivalencia. En otras palabras, dos variables sólo son
compatibles si su nombre de tipo es el mismo, independiente-
mente de la estructura de datos reales al que se refieren las
dos.
Este es un ejemplo clásico de incompatibilidad de tipos con
matrices (parte del ejemplo TypeCompRules):
type
TArrayOf10 = array [1..10] of Integer;

procedure TForm30.Button1Click(Sender: TObject);


var
array1: TArrayOf10;
array2: TArrayOf10
array3, array4: array [1..10] of Integer;

La guía de Delphi por Marco Cantù


176 - Indice

begin
array1 := array2;
array2 := array3; // Error
// E2010 Incompatible types: 'TArrayOf10' and 'Array'
array3 := array4;
array4 := array1; // Error
// E2010 Incompatible types: 'Array' and 'TArrayOf10'
end;

Como se puede ver en el código anterior, los cuatro arrays son


estructuralmente idénticos. Sin embargo, el compilador le
permite asignar sólo los que son de tipo compatible, ya sea
porque por su tipo tiene el mismo nombre explícito (como TA-
rrayOf10) o porque tienen el mismo implícito (el nombre de
tipo que ha generado el compilador), ya que los dos conjuntos
están declarados en una declaración única.
Esta norma sobre tipo de compatibilidad tiene unas excepcio-
nes muy limitadas, como las relacionadas con las clases deri-
vadas. Una nueva excepción a la regla, y esta es muy impor-
tante, es el tipo de compatibilidad de los tipos genéricos, que
probablemente también utiliza el compilador internamente
para determinar cuando es el momento de generar el nuevo
tipo para el genérico, con todos sus métodos.
La nueva norma establece que los tipos genéricos son compa-
tibles cuando la definición de la clase genérica y el tipo de ins-
tancia son el mismo, independientemente del nombre del tipo
asociado con esta definición. En otras palabras, el nombre
completo del tipo de instancia de los genéricos es una combi-
nación del tipo genérico y del tipo de instancia.
En el siguiente ejemplo las cuatro variables son del todo com-
patibles en su tipo:
type
TGenericArray<T> = class
anArray: array [1..10] of T;
end;

TIntGenericArray = TGenericArray<Integer>;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 177

procedure TForm30.Button2Click(Sender: TObject);


var
array1: TIntGenericArray;
array2: TIntGenericArray;
array3, array4: TGenericArray<Integer>;
begin
array1 := TIntGenericArray.Create;
array2 := array1;
array3 := array2;
array4 := array3;
array1 := array4;
end;

Funciones Genéricas Globales


(Bueno, Casi)
Como se ha mencionado anteriormente, no se puede declarar
una función global genérica, pero si se puede tener una clase
genérica con un método de clase, lo que es muy parecido. Esta
es una muestra de declaración, tomada de la demo TypeCom-
pRules:
type
TGlobalFunction<T> = class
public
class function AlmostGlobal: string;
class function WithParam (t172: T): string;
end;

No hay mucho que pueda hacer dentro de un método de clase


similar (al menos que usted use las restricciones a las que me
referiré más adelante en este capítulo), por lo que he escrito
algo de código empleando un tipo de funciones genéricas es-
peciales (comentadas de nuevo más adelante, puesto que no
es relevante comentarlas aquí).

72Laprimera vez que escribí este código, probablemente con reminiscencias de


mis días en C++, escribí con el parámetro (t: T). Huelga decir que en un
caso como en el sensible lenguaje Object Pascal, esto no es una gran idea. El
compilador realmente lo dejar pasar, pero surgirán errores siempre que se
refiera al tipo genérico T.

La guía de Delphi por Marco Cantù


178 - Indice

Usted puede llamar a diferentes versiones de esta "función ge-


nérica global” de la siguiente manera:
TGlobalFunction<string>.AlmostGlobal;
TGlobalFunction<Int64>.AlmostGlobal;
TGlobalFunction<TButton>.AlmostGlobal;

Sin embargo, si usted llama al método con un parámetro, el


tipo de parámetro debe coincidir con la declaración de tipo
genérico. Por lo tanto, las dos primeras líneas siguientes com-
pilan, las dos últimas no lo harán:
TGlobalFunction<TButton>.WithParam (btnGlobal);
TGlobalFunction<string>.WithParam ('foo');

TGlobalFunction<Integer>.WithParam (btnGlobal); // [Error]


TGlobalFunction<string>.WithParam (203); // [Error]

Instanciación del Tipo Genérico


Con la excepción de algunas optimizaciones, cada vez que us-
ted instancia un tipo de genérico, un nuevo tipo es generado
por el compilador. Este nuevo tipo no comparte código con di-
ferentes instancias del mismo tipo genérico.
Veamos un ejemplo (que se llama GenericCodeGen). El pro-
grama tiene una clase genérica definida como:
type
TSampleClass <T> = class
private
data: T;
public
procedure One;
function ReadT: T;
procedure SetT (value: T);
end;

Los tres métodos se aplican de la siguiente manera (nótese


que el método One es totalmente independiente del tipo gené-
rico):
procedure TSampleClass<T>.One;
begin
Form30.Log ('OneT');

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 179

end;

function TSampleClass<T>.ReadT: T;
begin
Result := data;
end;

procedure TSampleClass<T>.SetT(value: T);


begin
data := value;
end;

Ahora el programa principal utiliza el tipo genérico principal-


mente para disponer de las direcciones en-memoria de sus
métodos una vez que se haya generado una instancia (por el
compilador). Este es el código (que utiliza una función helper
Log para mostrar cadenas de registro en un control Memo):
procedure TForm30.Button1Click(Sender: TObject);
var
t1: TSampleClass<Integer>;
t2: TSampleClass<string>;
begin
t1 := TSampleClass<Integer>.Create;
t1.SetT (10);
t1.One;

t2 := TSampleClass<string>.Create;
t2.SetT ('hello');
t2.One;

Log ('t1.SetT: ' +


IntToHex (PInteger(@TSampleClass<Integer>.SetT)^, 8));
Log ('t2.SetT: ' +
IntToHex (PInteger(@TSampleClass<string>.SetT)^, 8));

Log ('t1.One: ' +


IntToHex (PInteger(@TSampleClass<Integer>.One)^, 8));
Log ('t2.One: ' +
IntToHex (PInteger(@TSampleClass<string>.One)^, 8));
end;

El resultado es algo como esto (los valores reales pueden va-


riar):
t1.SetT: C3045089
t2.SetT: 51EC8B55
t1.One: 4657F0BA
t2.One: 46581CBA

La guía de Delphi por Marco Cantù


180 - Indice

Como ya he anticipado, no sólo el método SetT obtiene una


versión diferente de memoria generada por el compilador
para cada tipo de datos utilizados, pero incluso el método One
recibe una versión nueva, a pesar de que son todos idénticos.
Además, si usted redeclara un tipo genérico idéntico, obten-
drá un nuevo conjunto de funciones en ejecución. Del mismo
modo, la misma instancia de un tipo genérico utilizada en di-
ferentes unidades, fuerza al compilador a que genere el
mismo código una y otra vez, posiblemente causando un im-
portante código hinchado. Por esta razón, si usted tiene una
clase genérica con muchos métodos que no dependen del tipo
genérico, se recomienda que defina una base de clase no-ge-
nérica con los métodos comunes heredados y una clase gené-
rica con el método genérico: de esta forma los métodos de la
clase base sólo se compilan y se incluyen en el ejecutable una
vez.

Funciones de Tipo Genérico


El mayor problema con las definiciones de tipo genérico que
hemos visto hasta ahora, es que hay muy poco que se pueda
hacer con los objetos de la clase de tipo genérico. Hay dos téc-
nicas que puede utilizar para superar esta limitación. La pri-
mera es hacer uso de las pocas funciones especiales de la bi-
blioteca en tiempo de ejecución (RTL) que soportan específi-
camente los tipos genéricos, y la segunda (y mucho más po-
tente), consiste en definir las clases genéricas con las restric-
ciones sobre los tipos que usted pueda utilizar.
Las voy a tratar en la primera parte de esta sección, y las res-
tricciones, en la siguiente sección. Como anteriormente he
mencionado, hay una nueva clase de función y dos funciones

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 181

clásicas que han sido específicamente modificadas para traba-


jar en un tipo de parámetro (T) de la definición del tipo gené-
rico:
 Default (T) es una nueva función que devuelve un “valor
cero” o valor nulo para el tipo actual73; que puede ser cero,
una cadena vacía, nil, y así sucesivamente;
 TypeInfo (T) devuelve la información del puntero en tiempo
de ejecución para la versión actual del tipo genérico;
 SizeOf (T) devuelve el tamaño en memoria del tipo en bytes
(en el caso de que el tipo de referencia sea una cadena o un
objeto, devolverá el tamaño de la referencia, que es de 4 by-
tes).
El ejemplo GenericTypeFunc tiene una clase genérica que
muestra las tres funciones de tipo genérico en acción:
type
TSampleClass <T> = class
private
data: T;
public
procedure Zero;
function GetDataSize: Integer;
function GetDataName: string;
end;

function TSampleClass<T>.GetDataSize: Integer;


begin
Result := SizeOf (T);
end;

function TSampleClass<T>.GetDataName: string;


begin
Result := GetTypeName (TypeInfo (T));
end;

procedure TSampleClass<T>.Zero;
begin

73Estamemoria sin inicializar tiene el mismo valor de una variable global del
mismo tipo. A diferencia de las variables locales, ya que de hecho, las varia-
bles globales se inicializan a “cero” por el compilador.

La guía de Delphi por Marco Cantù


182 - Indice

data := Default (T);


end;

En el método GetDataName he usado la función GetTypeName


(o la unidad TypInfo) en lugar de acceder directamente a la
estructura de datos, ya que realiza la conversión propia a
UTF-8 desde el valor codificado ShortString que hospeda el
tipo de nombre.
Habida cuenta de la declaración anterior, se puede compilar
el siguiente código de prueba, que se repite tres veces en tres
versiones diferentes de instancias al tipo genérico. He omitido
el código repetido, pero muestran las declaraciones utilizadas
que usan el acceso a los campos de datos, que cambian depen-
diendo del tipo real:
var
t1: TSampleClass<Integer>;
t2: TSampleClass<string>;
t3: TSampleClass<double>;
begin
t1 := TSampleClass<Integer>.Create;
t1.Zero;
Log ('TSampleClass<Integer>');
Log ('data: ' + IntToStr (t1.data));
Log ('type: ' + t1.GetDataName);
Log ('size: ' + IntToStr (t1.GetDataSize));

t2 := TSampleClass<string>.Create;
...
Log ('data: ' + t2.data);

t3 := TSampleClass<double>.Create;
...
Log ('data: ' + FloatToStr (t3.data));

Al ejecutar este código (del programa GenericTypeFunc) se


obtiene el siguiente resultado:
TSampleClass<Integer>
data: 0
type: Integer
size: 4
TSampleClass<string>
data:
type: string
size: 4
TSampleClass<double>

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 183

data: 0
type: Double
size: 8

Observe que puede utilizar las funciones de tipo genérico tam-


bién sobre tipos específicos, fuera del contexto de las clases
genéricas. Por ejemplo, puede escribir:
var
I: Integer;
s: string;
begin
I := Default (Integer);
Log ('Default Integer': + IntToStr (I));

s := Default (string);
Log ('Default String': + s);

Log ('TypeInfo String': +


GetTypeName (TypeInfo (string));

Mientras que las llamadas a Default son una nueva línea en


Delphi 2009 (aunque no demasiado útil fuera del contexto de
los genéricos), la llamada a TypeInfo74 del final ya era posible
en versiones anteriores de Delphi. Esta es la salida trivial:
Default Integer: 0
Default String:
TypeInfo String: string

Restricciones Genéricas
Como hemos visto, es muy poco lo que usted puede hacer en
los métodos de su clase genérica sobre el valor de tipo gené-
rico. Puede pasar alrededor (es decir, asignarlo) y realizar las
operaciones limitadas permitidas por el tipo de funciones ge-
néricas tal como acabo de describir.

74Nose puede aplicar la llamada TypeInfo a una variable, como en el código an-
terior TypeInfo(s) sino sólo a un tipo.

La guía de Delphi por Marco Cantù


184 - Indice

Para poder realmente llevar a cabo algunas operaciones de


clase con el tipo genérico, deberá generalmente añadirle una
restricción. Por ejemplo, si usted limita el tipo genérico a ser
una clase, el compilador le permite llamar igualmente a todos
los métodos de TObject sobre su tipo. También puede limitar
aún más la clase a ser parte de una determinada jerarquía o
implementar una interfaz específica, haciendo posible llamar
a la clase o al método de la interfaz en una instancia del tipo
genérico.

Restricciones de clase
La restricción más simple que puede adoptar es la limitación
de clase. Para usarla, usted puede declarar un tipo genérico
como:
type
TSampleClass <T: class> = class

Al especificar una restricción de clase esto implica que usted


sólo puede utilizar tipos derivados de objetos como sus tipos
genéricos. Con la declaración siguiente (tomada del proyecto
de la ClassContraint):
type
TSampleClass <T: class> = class
private
data: T;
public
procedure One;
function ReadT: T;
procedure SetT (t: T);
end;

usted puede crear las dos primeras instancias, pero no la ter-


cera:
sample1: TSampleClass<TButton>;
sample2: TSampleClass<TStrings>;
sample3: TSampleClass<Integer>; // Error

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 185

El error del compilador causado por esta última declaración


sería:
E2511 Type parameter 'T' must be a class type

¿Cuál es la ventaja de indicar esta restricción? En la clase ge-


nérica, ahora usted puede llamar a cualquier método de TOb-
ject ¡Incluyendo los virtuales! Este es el método One de la
clase genérica75 TSampleClass:
procedure TSampleClass<T>.One;
begin
if Assigned (data) then
begin
Form30.Log('ClassName: ' + data.ClassName);
Form30.Log('Size: ' + IntToStr (data.InstanceSize));
Form30.Log('ToString: ' + data.ToString);
end;
end;

Puede jugar con el programa para ver los efectos resultantes,


ya que define y utiliza algunas instancias del tipo genérico,
como en el siguiente fragmento de código:
var
sample1: TSampleClass<TButton>;
begin
sample1 := TSampleClass<TButton>.Create;
try
sample1.SetT (Sender as TButton);
sample1.One;
finally
sample1.Free;
end;

Tenga en cuenta que al declarar una clase con un método per-


sonalizado ToString, esta versión personalizada podrá ser lla-
mada cuando el objeto de datos es del tipo específico, a pesar

75Doscomentarios más. El primero es que InstanceSize devuelve el tamaño real


del objeto, a diferencia de la función genérica SizeOf utilizada anteriormente,
que devuelve el tamaño del tipo de referencia. En segundo lugar, el método
ToString es un nuevo método (relevante) en Delphi 2009 de TObject que
cubriremos en detalle en el capítulo 7, en la sección “Nuevos métodos de
TObject“.

La guía de Delphi por Marco Cantù


186 - Indice

de que el tipo actual sea siempre del tipo genérico. En otras


palabras, si usted tiene un TButton descendiente como:
type
TMyButton = class (TButton)
public
function ToString: string; override;
end;

Puede pasar este objeto como un valor de TSampleClass<TBut-


ton> o definir una instancia concreta del tipo genérico, y en
ambos casos llamando a One que termina con la ejecución de
la versión especifica de ToString:
var
sample1: TSampleClass<TButton>;
sample2: TSampleClass<TMyButton>;
mb: TMyButton;
begin
...
sample1.SetT (mb);
sample1.One;
sample2.SetT (mb);
sample2.One;

Al igual que una restricción de clase, usted puede tener una


restricción de registro, declarada como:
type
TSampleRec <T: record> = class

Sin embargo, es muy poco lo que tienen en común estos dife-


rentes registros (no hay ningún ancestro común), por lo que
esta declaración es un tanto limitada.

Restricciones Específicas de
Clase
Si su clase genérica necesita trabajar con un subconjunto es-
pecífico de clases (una jerarquía especifica), es posible que

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 187

desee recurrir a especificar una restricción basada sobre una


predeterminada clase base. Por ejemplo, si usted declara:
type
TCompClass <T: TComponent> = class

Las instancias de esta clase genérica sólo pueden aplicarse a


clases de componentes, es decir, cualquier clase descendiente
de Tcomponent.Esto le permite tener unos tipos genéricos muy
específicos (sí, le puede parecer extraño, pero eso es lo que
realmente es) y el compilador le permite utilizar todos los mé-
todos de la clase TComponent mientras usted trabaje sobre este
tipo genérico.
Si esto le parece extremadamente potente, piénselo dos veces.
Si considera lo que puede lograr con la herencia y el tipo de
normas de compatibilidad, puede ser capaz de abordar el
mismo problema utilizando técnicas orientadas a objetos en
lugar de tener que utilizar las clases genéricas. No estoy di-
ciendo que una restricción de clase específica nunca sea útil,
pero ciertamente no es tan potente como un nivel superior de
restricción de clase o (algo que me resulta muy interesante),
una interfaz basada en la restricción.

Restricciones de interfaz
En lugar de una clase genérica restringida a una clase deter-
minada, es más flexible en general, aceptar como parámetro
de tipo sólo clases implementadas por una interfaz determi-
nada. Esto hace posible llamar a una interfaz con instancias
de tipo genérico. Este empleo de restricciones de interfaz para
genéricos es también muy común en el entorno .NET. Permí-
tanme empezar por mostrar un ejemplo (llamado ). En primer
lugar, necesitamos declarar una interfaz:
type
IGetValue = interface

La guía de Delphi por Marco Cantù


188 - Indice

['{60700EC4-2CDA-4CD1-A1A2-07973D9D2444}']
function GetValue: Integer;
procedure SetValue (Value: Integer);
property Value: Integer
read GetValue write SetValue;
end;

Después, podemos definir una clase que la ponga en práctica:


type
TGetValue = class (TSingletonImplementation, IGetValue)
private
fValue: Integer;
public
constructor Create (Value: Integer = 0);
function GetValue: Integer;
procedure SetValue (Value: Integer);
end;

Las cosas empiezan a ser interesantes en la definición de una


clase genérica restringida a tipos que implementen una inter-
faz:
type
TInftClass <T: IGetValue> = class
private
val1, val2: T; // or IGetValue
public
procedure Set1 (val: T);
procedure Set2 (val: T);
function GetMin: Integer;
function GetAverage: Integer;
procedure IncreaseByTen;
end;

Observe que en el código de los métodos genéricos de esta


clase podemos escribir, por ejemplo:
function TInftClass<T>.GetMin: Integer;
begin
Result := min (val1.GetValue, val2.GetValue);
end;

procedure TInftClass<T>.IncreaseByTen;
begin
val1.SetValue (val1.GetValue + 10);
val2.Value := val2.Value + 10;
end;

Con todas estas definiciones, podemos usar la clase genérica


de la siguiente forma:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 189

procedure TFormIntfConstraint.btnValueClick(
Sender: TObject);
var
iClass: TInftClass<TGetValue>;
begin
iClass := TInftClass<TGetValue>.Create;
try
iClass.Set1 (TGetValue.Create (5));
iClass.Set2 (TGetValue.Create (25));
Log ('Average: ' + IntToStr (iClass.GetAverage));
iClass.IncreaseByTen;
Log ('Min: ' + IntToStr (iClass.GetMin));
finally
iClass.val1.Free;
iClass.val2.Free;
iClass.Free;
end;
end;

Para mostrar la flexibilidad de esta clase genérica, he creado


otra implementación totalmente diferente para la interfaz:
TButtonValue = class (TButton, IGetValue)
public
function GetValue: Integer;
procedure SetValue (Value: Integer);
class function MakeTButtonValue (Owner: TComponent;
Parent: TWinControl): TButtonValue;
end;

function TButtonValue.GetValue: Integer;


begin
Result := Left;
end;

procedure TButtonValue.SetValue(Value: Integer);


begin
Left := Value;
end;

Las funciones de clase (que no figuran en el libro) crean un


botón dentro del control Parent en una posición aleatoria,
como se muestra en el ejemplo de código siguiente:
procedure TFormIntfConstraint.btnValueButtonClick(
Sender: TObject);
var
iClass: TInftClass<TButtonValue>;
begin
iClass := TInftClass<TButtonValue>.Create;
try
iClass.Set1 (TButtonValue.MakeTButtonValue (

La guía de Delphi por Marco Cantù


190 - Indice

self, ScrollBox1));
iClass.Set2 (TButtonValue.MakeTButtonValue (
self, ScrollBox1));
Log ('Average: ' + IntToStr (iClass.GetAverage));
Log ('Min: ' + IntToStr (iClass.GetMin));
iClass.IncreaseByTen;
Log ('New Average: ' + IntToStr (iClass.GetAverage));
finally
iClass.Free;
end;
end;

Referencias de Interfaz vs.


Restricciones de Interfaz
Genéricas
En el último ejemplo he definido una clase genérica que fun-
ciona con cualquier objeto implementado con una interfaz
predeterminada. Podría haber obtenido un efecto similar
creando una clase estándar (no-genérica) basada en referen-
cias de interfaz. De hecho, podría haber definido una clase (de
nuevo parte del proyecto IntfConstraint) como:

type
TPlainInftClass = class
private
val1, val2: IGetValue;
public
procedure Set1 (val: IGetValue);
procedure Set2 (val: IGetValue);
function GetMin: Integer;
function GetAverage: Integer;
procedure IncreaseByTen;
end;

¿Cuál es la diferencia entre estos dos enfoques? Una primera


diferencia es que en la clase anterior usted puede pasar dos
objetos de tipos diferentes a los métodos “setter", siempre que
ambas clases implementen la interfaz dada, mientras que en
la versión genérica sólo se pueden pasar objetos de un tipo
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 191

dado (a cualquier instancia de la clase genérica). Así pues, la


versión genérica es más conservadora y estricta en términos
de tipo de control.
En mi opinión, la diferencia clave es que usando la versión ba-
sada en la interfaz significa tener en Delphi un mecanismo de
referencia de conteo en acción, mientras que utilizando la ver-
sión genérica la clase está tratando con objetos básicos de un
tipo dado y no contamos con el recuento de referencia. Por
otra parte, la versión genérica podría tener múltiples restric-
ciones, como una restricción de constructor que le permite
utilizar las diversas funciones genéricas (como preguntar por
el tipo actual del tipo genérico), algo que usted no puede ha-
cer al utilizar una interfaz. (Cuando se trabaja con una inter-
faz, de hecho, usted no tiene acceso a la base de métodos de
TObject).

En otras palabras, utilizando una clase genérica con una inter-


faz restringida hace que sea posible tener las ventajas de las
interfaces sin sus inconvenientes. Sin embargo, vale la pena
tener en cuenta que en la mayoría de los casos, los dos plan-
teamientos serían equivalentes, y en otros la solución basada
en la interfaz sería la más flexible.

Restricción del Constructor por


Defecto
Hay otro tipo de restricciones genéricas posibles, llamadas
constructor por defecto o constructor sin-parámetros. Si us-
ted necesita invocar el constructor por defecto para crear un
nuevo objeto de tipo genérico (por ejemplo, para llenar una
lista), puede usted utilizar esta restricción. En teoría (y según
la documentación), el compilador le dejará utilizarlo única-
mente para los tipos con un constructor por defecto. En la
La guía de Delphi por Marco Cantù
192 - Indice

práctica, si un constructor por defecto no existe, el compila-


dor lo dejará pasar y llamará al constructor por defecto de TO-
bject.

Una clase genérica con un constructor restringido puede es-


cribirse como sigue76 (este método está extraído del ejemplo
IntfConstraint):
type
TConstrClass <T: class, constructor> = class
private
val: T;
public
constructor Create;
function Get: T;
end;

Teniendo en cuenta esta declaración, puede utilizar el cons-


tructor para crear un objeto interno genérico, sin saber por
adelantado su tipo efectivo, y escribir:
constructor TConstrClass<T>.Create;
begin
val := T.Create;
end;

¿Cómo podemos usar esta clase genérica, y cuáles son las re-
glas? En los próximos ejemplos, he definido dos clases, una
con un constructor predeterminado (sin-parámetros), la se-
gunda con un único constructor que tiene un parámetro:
type
TSimpleConst = class
public
Value: Integer;
constructor Create; // set Value to 10
end;

TParamConst = class
public
Value: Integer;
constructor Create (I: Integer); // set Value to I

76También puede especificar el constructor de restricciones sin la restricción de


clase, ya que el primero probablemente implica el segundo. Listando ambos
hace el código más legible.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 193

end;

Como mencioné anteriormente, en teoría, debería ser capaz


de utilizar sólo la primera clase, mientras que en la práctica,
se pueden utilizar las dos:
var
constructObj: TConstrClass<TSimpleCost>;
paramCostObj: TConstrClass<TParamCost>;
begin
constructObj := TConstrClass<TSimpleCost>.Create;
Log ('Value 1: ' + IntToStr (constructObj.Get.Value));

paramCostObj := TConstrClass<TParamCost>.Create;
Log ('Value 2: ' + IntToStr (paramCostObj.Get.Value));

El resultado de este código es:


Value 1: 10
Value 2: 0

De hecho, el segundo objeto nunca es inicializado. Si depura


la aplicación, rastreando en el código, verá una llamada a TOb-
ject.Create (que no considero adecuada). Observe lo que su-
cede si intenta llamarla directamente:
with TParamConst.Create do

el compilador lanzará (correctamente) el error77:


[DCC Error] E2035 Not enough actual parameters

Resumen de las Restricciones

77Auncuando una llamada directa a TParamConst.Create fallará en tiempo de


compilación (como se explica aquí), una llamada que utilice una clase de re-
ferencia similar o cualquier otra forma de engaño tendrá éxito, lo que proba-
blemente explica el comportamiento del efecto del constructor restringido.

La guía de Delphi por Marco Cantù


194 - Indice

Genéricas y Su Combinación
Como hay tantas restricciones diferentes que usted puede im-
poner a un tipo genérico, permítame que provea un breve re-
sumen, en términos de código:
type
TSampleClass <T: class> = class
TSampleRec <T: record> = class
TCompClass <T: TButton> = class
TInftClass <T: IGetValue> = class
TConstrClass <T: constructor> = class

Lo que podría no comprender inmediatamente después de


observar las restricciones (y ciertamente, me llevó algún
tiempo acostumbrarme a esta idea) es que se pueden combi-
nar. Por ejemplo, puede definir que una clase genérica se li-
mite a una sub-jerarquía y que requiera también una determi-
nada interfaz, como en:
type
TInftComp <T: TComponent, IGetValue> = class
...
end;

No todas las combinaciones tienen sentido: por ejemplo, us-


ted no puede especificar tanto una clase como un registro,
cuando utilice una clase restringida combinada con una clase
específica restringida sería redundante. Por último, observe
que no hay nada como un método restringido, algo que puede
lograrse con un método-simple de restricción de interfaz (mu-
cho más complejo de expresar, sin embargo).

Contenedores Genéricos

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 195

Predefinidos
Desde los primeros días de las plantillas en el lenguaje C++,
uno de los usos más evidentes de las clases genéricas ha sido
la definición de los contenedores genéricos o listas. Cuando se
define una lista de objetos, como la propia de Delphi: TObje-
ctList, de hecho, usted tiene una lista que potencialmente
puede contener objetos de cualquier tipo. Usando la herencia
o la composición, usted de verdad puede definir contenedores
a medida para especificar un tipo, pero este es un enfoque78
tedioso (y potencialmente propenso a errores).
Delphi 2009 define un pequeño conjunto de contenedores de
clases genéricas que se pueden encontrar en la nueva unidad
Generics.Collections. Las cuatro clases principales de conte-
nedor están todas implementadas de forma independiente
(no existe herencia entre estas clases), todas implementadas
de forma similar (utilizando una matriz dinámica), y estando
todas mapeadas a los correspondientes contenedores de clase
no-genéricos de la unidad Contnrs:
type
TList<T> = class
TQueue<T> = class
TStack<T> = class
TDictionary<TKey,TValue> = class

La diferencia lógica entre estas clases debería ser bastante ob-


via teniendo en cuenta sus nombres. Una buena forma de pro-
barlas, es comprobar cuantos cambios tiene que realizar en

78Aquíno quiero cubrir la diferencia entre los diversos enfoques que usted po-
dría utilizar en Delphi para definir contenedores especializados antes que ge-
néricos disponibles, porque ahora estamos con los genéricos. Usted puede
encontrar material ampliado en mi libro Mastering Delphi 7 (Sybex) y en
otras ediciones de esa serie.

La guía de Delphi por Marco Cantù


196 - Indice

código anterior que utilice un contenedor no genérico de cla-


ses. Como ejemplo, he tomado un programa real del libro
“Mastering Delphi 2005” y convertido este al uso de generi-
cos79.

Usando TList<T>
El programa, llamado ListDemoMd2005, tiene una unidad
que define una clase TDate, y el formulario principal utilizaba
para referirlos un TList de fechas. Como punto de partida, he
añadido la cláusula uses que hace referencia a Generics.Colle-
ctions, y he cambiado la declaración del campo en el formula-
rio principal a:
private
ListDate: TList <TDate>;

Por supuesto, el evento OnCreate del formulario principal que


crea la lista necesita ser actualizado también, convirtiéndose
en:
procedure TForm1.FormCreate(Sender: TObject);
begin
ListDate := TList<TDate>.Create;
end;

Ahora podemos tratar de compilar el resto del código tal como


está. El programa tiene un error "deseado", intentando añadir
un objeto TButton a la lista. El correspondiente código que uti-
lizaba al compilar y que ahora lo que falla es:
procedure TForm1.ButtonWrongClick(Sender: TObject);
begin
// add a button to the list
ListDate.Add (Sender); // Error:
// E2010 Incompatible types: 'TDate' and 'TObject'

79El programa solamente utiliza unos pocos métodos, aunque esto no es una
gran prueba para la compatibilidad de interfaz entre las listas de genéricos y
no genéricos, pero decidí que valía la pena coger un programa existente en
lugar de hacer uno nuevo.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 197

end;

La nueva lista de fechas es más sólida en términos de control


de tipos que la lista genérica de punteros original. Después de
haber eliminado esa línea, el programa se compila y funciona.
Sin embargo, puede ser mejorado.
Este es el código original usado para mostrar todas las fechas
de la lista que figuran en un control ListBox:
var
I: Integer;
begin
ListBox1.Clear;
for I := 0 to ListDate.Count - 1 do
Listbox1.Items.Add (
(TObject(ListDate [I]) as TDate).Text);

Observe la mala apariencia del tipo emitido, debido de hecho


a que el programa utiliza una lista de punteros (TList), y no
una lista de objetos (TObjectList). La razón bien podría ser
que la demostración anterior original ¡Precede a la clase TOb-
jectList! Podría probablemente mejorar la escritura del pro-
grama con:
for I := 0 to ListDate.Count - 1 do
Listbox1.Items.Add (ListDate [I].Text);

Otra mejora de este fragmento puede venir de utilizar una


enumeración (algo que las listas genéricas predefinidas so-
portan plenamente), en lugar de un bucle simple:
var
aDate: TDate;
begin
for aDate in ListDate do
begin
Listbox1.Items.Add (aDate.Text);
end;

Finalmente, el programa puede ser mejorado mediante el uso


de un genérico TObjectList propietario de objetos TDate, pero
es un tema para la próxima sección.

La guía de Delphi por Marco Cantù


198 - Indice

Como mencioné anteriormente, la clase genérica TList<T>


tiene un alto grado de compatibilidad. Existen todos los méto-
dos clásicos, como Add, Insert, Remove e IndexOf. Las propieda-
des Capacity y Count están disponibles también. Curiosa-
mente, Items, se convierte en Item, pero, de todos modos,
siendo la propiedad por defecto, rara vez se referirá a esta ex-
plícitamente.

Ordenando un TList<T>
Lo que es interesante es entender cómo funciona la ordena-
ción (mi objetivo aquí es añadir la capacidad de ordenación al
ejemplo ListDemoMd2005). El método Sort se define así:
procedure Sort; overload;
procedure Sort(const AComparer: IComparer<T>); overload;

Donde el interfaz IComparer<T> se declara en la unidad Ge-


nerics.Defaults. Si llama a la primera versión el programa uti-
lizará el método comparer por defecto, iniciado por el cons-
tructor predeterminado de TList<T>. En nuestro caso esto será
inútil.
Lo que tenemos que hacer, en su lugar, es definir una correcta
implementación de la interfaz IComparer<T>. Para la compati-
bilidad del tipo, es necesario definir una implementación que
funcione sobre la clase específica TDate. Hay múltiples mane-
ras de lograr esto, incluyendo el uso de métodos anónimos
(analizados en la siguiente sección, incluso si este es un tema
que introduzco en el siguiente capítulo). Una técnica intere-
sante, también, porque me da la oportunidad de mostrar al-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 199

gún modelo de uso con genéricos, es aprovechar una clase es-


tructural80 que forma parte de la unidad Generics.Defaults y
se llama TComparer. La clase se define como una implementa-
ción genérica y abstracta de la interfaz, como sigue:
type
TComparer<T> = class(TInterfacedObject, IComparer<T>)
public
class function Default: IComparer<T>;
class function Construct(
const Comparison: TComparison<T>): IComparer<T>;
function Compare(
const Left, Right: T): Integer; virtual; abstract;
end;

Lo que tenemos que hacer es crear una instancia de esta clase


genérica para el tipo de datos específicos (TDate, en este ejem-
plo) y que también herede una clase concreta que implemente
el método Compare para este tipo específico. Las dos operacio-
nes se pueden hacer a la vez, utilizando un lenguaje de codifi-
cación que puede necesitar un cierto tiempo en digerirse:
type
TDateComparer = class (TComparer<TDate>)
function Compare(
const Left, Right: TDate): Integer; override;
end;

Si cree que este código parece muy raro, no está solo. La


nueva clase hereda de una instancia específica de la clase ge-
nérica, algo que podría expresar en dos pasos separados81
como:
type
TAnyDateComparer = TComparer<TDate>;

80Personalmente estoy llamando esta clase estructural, ya que ayuda a definir la


estructura del código, pero no agrega mucho en términos de la implementa-
ción actual. Aunque, podría tener un nombre mejor.
81Tenerlas dos declaraciones por separado puede ayudar a reducir el código ge-
nerado cuando usted reutilice el tipo base TAnyDateComparer en la misma
unidad.

La guía de Delphi por Marco Cantù


200 - Indice

TMyDateComparer = class (TAnyDateComparer)


function Compare(
const Left, Right: TDate): Integer; override;
end;

Usted puede encontrar la implementación actual de la función


Compare en el código fuente, pero no es el punto clave que
quiero subrayar aquí. Tenga en cuenta, sin embargo, que, in-
cluso si ordena la lista, su método IndexOf no se va a aprove-
char de ello (a diferencia de la clase TStringList).

Ordenando con un Método


Anónimo
El código de ordenación que se presenta en la sección anterior
parece muy complicado, y realmente lo es. Sería mucho más
fácil y limpio pasar la función de ordenación al método Sort
directamente. Anteriormente, esto se lograba generalmente
pasando una función puntero. En Delphi 2009 esto puede
hacerse pasando un método anónimo (una especie de método
puntero, con varias características extras, comentadas con de-
talle en el próximo capítulo82).
El parámetro IComparer<T> del método Sort de la clase
TList<T>, de hecho, puede utilizarse llamando al método cons-
tructor de TComparer<T>, pasando un método anónimo
como parámetro, el cual se define como:
type
TComparison<T> = reference to function(
const Left, Right: T): Integer;

82Lesugiero que eche un vistazo a esta sección incluso si usted no sabe mucho
sobre los métodos anónimos, y luego volver a leerlo de nuevo después de
profundizar en el siguiente capítulo.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 201

En la práctica, usted puede escribir una función que sea com-


patible con el tipo y pasarla como parámetro83:
function DoCompare (const Left, Right: TDate): Integer;
var
ldate, rDate: TDateTime;
begin
lDate := EncodeDate(Left.Year, Left.Month, Left.Day);
rDate := EncodeDate(Right.Year, Right.Month, Right.Day);
if lDate = rDate then
Result := 0
else if lDate < rDate then
Result := -1
else
Result := 1;
end;

procedure TForm1.ButtonAnonSortClick(Sender: TObject);


begin
ListDate.Sort (TComparer<TDate>.Construct (DoCompare));
end;

Si esto le parece bastante tradicional, considere la posibilidad


de que podría haber evitado la declaración de una función se-
parada y pasarla (su código fuente) como parámetro al mé-
todo Construct, como sigue:
procedure TForm1.ButtonAnonSortClick(Sender: TObject);
begin
ListDate.Sort (TComparer<TDate>.Construct (
function (const Left, Right: TDate): Integer
var
ldate, rDate: TDateTime;
begin
lDate := EncodeDate(Left.Year,
Left.Month, Left.Day);
rDate := EncodeDate(Right.Year,
Right.Month, Right.Day);
if lDate = rDate then

83El método anterior DoCompare funciona como un método anónimo aunque


tenga un nombre. Lo veremos más tarde en un fragmento de código que, sin
embargo, no es necesario. Tenga paciencia hasta el próximo capítulo para
obtener más información acerca de este nuevo lenguaje de construcción en
Delphi 2009. Note también que con un registro TDate yo podría haber defi-
nido menos y entonces usar más operadores, haciendo este código más sim-
ple, pero aún con una clase, yo podría haber colocado el código de compara-
ción en un método de la clase.

La guía de Delphi por Marco Cantù


202 - Indice

Result := 0
else if lDate < rDate then
Result := -1
else
Result := 1;
end));
end;

Este ejemplo debería haberle abierto el apetito ¡De aprender


más sobre los métodos anónimos! Por cierto, esta última ver-
sión es mucho más sencilla de escribir que la comparación
original tratada en el apartado anterior, aunque muchos desa-
rrolladores de Delphi tengan una clase derivada que puede
conseguir un aspecto más limpio y más fácil de entender (la
versión heredada separa mejor la lógica, haciendo la reutiliza-
ción de código potencialmente más fácil, pero, de todos mo-
dos, muchas veces no hará uso de ella).

Contenedores de Objetos
Además de las clases genéricas cubiertas al principio de este
apartado, hay también cuatro clases genéricas heredadas que
se derivan de las clases base definidas en la unidad Gene-
rics.Collections, imitando a las clases existentes de la unidad
Contnrs:
type
TObjectList<T: class> = class(TList<T>)
TObjectQueue<T: class> = class(TQueue<T>)
TObjectStack<T: class> = class(TStack<T>)

En comparación con sus clases de base, existen dos diferen-


cias fundamentales. Una de ellas es que estos tipos genéricos
sólo podrán utilizarse para los objetos, y la segunda es definir
un método personalizado Notification que en el caso de que
un objeto se elimine de la lista (opcionalmente junto a una lla-
mada al evento manejador OnNotify) se libere el objeto.
En otras palabras, la clase TObjectList<T> se comporta como
su equivalente no-genérico cuando se establezca su propiedad
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 203

OwnsObjects. Si se está preguntando por qué esto no es una


opción más, considere que TList<T> puede utilizarse directa-
mente para trabajar con tipos de objeto, a diferencia de su
equivalente no-genérico.
También hay una cuarta clase, de nuevo, llamada TObjectDic-
tionary<TKey, TValue>, que está definida de una forma dife-
rente, puesto que puede controlar el objeto clave, los objetos
de valor, o ambos. Véase el conjunto TDictionaryOwnerships y
el constructor de clase, para más detalles.

Usando un Diccionario Genérico


De todas las clases de contenedor genérico predefinidas, la
que probablemente necesita un estudio más detallado es el
diccionario genérico84, TObjectDictionary<TKey, TValue>.
Otras clases son igual de importantes, pero parecen ser más
fáciles de usar y comprender. Como ejemplo del uso de un
diccionario, he escrito una aplicación que obtiene los datos de
una tabla de base de datos, crea un objeto para cada registro,
y utiliza un índice compuesto con un número de cliente y una
descripción como clave. La razón de esta separación es que
una arquitectura similar puede fácilmente ser utilizada para
crear un proxy, en el cual la clave toma el lugar de la versión
light del objeto actual cargado desde la base de datos.
Estas son las dos clases utilizadas por el ejemplo de la Custo-
merDictionary para la clave, o llave, y el valor actual. La pri-
mera tiene sólo dos campos relevantes de la correspondiente

84Dictionaryen este caso significa una colección de elementos cada uno con un
valor (único) clave refiriéndose a estos. (También se conoce como una matriz
asociativa). En un diccionario clásico usted tiene palabras que actúan como
clave de sus definiciones, pero en términos de programación la clave no
tiene que ser una cadena (incluso si esto es un caso bastante frecuente).

La guía de Delphi por Marco Cantù


204 - Indice

tabla de la base de datos, mientras que la segunda tiene la es-


tructura completa de los datos (he omitido los ámbitos priva-
dos y los métodos getter y setter):
type
TCustomerKey = class
private
...
published
property CustNo: Double
read FCustNo write SetCustNo;
property Company: string
read FCompany write SetCompany;
end;

TCustomer = class
private
..
procedure Init;
procedure EnforceInit;
public
constructor Create (aCustKey: TCustomerKey);
property CustKey: TCustomerKey
read FCustKey write SetCustKey;
published
property CustNo: Double
read GetCustNo write SetCustNo;
property Company: string
read GetCompany write SetCompany;
property Addr1: string
read GetAddr1 write SetAddr1;
property City: string
read GetCity write SetCity;
property State: string
read GetState write SetState;
property Zip: string
read GetZip write SetZip;
property Country: string
read GetCountry write SetCountry;
property Phone: string
read GetPhone write SetPhone;
property FAX: string
read GetFAX write SetFAX;
property Contact: string
read GetContact write SetContact;
class var
RefDataSet: TDataSet;
end;

Mientras que la primera clase es muy simple (cada objeto se


inicializa cuando es creado), la clase TCustomer usa un modelo
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 205

de inicialización “perezosa” (lazzy o proxy) y mantiene una


referencia en torno a la fuente de base de datos compartida
(class var) a través de todos sus objetos. Cuando se crea un
objeto, se le asigna una referencia a la correspondiente TCus-
tomerKey, mientras que una clase data field se refiere a su
fuente de datos. En cada método getter, la clase comprueba si
el objeto ha sido verdaderamente iniciado antes de devolver
los datos, como en el caso siguiente:
function TCustomer.GetCompany: string;
begin
EnforceInit;
Result := FCompany;
end;

El método EnforceInit controla un comando local, eventual-


mente llama a Init para cargar los datos, desde la base de da-
tos, al objeto en memoria:
procedure TCustomer.EnforceInit;
begin
if not fInitDone then
Init;
end;

procedure TCustomer.Init;
begin
RefDataSet.Locate('custno', CustKey.CustNo, []);

//también podría cargar cada campo publicado con la RTTI


FCustNo := RefDataSet.FieldByName ('CustNo').AsFloat;
FCompany := RefDataSet.FieldByName ('Company').AsString;
FCountry := RefDataSet.FieldByName ('Country').AsString;
...
fInitDone := True;
end;

Teniendo en cuenta estas dos clases, he añadido con este fin


un diccionario especial a la aplicación. Esta clase de dicciona-
rio personalizado hereda de clases genéricas instanciadas con
los tipos apropiados y añade estas a un método específico:
type
TCustomerDictionary = class (
TObjectDictionary <TCustomerKey, TCustomer>)
public
procedure LoadFromDataSet (dataset: TDataSet);

La guía de Delphi por Marco Cantù


206 - Indice

end;

El método de carga rellena el diccionario, copiando los datos


en memoria solamente para los objetos clave:
procedure TCustomerDictionary.LoadFromDataSet(
dataset: TDataSet);
var
custKey: TCustomerKey;
begin
TCustomer.RefDataSet := dataset;
dataset.First;
while not dataset.EOF do
begin
custKey := TCustomerKey.Create;
custKey.CustNo := dataset ['CustNo'];
custKey.Company := dataset ['Company'];
self.Add(custKey, TCustomer.Create (custKey));
dataset.Next;
end;
end;

Este programa demo tiene un formulario principal y un mó-


dulo de datos alojando un ClientDataSet85. El formulario prin-
cipal tiene un control ListView que se llena cuando un usuario
simplemente presiona el botón.
Después de cargar los datos en el diccionario, el método
btnPopulateClick utiliza un enumerador en el diccionario de
claves:
procedure TFormCustomerDictionary.btnPopulateClick(
Sender: TObject);
var
custkey: TCustomerKey;
listItem: TListItem;
begin
DataModule1.ClientDataSet1.Active := True;
CustDict.LoadFromDataSet(DataModule1.ClientDataSet1);

for custkey in CustDict.Keys do

85Esposible que desee sustituir el componente ClientDataSet por un conjunto de


datos reales, mejorando considerablemente el ejemplo en términos de utili-
dad, como se puede ejecutar una consulta para las llaves y separar una para
los datos actuales de cada uno de los objetos TCustomer. Tengo un código si-
milar, pero añadirlo aquí nos distraería demasiado de la meta del ejemplo,
que es experimentar con el diccionario de clases genéricas.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 207

begin
listItem := ListView1.Items.Add;
listItem.Caption := custkey.Company;
listItem.SubItems.Add(FloatTOStr (custkey.CustNo));
listItem.Data := custkey;
end;
end;

Esto llena las dos primeras columnas del control ListView,


con los datos disponibles en los objetos clave. Cuando un
usuario selecciona un elemento del control ListView, el pro-
grama sin embargo, va a rellenar una tercera columna:
procedure TFormCustomerDictionary.ListView1SelectItem(
Sender: TObject; Item: TListItem; Selected: Boolean);
var
aCustomer: TCustomer;
begin
aCustomer := CustDict.Items [Item.data];
Item.SubItems.Add(IfThen (aCustomer.State <> '',
aCustomer.State + ', ' + aCustomer.Country,
aCustomer.Country));
end;

El método anterior obtiene el objeto formateado a la clave


dada, y utiliza sus datos. Detrás de esta escena, la primera vez
que se utiliza uno de estos objetos específicos, su propiedad
de método de acceso desencadena la carga de los datos com-
pletos sobre el objeto TCustomer.

Interfaces Genéricas
En la sección “Ordenando un TList<T>” puede ser que usted
haya notado algo extraño el uso de una interfaz predefinida,
que tenía una declaración genérica. Vale la pena ahondar al
detalle en esta técnica, ya que abre importantes oportunida-
des y cambios (aunque moderados) en la forma en que los in-
terfaces trabajan en el lenguaje.

La guía de Delphi por Marco Cantù


208 - Indice

El primer elemento técnico que hay que notar es que es per-


fectamente legal definir un interfaz86 genérico, tal como he
hecho en el ejemplo GenericInterface:
type
IGetValue<T> = interface
function GetValue: T;
procedure SetValue (Value: T);
end;

Tenga en cuenta esta diferencia con una interfaz estándar, en


el caso de una interfaz genérica no es necesario que usted es-
pecifique un GUID que se utiliza como ID de interfaz (o IID).
El compilador generará, para cada instancia, un IID de la in-
terfaz genérica, aunque esté implícitamente declarado. De he-
cho, usted no tiene que crear una instancia específica de la in-
terfaz genérica para su implementación, pero puede definir
una clase genérica que implemente la interfaz genérica:
type
TGetValue<T> = class (TInterfacedObject, IGetValue<T>)
private
fValue: T;
public
constructor Create (Value: T);
destructor Destroy; override;
function GetValue: T;
procedure SetValue (Value: T);
end;

Mientras que el constructor asigna el valor inicial del objeto,


el único objetivo del destructor es registrar que un objeto fue
destruido. Podemos crear una instancia de esta clase genérica
(así pues, generando una instancia específica del tipo de inter-
faz entre bastidores) escribiendo:
procedure TFormGenericInterface.btnValueClick(
Sender: TObject);
var
aVal: TGetValue<string>;

86Esta
es la versión genérica de la interfaz IGetValue del ejemplo IntfContraints
cubierto en el apartado anterior “Restricciones de Interfaz” de este capítulo.
En ese caso, el interfaz tenía un valor entero, ahora es de carácter genérico.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 209

begin
aVal := TGetValue<string>.Create (Caption);
try
Log ('TGetValue value: ' + aVal.GetValue);
finally
aVal.Free;
end;
end;

Un enfoque alternativo, como vimos anteriormente para el


ejemplo IntfConstraint, es utilizar una variable interfaz del
tipo correspondiente, haciendo la definición específica del
tipo de interfaz explícito (y no implícito como en el anterior
fragmento de código):
procedure TFormGenericInterface.btnIValueClick(
Sender: TObject);
var
aVal: IGetValue<string>;
begin
aVal := TGetValue<string>.Create (Caption);
Log ('IGetValue value: ' + aVal.GetValue);
//liberado automáticamente por ser referencia contada
end;

Por supuesto, también podemos definir una clase específica


que implemente la interfaz genérica, como en el siguiente es-
cenario (a partir del ejemplo GenericInterface):
type
TButtonValue = class (TButton, IGetValue<Integer>)
public
function GetValue: Integer;
procedure SetValue (Value: Integer);
class function MakeTButtonValue (Owner: TComponent;
Parent: TWinControl): TButtonValue;
end;

Observe que mientras que el genérico de clase TGetValue<T>


implementa el interfaz genérico IgetValue<T>, la clase especí-
fica TButtonValue implementa la interfaz específica IGetVa-
lue<Integer>. En concreto, como en un ejemplo anterior, la in-
terfaz es reformateada a la propiedad Left del control:
function TButtonValue.GetValue: Integer;
begin
Result := Left;
end;

La guía de Delphi por Marco Cantù


210 - Indice

En la clase anterior, la función de clase MakeTButtonValue es un


método listo-para-usar un método para crear un objeto de la
clase. Este método es utilizado por el tercer botón del formu-
lario principal, de la siguiente manera:
procedure TFormGenericInterface.btnValueButtonClick(
Sender: TObject);
var
iVal: IGetValue<Integer>;
begin
iVal := TButtonValue.MakeTButtonValue (
self, ScrollBox1);
Log ('Button value: ' + IntToStr (iVal.GetValue));
end;

Aunque esto no esté totalmente relacionado con las clases ge-


néricas, esta es la implementación de la función de clase Ma-
keTButtonValue:
class function TButtonValue.MakeTButtonValue(
Owner: TComponent; Parent: TWinControl): TButtonValue;
begin
Result := TButtonValue.Create(Owner);
Result.Parent := Parent;
Result.SetBounds(Random (Parent.Width),
Random (Parent.Height), Result.Width, Result.Height);
Result.Caption := 'btnv';
end;

Interfaces Genéricos
Predefinidos
Ahora que hemos explorado como definir interfaces genéricas
y combinarlas con el uso de genéricos y clases específicas, po-
demos volver a dar un segundo vistazo a la unidad Gene-
rics.Default. Esta unidad define dos interfaces genéricas de
comparación:
 IComparer<T> tiene un método Compare
 IEqualityComparer<T> tiene los métodos Equals y
GetHashCode

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 211

Estas clases están implementadas por algunas clases genéri-


cas y específicas, listadas a continuación (sin detalles de im-
plementación):
type
TComparer<T> = class(TInterfacedObject, IComparer<T>)
TEqualityComparer<T> = class(
TInterfacedObject, IEqualityComparer<T>)
TCustomComparer<T> = class(TSingletonImplementation,
IComparer<T>, IEqualityComparer<T>)
TStringComparer = class(TCustomComparer<string>)

En la lista anterior se puede ver que la clase base utilizada por


la implementaciones genéricas de las interfaces es la clásica
clase de referencia-contada TInterfacedObject o la nueva clase
TSingletonImplementation. Este es un extraño nombre87 de
clase que proporciona una implementación básica de IInter-
face sin recuento de referencia.

Como ya hemos visto anteriormente en la sección “Ordenando


un TList<T>” de este capítulo, estas clases de comparación
son utilizadas por el contenedor de genéricos. Para hacer las
cosas más complicadas, sin embargo, la unidad Generics.De-
fault se basa muy fuertemente en los métodos anónimos, por
lo que probablemente solamente deba mirarla después de leer
el capítulo siguiente.

Punteros inteligentes en

87El termino singleton es generalmente utilizado para definir una clase de la que
sólo se puede crear una instancia, y no una sin recuento de referencia. Con-
sidero que se trata de un término inapropiado.

La guía de Delphi por Marco Cantù


212 - Indice

Delphi
Al aproximarse a los genéricos, se puede llevar una primera
impresión falsa de que este lenguaje de construcción se usa
principalmente para las colecciones. Si bien este es el caso
más sencillo para el uso de clases genéricas, y muy a menudo
el primer ejemplo en los libros y documentos, los genéricos
son muy útiles más allá de la esfera de clases de colecciones (o
contenedores). En el último ejemplo de este capítulo voy a
mostrarle una colección de tipo no-genérica, que es la defini-
ción de un puntero inteligente.
Si usted viene de una formación estrictamente de Delphi, es
posible que no haya oído hablar de punteros inteligentes, una
idea que proviene del lenguaje C++. En C++ usted puede te-
ner punteros a objetos, con lo cual usted tiene que gestionar la
memoria directamente y manualmente, y las variables locales
del objeto son gestionadas de forma automática, pero tienen
muchas otras limitaciones (como la falta de polimorfismo88).
La idea de los punteros inteligentes es usar un objeto admi-
nistrado localmente para cuidar de la vida del puntero del ob-
jeto real que desea utilizar. Si esto le parece demasiado com-
plicado, espero que la versión de Delphi (y su código) le ayude
a aclararlos.
En Delphi los objetos son administrados por referencia, sin
embargo los registros tienen una vida útil vinculada con el
método en que se hayan declarado. Cuando el método fina-
liza, la zona de memoria para el registro se limpia. Así que lo

88El término polimorfismos en los lenguajes POO se utiliza para indicar la situa-
ción en la que se asigna a una variable de una clase base un objeto de una
clase derivada y llama a uno de los métodos virtuales de la clase base, poten-
cialmente acaba llamando la versión del método virtual de la subclase espe-
cífica.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 213

que podemos hacer es usar un registro para la gestión de la


vida de un objeto en Delphi. Por supuesto, queremos escribir
el código sólo una vez, por lo que usaremos un registro gené-
rico. Aquí está una primera versión:
type
TSmartPointer<T: class> = record
strict private
FValue: T;
function GetValue: T;
public
constructor Create(AValue: T);
property Value: T read GetValue;
end;

Los métodos Create y GetValue del registro podrían simple-


mente asignar y leer el valor. Usando este código puede crear
un objeto, crear un puntero inteligente envolviéndolo, y refe-
rirse del uno al otro:
var
sl: TStringList;
smartP: TSmartPointer<TStringList>;
begin
sl := TStringList.Create;
smartP.Create (sl);
sl.Add('foo');
smartP.Value.Add ('foo2');

Como usted puede haber resuelto, este código provoca una


pérdida de memoria ¡Exactamente igual que sin el puntero
inteligente! De hecho el registro se destruye cuando sale de su
propio ámbito, pero no tiene forma de liberar el objeto in-
terno. Considerando que un registro no tiene destructor
¿Cómo podemos gestionar la eliminación de objetos? Un
truco consiste en utilizar una interfaz en el interior del mismo
registro, así el registro liberará automáticamente la interfaz
de objetos. ¿Hay que añadir una interfaz para el objeto que
estamos envasando? Probablemente no, ya que esto impone
una limitación significativa en los objetos que podremos pasar
al puntero inteligente.

La guía de Delphi por Marco Cantù


214 - Indice

Una alternativa89 mejor es, probablemente, escribir una clase


envoltorio específica, vinculada a una interfaz, y utilizar el
mecanismo de recuento de referencia de la interfaz para el ob-
jeto envuelto. La clase interna puede tener el siguiente as-
pecto:
type
TFreeTheValue = class (TInterfacedObject)
private
fObjectToFree: TObject;
public
constructor Create(anObjectToFree: TObject);
destructor Destroy; override;
end;

constructor TFreeTheValue.Create(
anObjectToFree: TObject);
begin
fObjectToFree := anObjectToFree;
end;

destructor TFreeTheValue.Destroy;
begin
fObjectToFree.Free;
inherited;
end;

Mejor aún, en el ejemplo actual lo he declarado como un tipo


anidado del tipo genérico puntero inteligente. Todo lo que te-
nemos que hacer en el puntero inteligente del tipo genérico,
para habilitar esta característica, es añadir una interfaz de re-
ferencia e iniciarla con un objeto TFreeTheValue refiriéndose al
objeto contenido:
type
TSmartPointer<T: class> = record

89BarryKelly (del equipo de I+D de Delphi) ha puesto en marcha una arquitec-


tura similar con una interfaz de clase que utiliza un método anónimo para
liberar los objetos destino, pero no he tratado aún métodos anónimos y de
todos modos el código es más complicado. Su código se encuentra en:
http://barrkel.blogspot.com/2008/09/smart-pointers-in-delphi.html. Una
actualización de la versión utiliza otro método anónimo para simplificar el
código de acceso: http://barrkel.blogspot.com/2008/11/reference-counted-
pointers-revisited.html.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 215

strict private
FValue: T;
FFreeTheValue: IInterface;
function GetValue: T;
public
constructor Create(AValue: T); overload;
property Value: T read GetValue;
end;

El pseudo-constructor (los registros no tienen constructores


reales) se convierte en:
constructor TSmartPointer<T>.Create(AValue: T);
begin
FValue := AValue;
FFreeTheValue := TFreeTheValue.Create(FValue);
end;

Con este código en su lugar, ahora podemos escribir el si-


guiente código en un programa sin causar ninguna pérdida de
memoria:
procedure TFormSmartPointers.btnSmartClick(
Sender: TObject);
var
sl: TStringList;
smartP: TSmartPointer<TStringList>;
begin
sl := TStringList.Create;
smartP.Create (sl);
sl.Add('foo');
Log ('Count: ' + IntToStr (sl.Count));
end;

Al final del método el registro smartP se elimina, lo que pro-


voca que su objeto en la interfaz interna vaya a ser destruido,
liberando el objeto TStringList. Observe que esta eliminación
se lleva a cabo incluso cuando se plantea una excepción, por
lo que no es necesario proteger nuestro código con un bloque
try-finally90.

90Enla práctica, los bloques implícitos try-finally se añaden en todos los lu-
gares por dónde el compilador maneja la interfaz dentro del registro, pero no
tenemos que escribirlo (y el compilador es menos probable que se olvide).

La guía de Delphi por Marco Cantù


216 - Indice

En el programa, verifico que todos los objetos son efectiva-


mente destruidos y no haya pérdidas de memoria estable-
ciendo la opción global ReportMemoryLeaksOnShutdown a
True en el código de inicialización. A modo de prueba de re-
cuento, hay un botón en el programa que causa una fuga, que
se captura cuando el programa finaliza.
Por lo tanto, utilizando el registro de puntero inteligente, he-
mos sido capaces de eliminar la necesaria llamada a Free, y de
ahí la necesidad de un bloque try-finally, pero aún queda al-
gún código que escribir (y recordar escribirlo). Una extensión
a las clases de puntero inteligente es la inclusión de un opera-
dor de conversión Implicit, que proporciona la capacidad de
asignar el objeto destino al puntero inteligente:
class operator TSmartPointer<T>.
Implicit(AValue: T): TSmartPointer<T>;
begin
Result := TSmartPointer<T>.Create(AValue);
end;

Con este código (y aprovechando el campo Value) ahora pode-


mos escribir una versión más compacta del código, como por
ejemplo:
var
smartP: TSmartPointer<TStringList>;
begin
smartP := TStringList.Create;
smartP.Value.Add('foo');
Log ('Count: ' + IntToStr (smartP.Value.Count));

Como alternativa, podemos usar una variable TStringList y


utilizar un complicado constructor para iniciar el registro de
un puntero inteligente, incluso sin una referencia explícita al
mismo:
var
sl: TStringList;
begin
sl := TSmartPointer<TStringList>.
Create(TStringList.Create).Value;
sl.Add('foo');
Log ('Count: ' + IntToStr (sl.Count));

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 217

Como hemos empezado por este camino, también podemos


definir la conversión opuesta, y emplear la asignación de tipo
en lugar de la propiedad Value:
class operator TSmartPointer<T>.
Implicit(AValue: T): TSmartPointer<T>;
begin
Result := TSmartPointer<T>.Create(AValue);
end;

var
smartP: TSmartPointer<TStringList>;
begin
smartP := TStringList.Create;
TStringList(smartP).Add('foo2');

Ahora, usted también puede comprobar que en el código ante-


rior he utilizado siempre un seudo-constructor, pero que no
es necesario para un registro. Todo lo que necesitamos es la
forma de iniciar el objeto interno, posiblemente llamando a su
constructor, la primera vez que lo usemos. No podemos com-
probar si el objeto interno está Assigned, (asignado), porque
los registros (a diferencia de las clases) no se inicialicen con
cero. Sin embargo, podemos realizar esta prueba en la varia-
ble de la interfaz, que si se ha iniciado.
El código adicional de un tipo de registro de puntero inteli-
gente es un procedimiento Create (no puede ser un construc-
tor, ya que no son legales los constructores desparametrizados
para los registros) y un iniciador perezoso de la propiedad Va-
lue:
procedure TSmartPointer<T>.Create;
begin
Create (T.Create);
end;

function TSmartPointer<T>.GetValue: T;
begin
if not Assigned(FFreeTheValue) then
Create;
Result := FValue;
end;

La guía de Delphi por Marco Cantù


218 - Indice

Con este código tenemos ahora muchas formas de utilizar el


puntero inteligente, incluyendo el hecho de no liberarlo y ni
siquiera crearlo de forma explícita:
var
smartP: TSmartPointer<TStringList>;
begin
smartP.Value.Add('foo');
Log ('Count: ' + IntToStr (smartP.Value.Count));
end;

El hecho de que el método anterior cree una lista de cadenas y


se libere al final, parece sin duda un gran punto de partida del
modelo estándar de codificación que utilizan los desarrollado-
res de Delphi. Y esto es sólo un caso específico del uso de ge-
néricos para no usar colecciones de código. Para finalizar este
apartado, sin embargo, permítanme listar el código fuente
completo del registro genérico de punteros inteligentes que he
construido en varias iteraciones:
type
TSmartPointer<T: class, constructor> = record
strict private
FValue: T;
FFreeTheValue: IInterface;
function GetValue: T;
private
type
TFreeTheValue = class (TInterfacedObject)
private
fObjectToFree: TObject;
public
constructor Create(anObjectToFree: TObject);
destructor Destroy; override;
end;
public
constructor Create(AValue: T); overload;
procedure Create; overload;
class operator Implicit(AValue: T): TSmartPointer<T>;
class operator Implicit(smart: TSmartPointer <T>): T;
property Value: T read GetValue;
end;

El código completo y algunos de los patrones de uso mencio-


nados en esta sección están en el proyecto SmartPointers.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 219

A continuación
Después de cubrir los cambios en el tipo de cadena, en este
capítulo hemos empezado a explorar otra importante y nueva
característica del compilador de Delphi 2009, el soporte de
los tipos genéricos. Incluso si el capítulo que estamos aca-
bando es bastante largo, solamente hemos empezado a arañar
la superficie de lo que puede considerarse un nuevo para-
digma de programación para los desarrolladores Delphi.
Pero hay más: los métodos anónimos, tratados en el siguiente
capítulo, nos proporcionan otro cambio de paradigma, y se
pueden utilizar junto con los genéricos como ya he anticipado
en este capítulo. Tienen un papel muy útil en aplicaciones
multi-hilo y en muchos otros casos, como veremos en el pró-
ximo capítulo. Ciertamente Delphi 2009 no es una actualiza-
ción aburrida para aquellos que son, como yo, unos apasiona-
dos por los lenguajes de programación en general y del len-
guaje Object Pascal en particular.

La guía de Delphi por Marco Cantù


220 - Indice

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 221

Capítulo 6:
Métodos
Anónimos

Desde hace mucho tiempo, el lenguaje Delphi tiene tipos de


procedimiento (es decir, tipos que declaran punteros a proce-
dimientos y funciones91) y métodos puntero (es decir, tipos
que declaran punteros a métodos). Aunque puede ser que
rara vez usted los haya utilizado directamente, éstas son las
características clave con las que trabaja cada desarrollador de
Delphi. De hecho, los tipos punteros a métodos son la base
para los manipuladores de eventos de la VCL: cada vez que
declara un manipulador para un evento, incluso para un sim-
ple Button1Click usted de hecho está declarando un método
que se conectará a un evento (en este caso, el evento OnClick)
utilizando un método puntero.

91Enel caso de que quiera obtener más información sobre los tipos de procedi-
miento esta se recoge en el capítulo 6 de “Essential Pascal”, 4ª edición; los
tipos de punteros a método se describen en los libros de mi serie Mastering
Delphi.

La guía de Delphi por Marco Cantù


222 - Indice

Los métodos Anónimos extienden esta característica permi-


tiéndole pasar un código determinado a un método como un
parámetro, en lugar de como el nombre de un método defi-
nido en otro lugar. Esta no es, sin embargo, la única diferen-
cia. Lo que hace muy distinto los métodos anónimos a otras
técnicas, es la forma en que gestiona la vida de las variables
locales.
La definición anterior iguala la característica llamada “closu-
res” de otros lenguajes, como por ejemplo JavaScript. Si los
métodos anónimos de Delphi , son de hecho “closures",
¿Cómo es que Embarcadero se refiere a ellos usando un tér-
mino diferente? La razón es que C++ Builder ha estado utili-
zando el término “closures" para lo que llamamos en Delphi
manipuladores de eventos, así que nos encontraríamos con
una característica diferente con el mismo nombre, y esto po-
dría crear confusiones. Además, el lenguaje C# utiliza la ex-
presión métodos anónimos para un mecanismo similar al de
Delphi, por lo que tiene sentido utilizar el mismo apodo.
Si los métodos anónimos son un tipo de característica nueva
para Delphi, han girado alrededor durante muchos años en
diferentes formas y con diferentes nombres en otros lenguajes
de programación, lenguajes notablemente más dinámicos. He
tenido una amplia experiencia con los “closures” en Ja-
vaScript, en particular con la librería jQuery92 y la utilización
de AJAX. La correspondiente función en C# se llama, un dele-
gado anónimo.
Pero no quiero dedicar más tiempo a comparar los “closures”
y sus técnicas relacionadas con los diversos lenguajes de pro-
gramación, sino más bien describir en detalle cómo funcionan
estos en Delphi 2009.

92Más información sobre la librería jQuery de JavaScript en http://

jQuery.org La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 223

Sintaxis y Semántica de
los Métodos Anónimos
Un método anónimo en Delphi es un mecanismo para crear
un valor de método en un contexto93. Una definición bastante
críptica, pero que resume con mucha precisión y subraya la
diferencia clave con los punteros a métodos, el contexto de ex-
presión. Antes de llegar a este, sin embargo, permítanme que
empiece desde el principio con un sencillo ejemplo de código
(incluido en el proyecto AnonymFirst junto con la mayoría de
los demás de esta sección).
Esta es la declaración de un tipo de método anónimo, algo que
usted necesita ya que Delphi sigue siendo un lenguaje fuerte-
mente tipado:
type
TIntProc = reference to procedure (n: Integer);

Esto se diferencia del tipo de referencia a un método sólo en


las palabras clave que se utilizan para la declaración:
type
TIntMethod = procedure (n: Integer) of object;

Una Variable de Método Anónimo


Una vez que tenga un tipo de método anónimo usted puede,
en los casos más sencillos, declarar una variable de este tipo,
asignando un método anónimo con seguridad de tipo, y lla-
mar al método a través de esta variable:
procedure TFormAnonymFirst.btnSimpleVarClick(

93Enlas palabras del implementador de los métodos anónimos Barry Kelly del
I+D de Embarcadero, http://barrkel.blogspot.com.

La guía de Delphi por Marco Cantù


224 - Indice

Sender: TObject);
var
anIntProc: TIntProc;
begin
anIntProc :=
procedure (n: Integer)
begin
Memo1.Lines.Add (IntToStr (n));
end;
anIntProc (22);
end;

Compruebe la sintaxis utilizada para asignar el procedimiento


actual, con un código in-situ, a la variable. Esto es algo que no
se había visto nunca antes en Pascal.

Un Parámetro de Método
Anónimo
Como un ejemplo más interesante (con una sintaxis aún más
sorprendente), podemos enviar un método anónimo como pa-
rámetro a una función. Suponga que tiene una función to-
mando un método anónimo como parámetro:
procedure CallTwice (value: Integer;
anIntProc: TIntProc);
begin
anIntProc (value);
Inc (value);
anIntProc (value);
end;

El procedimiento llama al método pasado como parámetro


dos veces con dos valores enteros consecutivos, el uno pasado
como un parámetro y el otro como su consecutivo. Usted
llama a la función pasándole el actual método anónimo, di-
rectamente con el código in-situ, lo que parece sorprendente:
procedure TFormAnonymFirst.btnProcParamClick(
Sender: TObject);
begin
CallTwice (48,
procedure (n: Integer)
begin

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 225

Memo1.Lines.Add (IntToHex (n, 4));


end);
CallTwice (100,
procedure (n: Integer)
begin
Memo1.Lines.Add (FloatToStr(Sqrt(n)));
end);
end;

Desde el punto de vista de la sintaxis, vea como el procedi-


miento se pasa como un parámetro entre los paréntesis y no
termina con un punto y coma. El hecho, es que el código tiene
que llamar a los IntToHex con 48 y 49 y el FloatToStr sobre la
raíz cuadrada de 100 y 101, que produce el resultado si-
guiente:
0030
0031
10
10.0498756211209

Usando Variables Locales


Podríamos haber logrado el mismo efecto usando punteros de
método aunque con una sintaxis diferente y menos legible. Lo
qué hace al método anónimo claramente diferente es la forma
en la que puede referirse a las variables locales del método de
llamada. Considere el siguiente código:
procedure TFormAnonymFirst.btnLocalValClick(
Sender: TObject);
var
aNumber: Integer;
begin
aNumber := 0;
CallTwice (10,
procedure (n: Integer)
begin
Inc (aNumber, n);
end);
Memo1.Lines.Add (IntToStr (aNumber));
end;

La guía de Delphi por Marco Cantù


226 - Indice

En este caso, el método aún pasa por el procedimiento Call-


Twice utilizando los parámetros locales n, pero también una
variable local del contexto en el que se le ha llamado, aNumber.
¿Cuál es el efecto? Las dos llamadas al método anónimo mo-
dificarán la variable local, añadiendo el parámetro a esta, 10
en el primer momento y 11 en el segundo. El valor final de
aNumber será 21.

Extendiendo la Vida de las


Variables Locales
El ejemplo anterior muestra un interesante efecto, pero con
una secuencia de llamadas anidadas a funciones, el hecho de
que pueda utilizar la variable local no es tan sorprendente. El
poder de los métodos anónimos, sin embargo, radica en el he-
cho de que pueden utilizar una variable local y también am-
pliar su vida útil según sea necesario94. Un ejemplo nos de-
muestra este punto mejor que una larga explicación.
He añadido (utilizando una extensión de clase), al formulario
de clase TFormAnonymFirst del ejemplo AnonymFirst la propie-
dad de un método anónimo de tipo puntero (bueno, el mismo
método anónimo de tipo puntero que he usado en todo el có-
digo del proyecto):
private
FAnonMeth: TIntProc;
procedure SetAnonMeth(const Value: TIntProc);
public
property AnonMeth: TIntProc
read FAnonMeth write SetAnonMeth;

94Unos pocos detalles técnicos más, los métodos anónimos copian las variables y
los parámetros que utilizan para el almacenamiento dinámico cuando se
crean, y los mantienen vivos, mientras dure la instancia específica de este
método anónimo.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 227

Entonces he añadido al formulario del programa dos botones


más. El primero guarda la propiedad de un método anónimo
que utiliza una variable local (más o menos como en el mé-
todo anterior btnLocalValClick):
procedure TFormAnonymFirst.btnStoreClick(
Sender: TObject);
var
aNumber: Integer;
begin
aNumber := 3;
AnonMeth :=
procedure (n: Integer)
begin
Inc (aNumber, n);
Memo1.Lines.Add (IntToStr (aNumber));
end;
end;

Cuando este método se ejecuta, el método anónimo no es eje-


cutado, sólo es almacenado. La variable local aNumber es ini-
ciada a tres, no se modifica, y sale del ámbito local (cuando el
método se termine), y al final es desplazada. Esto es, como
mínimo, lo que usted esperaría de un código estándar de Del-
phi.
El segundo botón, que he añadido al formulario para este
paso específico, llama al método anónimo almacenado en la
propiedad AnonMeth:
procedure TFormAnonymFirst.btnCallClick(Sender: TObject);
begin
if Assigned (AnonMeth) then
begin
CallTwice (2, AnonMeth);
end;
end;

Cuando este código se ejecuta, llama a un método anónimo


que utiliza la variable local aNumber de un método que no esta
ya en la pila de llamadas. Sin embargo, desde que los métodos
anónimos capturan su contexto de ejecución, la variable está
todavía allí y puede utilizarse siempre que esta instancia dada

La guía de Delphi por Marco Cantù


228 - Indice

de este método anónimo (es decir, una referencia al método)


esté vigente.
Como una prueba más, haga lo siguiente. Pulse el botón Store
una vez, el botón llamará dos veces y verá usted como la
misma variable capturada está siendo utilizada95:
5
8
10
13

Ahora presione Store una vez más y pulse Call de nuevo. ¿Qué
es lo que pasa, cuál es el valor de la variable local restaurada?
Mediante la asignación de una nueva instancia del método
anónimo, el viejo método anónimo se suprime (junto con su
propio contexto de ejecución) y un nuevo contexto de ejecu-
ción es capturado, incluyendo una nueva instancia de la varia-
ble local. La secuencia completa Store - Call - Call – Store-
Call produce:
5
8
10
13
5
8

Esto es la consecuencia de este comportamiento, parecido a lo


que hacen algunos otros lenguajes, esto es lo que hace que los
métodos anónimos sean una característica del lenguaje extre-
madamente potente, que usted puede utilizar para poner en
práctica algo que en el pasado simplemente no era posible.

95La razón de esta secuencia es que el valor empieza en 3, cada llamada a


CallTwice pasó su parámetro a un método anónimo una primera vez (esta
es la 2) y entonces, una segunda vez, después de incrementarla (es decir, la
segunda vez pasa 3).

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 229

Más sobre los Métodos


Anónimos
Si la captura de una variable es una característica de las más
relevantes para los métodos anónimos, existen algunas técni-
cas más que valen la pena mirar, antes de centrarnos en algu-
nos ejemplos del mundo real.

El (Potencialmente) Paréntesis
Perdido
Observe como en el código anterior he utilizado el símbolo
AnonMeth para referirme al método anónimo, y no para invo-
carlo. Para invocarlo, debería haber escrito:
AnonMeth (2)

La diferencia es clara; tengo que pasar el parámetro adecuado


para invocar el método. Las cosas son un poco más confusas
con los métodos anónimos desparametrizados. Si se declara:
type
TAnyProc = reference to procedure;
var
AnyProc: TAnyProc;

La llamada a AnyProc debe ir seguida por un paréntesis vacío,


de lo contrario el compilador cree que usted está tratando de
obtener el método (su dirección) en vez de llamarlo:
AnyProc ();

Algo similar ocurre cuando se llama a una función que de-


vuelve un método anónimo, como en el caso siguiente to-
mado del habitual ejemplo AnonymFirst:
function GetShowMethod: TIntProc;
var

La guía de Delphi por Marco Cantù


230 - Indice

x: Integer;
begin
x := Random (100);
ShowMessage ('New x is ' + IntToStr (x));
Result :=
procedure (n: Integer)
begin
x := x + n;
ShowMessage (IntToStr (x));
end;
end;

La cuestión que surge ahora es ¿Cómo se invoca? Si simple-


mente llama
GetShowMethod;

Se compila y se ejecuta, pero lo único que hace es llamar el có-


digo de asignación del método anónimo, desperdiciando el
método anónimo devuelto por la función.
¿Cómo llamar al método anónimo real pasándole un paráme-
tro? Una opción es utilizar una variable temporal de método
anónimo:
var
ip: TIntProc;
begin
ip := GetShowMethod();
ip (3);

Compruebe en este caso, el paréntesis después de la llamada


GetShowMethod. Que si lo omite (una práctica estándar en Pas-
cal) obtendrá el siguiente error:
E2010 Incompatible types: 'TIntProc' and 'Procedure'

Sin el paréntesis el compilador cree que usted desea asignarle


la función propia GetShowMethod y no su resultado al puntero
de método ip. Sin embargo, utilizar una variable temporal po-
dría no ser la mejor opción en este caso, ya que convierte el
código en uno más complejo de una forma no natural. Una
simple llamada:
GetShowMethod(3);

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 231

no compila, ya que no puede pasar un parámetro al método.


Usted necesita añadir el paréntesis vacío a la primera lla-
mada, y el parámetro entero a la resultante del método anó-
nimo. Por extraño que parezca, puede escribir:
GetShowMethod()(3);

Una solución alternativa es utilizar la implementación interna


de métodos anónimos, y llamar al método Invoke a bajo-nivel,
que queda añadido por el compilador (en cuyo caso, puede
omitir el paréntesis vacío):
GetShowMethod.Invoke (3);

Detrás de los Métodos Anónimos


¿Qué es esto del método Invoke? ¿Qué sucede entre bastido-
res al implementar métodos anónimos? El código real gene-
rado por el compilador para los métodos anónimos se basa en
las interfaces, con una única invocación al método llamado
Invoke, además del habitual soporte del recuento de referencia
(que es útil para determinar la vida útil de los métodos anóni-
mos y el contexto que capturan).
Usted puede ver los métodos de interfaz en el editor si utiliza
el completado de código, de la siguiente manera:

Obtener los detalles de las interioridades probablemente sea


muy complicado y de valor limitado. Baste decir que la imple-
mentación es muy eficiente, en términos de velocidad, y re-
quiere alrededor de 500 bytes extras por cada método anó-
nimo.

La guía de Delphi por Marco Cantù


232 - Indice

En otras palabras, una referencia de método en Delphi se eje-


cuta con un interface de un solo método especial96 con un mé-
todo generado por el compilador que tiene la misma firma
que el método de referencia que se pone en practica. La inter-
faz utiliza todas las reglas COM y se aprovecha del recuento
de referencias para su desactivación automática. Además de
esta interfaz oculta, por cada invocación de un método anó-
nimo el compilador crea un objeto oculto que tiene la imple-
mentación del método y los datos necesarios para capturar el
contexto de su invocación Así es como usted consigue un
nuevo conjunto de variables capturadas para cada llamada al
método.

Tipos de Referencia Listos Para


Usar
Cada vez que utilice un método anónimo como un parámetro,
lo que usted necesita, es definir el correspondiente puntero de
referencia para el tipo de datos. Para evitar la proliferación de
tipos locales, Delphi ofrece una serie de referencias listas-
para-usar de tipos de puntero en la unidad SysUtils. Como
puede ver en el siguiente fragmento de código, la mayoría de
estas definiciones utilizan tipos parámetrizados, de modo que
con una sola declaración genérica usted tiene diferentes tipos
de referencias de puntero para cada tipo de datos posible:
type
TProc = reference to procedure;
TProc<T> = reference to procedure (Arg1: T);
TProc<T1,T2> = reference to procedure (
Arg1: T1; Arg2: T2);

96Si bien prácticamente la interfaz utilizada para un método anónimo se parece a


cualquier otro interfaz, el compilador distingue entre estos interfaces espe-
ciales de modo que no puede mezclarse en código.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 233

TProc<T1,T2,T3> = reference to procedure (


Arg1: T1; Arg2: T2; Arg3: T3);
TProc<T1,T2,T3,T4> = reference to procedure (
Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4);

Utilizando estas declaraciones, puede definir procedimientos


que tomen parámetros de métodos anónimos como en:
procedure UseCode (proc: TProc);
function DoThis (proc: TProc): string;
function DoThat (procInt: TProc<Integer>): string;

En el primer y segundo caso, se pasa un método anónimo sin


parámetro, en el tercero se pasa un método con un solo pará-
metro Integer:
UseCode (
procedure
begin
...
end);
strRes := DoThat (
procedure (I: Integer)
begin
...
end);

Del mismo modo, la unidad SysUtils define un conjunto de ti-


pos de métodos anónimos, con un valor de retorno genérico:
type
TFunc<TResult> = reference to function: TResult;
TFunc<T,TResult> = reference to function (
Arg1: T): TResult;
TFunc<T1,T2,TResult> = reference to function (
Arg1: T1; Arg2: T2): TResult;
TFunc<T1,T2,T3,TResult> = reference to function (
Arg1: T1; Arg2: T2; Arg3: T3): TResult;
TFunc<T1,T2,T3,T4,TResult> = reference to function (
Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4): TResult;
TPredicate<T> = reference to function (
Arg1: T): Boolean;

Estas definiciones son muy amplias, para que usted pueda


utilizar un sinnúmero de combinaciones de tipos de datos de
hasta cuatro parámetros de entrada y un tipo de retorno. La
última definición es muy similar a la segunda, pero corres-
ponde a un caso específico que es muy frecuente, teniendo

La guía de Delphi por Marco Cantù


234 - Indice

una función con parámetro genérico y devolviendo un boo-


leano.

Métodos Anónimos en el
Mundo Real
A primera vista, no es fácil de comprender plenamente el po-
der de los métodos anónimos y los escenarios que su uso
puede beneficiarnos. Esa es la razón por la que, en lugar de
seguir con más enrevesados ejemplos que abarquen el len-
guaje, decidí centrarme en algunos que tienen un impacto
práctico y proporcionan puntos de partida para una explora-
ción más profunda.

Gestores De Eventos Anónimos


Desde sus primeros días, uno de los rasgos distintivos de Del-
phi ha sido su implementación de manipuladores de eventos
(Handlers) usando punteros de método. Ahora, con el adve-
nimiento de los métodos anónimos, podría ser interesante
utilizar esta característica para adjuntar un nuevo comporta-
miento a un evento sin tener que declarar un método sepa-
rado, capturando su contexto de ejecución, evitando así la adi-
ción de campos adicionales a un formulario para pasar pará-
metros de un método a otro.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 235

Como ejemplo, he añadido un clic anónimo al evento de un


botón, declarando un tipo puntero de método propio y aña-
diendo un nuevo manipulador de eventos a una clase de botón
personalizado (definido como una clase interceptor):
type
TAnonNotif = reference to procedure (Sender: TObject);

// interceptor class
TButton = class (StdCtrls.TButton)
private
FAnonClick: TAnonNotif;
procedure SetAnonClick(const Value: TAnonNotif);
public
procedure Click; override;
public
property AnonClick: TAnonNotif
read FAnonClick write SetAnonClick;

end;
El código de esta clase es bastante simple, ya que el método
Setter guarda el nuevo puntero, y el método Click lo llama an-
tes de hacer el procesamiento estándar (es decir, llamando al
gestor del evento OnClick si este está disponible):
procedure TButton.SetAnonClick(const Value: TAnonNotif);
begin
FAnonClick := Value;
end;

procedure TButton.Click;
begin
if Assigned (FAnonClick) then
FAnonClick (self)

97Uninterceptor de clase es una clase derivada que tiene el mismo nombre que
su clase base. Que haya dos clases con el mismo nombre es posible porque
están en diferentes unidades, por lo que su nombre completo (Nombre-
DeUnidad.NombreDeClase) es diferente. Declarar un interceptor de clase
puede ser útil, así puede colocar simplemente un botón en el formulario y
adjuntarle un comportamiento adicional, sin tener que instalar un nuevo
componente en el IDE y reemplazar los controles de su formulario con el
nuevo tipo. El único truco que tiene que recordar es que si la definición del
interceptor de clase se encuentra en una unidad independiente (no la unidad
del formulario como en este ejemplo), esta unidad tiene que estar inscrita en
los estamentos de uses después de haber definido la unidad de la clase base.

La guía de Delphi por Marco Cantù


236 - Indice

inherited;

end;
¿Cómo puede usar este nuevo manipulador de eventos? Bási-
camente puede asignarle un método anónimo

procedure TFormAnonButton.btnAssignClick(
Sender: TObject);
begin
btnInvoke.AnonClick :=
procedure (Sender: TObject)
begin
ShowMessage ((Sender as TButton).Caption);
end;
end;

Ahora esto parece más bien inútil, ya que el mismo efecto


puede realizarse fácilmente utilizando un método estándar
como manipulador de eventos. Lo siguiente, en cambio, co-
mienza a establecer una diferencia, ya que el método anónimo
captura una referencia al componente que se asigna el mani-
pulador de evento, haciendo referencia al parámetro Sender.
Esto se puede hacer después de la asignación temporal a una
variable local, ya que el parámetro Sender del método anó-
nimo esconde el parámetro Sender del método btnKeepRef-
Click:
procedure TFormAnonButton.btnKeepRefClick(
Sender: TObject);
var
aCompRef: TComponent;
begin
aCompRef := Sender as TComponent;
btnInvoke.AnonClick :=
procedure (Sender: TObject)
begin
ShowMessage ((Sender as TButton).Caption +
' assigned by ' + aCompRef.Name);
end;
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 237

Mientras presiona el botón btnInvoke verá su “Caption” junto


con el nombre del componente que asigna el handler del mé-
todo anónimo:

Una situación aún más complicada queda demostrada por


otros dos botones, que reciben un manipulador de métodos
anónimos haciendo clic sobre el formulario con el botón iz-
quierdo o derecho del ratón, capturando la posición del pun-
tero del ratón:
procedure TFormAnonButton.FormMouseDown(
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
btnLeftInvokeForm.AnonClick :=
procedure (Sender: TObject)
begin
(Sender as TButton).Caption :=
'Last left on [' + IntToStr (X) +
',' + IntToStr (Y) + ']';
end
else
btnRightInvokeForm.AnonClick :=
procedure (Sender: TObject)
begin
(Sender as TButton).Caption :=
'Last right on [' + IntToStr (X) +
',' + IntToStr (Y) + ']';
end;
end;

Este es otro ejemplo de la captura del contexto de ejecución,


pero esta vez puede haber dos operaciones de captura al
mismo tiempo (cada una con sus propios datos activos en la
memoria), y podría haber incluso más de dos. Para aplicar la
misma operación con el método de punteros usted lo que
tiene que mantener es un array de clics de ratón vinculados a

La guía de Delphi por Marco Cantù


238 - Indice

cada componente de destino o usar la propiedad Tag de cada


componente para almacenar un puntero a esta información.

Cronometraje de Métodos
Anónimos
Con frecuencia los desarrolladores añaden código a las rutinas
existentes para comparar su velocidad relativa. Hice lo mismo
varias veces en los ejemplos de la Parte I del libro, para averi-
guar la velocidad de las cadenas Unicode. Suponiendo que
tiene dos fragmentos de código y que desea comparar su velo-
cidad ejecutando estas unos pocos millones de veces, usted
podría escribir lo siguiente (tomado del ejemplo StringCon-
vert del capítulo 2 y comentado en la sección “Conversión de
Cadenas”):
procedure TFormAnonTiming.btnClassicClick(
Sender: TObject);
var
str1: string;
str2: AnsiString;
I: Integer;
t1: TDateTime;
begin
str1 := 'Marco Cantù';
t1 := Now;
for I := 1 to MaxLoop2 do
str1 := AnsiUpperCase (str1);
t1 := now - t1;
Memo1.Lines.Add ('AnsiUpperCase (string): ' +
FormatDateTime('nn:ss.zzz', t1));

str2 := 'Marco Cantù';


t1 := Now;
for I := 1 to MaxLoop2 do
str2 := AnsiUpperCase (str2);
t1 := now - t1;
Memo1.Lines.Add ('AnsiUpperCase (AnsiString): ' +
FormatDateTime('nn:ss.zzz', t1));
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 239

En lugar de repetir el código de cronometrado una y otra vez,


usted puede escribir una función con el código de temporiza-
ción e invocar este fragmento de código a través de un método
anónimo sin parámetros:
function TimeCode (nLoops: Integer; proc: TProc): string;
var
t1: TDateTime;
I: Integer;
begin
t1 := Now;
for I := 1 to nLoops do
proc;
t1 := now - t1;
Result := FormatDateTime('nn:ss.zzz', t1);
end;

procedure TFormAnonTiming.btnAnonClick(Sender: TObject);


var
str1: string;
str2: AnsiString;
begin
str1 := 'Marco Cantù';
Memo1.Lines.Add ('AnsiUpperCase (string): ' +
TimeCode (MaxLoop2,
procedure ()
begin
str1 := AnsiUpperCase (str1);
end));

str2 := 'Marco Cantù';


Memo1.Lines.Add ('AnsiUpperCase (AnsiString): ' +
TimeCode (MaxLoop2,
procedure ()
begin
str2 := AnsiUpperCase (str2);
end));
end;

En el ejemplo del código también encontrará una versión un


poco mejorada (y más precisa) que utiliza GetTickCount en
lugar de la hora actual (Now), aunque si lo que busca es un cro-
nometrado realmente preciso, sería mejor usar el calendario
de servicios específicos ofrecidos por la API de Windows Que-
ryPerformanceCounter.

La guía de Delphi por Marco Cantù


240 - Indice

Observe, sin embargo, que si se ejecuta la versión estándar y


la basada en métodos anónimos obtendrá una salida como la
siguiente:
Classic
AnsiUpperCase (string): 00:00.588
AnsiUpperCase (AnsiString): 00:01.087
Anonymous
AnsiUpperCase (string): 00:00.644
AnsiUpperCase (AnsiString): 00:01.153

Como puede comprobar, la versión de método anónimo sufre


una penalización de aproximadamente un 8%. La razón es
que en lugar de ejecutar directamente el código local, el pro-
grama tiene que hacer una invocación virtual a la implemen-
tación del método anónimo. Aunque esta diferencia es consis-
tente, el código de prueba tiene perfecto sentido de todos mo-
dos. Sin embargo, si usted necesita exprimir el rendimiento
de su código, usar un método anónimo no será tan rápido
como escribir directamente el código, usando una función di-
recta. Utilizando un puntero a método probablemente esté en
algún lugar intermedio, en términos de rendimiento.

Sincronización de Procesos con


la VCL
En las aplicaciones multi-hilo que necesiten actualizar la in-
terfaz de usuario, usted no puede acceder a las propiedades de
los componentes visuales (o de objetos en memoria) que for-
man parte del hilo global, sin un mecanismo de sincroniza-
ción. La VCL, de hecho, no es un hilo de seguridad (como es el
caso de la mayoría de bibliotecas de la interfaz de usuario).
Dos hilos que accedan a un objeto, al mismo tiempo, podrían
comprometer su estado.
La clásica solución ofrecida por la clase TThread en Delphi es
la llamada de un método especial, Synchronize, pasando
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 241

como parámetro la referencia a otro método, el que se va a


ejecutar en condiciones de seguridad. Este segundo método
no puede tener parámetros, por lo que es una práctica común
agregar campos extras a la clase de hilo para pasar la informa-
ción de un método al otro.
Como ejemplo práctico, en el libro “Mastering Delphi 2005”,
escribí un ejemplo WebFind (un programa que ejecuta bús-
quedas en Google a través de HTTP y extrae los vínculos re-
sultantes de la página HTML), con las siguientes clases de
procesos Thread:
type
TFindWebThread = class(TThread)
protected
Addr, Text, Status: string;
procedure Execute; override;
procedure AddToList;
procedure ShowStatus;
procedure GrabHtml;
procedure HtmlToList;
procedure HttpWork (Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
public
strUrl: string;
strRead: string;
end;

Los tres campos string de la sección protected, y algunos de


los métodos extras, han sido introducidos para soportar la
sincronización con la interfaz del usuario. Por ejemplo, el ges-
tor de evento HttpWork conectado a un evento de un objeto in-
terno IdHttp (un componente Indy soportando el lado cliente
del protocolo HTTP), utilizado para tener el siguiente código,
que llama al método ShowStatus:
procedure TFindWebThread.HttpWork(Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
begin
Status := 'Received ' + IntToStr (AWorkCount) +
' for ' + strUrl;
Synchronize (ShowStatus);
end;

procedure TFindWebThread.ShowStatus;

La guía de Delphi por Marco Cantù


242 - Indice

begin
Form1.StatusBar1.SimpleText := Status;
end;

En Delphi 2009, el método Synchronize tiene dos diferentes


definiciones sobrecargadas:
type
TThreadMethod = procedure of object;
TThreadProcedure = reference to procedure;

TThread = class
...
procedure Synchronize(
AMethod: TThreadMethod); overload;
procedure Synchronize(
AThreadProc: TThreadProcedure); overload;

Por esta razón, podemos eliminar el campo de texto Status y


la función ShowStatus, y rescribir el gestor de evento HttpWork
utilizando la nueva versión de Synchronize y un método anó-
nimo:
procedure TFindWebThreadAnon.HttpWork(Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
begin
Synchronize (
procedure
begin
Form1.StatusBar1.SimpleText :=
'Received ' + IntToStr (AWorkCount) +
' for ' + strUrl;
end);
end;

Utilizando el mismo enfoque en todo el código de la clase, el


hilo de la clase se convierte en lo siguiente (puede encontrar
dos clases de hilo en la versión del ejemplo WebFind que
viene con el código fuente de este libro):
type
TFindWebThreadAnon = class(TThread)
protected
procedure Execute; override;
procedure GrabHtml;
procedure HtmlToList;
procedure HttpWork (Sender: TObject;
AWorkMode: TWorkMode; AWorkCount: Int64);
public
strUrl: string;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 243

strRead: string;
end;

Aquí tenemos otro método en su versión actualizada, esta


parte realiza el análisis del código HTML y extrae las direc-
ciones URL y su descripción de los links a un ListBox:
procedure TFindWebThreadAnon.HtmlToList;
var
strAddr, strText: string;
nText: integer;
nBegin, nEnd: Integer;
begin
Synchronize (
procedure
begin
Form1.StatusBar1.SimpleText :=
'Extracting data for: ' + StrUrl;
end);

strRead := LowerCase (strRead);


nBegin := 1;
repeat
// encontrar la parte inicial de referencia de HTTP
nBegin := PosEx ('href="http', strRead, nBegin);
if nBegin <> 0 then
begin
// encuentre el final de la etiqueta href (cierre
comillas)
nBegin := nBegin + 6;
nEnd := PosEx ('"', strRead, nBegin);
strAddr := Copy (strRead, nBegin, nEnd - nBegin);

// pasar
nBegin := PosEx ('>', strRead, nEnd) + 1;
// añadir la URL si "google" no es
if Pos ('google', strAddr) = 0 then
begin
nText := PosEx ('</a>', strRead, nBegin);
strText := copy (strRead, nBegin, nText - nBegin);
// eliminar caché y las referencias duplicadas
if (Pos ('cache', strText) = 0) then
begin
Synchronize (
procedure
begin
if Form1.ListBox1.Items.IndexOf (
strAddr) < 0 then
begin
Form1.ListBox1.Items.Add (strAddr);
Form1.DetailsList.Add (strText);
end;

La guía de Delphi por Marco Cantù


244 - Indice

end);
end;
end;
end;
until nBegin = 0;
end;

Creo que esto demuestra cómo, utilizando los métodos anóni-


mos, se simplifica el código necesario para la sincronización
de los hilos.

Bucle For Paralelo


Una de las principales razones que hay detrás de la introduc-
ción de los métodos anónimos en Delphi 2009, es el deseo de
crear una librería paralela, una colección de técnicas que per-
mitan que las aplicaciones aprovechen las CPUs multi-núcleo
que cualquier equipo moderno tiene hoy en día. En lugar de
manejar a mano el código de aplicaciones con subprocesos
múltiples, esta biblioteca le permitiría escribir un código casi
estándar que cree los hilos necesarios entre bastidores.
Esta librería es el tema de muchos blogs del arquitecto jefe de
Delphi Allen Bauer y otros miembros del equipo I+D. Está in-
cluida en la lista de las características que se esperan en la
próxima versión de Delphi, y en el plan de trabajo actual del
producto. Mientras tanto, los métodos anónimos le permiten
comenzar a experimentar en esta dirección. Aquí le presento
una versión simplificada de parallel for loop (esta versión
funciona, pero en general no es suficiente y no está completa-
mente libre de pequeños problemas). He visto soluciones más
completas, pero su código es demasiado complejo para discu-
tirlo aquí.
¿Qué es un parallel for? Se trata de un bucle for procesado en
paralelo por múltiples subprocesos, encargándose cada hilo

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 245

de una parte de las iteraciones. Empecemos por mirar el có-


digo fuente original, que utiliza una implementation98 terri-
blemente lenta de una función para calcular si un número es
un número primo:
const
Max = 50000;

procedure TFormParallelFor.btnPlainClick(
Sender: TObject);
var
I, Tot: Integer;
Ticks: Integer;
begin
// cuenta con los nº primos por debajo de un valor dado
Tot := 0;
Ticks := GetTickCount;
for I := 1 to Max do
begin
if IsPrime (I) then
Inc (Tot);
Application.ProcessMessages;
end;
Ticks := GetTickCount - Ticks;
Memo1.Lines.Add (Format (
'No threads: %d - %d', [Ticks, Tot]));
end;

Ahora sería bonito envolver el núcleo del método de este ciclo


o ‘loop’ (y la salida final del tiempo total) en distintos métodos
anónimos que se pasan a la función ParallelFor:
procedure TFormParallelFor.btnParallel2Click(
Sender: TObject);
var
Tot: Integer;
Ticks: Cardinal;
begin
Tot := 0;
Ticks := GetTickCount;
ParallelFor (1, Max, 2,
procedure (I: Integer)

98Estahecho a propósito, para ralentizar el código. Las funciones se vuelven más


lentas a medida que el número de pruebas crece. Esta es la razón por la cual
dividir el rango del nº de procesos en dos, la mitad para cada hilo no funcio-
nará: El hilo que trabaja con números más altos empleará más tiempo que el
otro.

La guía de Delphi por Marco Cantù


246 - Indice

begin
if IsPrime (I) then
InterlockedIncrement (Tot);
end);
Ticks := GetTickCount - Ticks;
Memo1.Lines.Add (Format (
'2 threads: %d - %d', [Ticks, Tot]));
end;

La llamada es casi idéntica. He sustituido el bucle for por la


llamada ParallelFor eliminado la llamada Application.Pro-
cessMessages no necesaria para un hilo), y sustituido Inc por
InterlockedIncrement de forma que los múltiples subprocesos
pueden acceder al valor global (el valor capturado por el mé-
todo anónimo) al mismo tiempo exacto.
La llamada a ParallelFor pasa el límite superior e inferior del
bucle, el número de hilos a usar, y el código real a ejecutar. En
este caso, el programa utilizará dos hilos, pero hay otros boto-
nes en el ejemplo solicitando un solo hilo o cuatro de estos. El
código último al final del método se ejecuta cuando el bucle se
haya terminado, porque ParallelFor espera a todos los hilos
que ha generado.
Antes de examinar la puesta en práctica real de mi simple Par-
allelFor, vamos a ver si ayuda de algún modo. He ejecutado el
programa en mi portátil de doble núcleo, en la versión clásica,
y con 1, 2 ó 4 hilos, calculando el número de primos por de-
bajo de 50.000. Aquí puedes ver la versión, el número de ci-
clos / milisegundos, y el resultado real:
No threads: 1514 - 5134
1 thread: 1544 - 5134
2 threads: 889 - 5134
4 threads: 1029 - 5134

Como era de esperar, la versión con un hilo tiene una pequeña


sobrecarga, pero ejecutando dos hilos tarda casi la mitad del
tiempo, mientras que el aumento del número de hilos añade
los gastos generales y de recursos adicionales de contención,
con lo que empeora el resultado (pero aún es mejor que la

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 247

versión original). En otras palabras, esto es un gran esfuerzo


pero tiene sentido, porque ParallelFor en el caso óptimo, per-
mite ganar más del 40% de los 1,5 segundos de la versión
original.
Aquí está el código de la función ParallelFor:
procedure ParallelFor (nMin, nMax, nThreads: Integer;
aProc: TProc<Integer>);
var
threads: array of TParallel;
I: Integer;
begin
// inicializa la datos para la clase TParallel
TParallel.CurrPos := nMin;
TParallel.MaxPos := nMax;
TParallel.cs := TCriticalSection.Create;
TParallel.ThCount := 0;

// crea los hilos


SetLength (threads, nThreads);
for I := 0 to Length (threads) - 1 do
begin
threads[I] := TParallel.Create; // suspended
threads[I].Proc := aProc;
threads[I].Resume;
end;

while TParallel.ThCount > 0 do


begin
Application.ProcessMessages;
Sleep (100);
end;
end;

Esta función global establece el número de Threads, los activa


y, a continuación espera a todos los hilos hasta el final. La
clase de dato ThCount de la clase TParallel de hecho, se incre-
menta por cada hilo en el arranque y decrece por el destructor
de clase (disparado automáticamente mediante el estableci-
miento de la propiedad FreeOnTerminate del constructor de
la clase del hilo).
El núcleo del trabajo se realiza por la clase del hilo que tiene
una sección crítica que se utiliza cuando se solicita el siguiente
valor a procesar:

La guía de Delphi por Marco Cantù


248 - Indice

type
TParallel = class(TThread)
private
FProc: TProc<Integer>;
protected
procedure Execute; override;
function GetNextValue: Integer;
public
constructor Create;
destructor Destroy; override;

property Proc: TProc<Integer>


read FProc write FProc;
class var
CurrPos: Integer;
MaxPos: Integer;
cs: TCriticalSection;
ThCount: Integer;
end;

Los métodos más interesantes son los que se utilizan para to-
mar el siguiente valor y realizar el proceso real sobre este. Su
código debe ser auto-explicativo:
procedure TParallel.Execute;
var
nCurrent: Integer;
begin
nCurrent := GetNextValue;
while nCurrent <= MaxPos do
begin
Proc (nCurrent);
nCurrent := GetNextValue;
end;
end;
function TParallel.GetNextValue: Integer;
begin
cs.Acquire;
try
Result := CurrPos;
Inc(CurrPos);
finally
cs.Release;
end;
end;

Espero que este ejemplo demuestre las posibilidades abiertas


por la introducción de los métodos anónimos.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 249

AJAX en Delphi
El último ejemplo de este apartado, llamado AnonAjax, es uno
de mis favoritos, por la sencilla razón que lo he aprendido uti-
lizando closures (o métodos anónimos) en JavaScript, escri-
biendo aplicaciones AJAX con la biblioteca jQuery.
La función global AjaxCall no es distinta de la función Paral-
lelFor del ejemplo anterior, ya que también produce un hilo.
Esta vez, sin embargo, la función termina sin esperar a que el
hilo se complete, pero pasa el hilo a un método anónimo tras
su finalización. La función es sólo una envoltura alrededor del
constructor del hilo:
type
TAjaxCallback = reference to procedure (
ResponseContent: TStringStream);

procedure AjaxCall (const strUrl: string;


ajaxCallback: TAjaxCallback);
begin
TAjaxThread.Create (strUrl, ajaxCallback);
end;

Todo el código se encuentra en la clase TAjaxThread, una clase


del hilo con un componente cliente Indy HTTP interno, utili-
zado para acceder a una determinada dirección URL de forma
asíncrona:
type
TAjaxThread = class (TThread)
private
fIdHttp: TIdHttp;
fURL: string;
fAjaxCallback: TAjaxCallback;
protected
procedure Execute; override;
public
constructor Create (const strUrl: string;
ajaxCallback: TAjaxCallback);
destructor Destroy; override;
end;

El constructor realiza unas inicializaciones, copiando sus pa-


rámetros a los correspondientes campos locales de la clase del
La guía de Delphi por Marco Cantù
250 - Indice

hilo y creando el objeto fIdHttp. La verdadera sustancia de


esta clase reside en su método Execute, que realiza la solicitud
HTTP, guardando el resultado en un stream que posterior-
mente se borra y pasa a la función de retrollamada (callback) -
el método anónimo:
procedure TAjaxThread.Execute;
var
aResponseContent: TStringStream;
begin
aResponseContent := TStringStream.Create;
try
fIdHttp.Get (fURL, aResponseContent);
aResponseContent.Position := 0;
fAjaxCallback (aResponseContent);
finally
aResponseContent.Free;
end;
end;

Como ejemplo de uso, el ejemplo AnonAjax tiene un botón


para copiar el contenido de una página Web a un control
Memo (añadiendo la URL solicitada al principio):
procedure TFormAnonAjax.btnReadClick(Sender: TObject);
begin
AjaxCall (edUrl.Text,
procedure (aResponseContent: TStringStream)
begin
Memo1.Lines.Text := aResponseContent.DataString;
Memo1.Lines.Insert (
0, 'From URL: ' + edUrl.Text);
end);
end;

Después que la petición HTTP haya terminado, usted puede


hacer cualquier tipo de proceso que quiera con el.
Otro ejemplo sería extraer los enlaces del archivo HTML (de
forma similar al ejemplo WebFind tratado anteriormente).
Una vez más, para que esta función sea flexible, toma como
parámetro el método anónimo para ejecutarlo para cada uno
de los enlaces:
type
TLinkCallback = reference to procedure (
const strLink: string);

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 251

procedure ExtractLinks (strData: string;


procLink: TLinkCallback);
var
strAddr: string;
nBegin, nEnd: Integer;
begin
strData := LowerCase (strData);
nBegin := 1;
repeat
nBegin := PosEx ('href="http', strData, nBegin);
if nBegin <> 0 then
begin
// encuentra el final de la referencia HTTP
nBegin := nBegin + 6;
nEnd := PosEx ('"', strData, nBegin);
strAddr := Copy (strData, nBegin, nEnd - nBegin);
// seguir adelante
nBegin := nEnd + 1;
// ejecutar inmediatamente el método
procLink (strAddr)
end;
until nBegin = 0;
end;

Si aplica esta función al resultado de una llamada AJAX y pro-


porciona un nuevo método para su proceso, usted termina
con dos llamadas a métodos anónimos anidados, al igual que
en el segundo botón del ejemplo AnonAjax:
procedure TFormAnonAjax.btnLinksClick(Sender: TObject);
begin
AjaxCall (edUrl.Text,
procedure (aResponseContent: TStringStream)
begin
ExtractLinks(aResponseContent.DataString,
procedure (const aUrl: string)
begin
Memo1.Lines.Add (aUrl + ' in ' + edUrl.Text);
end);
end);
end;

En este caso, el control Memo recibirá una colección de enla-


ces, en lugar del HTML de la página devuelta. Una variación
de la rutina de extracción del enlace anterior, sería una rutina
de extracción de imágenes. La función ExtractImages graba la
fuente (src) de la etiqueta img del archivo HTML devuelto, y

La guía de Delphi por Marco Cantù


252 - Indice

llama a otro método anónimo TLinkCallback-compatible


(véase el código fuente para más detalles sobre la función).
Ahora usted puede visualizar la apertura de una página
HTML (con la función AjaxCall), extrae los links de imágenes,
y utiliza AjaxCall de nuevo para capturar las imágenes actua-
les. Esto implica utilizar un método anónimo (closure) triple-
mente anidado, en una estructura de codificación que algunos
programadores de Delphi pueden encontrar ilegible99 (¡se
tarda un tiempo en acostumbrarse!), pero es, sin duda, muy
potente y expresivo:
procedure TFormAnonAjax.btnImagesClick(Sender: TObject);
var
nHit: Integer;
begin
nHit := 0;
AjaxCall (edUrl.Text,
procedure (aResponseContent: TStringStream)
begin
ExtractImages(aResponseContent.DataString,
procedure (const aUrl: string)
begin
Inc (nHit);
Memo1.Lines.Add (IntToStr (nHit) + '.' +
aUrl + ' in ' + edUrl.Text);
if nHit = 1 then // Carga inicial
begin
AjaxCall (aUrl,
procedure (aResponseContent: TStringStream)
begin
// carga imagen sólo del tipo actual
Image1.Picture.Graphic.
LoadFromStream(aResponseContent);
end);
end;
end);
end);
end;

99Este
fragmento de código es el tema de un post en mi blog, “Anónimo, Anó-
nimo, Anónimo” de septiembre del 2008, que atrajo ya algunos comentarios,
como puede ver en: http://blog.marcocantu.com/blog/anonymous_3.html.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 253

Aparte del hecho de que el gráfico sólo funciona en el caso de


que usted esté cargando un archivo con el mismo formato que
el que ya se encuentra en el componente de la imagen, el có-
digo y sus resultados son a la vez impresionantes. Aquí esta el
efecto de pulsar el botón btnImages con la URL de mi blog:

Tenga en cuenta en particular, la secuencia numérica, basada


en la captura de la variable local nHit. ¿Qué sucede si se pulsa
el botón dos veces, en una secuencia rápida? Cada uno de los
métodos anónimos recibirá una copia diferente del contador
nHit, y podrían, potencialmente, ser mostrados fuera de se-
cuencia en la lista, según el segundo hilo comience a producir
sus resultados antes que el primero.

Debatiendo la Demo AJAX


Después de añadir en mi blog este ejemplo, se creo un cierto
debate acerca de la legibilidad y la utilidad de los métodos
anónimos. Respondí a estas voces críticas (algunas de las cua-
les tienen sus razones) con otro post en el blog, en el que trato

La guía de Delphi por Marco Cantù


254 - Indice

de aclarar la situación. Puesto que se añade información adi-


cional a la utilización de los métodos anónimos (y algunas
otras alternativas) creo que vale la pena un resumen, en una
versión ligeramente retocada:
La intervención que hice ayer sobre métodos anóni-
mos, una nueva función en Delphi 2009, agita a la
controversia. Estoy de acuerdo en la lectura de los co-
mentarios, pero ustedes también deben considerar que
el uso de tres métodos anónimos anidados ha sido sufi-
cientemente extendido, no un escenario de uso común.
Empecemos desde el principio y examinemos sólo un
paso. Quiero hacer una llamada HTTP y procesar el
resultado. Esto tiene que hacerse en un hilo, ya que no
deseo que se produzca un bloqueo. Así que tanto si us-
ted usa métodos anónimos como no, es necesario defi-
nir una clase TThread personalizada. Ahora suponga-
mos que usted quiere usar la misma clase thread (o su
soporte de código HTTP) para situaciones ligeramente
diferentes. Para ello tiene dos opciones tradicionales:
1. Heredar una clase para cada escenario de uso y usar
su patrón de plantilla: el método Execute del hilo lla-
mará a una función virtual de cada clase específica
que puede sobrescribir. Fantástico, pero en el caso de
que el código específico sea limitado, la creación de
muchas clases similares, utilizándolas solamente para
un solo objeto en una situación concreta, está lejos de
ser una buena arquitectura.
2.La clásica alternativa a la herencia en Delphi es utili-
zar eventos. De hecho, usted no heredará de TButton to
para sobrescribir el método Click, pero asignará un
procedimiento externo para el evento, usando punte-
ros a métodos. Cada personalización es un método se-
parado que asignar.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 255

Los punteros a métodos y métodos anónimos no son


tan diferentes. En uno de los casos usted puede escribir
el procedimiento in-situ, pero eso es una opción. Para
facilitar la lectura, puede escribir el código de los mé-
todos como una serie de funciones separadas, cada
una asignada a un puntero de método anónimo que se
llamará más adelante. ¿Será esto más legible? Posible-
mente, aún así (a partir de mi experiencia en JavaS-
cript) creo que es más una cuestión de acostumbrarse
a un estilo u otro. En otras palabras, sintaxis aparte, el
concepto de un método anónimo no está lejos de la de
un método puntero, pero el hecho de que se introduzca
un nuevo modelo de vida para las variables puede ser-
vir de ayuda.
Esto me lleva a otro punto, ¿por qué no seguir usando
los punteros de método? El hecho de tener que asignar
memoria para cada llamada de un evento (en el caso
de la ejecución de código en paralelo) estaría muy lejos
de ser trivial en muchos casos en los que los métodos
anónimos simplemente funcionan "por arte de magia”.
Como señaló un comentarista sobre el código, si pulsas
el botón muchas veces la variable de conteo nHit se du-
plica y se captura para cada invocación del método
anónimo, por lo que no sólo viven más allá de la ubica-
ción de su pila original, sino que puede tener múltiples
instancias al mismo tiempo.
Significa esto que ¿todos y cada uno de los códigos de
Delphi se beneficiarán de esta nueva tecnología? Por
supuesto que no, creo que es útil sólo en una porción de
los casos posibles. Sigo convencido de que una llamada
tipo-Ajax es un buen escenario y que se necesitará al-
gún tiempo para que la comunidad Delphi en general
pueda dominar esta nueva característica del lenguaje.

La guía de Delphi por Marco Cantù


256 - Indice

A continuación
Ahora que he cubierto las dos nuevas principales característi-
cas del lenguaje de Delphi 2009, los genéricos y los métodos
anónimos, es el momento de empezar a estudiar otros muchos
(pero aún relevantes) cambios menores. Este será el tema de
la primera parte del próximo capítulo, que también abarca los
cambios en la RTL desde Delphi 2007.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 257

La guía de Delphi por Marco Cantù


258 - Indice

Capítulo 7: Más
Cambios del
Lenguaje y de la
RTL

En los primeros capítulos de Unicode, y en los dos últimos so-


bre características del lenguaje, he tratado la mayoría de las
novedades del compilador y también he presentado un gran
número de nuevas clases de la RTL, desde TCharacter y TEn-
coding hasta los nuevos contenedores genéricos. Puede encon-
trar una lista completa en la sección “Resumen de Nuevas
Unidades y Nuevas clases RTL” al final de este capítulo. Aquí
voy a mostrar otras nuevas características del compilador y de
las áreas de la RTL que no encajan en ninguno de los capítu-
los anteriores.

Otras Nuevas
La guía de Delphi por Marco Cantù | para Desarrollo
sistemas integrados control, SA
Indice - 259

Características del
Lenguaje
Con tantas nuevas características importantes en el lenguaje
Object Pascal es fácil perderse alguna de las menores, que hu-
bieran sido más significativas en otras versiones con un me-
nor conjunto de cambios.

Versión del compilador


La definición específica del compilador de Delphi 2009 es
VER200. Si usted necesita tener un código específico para
una de las más recientes versiones de Delphi, puede basar sus
declaraciones $IFDEF en las siguientes definiciones:

D2006 VER180

D2007.Win VER185 y VER180


32

D2007.Net VER190

Delphi VER200

2009

Como es habitual, usted también puede utilizar la versión in-


terna de constantes con instrucciones $IF, con la ventaja de
ser capaces de utilizar >= en lugar de una coincidencia especí-
fica. Las versiones constantes se llaman CompilerVersion y
RTLVersion y en Delphi 2009 están asignadas al valor de punto
flotante 20.00.

La guía de Delphi por Marco Cantù


260 - Indice

A continuación se muestra un fragmento de código con las


pruebas y definiciones sobre la base de una de las constantes,
extraído del proyecto MinorLang:
{$IFDEF VER200}
ShowMessage ('Delphi 2009');
{$ENDIF}
{$IF RTLVersion >= 20}
ShowMessage ('Delphi 2009 or newer');
{$IFEND}

Una Comentada Directiva


Deprecated
La directiva deprecated, se utiliza para indicar que un símbolo
está disponible solamente por razones de compatibilidad,
puede ahora ir seguido por un mensaje que se mostrará como
parte de la advertencia del compilador. Si se define un proce-
dimiento y lo llama como en el siguiente fragmento de código
(de la demo MinorLang):
procedure DoNothing;
deprecated 'use DoSomething instead';
begin
end;

procedure TFormMinorLang.btnDepracatedClick(
Sender: TObject);
begin
DoNothing;
end;

En la llamada local (en el método btnDepracatedClick) obten-


drá la siguiente advertencia:
W1000 Symbol 'DoNothing' is deprecated: 'use DoSomething
instead'

Este método es mucho mejor que la práctica anterior de aña-


dir un comentario a la declaración del símbolo obsoleto, te-
niendo que hacer clic en el mensaje de error para obtener la

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 261

línea de código fuente en la que se utiliza, saltar a la declara-


ción de la ubicación, y encontrar el comentario.
Huelga decir que el código anterior no compila en Delphi
2007, donde usted recibiría el error:
E2029 Declaration expected but string constant found

La nueva característica deprecated se utiliza raramente en la


RTL y la VCL de Delphi 2009, aunque estoy esperando que
los proveedores de terceros sepan abstenerse de utilizarla de-
bido a la incompatibilidad con versiones anteriores del compi-
lador, aunque ahora pueden utilizar:
{$IF RTLVersion >= 20}
deprecated 'use DoSomething instead';
{$IFEND}

Salidas con un Valor


Tradicionalmente las funciones en Pascal se utilizan para
asignar un resultado utilizando el nombre de la función, como
en:
function ComputeValue: Integer;
begin
...
ComputeValue := 10;
end;

Desde hace mucho tiempo Delphi ha previsto una alternativa


de codificación, utilizando el identificador Result para asignar
un valor de retorno a una función:
function ComputeValue: Integer;
begin
...
Result := 10;
end;

Los dos enfoques son idénticos y no alteran el flujo del código.


Si tiene la necesidad de asignar el resultado de la función ac-

La guía de Delphi por Marco Cantù


262 - Indice

tual y detener la ejecución actual usted puede utilizar dos de-


claraciones separadas, asignar el resultado y, a continuación,
llamar a Exit. Los siguientes fragmentos de código en busca
de una cadena que contiene un número dado en una lista de
cadenas (parte del ejemplo MinorLang) muestra un ejemplo
clásico de este enfoque:
function FindExit (sl: TStringList; n: Integer): string;
var
I: Integer;
begin
for I := 0 to sl.Count do
if Pos (IntToStr (n), sl[I]) > 0 then
begin
Result := sl[I];
Exit;
end;
end;

En Delphi 2009 puede reemplazar los dos declaraciones con


una nueva llamada especial a Exit pasando el valor de retorno
de la función, de un modo parecido a la declaración return del
lenguaje C. De modo que usted puede escribir el código ante-
rior en una versión más compacta (también porque, con una
sola declaración, puede evitar el begin/end):
function FindExitValue (
sl: TStringList; n: Integer): string;
var
I: Integer;
begin
for I := 0 to sl.Count do
if Pos (IntToStr (n), sl[I]) > 0 then
Exit (sl[I]);
end;

Configuración de Propiedades
por Referencia
En el marco de Delphi 6, el compilador le permitía definir
propiedades utilizando un método de configuración que tenía
un parámetro de referencia. Esta no deseada característica fue
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 263

retirada más tarde, ya que podía dar lugar a errores. Ahora, en


aras de la programación COM, se han añadido al lenguaje
nuevas propiedades100 “put by ref”. Sin embargo, usted tiene
que activarlo específicamente, para disponer de esta caracte-
rística, con una nueva directiva del compilador:
{$VARPROPSETTER ON}

Sin esta directiva, el código siguiente no compila y obtenemos


el error “E2282 Property setters cannot take var parame-
ters”(No puede asignar a la propiedad parámetros varia-
bles):
type
TMyIntegerClass = class
private
fNumber: Integer;
function GetNumber: Integer;
procedure SetNumber(var Value: Integer);
public
property Number: Integer
read GetNumber write SetNumber;
end;

Esta clase forma parte del ejemplo VarProp. Ahora lo que es


muy extraño es que usted puede tener efectos adversos esta-
bleciendo esta propiedad setter:
procedure TMyIntegerClass.SetNumber(var Value: Integer);
begin
Inc (Value); // efecto adverso
fNumber := Value;
end;

El otro efecto muy inusual, es que usted ya no le puede asig-


nar un valor constante a esta propiedad, sólo una variable (lo
que debería esperarse, al igual que cualquier llamada que im-
plique un parámetro pasado por referencia):
var
mc: TMyIntegerClass;

100Este es el nombre, que el miembro del equipo de Delphi I+D Chris Bensen dio
a esta característica y que introduce el tema en su blog: http://chrisben-
sen.blogspot.com/2008/04/delphi-put-by-ref-properties.html

La guía de Delphi por Marco Cantù


264 - Indice

begin
...
mc.Count := 10; // Error: E2036 Variable required
mc.Number := n;

Una vez más, esta característica ha sido introducida para el


soporte COM, y lo verá al comienzo de la librería de tipos de
archivos. Curiosamente, si se define una propiedad “put by
ref” de tipo cadena, usted no le puede pasar una variable
string a esta... pero el compilador también le permite asignar
una cadena constante, lo que provocará un error en tiempo de
ejecución. Puedes comprobarlo en el ejemplo VarProp.

Cambios en Overloading
El compilador de Delphi 2009 considera un buen número de
cambios internos en el modo en que el compilador selecciona
la función a llamar, en el caso de múltiples versiones sobre-
cargadas, en dónde ninguna de ellas tenga una coincidencia
exacta. Esto es particularmente cierto cuando están involucra-
das variantes.
Hay dos situaciones diferentes que pueden venir a través de:
 El Código que se utiliza para compilar emite ahora un error
de compilación
 El Código que se utiliza para llamar a un método y que ahora
termina llamando a otro diferente
Huelga decir que, si bien la primera puede ser molesta, esta se
puede arreglar fácilmente añadiendo una conversión explícita
al tipo, en cambio la segunda es mucho más sutil y peligrosa,
ya que solamente descubrirá que las cosas van mal al ejecutar
el programa.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 265

El análisis completo caso-por-caso, significaría un consumo


extremo de tiempo, puesto que las combinaciones son casi in-
finitas. Lo que he hecho, en cambio, es crear un programa con
unos cuantos casos específicos e interesantes de prueba, y que
muestran lo que ocurre en Delphi 2007 y en Delphi 2009. Es
por eso que en la carpeta VariantOver encontrará dos proyec-
tos, uno para cada una de estas versiones del IDE.

Código que Desencadena un


Error de Compilación
Permítanme comenzar con un ejemplo que ya no compilará
más. Supongamos que usted tiene dos métodos sobrecargados
como:
procedure ShowValue(I: Integer); overload;
procedure ShowValue(s: string); overload;

Ahora en Delphi 2007, usted puede llamarlos de la siguiente


forma:
var
v: variant;
begin
v := 3;
ShowValue (v);

Sin embargo si usted escribe:


v := 'foo';
ShowValue (v);

Esto causa un error en tiempo de ejecución, quejándose de


una mala conversión de variant a boolean. Delphi 2009 ha
cambiado esto considerablemente. En las dos llamadas a
ShowValue con un parámetro variante, simplemente se niega a
compilar, alegando que no puede determinar a cuál de las dos
versiones llamar:
[DCC Error] E2251 Ambiguous overloaded call to 'ShowValue'
Related method: procedure

La guía de Delphi por Marco Cantù


266 - Indice

TFormVariantOver.ShowValue(Integer);
Related method: procedure
TFormVariantOver.ShowValue(string);

Es fantástico que ahora pueda ver más detalles que anterior-


mente. En el panel de mensajes de error, verá un mensaje de
error con un signo más que se puede desplegar para obtener
más detalles:

Una vez más, obtener un error de compilación (y uno deta-


llado en este caso) no es tan malo, ya que esto le da una op-
ción para reconocer el problema potencial y solucionarlo mol-
deando la variante a un tipo específico:
ShowValue (Integer(v));
ShowValue (string(v));

Este código funciona en Delphi 2009 y aún mejor en Delphi


2007, de este modo, el error en ejecución por conversión de
variant, no volverá a suceder más.

Código que Llama a un Método


Diferente
Las cosas no son tan agradables en las situaciones en las que
el compilador de Delphi 2009 termina llamando a un método
diferente del previsto en las versiones anteriores. Esta cir-
cunstancia es muy rara, pero es técnicamente posible (incluso
en algunos intrincados casos).
Considere el código siguiente (parte del ejemplo Varian-
tOver), declarando un tipo récord con una conversión Im-
plicit:
type
TMyRecord = record

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 267

private
X: Integer;
public
class operator Implicit (
const Value: Variant): TMyRecord;
end;

Ahora supongamos que tiene dos métodos sobrecargados, to-


mando cada uno un parámetro record o Integer:
procedure ShowValue3 (const R: TMyRecord); overload;
procedure ShowValue3 (X: Integer); overload;

¿Qué sucede si usted pasa una variant? El compilador puede


convertir cualquier variant a un Integer o utilizar la operación
Implicit para convertirla en un registro. Esta es la llamada:
var
v: Variant;
begin
v := 10;
ShowValue3 (v);
end;

Lo que pasa es que este código en Delphi 2007 llama a la so-


brecarga del entero, mientras que el compilador de Delphi
2009 da prioridad a la sobrecarga Implicit.

Nuevos y Alias de Tipos Enteros


Aunque no es estrictamente un cambio del compilador, sino
más bien un añadido en la unidad System, ahora se puede uti-
lizar un grupo de alias más fáciles-de-recordar como alias a ti-
pos de datos enteros, con o sin signo. Estos son los tipos pre-
definidos del compilador con o sin signo:

Enteros Naturales

ShortInt Byte

SmallInt Word

La guía de Delphi por Marco Cantù


268 - Indice

Integer Cardinal

NativeInt NativeUInt

Int64 UInt64

Estos tipos ya estaban en Delphi 2007 y en versiones anterio-


res, pero los de 64 bits sólo desde las últimas versiones del
compilador. Los tipos NativeInt y NativeUInt, que deberían
depender de la versión del compilador (32 bits y el futuro de
64 bits) ya estaban en Delphi 2007, pero no se documentaron
y, peor aún, ¡no eran correctos!
Si usted necesita un tipo de datos que coincida con el tamaño
nativo entero de la CPU, estos son los tipos a usar. De hecho,
el tipo Integer, se espera que se mantenga sin cambios al pa-
sar de los compiladores 32-bits a 64-bits.
El siguiente grupo de alias predefinidos, añadidos por la uni-
dad System son, sin embargo, una nueva gama en Delphi
2009:
type
Int8 = ShortInt;
Int16 = SmallInt;
Int32 = Integer;
UInt8 = Byte;
UInt16 = Word;
UInt32 = Cardinal;

Aunque no añadan nada nuevo, son probablemente más fáci-


les de usar, ya que generalmente es difícil recordar si un
ShortInt es más pequeño que un SmallInt, donde es fácil de
recordar la implementación efectiva de Int16 o Int8101.

101Estos nuevos tipos de alias son al estilo de C.

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 269

Nuevos Métodos de
TObject
La estructura de la clase TObject se ha mantenido bastante es-
table a lo largo de los años. En Delphi 2009 se ven algunas
mejoras interesantes. La madre de todas las clases de Delphi
no sólo tiene cuatro nuevos métodos, sino que tres de estos
son métodos virtuales que se supone puede usted redefinir en
sus propias clases. Si ha utilizado el entorno .NET (con Delphi
para .NET u otros lenguajes) usted reconocerá inmediata-
mente que estos métodos son parte de la clase System.Object
de la librería102 de clases .NET.

El Método ToString
La función virtual ToString es un marcador de posición para el
retorno de la representación textual (la descripción) de un de-
terminado objeto. El valor implementado por defecto de este
método en la clase TObject devuelve el nombre de la clase:
function TObject.ToString: string;
begin
Result := ClassName;
end;

Algunas de las nuevas clases en Delphi 2009 sobrescriben la


función virtual ToString, como TStringBuilder y TStringWriter.
Entre las clases actuales, el método ha sido redefinido en la

102Losnombres de métodos similares utilizados para la base de las clases dispo-


nibles en Java, se utilizan comúnmente en JavaScript, y en otros lenguajes
OO. El origen de algunos de ellos, al igual que de toString, se remonta a los
tiempos de Smalltalk.

La guía de Delphi por Marco Cantù


270 - Indice

clase Exception, para devolver los mensajes a una lista de ex-


cepciones (tal como se contempla en la sección “El Meca-
nismo InnerException” más adelante en este capítulo).
En general, pienso, que tener una forma estándar de devolver
la representación textual de cualquier objeto, es bastante in-
teresante. Espero que la VCL pueda ser actualizada para dar
mejor soporte a esta función en varios de sus componentes y
controles.
Tenga en cuenta que el método ToString sobrecarga a me-
nudo el “parse token String” o el símbolo toString definido en
la unidad Classes. Por esta razón, usted verá a menudo este
símbolo referenciado como Classes.toString.

El Método Equals
La función virtual Equals es un marcador de posición para
comprobar si dos objetos tienen el mismo valor lógico, una
operación distinta a la de comprobar si dos variables se refie-
ren al mismo objeto, algo que puede lograr con el signo =. Sin
embargo, y esto es realmente confuso, la aplicación por de-
fecto hace exactamente esto:
function TObject.Equals(Obj: TObject): Boolean;
begin
Result := Obj = Self;
end;

Hay algunos casos en el código fuente de la VCL en que la lla-


mada a Equals se utiliza, de hecho, más como un sustituto de
la evaluación con =. El enfoque opuesto se utiliza por ejemplo
en Tstrings.Equals, en el que la clase compara el número de
strings y las strings reales una a una.
La única sección de la librería en la que esta técnica es bas-
tante utilizada (y probablemente la razón por la que se ha

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 271

añadido) es el soporte a los genéricos, en particular en las uni-


dades Generics.Default y Generics.Collections. Sin embargo,
la definición de un mecanismo de equivalencia de objetos “by
value” es común en muchas librerías y entornos de trabajo de
Delphi, y disponer de una forma estándar de hacerlo es, sin
duda, una gran ventaja.

El Método GetHashCode
La función virtual GetHashCode es tomada de otro marcador de
posición del entorno .NET, para que cada clase pueda calcular
el código hash de sus objetos. El valor por defecto devuelve un
valor aparentemente aleatorio103, la dirección del objeto en sí
mismo:
function TObject.GetHashCode: Integer;
begin
Result := Integer(Self);
end;

La función virtual GetHashCode se utiliza normalmente en las


secciones VCL.NET de la VCL, pero disponer de la misma fun-
ción en ambos lados, ciertamente puede ayudar a unificar el
código fuente en un futuro próximo. Una vez más, algunas de
las unidades, que proporcionan soporte a los genéricos, utili-
zan la función GetHashCode.

103Conla dirección de los objetos, creados generalmente tomando de un juego


limitado de áreas de la pila, la distribución de este número no es ajustada, y
esto puede afectar negativamente a un algoritmo de hashing.

La guía de Delphi por Marco Cantù


272 - Indice

El Método UnitName
El otro método (no descrito) es la función de clase UnitName,
que no es una función virtual, y devuelve el nombre de la uni-
dad en el que la clase esta definida. Anteriormente usted ha-
bría tenido que recurrir a técnicas de bajo nivel (accediendo a
la representación interna de una clase) para acceder a esta
misma información.

Importando un Ejemplo de .NET


Para demostrar las nuevas características de TObject, en lugar
de escribir una nueva demostración, he decidido importar de
Delphi .NET un ejemplo que escribí para “Mastering Delphi
2005”, y originalmente llamado FclSystemObject, ya que se
centró en la clase System.Object. El nuevo ejemplo se llama
SystemObject aunque se refiere a la clásica clase básica
TObject.

En primer lugar, el ejemplo tiene una clase que sobrescribe


dos de los nuevos métodos virtuales TObject:
type
TAnyObject = class
private
Value: Integer;
name: string;
public
constructor Create (aName: string; aValue: Integer);
function Equals(obj: TObject): Boolean; override;
function ToString: string; override;
end;

En la implementación de los tres métodos, simplemente he


cambiado una llamada a GetType por esta a ClassType:
constructor TAnyObject.Create(aName: string;
aValue: Integer);
begin
inherited Create;
name := aName;

La guía de Delphi por Marco Cantù | para


Indice - 273

Value := aValue;
end;

function TAnyObject.Equals(obj: TObject): Boolean;


begin
Result := (obj.ClassType = self.ClassType) and
((obj as TAnyObject).Value = self.Value);
end;

function TAnyObject.ToString: string;


begin
Result := Name;
end;

Observe que los objetos se consideran iguales si son de la


misma clase exacta y coinciden en sus valores, mientras que
su cadena de representación sólo incluye el nombre del
campo. El programa crea algunos objetos de esta clase cuando
se inicia, asociándolos con los elementos de dos combo boxes:
procedure TFormSystemObject.FormCreate(Sender: TObject);
begin
ao1 := TAnyObject.Create ('ao1', 10);
ao2 := TAnyObject.Create ('ao2 or ao3', 20);
ao3 := ao2;
ao4 := TAnyObject.Create ('ao4', 20);

ComboBox1.Items.AddObject (ao1.ToString, ao1);


ComboBox1.Items.AddObject (ao2.ToString, ao2);
ComboBox1.Items.AddObject (ao3.ToString, ao3);
ComboBox1.Items.AddObject (ao4.ToString, ao4);

ComboBox2.Items.AddObject (ao1.ToString, ao1);


ComboBox2.Items.AddObject (ao2.ToString, ao2);
ComboBox2.Items.AddObject (ao3.ToString, ao3);
ComboBox2.Items.AddObject (ao4.ToString, ao4);
end;

Observe que dos referencias (ao2 y ao3) apuntan a un mismo


objeto en memoria, y que el último objeto (ao4) tiene el
mismo valor numérico. Como usted selecciona los elementos
en los dos cuadros combinados, puede comparar los objetos
que haya seleccionado, utilizando los signos iguales y ha-
ciendo una comparación por referencia directa:
procedure TFormSystemObject.btnCompareClick(
Sender: TObject);
begin
Log ('Comparing ' +
La guía de Delphi por Marco Cantù
274 - Indice

ComboBox1.Items [ComboBox1.ItemIndex] +
' and ' +
ComboBox2.Items [ComboBox2.ItemIndex]);
Log ('Equals: ' + BoolToStr (
ComboBox1.Items.Objects [ComboBox1.ItemIndex].Equals (
ComboBox2.Items.Objects [ComboBox2.ItemIndex]),
True));
Log ('Reference = ' + BoolToStr (
ComboBox1.Items.Objects [ComboBox1.ItemIndex] =
ComboBox2.Items.Objects [ComboBox2.ItemIndex],
True));
end;

Estos son algunos de los resultados:


Comparing ao1 and ao4
Equals: False
Reference = False
Comparing ao2 or ao3 and ao2 or ao3
Equals: True
Reference =: True
Comparing ao2 or ao3 and ao4
Equals: True
Reference =: False

El programa tiene otro botón utilizado para probar algunos de


estos métodos sobre el botón en si mismo:
procedure TFormSystemObject.btnTestClick(
Sender: TObject);
var
btn2: TButton;
begin
btn2 := btnTest;
Log ('Equals: ' +
BoolToStr (btnTest.Equals (btn2), True));
Log ('Reference = ' +
BoolToStr (btnTest = btn2, True));
Log ('GetHashCode: ' +
IntToStr (btnTest.GetHashCode));
Log ('ToString: ' + btnTest.ToString);
end;

La salida es la siguiente (con un valor de hash que podría


cambiar según la ejecución):
Equals: True
Reference = True
GetHashCode: 28253904
ToString: TButton

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 275

Resumen de la Clase
Como resumen104, esta es la interfaz completa de la clase
TObject en Delphi 2009 (Advierta los diferentes tipos de datos
string utilizados):
type
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer):
TObject;
procedure CleanupInstance;
function ClassType: TClass; inline;
class function ClassName: string;
class function ClassNameIs(const Name: string):
Boolean;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Longint; inline;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(
const Name: ShortString): Pointer; overload;
class function MethodAddress(const Name: string):
Pointer; overload;
class function MethodName(Address: Pointer): string;
function FieldAddress(const Name: ShortString):
Pointer; overload;
function FieldAddress(const Name: string):
Pointer; overload;
function GetInterface(const IID: TGUID; out Obj):
Boolean;
class function GetInterfaceEntry(const IID: TGUID):
PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
class function UnitName: string;
function Equals(Obj: TObject): Boolean; virtual;
function GetHashCode: Integer; virtual;
function ToString: string; virtual;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;

104Otroconjunto de cambios afecta a la distribución interna de los datos de la


clase de referencia en Delphi 2009. Por ejemplo, la ubicación vmtParent fue
-36 en las versiones anteriores, pero es -48 en Delphi 2009. Si utiliza las
simbólicas entradas vmt, su código se compilará sin cambios, pero si se uti-
liza el equivalente numérico de localización usted puede meterse en un lío.

La guía de Delphi por Marco Cantù


276 - Indice

procedure BeforeDestruction; virtual;


procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
Los métodos sobrecargados como MethodAddress y FieldAddress to-
man cualquier UnicodeString (UTF-16, como es habitual) o un parámetro
ShortString que es tratado como una cadena UTF-8. De hecho, las ver-
siones que toman una cadena Unicode básica, se convierten con la llamada a
la función UTF8EncodeToShortString:
function TObject.FieldAddress(
const Name: string): Pointer;
begin
Result := FieldAddress(UTF8EncodeToShortString(Name));
end;

Unicode y Nombres de Clase


Internamente, los nombres de clase en Delphi 2009 utilizan el tipo Short-
String, pero con una codificación UTF-8 (y no el nivel de la codificación es-
tándar ANSI de tipo ShortString), tanto con TObject como con los niveles
de la RTTI. Por ejemplo, el método ClassName se ha implementado como:
class function TObject.ClassName: string;
begin
Result := UTF8ToString (
PShortString (PPointer (
Integer(Self) + vmtClassName)^)^);
end;

En Delphi 2007 el mismo método es idéntico, excepto por la


llamada a UTF8ToString. Del mismo modo, en la unidad Typ-
Info, todas las funciones que tienen acceso a nombres de
clase convierten la representación interna UTF-8 ShortString
en una UnicodeString. Por ejemplo:
function GetTypeName(TypeInfo: PTypeInfo): string;
begin
Result := UTF8ToString(TypeInfo^.Name);
end;

La guía de Delphi por Marco Cantù | para


Indice - 277

Algo similar ocurre para los nombres de las propiedades, con


la mayoría de las funciones TypInfo, llamando a la nueva fun-
ción InternalGetPropInfo declarada como:
function InternalGetPropInfo(TypeInfo: PTypeInfo;
const PropName: UTF8String): PPropInfo;

Cambios en el Soporte
Threading
Ya hemos visto en el capítulo 6, y, en particular en la sección
“Sincronización de procesos con la VCL” que la clase TThread
se ha ampliado para aprovechar las ventajas de los métodos
anónimos. Las extensiones toman la forma de nuevas versio-
nes sobrecargadas de los métodos Synchronize y Queue, aunque
el código Delphi anterior debería trabajar sin problemas.
Otra nueva característica de Delphi 2009 es la presencia del
registro105 TMonitor, una estructura de datos definidos en la
unidad System que usted puede utilizar para proporcionar ac-
ceso sincronizado a cualquier objeto. Este soporte monitor,
que se asemeja a la clase correspondiente del entorno .NET,
nos permite definir un proceso (Thread) ligado a un objeto es-
pecífico. En lugar de tener un semáforo global de sincroniza-
ción, puede establecer un control para cada uno al que usted

105Elregistro TMonitor se define en la unidad System. Esto es importante sa-


berlo, ya que es muy fácil tener conflictos con la clase TMonitor, definida en
la unidad Forms. Si la unidad se refiere a los formularios, de hecho, no hay
ningún modo de que usted pueda listar System después de esta, ya que Sys-
tem es invariablemente la primera unidad referida por cualquier otra uni-
dad. En otras palabras, usted a menudo tiene que escribir System.TMonitor
para referirse al registro. Aunque se haya nombrado después de su contra-
partida .Net, me parece que esta elección genera bastante confusión.

La guía de Delphi por Marco Cantù


278 - Indice

desee permitir un acceso simultaneo, dejando que un hilo lo


utilice simultáneamente.
En otras palabras, el registro TMonitor concede un bloqueo
para un objeto a un solo hilo. Sin embargo, múltiples hilos de
procesos pueden trabajar en diferentes objetos al mismo
tiempo. Para adquirir un bloqueo sobre un objeto puede lla-
mar a los métodos Enter o TryEnter, mientras que para la libe-
ración de un bloqueo (generalmente en el último bloque),
debe utilizar el método Exit. El registro TMonitor en Delphi
soporta bloqueos y también variables condicionales, vía los
métodos Wait, Pulse, y PulseAll, un tema complejo que he de-
cidido no tratar con un ejemplo, sino centrarlo solamente en
un escenario simple.
El ejemplo ListMonitor tiene un formulario con tres cuadros
de lista y múltiples hilos de secuencia para acceder a las listas
de forma aleatoria. Cada hilo de la operación es ficticiamente
lento (gracias a la llamada a Sleep), pero los otros hilos intere-
sados en la utilización del cuadro de lista tendrán que esperar.
La clase Thread utilizada por el programa tiene un código
base, que escribe un mensaje para iniciar y detener la lista, es-
perando en medio. Este código esta protegido con un TMonitor
conectado con los objetos ListBox, para evitar así que cual-
quier otro hilo utilice la lista antes de que el primer hilo haya
terminado:
type
TAddToListThread = class(TThread)
protected
procedure Execute; override;
end;

procedure TAddToListThread.Execute;
var
aList: TListBox;
I: Integer;
begin
while not Terminated do
begin

La guía de Delphi por Marco Cantù | para


Indice - 279

aList := Application.MainForm.FindComponent (
'ListBox' + IntToStr (GetTickCount mod 3 + 1))
as TListBox;
System.TMonitor.Enter (aList);
try
aList.Items.Add(IntToStr (GetCurrentThreadID) +
' starting: ' + TimeToStr (Now));
// wait loop, omitted
aList.Items.Add(IntToStr (GetCurrentThreadID) +
' stopping: ' + TimeToStr (Now));
finally
System.TMonitor.Exit (aList);
end;
end;
end;

Como he mencionado anteriormente, el programa de prueba


para esta clase de Thread tiene tres ListBox, que se añaden a
una lista cuando el programa se inicia, más una lista de
Threads (observe que estoy utilizando colecciones genéricas
en ambos casos):
procedure TFormListMonitor.FormCreate(Sender: TObject);
begin
fThreads := TObjectList<TThread>.Create;
fListBoxes := TList<TListBox>.Create;
fListBoxes.Add (ListBox1);
fListBoxes.Add (ListBox2);
fListBoxes.Add (ListBox3);
end;

Los hilos se añadirán a la lista, los tres al mismo tiempo,


cuando se pulse un botón:
procedure TFormListMonitor.btnStartThreadsClick(
Sender: TObject);
var
I: Integer;
begin
for I := 1 to 3 do
fThreads.Add (TAddToListThread.Create (False));
end;

El otro botón se utiliza para comprobar el bloqueo en los


ListBox, utilizando la llamada TryEnter y rescatándolo de in-
mediato cuando el objeto este disponible:
procedure TFormListMonitor.btnStatusClick(
Sender: TObject);
var

La guía de Delphi por Marco Cantù


280 - Indice

aListBox: TListBox;
begin
for aListBox in fListBoxes do
begin
if System.TMonitor.TryEnter(aListBox) then
try
aListBox.Items.Add('Available');
finally
System.TMonitor.Exit(aListBox);
end;
end;
end;

Esto es una muestra del resultado, en el que se puede notar


que la puesta en marcha y detención de los mensajes está
siempre vinculados por el hilo:

Construyendo Cadenas
Hemos visto que la aparición de múltiples tipos de cadenas
causa posibles escollos en la concatenación de cadenas (véase
la sección “Conversión de Cadenas” en el capítulo 2 y el apar-
tado “Operaciones de Cadena Que Fallan o Ralentizan” en el
La guía de Delphi por Marco Cantù | para
Indice - 281

Capítulo 3). La combinación de estas cuestiones, junto al de-


seo de unificar el código Win32 y .NET siempre que sea posi-
ble, no es sorprendente que Embarcadero tomase la idea de
.NET y la añadiese a la RTL nativa. La idea empezó con una
clase específica para crear una cadena, añadiendo múltiples
elementos de los diversos tipos de datos. Llamada String-
Builder en .NET, esta clase ha sido adecuadamente renom-
brada en Delphi a: TStringBuilder.
Como un ejemplo simple del uso de la clase TStringBuilder
considere el siguiente fragmento de código (tomado del pro-
yecto StringBuilder)
var
sBuilder: TStringBuilder;
str1: string;
begin
sBuilder := TStringBuilder.Create;
try
sBuilder.Append(12);
sBuilder.Append('hello');
str1 := sBuilder.ToString;
finally
sBuilder.Free;
end;
end;

Observe en este código la utilización del bloque try-finally, a


diferencia de un string con recuento de referencia, usted tiene

La guía de Delphi por Marco Cantù


282 - Indice

que acordarse de liberar el objeto TStringBuilder. Otro ele-


mento que puede comprobar, es que hay muchos diferentes
tipos de datos que usted puede pasar como parámetro a la
función Append. Una lista completa se mostrará en el editor
gracias al Completado de código, como usted puede ver en la
imagen de la página siguiente:
Otros métodos interesantes que se incluyen en la clase
TStringBuilder son AppendFormat (con una llamada interna a
Format) y AppendLine que agrega el valor sLineBreak. Junto con
Append, disponemos de las correspondientes series de métodos
sobrecargados Insert, un método Remove y algunos métodos
Replace.

Cuando haya terminado, puede utilizar para obtener el resul-


tado de las diversas operaciones, y además comprobar tam-
bién su propiedad Length o el acceso a determinados Chars por
su índice. Tenga en cuenta que, sin embargo la semántica del
método Chars es diferente de la operación string[]. El pri-
mero usa un índice basado en 0, mientras que el de código es-
tándar de Delphi utiliza un índice basado en 1. Como prueba,
tenga en cuenta este método (parte de la demo ):
procedure TFormSBuilder.btnCharPosClick(Sender: TObject);
var
str1: string;
sBuilder: TStringBuilder;
begin
str1 := '1234567890';
Log ('str1[4]: ' + str1[4]);
sBuilder := TStringBuilder.Create (str1);
try
Log ('sBuilder.Chars[4]: ' + sBuilder.Chars[4]);
finally
sBuilder.Free;
end;
end;

La salida de este código obtendrá el 4º carácter (utilizando el


acceso directo) y el 5º (utilizando la propiedad Chars):
str1[4]: 4
sBuilder.Chars[4]: 5

La guía de Delphi por Marco Cantù | para


Indice - 283

Entiendo el deseo de hacer esta clase compatible con su ho-


mólogo .NET, pero estas diferencias de acceso a caracteres
parecen hacer lo contrario.

Métodos de Encadenamiento en
StringBuilder
Una característica específica de la clase TStringBuilder es que
la mayoría de los métodos son funciones que devuelven el ob-
jeto actual. Esta codificación del lenguaje abre la posibilidad
de los métodos encadenados106, lo que supondría llamar a un
método sobre el objeto devuelto por el anterior. En vez de es-
cribir:
sBuilder.Append(12);
sBuilder.AppendLine;
sBuilder.Append('hello');

puede usted escribir:


sBuilder.Append(12).AppendLine.Append('hello');

que puede formatearse así:


sBuilder.
Append(12).
AppendLine.
Append('hello');

Tiendo a preferir esta sintaxis a la original, ya sé que sólo es


una sintáctica azucarada y algunas personas prefieren la ver-
sión original con el objeto explicado detalladamente en cada
línea. En cualquier caso, tenga en cuenta que las distintas lla-

106Sobremétodos de encadenado (y otras técnicas de ayuda a la escritura de


Lenguajes Específicos de Dominio con Delphi), visite esta entrada en mi blog
: http://blog.marcocantu.com/blog/static_internal_dsl_delphi.html

La guía de Delphi por Marco Cantù


284 - Indice

madas Append no devuelven objetos nuevos (no hay posibilida-


des de fugas de memoria), sino exactamente el mismo objeto
al que le está aplicando los métodos.

La Velocidad en la Construcción
de Cadenas
Huelga decir que la mayoría de los desarrolladores de Delphi
inmediatamente se preguntaron si utilizando la clase TString-
Builder haría que su código fuera más rápido o más lento
cuando usasen la concatenación de una cadena simple u otras
operaciones de cadena. La respuesta abreviada es que el ren-
dimiento es similar, con una ligera ventaja para la concatena-
ción clásica de cadenas, a pesar de que las situaciones del
mundo real son probablemente diferentes de las pruebas sen-
cillas que he realizado en el ejemplo StringBuilder.
He hecho tres pruebas diferentes: simple concatenación de
cadenas, la adición de números enteros a una cadena, y la in-
serción de una cadena dentro de otra cadena (que es mucho
más lenta y ejecutada una parte de las veces). La lista com-
pleta está en el ejemplo, aquí están las líneas clave de los 6
métodos (con exclusión de las declaraciones utilizadas para
crear y liberar los objetos TStringBuilder):
// 1a. concatenación de cadenas
for I := 1 to MaxLoop do
str1 := str1 + str2;

// 1b. appending strings


for I := 1 to MaxLoop do
sBuilder.Append(str2);

// 2a. concatenar números


for I := 1 to MaxLoop do
str1 := str1 + IntToStr (I);

// 2b. añadiendo los números


for I := 1 to MaxLoop do

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 285

sBuilder.Append(I);

// 3a. inserción en la cadena


for I := 1 to MaxLoop div 100 do
Insert('hello', str1, 7); // 1-basado en la posición

// 3b. inserción en la cadena constructor


for I := 1 to MaxLoop div 100 do
sBuilder.Insert (6, 'hello'); // 0-basado en la posición
// 4a. concatenación de caracteres
for I := 1 to MaxLoop do
str1 := str1 + ch;

// 4b. appending a character


for I := 1 to MaxLoop do
sBuilder.Append(ch);

Si ejecuta este programa (eventualmente, cambiando la cons-


tante MaxLoop para probar lo que sucede con cadenas pequeñas
y grandes), obtendrá el siguiente resultado:
1a. Concatenation: 78
1b. TStringBuilder: 109

2a. Concatenation: 265


2b. TStringBuilder: 359

3a. Concatenation: 156


3b. TStringBuilder: 156

4a. Char Concat: 93


4b. TStringBuilder: 31

Como mencioné al principio, los valores están muy cercanos,


con alguna ventaja para la concatenación de simples cadenas,
con la única excepción entre la concatenación o adición de ca-
racteres individuales. La razón es que al agregar un único ca-
rácter, la versión sobrecargada de Append que está en ejecu-
ción, está específicamente optimizada, mientras que el código
genérico de concatenación trata el carácter como un único
string igualmente. La inserción de operaciones se toma casi la
misma cantidad de tiempo.
La razón de la diferencia en el caso de una cadena de concate-
nación depende del hecho de que el código que se ejecuta por
detrás para la simple concatenación, está optimizado a bajo

La guía de Delphi por Marco Cantù


286 - Indice

nivel en código ensamblador. Por otro lado, la clase TString-


Builder tiende a reducir las asignaciones de memoria, ya que
inicialmente reserva una memoria extra: Cada vez que el ob-
jeto necesita más memoria, su método ExpandCapacity du-
plica su capacidad actual. Esto puede consumir más memoria
de la que necesita, pero reduce el número de asignaciones (y
las potenciales operaciones de copia).
Para verificar cómo trabaja la asignación, he añadido un mé-
todo btnCapacityClick a la demo StringBuilder que muestra
cómo la propiedad Length y Capacity de un objeto TString-
Builder crece mientras se está ampliando:
for J := 1 to 10 do
begin
for I := 1 to 200 do
sBuilder.Append (I);
Log ('Len/Cap: ' + IntToStr (sBuilder.Length) +
':' + IntToStr (sBuilder.Capacity));
end;

Esta es la salida, en la que usted puede comprobar la duplica-


ción de la capacidad extra de memoria cuando esta sea pre-
ciso:
Len/Cap: 492:512
Len/Cap: 984:1024
Len/Cap: 1476:2048
Len/Cap: 1968:2048
Len/Cap: 2460:4096
Len/Cap: 2952:4096
Len/Cap: 3444:4096
Len/Cap: 3936:4096
Len/Cap: 4428:8192
Len/Cap: 4920:8192

Incluso si los incrementos de velocidad, utilizando TString-


Builder versus una cadena simple en la concatenación, no
siempre sean significativos, la nueva clase tiende a hacer el

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 287

código más flexible y más fácil de escribir, por lo que le reco-


mendamos cambiar su código actual para utilizarlo cuando
sea posible107.

Migrando un Ejemplo Delphi .NET


Como prueba de la compatibilidad del código fuente en Del-
phi de la clase TStringBuilder con la clase .NET StringBuilder,
he tomado otro ejemplo que tenía escrito en Delphi para .NET
en mi libro “Mastering Delphi 2005” y he convertido este en
una aplicación VCL.
El programa tiene dos botones radio para seleccionar una ca-
dena de distinto tamaño, un ListBox para la salida, y dos bo-
tones para el proceso de las cadenas con un TStringBuilder o
una cadena simple. Simplemente definiendo un tipo de alias
TStringBuilder para StringBuilder, el núcleo del método com-
pila sin un solo cambio:
// código actual
strB := StringBuilder.Create;
if rbShort.Checked then
strB.Append (strSampleShort)
else
strB.Append (strSampleLong);

for I := 1 to maxCount do
begin
nPos := I mod strB.Length;
strB.Remove(nPos, 1);
strB.Insert(nPos, strB [(I*2) mod strB.Length]);
end;
ListBox1.Items.Add (strB.ToString);
// final del código

107Para
obtener más ideas sobre esta cuestión, véase también mi blog “No tan rá-
pido, TstringBuilder” en: http://blog.marco-
cantu.com/blog/not_so_fast_tstringbuilder.html.

La guía de Delphi por Marco Cantù


288 - Indice

Todo lo que he tenido que hacer es acordarme de añadir una


llamada a Free, ya que no disponemos de una referencia de
cuenta como en la plataforma .NET. La cadena de procesa-
miento de código, en cambio, es totalmente diferente entre
Delphi Win32 y la versión .NET FCL, así que tuve que reescri-
birla por completo. Lo que es interesante advertir es que en
este código, en el que se mantiene la eliminación y la inser-
ción de datos en la cadena, el código basado en TstringBuilder
es notablemente más rápido:
btnSBuilderClick: 00.112
btnStringClick: 00.246

A diferencia de .NET (donde los strings son inmutables y las


operaciones directas con strings son terriblemente lentas) uti-
lizar una cadena más corta o más larga hace que casi no haya
ninguna diferencia, pero he dejado los botones de radio y las
cadenas constantes relacionadas en el código para mantener
el ejemplo lo más cercano posible al original.

Usando Readers y Writers


Un acercamiento totalmente alternativo para la construcción
de grandes cadenas, o la escritura y la lectura de streams, es
utilizar las nuevas clases de reader y writer introducidas en
Delphi 2009, de nuevo mapeadas sobre sus homólogos en
.NET.
En Delphi tradicionalmente hubo un par de clases similares
(TReader y TWriter) pero ellas están específicamente dirigidas
a las propiedades de flujo de los archivos DFM. Las nuevas
clases, en cambio, son más adecuadas para enfoques más ge-
nerales y se centran en la lectura y escritura de datos textua-
les.

La guía de Delphi por Marco Cantù | para


Indice - 289

Hay cuatro nuevas clases de lectura y escritura, que se definen


en la unidad Classes:
 TStringReader y TStringWriter trabajan en una cadena en
memoria (directamente o usando un TStringBuilder)
 TStreamReader y TStreamWriter trabajan en un stream gené-
rico (un archivo Stream, un Stream en memoria, y más)
Estas cuatro clases heredan de las clases abstractas bases
TTextReader y TTextWriter que proporcionan la interfaz a
una lista de operaciones. Cada uno de los lectores pone en
práctica unas técnicas de lectura básicas:
function Read: Integer; overload;
function Read(const Buffer: TCharArray;
Index, Count: Integer): Integer; overload;
function ReadBlock(const Buffer: TCharArray;
Index, Count: Integer): Integer;
function ReadLine: string;
function ReadToEnd: string;

Cada uno de los escritores tiene dos conjuntos de operaciones


sobrecargadas sin (Write) y con ( WriteLine) un separador de
final-de-línea. Aquí está la primera serie:
procedure Write(Value: Boolean); overload;
procedure Write(Value: Char); overload;
procedure Write(const Value: TCharArray); overload;
procedure Write(Value: Double); overload;
procedure Write(Value: Integer); overload;
procedure Write(Value: Int64); overload;
procedure Write(Value: TObject); overload;
procedure Write(Value: Single); overload;
procedure Write(const Value: string); overload;
procedure Write(Value: Cardinal); overload;
procedure Write(Value: UInt64); overload;
procedure Write(const Format: string;
Args: array of const); overload;
procedure Write(Value: TCharArray;
Index, Count: Integer); overload;

En las implementaciones actuales las operaciones de escritura


transforman su contenido en una cadena, antes de escribir
(esto es diferente de los TReader y Twriter originales de Del-
phi, que también podían trabajar con datos binarios).

La guía de Delphi por Marco Cantù


290 - Indice

Para escribir un Stream, la clase TStreamWriter utiliza un


Stream o crea uno con el nombre del archivo y la codificación
pasadas como parámetros. De modo que podamos escribir,
como lo hice en la demo ReaderWriter:
var
sw: TStreamWriter;
begin
sw := TStreamWriter.Create('test.txt',
False, TEncoding.UTF8);
try
sw.WriteLine ('Hello, world');
sw.WriteLine ('Have a nice day');
sw.WriteLine (Left);
finally
sw.Free;
end;

Para leer la TStreamReader, usted puede trabajar de nuevo en


un stream o un archivo (en cuyo caso puede detectar la codifi-
cación del BOM):
var
sr: TStreamReader;
begin
sr := TStreamReader.Create('test.txt', True);
try
while not sr.EndOfStream do
Memo1.Lines.Add (sr.ReadLine);
finally
sr.Free;
end;

Observe cómo usted puede comprobar el estado de


EndOfStream. En comparación con el código clásico de Del-
phi utilizado para leer y escribir un string desde un string, pa-
sando como parámetro sin-tipo el primer carácter de la ca-
dena (str[1], mas-abajo), la legibilidad esta muy mejorada.
Como comparación, este es un fragmento del código clásico:
var
fstr: TFileStream;
str: string;
begin
fstr := TFileStream.Create (test.txt', fmCreate);
try
str := 'Hello, world';
fstr.Write(str[1], Length (str));

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 291

finally
fstr.free;
end;

Para escribir a una cadena en memoria, puede usar un clase


específica de stream o usar una clase TstringWriter, la cual
usa cualquier objeto TStringBuilder pasándolo a su construc-
tor o creando este una interna. Al final, usted puede pedir la
cadena completa. El TStringReader trabaja en una cadena
pasada como parámetro a su único constructor, pero no hay
una forma fácil de detectar el final del string. La única solu-
ción lista-para-usar que he encontrado (sin ampliar esta
clase), ha sido para ver si la llamada Peek devuelve algún va-
lor. El siguiente manipulador de eventos (otra vez de la apli-
cación ReaderWriter) llena una cadena en memoria con un
writer que lo lee desde atrás:
procedure TFormReaderWriter.btnWriteAndReadClick(
Sender: TObject);
var
sw: TStringWriter;
sr: TStringReader;
theString: string;
begin
sw := TStringWriter.Create;
try
sw.WriteLine ('Hello, world');
sw.WriteLine ('Have a nice day');
sw.WriteLine (Left);
theString := sw.ToString;
finally
sw.Free;
end;
sr := TStringReader.Create(theString);
try
while sr.Peek <> -1 do
Memo1.Lines.Add (sr.ReadLine);
finally
sr.Free;
end;
end;

Comparado con el uso directo de streams (o strings), estas


clases son particularmente prácticas de utilizar, y proporcio-
nan un buen rendimiento. Cuando usted tiene que crear una

La guía de Delphi por Marco Cantù


292 - Indice

cadena muy grande con datos (como un archivo externo


XML) con la utilización de un stream con un escritor apro-
piado, puede proporcionarle un alto nivel de rendimiento.
Otro elemento interesante, es el de disponer de una interfaz
estándar adecuada, usted puede escribir algoritmos o clases
que trabajan con las dos clases abstractas, y , y que se pueden
utilizar para trabajar con cadenas o streams en memoria.
Como ejemplo de este enfoque he escrito una clase, extrema-
damente simple, de la clase XmlWriter, que no hereda de
TTextWriter sino que más bien lo encapsula. Esta es la defini-
ción de la clase, disponible en el proyecto :
type
TTrivialXmlWriter = class
private
fWriter: TTextWriter;
fNodes: TStack<string>;
public
constructor Create (aWriter: TTextWriter);
destructor Destroy; override;
procedure WriteStartElement (const sName: string);
procedure WriteEndElement;
procedure WriteString (const sValue: string);
end;

Internamente, la clase utiliza una pila de strings para hacer


un seguimiento de los elementos XML que se han abierto y no
cerrado, proporcionando un cierre semi-automático con el
método WriteEndElement:
procedure TTrivialXmlWriter.WriteStartElement(
const sName: string);
begin
fWriter.Write('<' + sName + '>');
fNodes.Push (sname);
end;
procedure TTrivialXmlWriter.WriteEndElement;
begin
fWriter.Write('</' + fNodes.Pop + '>');
end;

Este es un ejemplo de cómo se puede utilizar esta clase, para


rellenar un TStringWriter (el código actual de la demo es un
poco más largo):
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 293

procedure TFormReaderWriter.btnXmlCorrectClick(
Sender: TObject);
var
sw: TStringWriter;
txw: TTrivialXmlWriter;
theString: string;
begin
sw := TStringWriter.Create;
try
txw := TTrivialXmlWriter.Create (sw);
try
txw.WriteStartElement('book');
txw.WriteStartElement('title');
txw.WriteString('Delphi 2009 Handbook');
txw.WriteEndElement;
txw.WriteEndElement;
finally
txw.Free;
end;
theString := sw.ToString;
finally
sw.Free;
end;
Memo1.Lines.Text := theString;
end;

Para hacer la clase un poco más interesante, el destructor se


encarga de cerrar todos los nodos XML que se dejaron abier-
tos (aunque también podría plantear un error, en caso de que
tal situación se produzca).

Mejoras Excepción(ales)
Junto con TObject, otro núcleo de la clase Delphi que ha visto
un par de importantes mejoras en Delphi 2009, es la clase Ex-
ception. Por un lado, hay una nueva función virtual que se
llama después de que un objeto excepción haya sido creado,
pero antes de que se haya lanzado. Por otro lado, existe ahora
el soporte para anidar (o interiorizar) las excepciones. Por úl-
timo, aunque esto no es tan importante, hay algunas clases
nuevas de excepciones en la unidad SysUtils.

La guía de Delphi por Marco Cantù


294 - Indice

El Mecanismo InnerException
¿Qué sucede si usted plantea una excepción dentro de un ma-
nipulador de excepciones? La respuesta tradicional de Delphi
es que las nuevas excepciones sustituirán a la existente, razón
por la cual esta es una práctica común para combinar al me-
nos los mensajes de error, con código como éste (que carece
de cualquier operación real, y muestra sólo las declaraciones
relacionadas con las excepciones):
procedure TFormExceptions.ClassicReraise;
begin
try
// hacer algo ...
raise Exception.Create('Hello');
except on E: Exception do
// intentar arreglar algunos ...
raise Exception.Create('Another: ' + E.Message);
end;
end;

Este código es parte del ejemplo ExceptionsTest. Al llamar al


método y al manipular la excepción, usted vera una única ex-
cepción con el mensaje combinado:
procedure TFormExceptions.btnTraditionalClick(
Sender: TObject);
begin
try
ClassicReraise;
except
on E: Exception do
Log ('Message: ' + E.Message);
end;
end;

La salida (evidente) es:


Message: Another: Hello

Para hacer las excepciones un poco más flexibles en el caso de


operaciones relacionadas con bases de datos, desde los prime-
ros días de la BDE de Delphi, se presentó la idea de una ex-
cepción DBError con una lista interna de los códigos de error,

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 295

pero esto no es muy flexible. Ahora en Delphi 2009, para au-


mentar la compatibilidad con .NET y mejorar la gestión de ex-
cepciones en el framework de dbExpress108, existe soporte
systemwide para excepciones anidadas.
Dentro de un manipulador de excepciones, puede crear y lan-
zar una nueva excepción y mantener la actual viva, conec-
tando esta con la nueva excepción. Para lograr esto, la clase
tiene una nueva propiedad , refiriéndose a la anterior excep-
ción, y una propiedad que le permite acceder a la primera ex-
cepción de una serie, pudiendo ser esta anidación de excep-
ciones recursiva. Estos son los nuevos elementos de la clase
Exception relacionados con la gestión de excepciones anida-
das, además de un par de nuevos métodos como el destructor
y 109:
type
Exception = class(TObject)
private
FInnerException: Exception;
FAcquireInnerException: Boolean;
protected
procedure SetInnerException;
public
destructor Destroy; override;
function GetBaseException: Exception; virtual;
function ToString: string; override;
property BaseException: Exception
read GetBaseException;
property InnerException: Exception
read FInnerException;
class procedure RaiseOuterException(
E: Exception); static;

108La
razón por la que he conectado esta nueva función del framework de dbEx-
press es simple: las unidades DBXCommon y la DBXPlatform son actual-
mente las dos únicas unidades de la VCL (para Win32), que se refieren a la
propiedad InnerException.
109Heomitido en esta lista de la clase Exception otras nuevas características
comparadas con Delphi 2007, como la nueva clase de datos de punteros a
funciones que pueden utilizarse para conectar dentro de la pila de rastreo de
la excepción (un tema avanzado que no voy a tratar en el libro) y el nuevo
método RaisingException detallado en el siguiente apartado.

La guía de Delphi por Marco Cantù


296 - Indice

class procedure ThrowOuterException(


E: Exception); static;
end;

Desde la perspectiva de un usuario, para lanzar una excepción


preservando la actual, usted debería de llamar al método de
clase (o la ThrowOuterException, idéntica a la que utiliza la no-
menclatura de C++). Cuándo maneje una excepción similar
usted puede usar las nuevas propiedades para acceder a más
información. Observe que usted puede llamar sólo la dentro
de un manipulador de excepción tal como la documentación
del código fuente dice:
Use esta función para lanzar otra instancia de excep-
ción desde un manipulador de excepciones y si desea
“adquirir” la excepción activa y encadenarla a la
nueva excepción y preservar el contexto. Esto provo-
cará que el campo FInnerException se asigne a la actual
excepción en juego.
Usted sólo debe llamar a este procedimiento de excep-
ción dentro de un bloque Except en el que esta nueva
excepción se espera que sea manejada desde otro lu-
gar.
Para comprobar un ejemplo real, puede usted referirse al
proyecto ExceptionsTest. En este proyecto he añadido un mé-
todo que plantea una excepción anidada al nuevo estilo (en
comparación con el método ClassicReraise detallado anterior-
mente):
procedure TFormExceptions.MethodWithNestedException;
begin
try
raise Exception.Create ('Hello');
except
Exception.RaiseOuterException (
Exception.Create ('Another'));
end;
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 297

Ahora en el manipulador para esta excepción externa pode-


mos acceder a ambos objetos de la excepción (y también ver el
efecto de llamar al nuevo método ToString):
try
MethodWithNestedException;
except
on E: Exception do
begin
Log ('Message: ' + E.Message);
Log ('ToString: ' + E.ToString);
if Assigned (E.BaseException) then
Log ('BaseException Message: ' +
E.BaseException.Message);
if Assigned (E.InnerException) then
Log ('InnerException Message: ' +
E.InnerException.Message);
end;
end;

El resultado de esta llamada es el siguiente:


Message: Another
ToString: Another
Hello
BaseException Message: Hello
InnerException Message: Hello

Hay dos elementos relevantes a tener en cuenta. El primero es


que en el caso de una única excepción anidada, la propiedad
BaseException y la propiedad InnerException ambas se refie-
ren al mismo objeto de excepción, el original. El segundo es
que, si bien el mensaje de la nueva excepción sólo contiene el
mensaje actual, llamando a ToString puede tener acceso a la
combinación de todos los mensajes de las excepciones anida-
das, separados por un sLineBreak (como puede ver en el có-
digo del método Exception.ToString). La elección de la utiliza-
ción de un salto de línea en este caso produce un resultado
dispar, pero una vez que se sabe de ella, puede darle el for-
mato que prefiera, en sustitución de los saltos de línea con un
símbolo de su elección o asignándolo a las propiedades Text
de un string list.

La guía de Delphi por Marco Cantù


298 - Indice

Como un ejemplo más, déjeme mostrarle lo que sucede


cuando se lanzan dos excepciones anidadas, este es el nuevo
método:
procedure TFormExceptions.MethodWithTwoNestedExceptions;
begin
try
raise Exception.Create ('Hello');
except
begin
try
Exception.RaiseOuterException (
Exception.Create ('Another'));
except
Exception.RaiseOuterException (
Exception.Create ('A third'));
end;
end;
end;
end;

Esta llamada es un método idéntico al que vimos anterior-


mente y que produce la salida siguiente:
Message: A third
ToString: A third
Another
Hello
BaseException Message: Hello
InnerException Message: Another

Esta vez, la propiedad BaseException y la propiedad InnerEx-


ception se refieren a objetos diferentes y la salida de ToString
abarca tres líneas.

Preprocesamiento de
Excepciones
Una de las características de la clase Exception es una nueva
función virtual protegida, declarada como:
procedure RaisingException(P: PExceptionRecord); virtual;

De acuerdo con la documentación (es decir, un comentario en


el código fuente, no de la ayuda en línea):
La guía de Delphi por Marco Cantù | para
Indice - 299

Esta función virtual se llama justo antes de que esta


excepción esté a punto de ser lanzada. En el caso de
una excepción externa no Delphi, esta se llama poco
después de que el objeto se cree desde la condición
"raise" que ya está en proceso.
La implementación de la función en la clase Exception ges-
tiona la excepción interna (llamando internamente a
SetInnerException), que probablemente explica la razón por la
que se introdujo en el primer lugar, al mismo tiempo que el
mecanismo de excepción interno.
En cualquier caso, ahora que tenemos esta característica dis-
ponible podemos tomar ventaja de ello. Sobrescribiendo este
método, de hecho, tenemos una única función posterior a su
creación que es llamada invariablemente, independiente-
mente del constructor utilizado para crear la excepción. En
otras palabras, usted puede evitar definir un constructor per-
sonalizado para su clase exception y permitir a los usuarios
llamar a uno de los muchos constructores de la clase base Ex-
ception, y así obtener un comportamiento personalizado.
Como ejemplo, usted puede registrar cualquier excepción de
una clase (o subclase).
Esta es una clase de excepción personalizada (definida de
nuevo en el ejemplo ExceptionsTest) que anula el método
RaisingException:
type
ECustomException = class (Exception)
protected
procedure RaisingException(
P: PExceptionRecord); override;
end;

procedure ECustomException.
RaisingException(P: PExceptionRecord);
begin
// registrar la información de la excepción (¡a un
//archivo sería más inteligente!)
FormExceptions.Log('Exception Addr: ' + IntToHex (

La guía de Delphi por Marco Cantù


300 - Indice

Integer(P.ExceptionAddress), 8));
FormExceptions.Log('Exception Mess: ' + Message);

// modifica el mensaje
Message := Message + ' (filtered)';

// proceso estándar
inherited;
end;

Lo que la implementación de este método hace es registrar al-


guna información sobre la excepción, modificar el mensaje de
excepción e invocar el procesamiento estándar de las clases
bases (necesario para trabajar el mecanismo de excepción
anidada). El método se invoca después de que el objeto excep-
tion haya sido creado pero antes de que la excepción se lance.
¡Esto es así porque la salida producida por las llamadas al Log
se genera antes que la excepción y es capturada por el depura-
dor! Del mismo modo, si usted pone un punto de interrupción
en el método RaisingException, el depurador se detendrá allí
antes de la captura de la excepción.

Nuevas Clases de Excepción


Otra nueva característica de las excepciones es la disponibili-
dad de algunas nuevas clases de excepción específicas, dispo-
nibles globalmente (tal como se definen en la unidad SysU-
tils):
type
EArgumentException = class(Exception);
EArgumentOutOfRangeException =
class(EArgumentException);
ENoConstructException = class(Exception);
EMonitor = class(Exception);
EMonitorLockException = class(EMonitor);
ENoMonitorSupportException = class(EMonitor);
EProgrammerNotFound = class(Exception);

La última de estas nuevas clases de excepción, EProgram-


merNotFound, parece más una broma y nunca es utilizada en el

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 301

código fuente de la VCL. Sin embargo, puedo pensar en algu-


nas divertidas formas110 de utilizarla.

Resumen de las Nuevas


Unidades y Las Nuevas
Clases RTL
Con todas las clases de RTL actualizadas al soporte Unicode y
a los genéricos, además de muchas características adicionales,
es fácil perderse algunos de los cambios. Por esta razón, des-
pués de la cubrir la librería de tiempo de ejecución (que esta
sólo parcialmente tratada en este capítulo), vale la pena dar
un vistazo a las nuevas unidades y clases de la RTL. He hecho
una lista global de rutinas, y esto ha sido una tarea enorme.
Las siguientes son las nuevas unidades de la librería de
tiempo de ejecución, con la lista de las clases que definen, en
cada caso:
AnsiStrings (no classes)
Character TCharacter

110Tengouna larga tradición en hablar sobre el “lado divertido de Delphi”, como


se puede ver en mi sitio web en la dirección http://www.marco-
cantu.com/funside.

La guía de Delphi por Marco Cantù


302 - Indice

Generics.Collec- TArray
TEnumerator<T>
tion TEnumerable<T>
TList<T>
TQueue<T>
TStack<T>
TDictionary<TKey,TValue>
TObjectList<T>
TObjectQueue<T>
TObjectStack<T>
TObjectDictionary<TKey,TValue>

Generics.Default TComparer<T>
TEqualityComparer<T>
TSingletonImplementation
TDelegatedEqualityComparer<T>
TDelegatedComparer<T>
TCustomComparer<T>
TStringComparer

Las siguientes son las nuevas clases de Delphi 2009 (o los rec-
ords con sus métodos) que se han añadido a las actuales uni-
dades de la RTL. Fíjese, en particular, que lo que hay aquí son
las clases de la unidad SysUtils (y no sólo las de la unidad
Classes), mientras que la unidad System añade dos registros
con métodos. Está es la lista:
Classes TBytesStream
TTextReader
TTextWriter
TStringReader
TStringWriter
TStreamWriter
TStreamReader
SyncObjs TSemaphore
TConditionVariableMutex
TConditionVariableCS
System TMonitor

SysUtils TStringBuilder
TEncoding
TMBCSEncoding
TUTF7Encoding
TUTF8Encoding
TUnicodeEncoding
TBigEndianUnicodeEncoding
ZLib TCustomZStream

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 303

Más y Menos FastCode


En las últimas versiones de Delphi, Embarcadero ha tomado
(de forma que es compatible con la licencia original) varias
rutinas del proyecto111 FastCode. Ahora, aunque estas rutinas
están todavía en el producto, la mayoría de ellas relacionadas
con la gestión de strings y no con la nueva unidad , y proba-
blemente no vayan a ser utilizadas tanto como las correspon-
dientes rutinas UnicodeString. En el aspecto positivo, para la
integración de FastCode, hay una nueva versión optimizada
de la función en la unidad .

A continuación
Este capítulo sobre la variedad de cambios en el compilador y
las nuevas características de la librería de Delphi en tiempo de
ejecución, completa la segunda parte del libro, que se ha cen-
trado en el compilador y en el IDE. La tercera parte del libro
se centrará en la VCL, incluyendo las nuevas características de
soporte a Vista, la interfaz de usuario Ribbon, el soporte para
traducción, y el soporte COM. Después trataremos la pro-
gramación con bases de datos y las aplicaciones multi-tarea
sobre bases de datos.

111Véasehttp://www.fastcodeproject.org para obtener más detalles sobre el pro-


yecto FastCode y su situación actual (la última vez que he chequeado su acti-
vidad era muy baja).

La guía de Delphi por Marco Cantù


304 - Indice

Apartado III: La
VCL y Las Bases
de Datos

La parte principal de la “Experiencia Delphi “ se refiere a su li-


brería clave, la Librería de Componentes Visuales (VCL), y en
particular su subconjunto centrado en el desarrollo con bases
de datos, incluyendo cliente/servidor y las arquitecturas de 3-
capas. Muchos de los controles visuales y algunos de los com-
ponentes de acceso a bases de datos han recibido una impor-
tante actualización en Delphi 2009, y son objeto de esta ter-
cera parte del libro, que abarca también COM, los nuevos con-
troles Ribbon, y la nueva arquitectura en múltiples niveles
DataSnap 2009.

 Capítulo 8: Mejoras de la VCL


 Capítulo 9: Soporte COM en Delphi 2009
 Capítulo 10: El componente Ribbon
 Capítulo 11: Datasets y dbExpress
 Capítulo 12: DataSnap 2009
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 305

Capítulo 8:
Mejoras En la
VCL

Aunque quizá no tan importante como el soporte Unicode u


otros cambios del compilador, la VCL en Delphi 2009 recibe
una serie de pequeñas pero importantes mejoras (algunas de
las cuales habían sido solicitadas hacía tiempo por los usua-
rios de Delphi). En este capítulo, voy a centrarme en las mejo-
ras realizadas y nuevos controles añadidos, mientras que el si-
guiente lo dedicaré al nuevo componente Ribbon.
La VCL es una de las piedras angulares de Delphi y su arqui-
tectura ha contribuido significativamente al éxito de esta he-
rramienta. Aún hoy, mirando la interfaz de usuario de otros

La guía de Delphi por Marco Cantù


306 - Indice

entornos diseñados después de muchos años por grandes em-


presas de TI, la VCL destaca considerablemente112.
Con los cuatro nuevos componentes (BalloonHint, Buttone-
dEdit, CategoryPanelGroup, y LinkLabel), y además el so-
porte Ribbon y sus innumerables pequeñas mejoras, la VCL
ha visto una significativa actualización en Delphi 2009. Algu-
nas de estas actualizaciones son específicas para Windows XP
o Windows Vista y, sobretodo, aumentan el soporte para Vista
en la VCL desde Delphi 2007113.

Mejoras Fundamentales
de la VCL
Además de los diversos cambios y correcciones para compo-
nentes y controles específicos, tratados en este capítulo, hay
algunas mejoras introducidas en el núcleo de la VCL a nivel de
clase que benefician a todos y cada uno de los controles visua-
les de la librería.
Una de estas mejoras es el hecho de haber añadido la propie-
dad ParentDoubleBuffered en la clase TWinControl que hace
más fácil disponer de doble buffer de memoria para un for-
mulario completo o un grupo de controles. El doble buffering

112Siusted compara la VCL con la librería WinForms del .NET Framework o con
las principales librerías Java verá a qué me refiero. Uno de los elementos
clave de la VCL, es su estricta relación con la API de Windows, sin embargo,
este es también uno de sus puntos débiles, como portar fuera del mundo
Windows lo cual se ha demostrado difícil (como el proyecto CLX y en parte
como a demostrado la VCL para .NET).
113El
soporte de Vista en la VCL está cubierto al detalle en mi “Delphi 2007
Handbook”.

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 307

es útil para garantizar la correcta salida de un formulario que


tenga superficie de cristal en Vista (utilizando la propiedad
GlassFrame introducida en Delphi 2007) o en otras situaciones
en las que un programa esté realizando operaciones de filtros
con diferentes imágenes.
Por la misma razón, la propiedad correspondiente Dou-
bleBuffered se publica ahora en la mayoría de los controles
(aún más que en versiones anteriores).

Avisos y Globos Personalizados


La clase TControl introduce una nueva propiedad, Custom-
Hint, junto con la propiedad padre, ParentCustomHint, que
permite que los objetos hijos compartan el valor definido por
el control padre:
property CustomHint: TCustomHint
read GetCustomHint write SetCustomHint;
property ParentCustomHint: Boolean
read FParentCustomHint write SetParentCustomHint
default True;

Esta nueva propiedad le permite enganchar un aviso persona-


lizado a cualquier objeto de un componente visual, siempre
que sea un objeto de cualquier clase que herede de TCustom-
Hint. Una de estas clases es la introducida en Delphi 2009, la
clase TBalloonHint, un componente muy simple que agrega
muy poco a lo que la clase base TCustomHint ya proporciona.
La arquitectura de CustomHint, sin embargo, es más que dis-
poner solamente de soporte para el globo de hint personali-
zado, ya que usted puede añadir su propia clase de aviso per-
sonalizado y utilizarlo para cualquier control.

La guía de Delphi por Marco Cantù


308 - Indice

Lo que usted puede utilizar fuera-de-la-caja es un compo-


nente BalloonHint. Simplemente coloque este componente
no-visual en un formulario y engánchelo a la propiedad Cus-
tomHint de un control para cambiar la forma en que su men-

saje será mostrado. Puede ver un componente BalloonHint a


continuación:
Estos son los ajustes relacionados con el archivo DFM del
ejemplo HintsDemo:
object btnCustomHint: TButton
Hint = 'Este es un mensaje para el botón'
CustomHint = BalloonHint1
ShowHint = True
end
object BalloonHint1: TBalloonHint
Images = ImageList1
end

El componente BalloonHint utiliza el hint proporcionado por


el control al que esta enganchado. Cuando el usuario mueva el
ratón sobre el botón, el aviso se muestra de una forma mucho
más bonita que anteriormente:

Utilizando las propiedades ParentShowHint y ParentCustomHint


le permiten definir esta configuración en un panel y disponer
activo un globo para los hints de uno los controles hospeda-
dos en el panel. Por ejemplo, puede ver el control de Panel1
del proyecto HintsDemo.
Usted se podría haber percatado que en el extracto del DFM
anterior, del componente BalloonHint, que este dispone de
una propiedad Images, pero no se muestra ninguna. Una
forma de establecer otras propiedades en tiempo de ejecución
La guía de Delphi por Marco Cantù | para
Indice - 309

del componente BalloonHint, incluyendo el Title y el ImageIn-


dex, para así obtener un bonito aspecto personalizado, es in-
vocar el aviso manualmente, por ejemplo desde el evento On-
MouseEnter del control:
procedure TForm30.btnShowHintMouseEnter(Sender: TObject);
begin
BalloonHint1.Title := 'Hint Title';
BalloonHint1.ImageIndex := 1;
BalloonHint1.Description :=
'Este es un mensaje sugiriendo lo que el usuario puede
hacer';
BalloonHint1.HideAfter := 5000;
BalloonHint1.ShowHint;
end;

procedure TForm30.btnShowHintMouseLeave(Sender: TObject);


begin
BalloonHint1.HideHint;
end;

El aviso, visible en la imagen inferior, se ocultará después de 5


segundos, o tan pronto como el ratón salga del control:

Como esto exigiría una gran cantidad de trabajo, hay otra


forma más fácil para configurar el título y el índice de la ima-
gen del objeto custom hint personalizado relacionado con el
control. Desde los primeros días de Delphi, la propiedad Hint
le permite especificar un mensaje corto (utilizada como aviso)
y una versión más larga (en general para el mensaje en un
StatusBar) separados por el carácter de barra vertical (|).
Usando la asociación del hint personalizado, la propiedad
Hint ahora se interpreta así:
title|message|imageindex

Así, por ejemplo, en el proyecto HintsDemo he personalizado


un botón de la siguiente forma (el valor del hint es una sola
cadena):
object Button3: TButton
La guía de Delphi por Marco Cantù
310 - Indice

Hint =
'This is a button|' +
'This is a longer description for the button, ' +
'taking some space|2'
CustomHint = BalloonHint1
Caption = 'Button3'
end

Mejoras para
Componentes Estándares
Si hay algunas características nuevas que afecten a todos los
controles, la mayoría de las mejoras en la VCL de Delphi 2009
son específicas de controles individuales. El núcleo de los con-
troles estándar, así como los controles comunes, se han am-
pliado de una versión de Windows a otra por parte de Micro-
soft, mientras que la VCL ha descuidado a menudo el soporte
a estas nuevas características del nuevo sistema operativo, a
veces por las razones de compatibilidad hacia atrás (como
aplicaciones similares podrían tener problemas si se ejecutan
en versiones anteriores del sistema operativo, de las que en
cualquier caso Delphi ya ha dejado de dar soporte).
En esta sección voy a centrarme en las mejoras de algunos de
los controles básicos de Windows, tales como botones y cua-
dros de edición. En la siguiente sección me voy a centrar en
las mejoras (más limitadas) de los controles comunes.

Los Botones Adquieren Nuevas


La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 311

Características
Usted podría pensar que en el clásico Windows los botones de
pulsación están bien establecidos, y son controles estables.
Pero, en la actualidad, esto no es cierto. Desde Windows XP,
usted puede conectar la imagen de una lista de imágenes a un
botón, y tener un botón gráfico con mapa de bits, sin tener
que derivar un control personalizado auto-dibujado tal como
hizo Delphi desde los primeros días con el control BitBtn (bit-
map button). En Delphi 2009 usted puede obtener ahora el
mismo efecto gráfico con un simple y estándar TButton. El
soporte de ImageList se obtiene a través de una serie de pro-
piedades que puede utilizar para determinar qué imagen
quiere usar en cada uno de los varios estados del botón. Aquí
está la lista de la nueva imagen relacionada con las propieda-
des de la clase TCustomButton listada sólo con sus tipos:
property DisabledImageIndex: TImageIndex ...
property HotImageIndex: TImageIndex ...
property ImageAlignment: TImageAlignment ...
property ImageIndex: TImageIndex ...
property ImageMargins: TImageMargins ...
property Images: TCustomImageList ...
property PressedImageIndex: TImageIndex ...
property SelectedImageIndex: TImageIndex ...

Desde que esta característica fue introducida en la API Win32


en Windows XP, si su aplicación necesita ser ejecutada en
Windows 2000, deberá utilizarla con cuidado o evitar usarlos
juntos.
Del mismo modo, si su programa está destinado a ser ejecu-
tado en Vista, usted puede activar más características nuevas,
como el comando de enlace de estilo utilizado por muchos
cuadros de diálogo estándares del sistema operativo y estilos
de botón de relieve que le permiten conectar al botón un
menú desplegable, que se activa pulsando la pequeña flecha
hacia abajo. El diseño general del botón está determinado por

La guía de Delphi por Marco Cantù


312 - Indice

el valor de la nueva propiedad Style de tipo enumerado, defi-


nido como un tipo anidado114 de la clase TCustomButton:
type
TButtonStyle = (bsPushButton, bsCommandLink,
bsSplitButton);

Hay otras propiedades que usted puede usar, dependiendo del


estilo seleccionado:
 Con el estilo de botón Split (en la API, valor de estilo
BS_SPLITBUTTON) puede utilizar la propiedad DropDown-
Menu (de tipo TPopupMenu) y personalizarla en el evento
OnDropDownClick.

 En el caso del tipo de comando Link (el valor de estilo


BS_COMMANDLINK en la API) que puede utilizar el icono
por defecto (una flecha verde) o una imagen específica (tal
como se mencionó anteriormente), y proporcionar más infor-
mación sobre la acción con la nueva propiedad de cadena
CommandLinkHint.
Por último, la propiedad ElevationRequired aplicable tanto a
ambos, un botón estándar y a un comando Link, permite mos-
trar la protección de Windows que se utilizará si el botón con-
duce a una operación protegida UAC. La propiedad Elevation-
Required envía el mensaje BCM_SETSHIELD al botón.

El uso de todas estas nuevas propiedades pueden afectar a


cómo diseñe su aplicación de una forma bastante radical, aun-
que usted sólo puede obtener algunos de estos efectos de la
interfaz de usuario si la aplicación se ejecuta en Windows

114Los tipos anidados, introducidos en Delphi 2006, permiten definir un tipo


dentro de un tipo ya existente . Un tipo anidado está sujeto a la visibilidad de
las normas adoptadas por la clase de alojamiento que puede ser privada o
pública. En este caso, el tipo TCustomButton.TButtonStyle es público. Los
tipos anidados proporcionan una especie de espacio de nombre, como el
nombre completo que incluye el exterior de la clase. Para verlo en detalle, le
recomiendo mi libro “Delphi 2007 Handbook”.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 313

Vista (o versiones posteriores del sistema operativo). Estas


propiedades no son muy complejas de utilizar, puede ver en
detalle la descripción del ejemplo ButtonsDemo, simplemente
catalogaré sus elementos clave, después de mostrarle el for-
mulario en tiempo de diseño:

Este es el resumen del archivo DFM del proyecto:

object FormButtonsDemo: TFormButtonsDemo


object Button1: TButton
ImageIndex = 0
Images = ImageList1
PressedImageIndex = 1
end
object Button2: TButton
ImageIndex = 1
Images = ImageList1
PressedImageIndex = 2
end
object Button3: TButton
DropDownMenu = PopupMenu1
Style = bsSplitButton
end

La guía de Delphi por Marco Cantù


314 - Indice

object Button4: TButton


CommandLinkHint = 'This is a command link hint'
Style = bsCommandLink
end
object Button5: TButton
CommandLinkHint = 'Another hint'
ImageIndex = 1
Images = ImageList1
Style = bsCommandLink
end
object Button6: TButton
ElevationRequired = True
Style = bsCommandLink
end
object ImageList1: TImageList...
object PopupMenu1: TPopupMenu...
end

Etiquetas Brillantes y de Enlaces


Otro componente clásico que se ha ampliado, principalmente
con la introducción de una nueva clase que representa una
versión modificada del control de Microsoft , es el compo-
nente TLabel. La característica adicional de este componente
no es algo que pueda usarse en todas y cada una de las aplica-
ciones, ya que le permite añadir un efecto resplandor en las
etiquetas pintadas en un superficie de vidrio en Windows
Vista.
En Delphi se activa esta característica utilizando la nueva pro-
piedad GlowSize de la clase TLabel, que es un número entero
que utilizará básicamente como un valor booleano, y que si
usted pone cualquier valor superior a 0 parece producir exac-
tamente el mismo efecto: pintando un píxel de color blanco

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 315

bordeado alrededor de la etiqueta115. Es muy difícil ver la dife-


rencia entre una etiqueta estándar y una brillante impresa en
la pantalla del ejemplo LabelsDemo (que será mucho más
claro si ejecuta la demo):

En la parte inferior y superior del formulario puede ver dos


instancias del nuevo componente LinkLabel, una envoltura de
la clase de Windows SysLink. Este es un nuevo control que
sólo puede usarse en Windows XP o versiones posteriores (no
en Windows 2000, donde se vuelve a un control STATIC, que
es una etiqueta simple, como pueden ver en TCustomLinkLa-
bel.CreateParams).

Usted puede utilizar la propiedad UseVisualStyle para pasar


de un código de visualización estándar (como cualquier otra
etiqueta) a una UI (interfaz de usuario) más moderna, aunque
en este último caso parece que el control está ignorando la
fuente asignada. En ambos casos, usted puede utilizar la pro-
piedad Caption para especificar el texto incluido en un for-
mato de hipervínculo HTML (que es un enlace con el atributo

115Ladocumentación de la API de Windows para el campo correspondiente de la


estructura de datos DTTOPS utilizada para pintar los temas solicitados para
el control “el tamaño de un brillo que será utilizado de fondo antes que cual-
quier texto sea dibujado”, pero de nuevo en este caso el número parece no
tener efecto práctico. Otro elemento que no corresponde a la documentación
es que se puede realmente tener un brillo incluso cuando no se pinta sobre la
superficie de cristal, aunque en este caso, el efecto visual no es tan bonito.

La guía de Delphi por Marco Cantù


316 - Indice

href:no trate de utilizar otras etiquetas HTML, ya que serian


mostradas en el texto):
object LinkLabel2: TLinkLabel
Caption = 'A new link to <a
href="http://blog.marcocantu.com">my blog</a>'
UseVisualStyle = True
OnLinkClick = LinkLabel1LinkClick
end

Cuando el usuario haga click sobre el link, el control disparará


el evento OnLinkClick pasando la URL como parámetro:
procedure TFormLabelsDemo.LinkLabel1LinkClick(
Sender: TObject; const Link: string;
LinkType: TSysLinkType);
begin
ShowMessage ('Link clicked: ' + Link);
end;

Usted puede manejar este evento y arrancar el navegador por


defecto con una llamada ShellExecute, pasando la URL com-
pleta como parámetro.

Envoltura de Texto en
RadioGroup
El componente RadioGroup (la combinación personalizada de
un GroupBox con los actuales controles RadioButton) obtiene
una sola nueva característica, el soporte de envoltorio sobre la
palabra que los botones de radio contienen por si mismos.
El inconveniente en este caso es que el componente no será
capaz de calcular el espaciamiento vertical de los distintos ele-
mentos correctamente, y si usted tiene (por ejemplo) un ele-
mento, con tres líneas de texto, podría resultar que la parte
superior de la primera línea o la parte inferior de la última lí-
nea no sea visible.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 317

Un ejemplo de envoltura y uno de los problemas que esto


puede causar son visibles en el formulario RadioGroupDemo:

Edits Consigue Muchas


Características Nuevas
El control Edit es otro control clásico y estándar de Windows
que a lo largo de los años siempre se ha estado actualizando
(en particular, en Windows XP), que la VCL no trato correcta-
mente, aunque los programadores de Delphi podían habili-
tarlo directamente. Ahora algunas de estas características116
son de fácil acceso utilizando las nuevas propiedades de la
clase TEdit:
 La propiedad de alineación permite la alineación del texto en
un control de edición, una característica que anteriormente
sólo estaba disponible para los controles DBEdit (e imple-
mentado en el código nativo VCL, ya que no estaba disponible

116Paraobtener una lista detallada de los estilos de ventanas para un control Edit
a nivel de la API, consulte la Documentación del SDK en: http://msdn.mi-
crosoft.com/en-us/library/bb775464.aspx.

La guía de Delphi por Marco Cantù


318 - Indice

en las primeras versiones de la API de Win32). Estableciendo


el ajuste de alineación se activan los estilos de Windows
ES_LEFT, ES_RIGHT, o ES_CENTER, lo que eventualmente requiere
que el sistema recree la ventana de edición (por lo que usted
debería tratar de evitar cambiar esta propiedad en tiempo de
ejecución, una vez que el EditBox haya sido mostrado).
 La propiedad NumbersOnly establece el estilo de la
ES_NUMBER del control Edit, que requiere Windows XP o
superior. Esta aplica un filtro de entrada que impide al usua-
rio teclear dígitos no numéricos, permitiendo pegar texto no
numérico (y que el programa fije libremente la propiedad
Text).

 La propiedad TextHint soporta in-situ mostrar un texto de su-


gerencia cuando el cuadro de edición esté vacío (de nuevo esto
requiere Windows XP o posterior). El hint personalizado po-
dría actuar como el sustituto de una etiqueta descriptiva, o
mostrar la sugerencia a la acción del usuario.
 La propiedad PasswordChar le permite establecer una contra-
seña personalizando el char (sustituye los asteriscos por de-
fecto, en Windows XP, o puntos redondeados, en Windows
Vista) con un carácter o símbolo de su propia elección. Esta
función no sólo requiere Windows XP o posterior, sino tam-
bién una aplicación que utilice temas.
Estas propiedades están disponibles también en los compo-
nentes que se relacionan con el control Edit, como la La-
beledEdit (una combinación de un Edit y Label) y el clásico
control de la VCL MaskEdit. El control DBEdit, en cambio, no
proporciona las nuevas características de otros controles de
edición. En realidad, para ser más preciso, hereda las nuevas
características de la clase base TCustomEdit pero no se exponen
sus propiedades publicadas.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 319

En el siguiente tiempo de ejecución del ejemplo EditFamily-


Demo, puede ver algunas de estas características (y otras que
explicaré más tarde) en acción:
En la parte izquierda del formulario puede ver cuatro cuadros
de edición utilizando algunas de las nuevas características. La
primera tiene su texto alineado a la derecha, la segunda mues-
tra una sugerencia, la tercera permite sólo entradas numéri-
cas, y la cuarta utiliza el Unicode CodePoint 25A0 (Recuadro
Negro) como carácter para la contraseña. (Es bueno que
pueda usar cualquier símbolo Unicode como carácter de con-
traseña.)
Esta es la parte más relevante del archivo DFM, describiendo
las propiedades de estos cuatro controles:
object edRightAlign: TEdit
Alignment = taRightJustify
Text = 'Text on the right'
end
object edTextHint: TEdit
TextHint = 'Your name'
end
object edNumber: TEdit
NumbersOnly = True
Text = '3'
end
object edPassword: TEdit
PasswordChar = #9632
Text = 'password'
La guía de Delphi por Marco Cantù
320 - Indice

end

El botón tras el primer edit le permite cambiar la alineación


de la propiedad en una manera equitativa, al aumentar el va-
lor de la enumeración y calculando el módulo (el resto de la
división), con el valor más alto posible:
procedure TFormEditFamily.btnAlignClick(Sender: TObject);
begin
edRightAlign.Alignment := TAlignment (
(Ord(edRightAlign.Alignment) + 1) mod
(Ord(High(TAlignment)) + 1));
end;

Un componente estrictamente-relacionado117, el Memo, tiene


una nueva propiedad, CharCase, que le permite forzar el texto
a minúsculas o mayúsculas. El texto no es sólo mostrado con
un determinado estilo, sino que en realidad es convertido. Si
usted asigna un texto de tipo mixto al control y después extrae
el texto, usted recuperará la versión convertida, como las dos
primeras líneas de la demostración del siguiente gestor del
evento:
procedure TFormEditFamily.btnUpcaseClick(
Sender: TObject);
begin
memoLowercase.Lines.Text := 'Mixed Case Text Added';
ShowMessage (memoLowercase.Lines.Text);

memoLowercase.CharCase := ecUpperCase;
memoLowercase.Lines.Text := 'Cantù';
end;

La primera vez que se pulsa el botón, el texto en el cuadro de


mensaje se muestra en minúsculas. La segunda vez será en
mayúsculas. Incluso el carácter al final de mi apellido se con-
vertirá en una u mayúscula acentuada.

117Enel API de Windows, no hay diferencia entre un Edit y un Memo, ambos son
controles EDIT, se inician con una sola línea o con un estilo de línea múlti-
ple.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 321

ComboBoxes y Hints
En la API de Windows, los ComboBoxes tiene una relación es-
tricta con los Edit boxes, ya que consiguieron su nombre (y al-
gunas de sus implementaciones de código) de una combina-
ción de un Edit y un ListBox. Por lo que usted apenas debería
sorprenderse por ver una propiedad TextHint añadida tam-
bién a la clase TComboBox, al igual que en la clase TEdit. Una
vez más, esta función requiere Windows XP o una versión
más moderna de Windows118.
Hay un simple cuadro combinado con un texto personalizado
en el ejemplo EditFamilyDemo como usted puede ver en la úl-
tima imagen mostrada (un par de páginas atrás).

Nuevo Control ButtonedEdit


Un nuevo control que amplia el comportamiento del control
Edit, es el componente ButtonedEdit, que es un control perso-
nalizado de la VCL definido en la unidad ExtCtrls. Este es bá-
sicamente un cuadro de edición que puede tener unos peque-
ños botones en el lado izquierdo o derecho, que sirven para
interactuar con el cuadro de edición en sí. Por ejemplo, usted
puede agregar un botón Cancelar que vacíe la casilla de edi-
ción, y una búsqueda o un botón LookUp que valide la en-
trada o busque alguna información relacionada.

118Elcódigo interno utilizado para crear la personalización de texto para un Com-


boBox en Windows XP y Windows Vista es diferente, pero es la VCL quien lo
maneja por nosotros. Si está interesado en ampliar esta información vea las
dos secciones del método TCustomComboBox.DoSetTextHint para la versión
5.1 de Windows (XP) y 6 (Vista).

La guía de Delphi por Marco Cantù


322 - Indice

El IDE de Delphi utiliza este componente para la opción de


búsqueda de la Paleta Herramientas, como se puede ver a
continuación:

Este componente, que requiere XP o versiones posteriores de


Windows, incluye todas las características nuevas del control
Edit, con un aspecto-moderno como el texto hint. La configu-
ración de los botones de los lados del cuadro de edición es
bastante simple. El componente incluye las propiedades Left-
Button y RightButton, del tipo TEditButton, definidas así:
type
TEditButton = class(TPersistent)
published
property DisabledImageIndex: TImageIndex;
property DropDownMenu: TPopupMenu;
property Enabled: Boolean;
property HotImageIndex: TImageIndex;
property ImageIndex: TImageIndex;
property PressedImageIndex: TImageIndex;
property Visible: Boolean;
end;

Todas las referencias a la imagen están en el componente Im-


ageList que puede conectar al control ButtonedEdit. Usted
puede adjuntar un método al clic sobre cualquiera de los boto-
nes usando los eventos OnLeftButtonClick y OnRightButton-
Click del control ButtonedEdit; también se puede adjuntar un
menú Popup a los botones utilizando la propiedad DropDown-
Menu de la clase TEditButton.

En la demo ButtonEdits he codificado algunos escenarios de


uso muy simples, sólo para poder dar una idea de cómo se
puede trabajar con este componente. Mostrando, al mismo

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 323

tiempo, algunas de las otras características nuevas de los Edit-


Boxes. El formulario principal del ejemplo luce tres controles
ButtonedEdit, dos con un solo botón y uno con dos botones.
Los controles también tienen un texto hint y uno de ellos tiene
un menú desplegable asignado. Puede ver en la siguiente ima-
gen el formulario en tiempo de ejecución (con el menú desple-
gable activo):

El primer control es una caja de edición numérica con un bo-


tón deshacer:
object edUndo: TButtonedEdit
Images = ImageList1
NumbersOnly = True
RightButton.ImageIndex = 0
RightButton.Visible = True
TextHint = 'A number'
OnRightButtonClick = edUndoRightButtonClick
end

El evento edUndoRightButtonClick llama al método Undo del


control ButtonedEdit. El segundo control Edit dispone de dos
botones, uno para pegar desde el portapapeles, y el segundo
para borrar el contenido del cuadro de edición (por lo tanto,
restableciendo el texto de sugerencia):
object edClear: TButtonedEdit
Images = ImageList1
LeftButton.ImageIndex = 3
LeftButton.Visible = True
RightButton.ImageIndex = 1

La guía de Delphi por Marco Cantù


324 - Indice

RightButton.Visible = True
TextHint = 'Some text'
OnLeftButtonClick = edClearLeftButtonClick
OnRightButtonClick = edClearRightButtonClick
end

El tercer EditBox tiene un botón que actúa como histórico, y


guarda la línea de texto que se anota en la ventana, permi-
tiendo al usuario seleccionarlo de nuevo:
object edHistory: TButtonedEdit
Images = ImageList1
RightButton.DropDownMenu = PopupMenu1
RightButton.ImageIndex = 2
RightButton.Visible = True
TextHint = 'Edit or pick'
OnExit = edHistoryExit
end

El componente trabaja añadiendo el nuevo texto al menú po-


pup según el usuario abandona el texto comprobando que no
se encuentre en el menú, siempre que el texto no esté ya en el
menú:
procedure TFormButtonEdits.edHistoryExit(
Sender: TObject);
begin
if (edHistory.Text <> '') and
(PopupMenu1.Items.Find (edHistory.Text) = nil) then
begin
PopupMenu1.Items.Add (NewItem (edHistory.Text, 0,
False, True, RestoreText, 0, ''));
end;
end;

Los elementos predefinidos del menú, y cada nuevo elemento


del menú añadido dinamicamente, son conectados con el ma-
nipulador de eventos RestoreText que toma el título de los ele-
mentos del menú seleccionados, quita cualquier acceso rá-
pido, y los copia al EditBox:
procedure TFormButtonEdits.RestoreText(Sender: TObject);
begin
edHistory.Text := StripHotkey (
(Sender as TMenuItem).Caption);
end;

La guía de Delphi por Marco Cantù | para


Indice - 325

Actualizaciones de
Controles Comunes
Las mejoras de los controles estándares de la VCL de Delphi
2009 son sumamente importantes puesto que también están
relacionadas con los controles habituales de uso común como
los edits y los botones. La nueva versión de la VCL también
incluye muchas mejoras en los demás controles establecidos
por la API de Windows desde que esta mutó a la versión de 32
bits del sistema operativo. A continuación comentamos algu-
nos controles que tienen características nuevas:
 ImageList: Aunque a no todos los desarrolladores les benefi-
ciará esto, ahora el componente ImageList soporta imágenes
alpha, lo que le permite establecer una profundidad de color
personalizada, con una nueva propiedad y su correspondiente
nombre (ColorDepth).
 TreeView: Otro componente con escasos cambios en el pasado
es el TreeView, que ahora le permite definir un índice de ima-
gen para los nodos expandidos y (en Windows XP o poste-
rior) soporta también nodos de árbol deshabilitados.

Agrupaciones en un ListView
Un control común que merece la pena explorar con más deta-
lle es el ListView, que en Delphi 2009 recibe el soporte directo
para agrupación. Esta característica requiere Windows XP o
Vista, esta última con funciones ampliadas.
Hay tres nuevas propiedades en el control ListView. El opera-
dor Booleano GroupView permite este nuevo tipo de repre-
sentación, el GroupHeaderImages se refiere a un ImageList

La guía de Delphi por Marco Cantù


326 - Indice

conteniendo las imágenes para las cabeceras de grupo, y la


propiedad Groups es una colección de definiciones de grupo.
Cada grupo puede tener un título principal (Header), un icono
relacionado (TitleImage), una descripción (Subtitle), una lí-
nea de pie de página (Footer), además de algunos elementos
de texto y las propiedades de alineación de cabeceras y pies.
Un conjunto de opciones que le permite configurar el grupo
cómo colapsable, quitar la cabecera, ocultar el grupo, etcé-
tera119.
Puede ver un ejemplo de agrupación en un ListView en el for-
mulario principal de la aplicación GroupingList, que se mues-
tra a continuación en tiempo de diseño:

119Algunosde los elementos de texto adicional de los grupos se muestran sólo en


casos específicos, como cuando el grupo de cabecera está centrado (en cuyo
caso varios elementos podrían acabar sobrepuestos). Otras características
del ListView, al igual que el modo subgrupo, están lejos de ser evidentes de
activar. No estoy seguro de cuanto de esto se debe a la VCL y cuánto a la API
de Windows, pero mirando el SDK no hay casi documentación extendida so-
bre estas características... que probablemente envuelva a algún componente
algun tipo de funcionalidad supuesta.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 327

Esta es la definición de los grupos dentro del control ListView


(en formato DFM), al que he añadido un par descripciones su-
plementarias que se mostrarán sólo si usted centra las cabece-
ras del grupo:
object ListView1: TListView
Groups = <
item
Header = 'Arrows'
Footer = 'Footer: You can pick any of the arrows ' +
'for the caption'
GroupID = 0
State = [lgsNormal, lgsCollapsible]
HeaderAlign = taLeftJustify
FooterAlign = taLeftJustify
Subtitle = 'Subtitle: Arrow group subtitle'
TopDescription = 'Top Descr: A group of arrows'
TitleImage = 0
SubsetTitle = 'Subset title...'
end
item
Header = 'Houses'
Footer = 'Which house would you prefer?'
GroupID = 1
State = [lgsNormal, lgsCollapsible]
HeaderAlign = taLeftJustify
FooterAlign = taLeftJustify
Subtitle = 'Houses with different colors for ' +
'the roof...'
TitleImage = 1
ExtendedImage = -1
end>
GroupHeaderImages = ImgGroups
GroupView = True
end

El único código del ejemplo se utiliza para cambiar la alinea-


ción de la cabecera y el pie de página de cada grupo. Este es el
gestor de evento de uno de los tres botones de la barra de he-
rramientas:
procedure TFormGroupingList.tbRightClick(
Sender: TObject);
var
aGroup: TCollectionItem;
begin
for aGroup in ListView1.Groups do
begin
(aGroup as TListGroup).HeaderAlign := taRightJustify;
(aGroup as TListGroup).FooterAlign := taRightJustify;

La guía de Delphi por Marco Cantù


328 - Indice

end;
end;

Junto con el soporte de grupos, el control ListView tiene otro


evento nuevo sin relacionar, OnItemChecked, que se dispara
cuando un usuario selecciona un elemento del ListView.

Marquesina y más acerca de los


Controles ProgressBar
El componente ProgressBar es otro control habitual al que se
le ampliaron características en los últimos años que no esta-
ban directamente soportadas por la VCL. Si no está usando te-
mas en tiempo de ejecución puede establecer un color perso-
nalizado para la barra y su fondo, utilizando las propiedades
BarColor y BackgroundColor.
Una bonita característica, disponible en Windows XP y Vista,
es el estilo de marquesina de la barra de progreso, en el que se
sigue moviendo la barra para mostrar al programa trabajando
sin indicar una posición específica. Esta es una buena opción
para las situaciones en las que usted no sabe cuánto tiempo
tomará la operación, pero quiere indicar al usuario que debe
esperar, puesto que la operación solicitada se está llevando a
cabo. Para permitir esto, debe establecer la propiedad Style a
pbstMarquee y opcionalmente, cambiar el valor predetermi-
nado de la propiedad MarqueeInterval, para hacer que el ele-
mento gráfico se mueva más lento o más rápido. Como ejem-
plo, estos son los valores de la primera ProgressBar de la
demo SuperProgress:
object ProgressBar1: TProgressBar
Style = pbstMarquee
MarqueeInterval = 20
end

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 329

En Windows Vista, el control ProgressBar tiene una función


extendida, denominada smooth reverse. Si está estableciendo
la posición de progreso y avanza una gran cantidad de unida-
des, el control se moverá suavemente con una pequeña ani-
mación a la nueva posición. Al activar smooth reverse sucede
lo mismo si (inesperadamente) te mueves en sentido inverso:
en lugar de retroceder bruscamente se moverá gradualmente.
Este efecto no puede ser capturado en una imagen estática,
por lo que debería ejecutar la demo SuperProgress para ver
este efecto. El segundo y tercer controles de la demo difieren
sólo por su valor de la propiedad SmoothReverse. Hay tres
botones debajo de la barra de progreso que le permiten iterar
o moverse hacia adelante o atrás, para experimentar con el
efecto visual. El código es bastante trivial, así que lo he omi-
tido en el texto del libro.
Los tres botones radio del formulario principal del ejemplo le
permiten cambiar la propiedad de estado del segundo Pro-
gressBar. Se trata de otra nueva propiedad del control en Del-
phi 2009. Usted puede cambiar el estado por defecto a una
pausa o estado de error, y la barra de progreso verde cambia a
amarillo o rojo, respectivamente:
procedure TFormSuperProgress.RadioGroup1Click(
Sender: TObject);
begin
case RadioGroup1.ItemIndex of
0: ProgressBar2.State := pbsNormal;
1: ProgressBar2.State := pbsError;
2: ProgressBar2.State := pbsPaused;
end;
end;

Casillas de Verificación en una

La guía de Delphi por Marco Cantù


330 - Indice

Cabecera
Otro control común, aunque no usado con frecuencia, es el
componente HeaderControl. En la VCL de Delphi 2009, este
control soporta ahora secciones de cabecera de ancho fijo (uti-
lizando la propiedad NoSizing), cabeceras extras no visibles si
no se ajustan (utilizando su propiedad Overflow) y, más in-
teresante aún, casillas de verificación en las secciones de ca-
becera.
Para activar esta función usted tiene que conectarlo para el
control en su conjunto y después para cada sección individual,
según lo indicado por las propiedades del control (y su grupos
de secciones) en el ejemplo CheckBoxHeader:
object HeaderControl1: THeaderControl
Sections = <
item
AutoSize = True
CheckBox = True
Text = 'one'
end
item
AutoSize = True
CheckBox = True
Checked = True
Text = 'two'
end
item
AutoSize = True
CheckBox = True
Text = 'three'
end
item
AutoSize = True
CheckBox = True
Text = 'four'
end>
OnSectionCheck = HeaderControl1SectionCheck
CheckBoxes = True
end

Con estas propiedades para el control, el formulario del ejem-


plo (en tiempo diseño) es el siguiente:

La guía de Delphi por Marco Cantù | para


Indice - 331

Sin embargo, el problema con esta nueva característica, es


que el nuevo evento OnSectionCheck no se dispara. El método
del gestor interno del mensaje CNNotify intercepta los mensa-
jes específicos y actualiza correctamente la situación de la
propiedad Checked de la sección de encabezado, pero este pro-
cedimiento “setter" falla al activar el correspondiente
evento120.

RichEdit 2.0
El componente RichEdit se utiliza en Delphi para encapsular
la versión original del correspondiente control común. Para
compatibilizar una nueva versión del control, la VCL en Del-
phi 2009 usa ahora la versión 2 de la DLL específica que hos-
peda este control común:
RichEditModuleName = 'RICHED32.DLL'; // Delphi 2007
RichEditModuleName = 'RICHED20.DLL'; // Delphi 2009

En la práctica, esto significa una nueva versión más robusta,


pero no un montón de características nuevas. De hecho es
todo lo contrario, ya que el componente VCL no sufre cam-
bios. Esto significa que la mayor parte de su código anterior
en Delphi, basado en este control común, seguirá funcio-
nando, y la VCL se hará cargo de algunas diferencias internas

120Aparentemente es un error comunicado a Embarcadero, aunque con bastante


retraso en el ciclo de publicación.

La guía de Delphi por Marco Cantù


332 - Indice

entre las versiones. La única excepción se debe a la diferencia


en el formato del separador121 de línea.
Un elemento interesante es cómo RichEdit maneja texto Uni-
code y como guardar y cargar desde y hacia archivos Unicode.
El código añadido para este control en Delphi 2009 hace el
trabajo más o menos como cualquier otro TStringList. Usted
puede especificar una codificación al guardar, y si no especi-
fica una cuando se carga un conjunto el componente usa el
BOM para determinar el formato correcto. El formato por de-
fecto para guardar, sin embargo, sigue siendo el código de pá-
gina por defecto utilizado por el sistema operativo.
En el ejemplo UniRichEdit todo lo que he querido mostrar era
guardar el archivo actual como UTF-16, así que he añadido
dos botones al formulario con el siguiente código:
procedure TFormUniRichEdit.Button1Click(Sender: TObject);
begin
RichEdit1.Lines.SaveToFile('local.rtf',
TEncoding.Unicode);
end;

procedure TFormUniRichEdit.Button2Click(Sender: TObject);


begin
RichEdit1.Lines.LoadFromFile('local.rtf');
end;

El programa utiliza un componente de ActionList relleno con


algunas de las acciones estándares de edición, que han perma-
necido sin cambios (como prueba de que usted puede actuali-
zar fácilmente las aplicaciones existentes). Las distintas accio-
nes se exponen en una barra de herramientas. Si ejecuta el

121Unposible efecto (negativo) secundario de la adopción del control común de la


versión 2 de la edición de Windows Rich Edit es el causado por un cambio en
la forma de gestión del final de línea. Anteriormente, las líneas nuevas se re-
presentaban por la secuencia CR LF (#13 #10), mientras que en la nueva ver-
sión se representan como CR (#13). Si tiene un código existente que procesa
el texto del control o su selección actual, es posible que no funcione correcta-
mente, simplemente por este cambio.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 333

programa y carga el archivo por defecto que he añadido a su


carpeta, debe ver la salida como la siguiente:

Componentes Nativos de
la VCL
Después de examinar los controles estándar y los controles
comunes del API de Windows, envueltos por los controles co-
rrespondientes VCL, ha llegado el momento de centrarse en
las nuevas características de los controles VCL nativos y un
nuevo control, el componente CategoryPanels.
En particular, voy a comenzar por examinar algunas amplia-
ciones en la arquitectura del Action Manager, dedicar algún
tiempo a los paneles, a algunos componentes globales de la
VCL, y echar un vistazo a la ampliación del soporte a los gráfi-
cos, que ahora incluye Imágenes PNG, para ambos compo-
nentes, Image e ImageList.

La guía de Delphi por Marco Cantù


334 - Indice

Los Componentes Action


Manager
Un conjunto de los componentes nativos que han visto algu-
nas mejoras menores son los de la arquitectura122 del Action
Manager. El componente ActionManager tiene tres nuevas
propiedades para imágenes grandes, imágenes deshabilitadas,
y las dos, grandes y deshabilitadas.
El PopupActionBar tiene soporte para los estilos de action
bar, utilizando el nueva propiedad Style. Todo el conjunto de
menús de acción y barras de herramientas cuenta con el so-
porte de temas mostrados en Windows XP o posteriores. Si
establece la plataforma defaultstyle para el ActionManager,
el componente visual asociado recogerá el aspecto de la ver-
sión actual del sistema operativo en tiempo de ejecución.

Sobre Paneles
Hasta hace unos años, el componente Panel era un compo-
nente utilizado comúnmente con funciones limitadas, aunque
soportase el acoplamiento. En Delphi 2006, tuvo un significa-
tivo rediseño, con la llegada de los paneles que no utilizan po-
sicionamiento absoluto de los controles que contienen, sino

122Elpapel de la arquitectura del ActionManager y de los componentes relaciona-


dos va a aumentar considerablemente a partir de Delphi 2009 gracias a la
conexión con los componentes Ribbon, tratados en el Capítulo 9.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 335

una norma específica de posicionamiento. Estos paneles espe-


ciales son el control FlowPanel y el control123 GridPanel. To-
dos estos controles se benefician de una pequeña novedad, ya
que ahora puede desactivar el título (casi generalmente inútil)
del panel, al asignar la nueva propiedad ShowCaption a False
en lugar de establecer la propiedad Caption a una cadena va-
cía.
Quizás se pregunte si era realmente necesario, ya que nos las
hemos arreglado para vivir sin esta característica durante tan-
tos años, pero el hecho es que los paneles pueden ser alojados
por un nuevo control personalizado VCL, llamado Cate-
goryPanelGroup. Este panel de contenedores utiliza el título
del panel hospedado como título propio, por lo que el valor de
Caption se necesita para identificar los paneles, incluso
cuando usted no quiera que este sea visible.

El Nuevo Control
CategoryPanelGroup
Una familia de componentes para los que hemos visto el ma-
yor número de controles VCL disponibles en los últimos años
ha sido la denominada barra lateral de la familia Outlook Si-
debar, imitando así esta interfaz que estableció originalmente
Microsoft en su programa de correo electrónico.
En las aplicaciones modernas, los estilos han cambiado mu-
cho desde la colección original de grandes iconos utilizados
para las diversas secciones del programa, pero el uso de una

123Una vez más, se puede hacer referencia a mi “Delphi 2007 Handbook” para
obtener más información acerca de estos controles. Perdone que me refiera a
mi anterior libro, pero hay poca información pública relativa a estos contro-
les... y mi anterior libro realmente complementa esta información.

La guía de Delphi por Marco Cantù


336 - Indice

barra lateral con las opciones y comandos continúa. Por pri-


mera vez, Delphi 2009 ofrece un componente similar fuera de
la norma.
El control visual CategoryPanelGroup es un contenedor de
controles CategoryPanel. Puede crear estos paneles de catego-
rías utilizando el menú del CategoryPanelGroup en tiempo de
diseño o llamando a su método CreatePanel en tiempo de eje-
cución. Cada elemento CategoryPanel se refiere su contenedor
usando su propiedad PanelGroup, mientras que el grupo tiene
una propiedad Panel (un TList mínimo de punteros) o una
lista de controles hijos, en la propiedad estándar Controls.
Si prueba a añadir cualquier otro dispositivo de control direc-
tamente a la CategoryPanelGroup, el IDE le mostrará el error
“Only CategoryPanels can be inserted into a CategoryPanel-
Group”. Por supuesto, una vez que haya definido algunos Ca-
tegoryPanels puede añadirles prácticamente cualquier tipo de
control que le guste. Aquí está la interfaz de usuario de este
control a partir de la demostración CategoryPanels:

El grupo de control y cada uno de los paneles tienen una gran


cantidad de propiedades que puede utilizar para personalizar

La guía de Delphi por Marco Cantù | para


Indice - 337

la interfaz de usuario, la gestión de las cabeceras con múlti-


ples imágenes en función de su estado colapsado o expandido,
activar degradados de fondos para las cabeceras, cambiar la
fuente y los colores de los símbolos Chevron, y mucho más.
Estos son los ajustes de los paneles anteriores (a partir de los
que he eliminado detalles de los controles hospedados):
object CategoryPanelGroup1: TCategoryPanelGroup
VertScrollBar.Tracking = True
HeaderFont.Color = clWindowText
HeaderFont.Name = 'Tahoma'
Images = ImageList1
object CategoryPanel1: TCategoryPanel
Caption = 'CategoryPanel1'
CollapsedImageIndex = 0
ExpandedImageIndex = 0
object Button1: TButton...
object Button2: TButton...
end
object CategoryPanel2: TCategoryPanel
Caption = 'CategoryPanel2'
Collapsed = True
CollapsedImageIndex = 2
ExpandedImageIndex = 1
object CheckBox1: TCheckBox...
object CheckBox2: TCheckBox...
object CheckBox3: TCheckBox...
end
object CategoryPanel3: TCategoryPanel
Caption = 'CategoryPanel3'
object GridPanel1: TGridPanel
Align = alClient
Caption = 'GridPanel1'
ControlCollection = <...>
ShowCaption = False
object Button3: TButton...
object Button4: TButton...
object Button5: TButton...
object Button6: TButton...
end
end
end

Si nos fijamos en las imágenes de cabecera, el primer panel


utiliza la misma para ambos estados, el segundo utiliza dos
imágenes diferentes para su estado expandido o contraido,
mientras que el tercero no tiene ninguna imagen y utiliza los

La guía de Delphi por Marco Cantù


338 - Indice

símbolos de galón Chevron predeterminados. El tercer Cate-


goryPanel no hospeda sus controles directamente, pero tiene
un GridPanel (con 4 botones) alineados a la totalidad de su
espacio. Este es un ejemplo de cómo usted puede combinar
un CategoryPanel con paneles proporcionando un posiciona-
miento personalizado. El programa tiene un poco de código.
Un primer botón se utiliza para añadir un nuevo CategoryPa-
nel dinamicamente en el grupo y colocar un botón sobre este:
procedure TFormCategoryPanels.btnAddCategoryClick(
Sender: TObject);
var
newPanel: TCategoryPanel;
begin
newPanel := CategoryPanelGroup1.CreatePanel(self)
as TCategoryPanel;
NewPanel.Caption := 'Dynamic Panel';
with TButton.Create(self) do
begin
Caption := 'New button';
Parent := NewPanel;
SetBounds (10, 10, Width, Height);
end;
end;

Observe que el método CreatePanel del control CategoryPan-


elGroup devuelve un TcustomCategoryPanel genérico, así que
tuve que convertir el tipo al tipo específico TCategoryPanel
para referirme a la propiedad Caption. La razón de este apa-
rentemente extraño comportamiento, es que el control de
grupo de paneles de categoría crea un objeto de una clase, que
usted puede configurar en una clase heredada sobrecargando
la función virtual GetCategoryPanelClass. Sin embargo, esto
es un poco inusual, ya que la mayoría de los demás controles
VCL le permiten personalizar la clase de un objeto interno re-
ferido a una clase específica y no a una clase base parcial-
mente definida, como en este caso. Aparentemente, este enfo-
que hace al control más personalizable, ya que puede tener
una implementación de clase diferente no exponiendo algu-
nas de las propiedades de clase base, pero también hace que el
código asuma el tipo dado (y lo que tiene que asumir es un
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 339

tipo capaz de utilizar los paneles individuales, como en el có-


digo anterior), muy propenso a errores. El código anterior fa-
llaría en caso de una clase del panel de categoría.
Un segundo botón muestra las dos formas diferentes de los
paneles de categoría existentes de un grupo que he mencio-
nado anteriormente, la matriz de controles genéricos o la es-
pecífica (pero con menos salvaguarda del tipo) propiedad Pan-
els. El primero, de hecho, es una matriz de controles, mien-
tras que el segundo es una lista de punteros124:
procedure TFormCategoryPanels.btnListPanelsClick(
Sender: TObject);
var
I: Integer;
begin
ListBox1.Clear;
for I := 0 to CategoryPanelGroup1.ControlCount - 1 do
ListBox1.Items.Add (
(CategoryPanelGroup1.Controls[I] as TCategoryPanel).
Caption);
for I := 0 to CategoryPanelGroup1.Panels.Count - 1 do
ListBox1.Items.Add (
TCategoryPanel(CategoryPanelGroup1.Panels[I]).
Caption);
end;

Actualización de
El componente TrayIcon, introducido por primera vez en Del-
phi 2007, tiene un nuevo evento OnBalloonClick, utilizado
para la manipulación del clic sobre el área del globo de ayuda,
cuando esta sea visible. Para mostrar esto en la práctica he
ampliado el ejemplo MyTrayIcon de Delphi 2007 Handbook

124Esto habría sido un buen caso para usar una lista genérica o lista de objetos,
en lugar de un simple TList, pero como esto se añade en paralelo a las nue-
vas características del compilador, no es sorprendente que no dependa de
ellos.

La guía de Delphi por Marco Cantù


340 - Indice

en el nuevo MyTrayIconClick, que tiene la siguiente definición


en el DFM para su formulario principal:
object FormMyTrayIconClick: TFormMyTrayIconClick
object TrayIcon1: TTrayIcon
BalloonHint = 'sample balloon hint'
BalloonTitle = 'hi'
BalloonTimeout = 1000
BalloonFlags = bfInfo
Icon.Data = {...}
PopupMenu = PopupMenu1
Visible = True
OnBalloonClick = TrayIcon1BalloonClick
OnMouseDown = TrayIcon1MouseDown
end
object PopupMenu1: TPopupMenu...
end

Si bien el manejador de evento TrayIcon1MouseDown muestra el


globo de sugerencia, el método TrayIcon1BalloonClick muestra
un simple cuadro de mensaje, sólo para probar que el maneja-
dor de evento funciona.

Fuentes por defecto para la


Aplicación y Objetos Globales en
Pantalla
Las aplicaciones Delphi empiezan con un tipo de letra predefi-
nida por defecto, fijado en tiempo de diseño. Esto es proble-
mático cuando es posible que usted desee que el mismo ejecu-
table funcione sin problemas en distintas versiones de Win-
dows, como XP y Vista, que utilizan diferentes tipos de letra
por defecto.
El objeto global Application en Delphi 2009 puede proporcio-
nar una fuente estándar a todos los formularios que tengan
ParentFont con valor True. Para lograr esto se puede estable-
cer la propiedad DefaultFont para el objeto Application. Esto

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 341

puede utilizarse, por ejemplo, para comprobar y migrar el as-


pecto de una aplicación a Vista, estableciendo la fuente de
cada formulario a Segoe UI más fácilmente que en el pasado.
Observe que esta propiedad, así como el estado de la configu-
ración de ParentFont esté seleccionado cuando el formulario
se cree, ya que no produce ningún efecto si lo cambia en
tiempo de ejecución.
Es por eso que en el ejemplo AppFont he añadido la defini-
ción de fuente en el archivo de código fuente del proyecto (la
función CheckWin32Version se trata más adelante en la sec-
ción “Soporte de Vista Ampliado”):
begin
Application.Initialize;
if CheckWin32Version(6) then // at least Vista
begin
Application.DefaultFont.Name := 'Segoe UI';
Application.DefaultFont.Size := 9;
end;
Application.MainFormOnTaskbar := True;

En este punto todo lo que tuve que hacer fue activar la propie-
dad de ParentFont en los dos formularios en tiempo de di-
seño. Dos botones, uno en cada formulario, muestran su nom-
bre de fuente, como prueba de que esta técnica funciona (aun-
que probablemente pueda ver los diferentes tipos de fuentes
en estos formularios). Estas son las propiedades del formula-
rio secundario:
object FormSecondary: TFormSecondary
Caption = 'Secondary'
ParentFont = True
Visible = True

Ahora que los formularios tienen la propiedad establecida en


ParentFont a True, podemos continuar y cambiar dinámica-
mente la fuente en todos los formularios escribiendo código
como:
procedure TFormAppFont.btnChangeFontClick(
Sender: TObject);
begin

La guía de Delphi por Marco Cantù


342 - Indice

Application.DefaultFont.Name := 'Times New Roman';


Application.DefaultFont.Size := 10;
end;

Del mismo modo, el objeto global Screen ahora se pueden


usar para personalizar la fuente que Delphi utiliza por defecto
en cuadros de mensajes nativos, gracias a la nueva propiedad
MessageFont. Después de configurar este valor, usted podrá
llamar a las rutinas como ShowMessage y MessageDlg y obte-
ner un formulario que utilice el tipo de letra determinado.
Este será el caso, sin embargo, sólo si las llamadas no son re-
dirigidas a los nuevos diálogos de tarea introducidos por
Vista. En otras palabras, esta propiedad MessageFont sólo tiene
efecto si no se está ejecutando en Vista, o si se está ejecutando
en Vista y desactiva la variable global UseLatestCommonDi-
alogs.
Ambos casos quedan demostrados por el manejador del
evento en el siguiente botón, parte de la aplicación AppFont:
procedure TFormAppFont.btnScreenFontClick(
Sender: TObject);
begin
Screen.MessageFont.Name := 'Segoe UI';
Screen.MessageFont.Size := Screen.MessageFont.Size + 2;
UseLatestCommonDialogs :=
cbUseLatestCommonDialogs.Checked;
ShowMessage ('Hello');
MessageDlg ('Hello', mtINformation, [mbOK], 0);
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 343

Si está ejecutando este programa en Vista, cambiando el es-


tado del checkbox, se puede ver un cuadro de mensaje perso-
nalizado con una fuente cada vez mayor o una tarea estándar

de diálogo, con la fuente por defecto del sistema. Este es un


ejemplo de esta fuente grande en un cuadro de diálogo:

Mejoras en Soporte de Gráficos


En sus primeros días, el soporte gráfico de Delphi se limitaba
a mapas de bits. A lo largo de los años, se han aumentado los
formatos de imagen que puede usar en el componente Image,
incluyendo soporte125 al formato JPEG. En Delphi 2009, el so-
porte para múltiples formatos de imagen se ha ampliado a
PNG y todos los formatos se pueden utilizar ahora con el con-
trol Image, así como con el control ImageList.

125Técnicamente, tenga en cuenta que todo lo relacionado con el soporte JPEG


(específicamente el de la unidad Jpeg.pas) se ha trasladado a un nuevo pa-
quete, llamado vclimg.bpl. Si usted tenía referencias a otros paquetes, tendrá
para actualizarlos manualmente.

La guía de Delphi por Marco Cantù


344 - Indice

Además, el control ImageList soporta formatos con niveles de


color específico, aunque aumentando su valor, se borrarán to-
das las imágenes de la lista actual. También ha habido mejo-
ras en el editor de ImageList y soporte de canal alfa (transpa-
rencias).
La clase TBitmap soporta ahora el canal alfa, utilizando la
nueva propiedad AlphaFormat, mientras que la clase TGraphic
tiene soporte de transparencia en imágenes usando la propie-
dad SupportsPartialTransparency.
Como hay muchos cambios, he recogido algunos destacables
en el programa GraphicTest, comenzando con el cambio más
significativo, que es el
soporte nativo para varios formatos, incluyendo PNG (que es
nuevo). El soporte para estos formatos proviene de un con-
junto de unidades que definen las clases heredadas de
TGraphic que usted puede incluir selectivamente en su aplica-
ción. Estas son las unidades y clases:
For- Unit Class
mat
JPEG jpeg.pas TJPEGImage

GIF GIFImg.pas TGIFImage

PNG pngimage.p TPngImage


126 as

126Elsoporte de imágenes PNG en Delphi 2009 viene del proyecto pngdelphi de


Gustavo Daud, inicialmente disponible en Source Forge. El soporte GIF, que
está disponible desde hace tiempo, se basó en la versión de Anders Melan-
der. Esto de alguna manera, explica las diferencias en los nombres de estas
unidades.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 345

Simplemente incluyendo la correspondiente unidad, se puede


cargar directamente un archivo de estos formatos (más el es-
tándar de mapa de bits, iconos, y el formato Metafile) en un
componente de Image. Como el formato es determinado por
la extensión del archivo, usted puede cargar fácilmente fiche-
ros127 con diferentes formatos gráficos con un código sencillo
(parte del ejemplo GraphicsTest) como este:
procedure TFormGraphicsTest.btnLoadImageClick(
Sender: TObject);
var
strFilename: string;
begin
case fImgNo of
0: strFilename := 'adog.jpg';
1: strFilename := 'Athene.png';
2: strFilename := 'CodeGear.gif';
end;
Image1.Picture.LoadFromFile(strFileName);
fImgNo := (fImgNo + 1) mod 3
end;

El programa tiene también el código para crear un mapa de


bits vacío en memoria. Un usuario puede dibujar en este tipo
de mapa de bits al mover el ratón sobre el control de la ima-
gen. El mapa de bits se pueden guardar en los tres formatos
diferentes. Por ejemplo, el código para guardar el archivo en
formato JPEG es el siguiente:
var
jpgImg: TJPEGImage;
begin
jpgImg := TJPEGImage.Create;
try
jpgImg.Assign(Image1.Picture.Graphic);
jpgImg.SaveToFile('test.jpg');
finally
jpgImg.Free;
end;

127Elformato de archivo se determina observando la extensión de archivo, de


acuerdo con un conjunto de registros internos. No hay ninguna comproba-
ción real de los datos de la imagen para determinar su formato.

La guía de Delphi por Marco Cantù


346 - Indice

Para evitar la repetición de este código para los formatos PNG


y GIF, he escrito una simple rutina para gestionar las diversas
diferencias:
procedure SaveWithClass (graph: TGraphic;
graphClass: TGraphicClass; const strFilename: string);
var
grapImg: TGraphic;
begin
grapImg := graphClass.Create;
try
grapImg.Assign(graph);
grapImg.SaveToFile(strFilename);
finally
grapImg.Free;
end;
end;

Esto sólo funciona con la configuración por defecto, aunque


necesitará utilizar la clase especifica descendiente de
TGraphic para activar su nivel de compresión y otras opciones
específicas para el formato dado. En el programa de demos-
tración, esta rutina se llama de esta forma:
SaveWithClass (Image1.Picture.Graphic,
TPngImage, 'test.png');
SaveWithClass (Image1.Picture.Graphic,
TGIFImage, 'test.gif');

El soporte para múltiples formatos de imagen no se refiere ex-


clusivamente al componente Image, sino que se ha ampliado
también al componente ImageList.
Esto significa que dispone ahora, por ejemplo, de listas de
imágenes basadas en PNG. Ya he utilizado, anteriormente en
otras demostraciones de este capítulo, un ImageList en el que
he cargado imágenes PNG de la librería con la licencia de
Embarcadero ya incluida en Delphi (e instalada, por defecto,
en el directorio \Archivos de programa\Archivos comu-
nes\CodeGear Shared\Images\GlyFX).
Si usted está interesado en hacer esto, y tiene necesidad de
manejar el color transparente, (algunas de las imágenes de la

La guía de Delphi por Marco Cantù | para


Indice - 347

biblioteca GlyFX lo requieren si desea utilizarlas para boto-


nes, List Views, Tree Views y la mayoría de los controles vi-
suales), tiene que establecer la propiedad ColorDepth to
cd32Bit antes de cargar las imágenes. Tenga cuidado ya que
establecer esta propiedad limpia el ImageList. Como ejemplo,
en el programa GraphicsTest hay un componente ImageList
con los siguientes valores:
object ImageList1: TImageList
ColorDepth = cd32Bit
Bitmap = {}
end

Si no hace este paso antes de cargar las imágenes, estas ten-


drán un fondo negro que es muy difícil de eliminar con el có-
digo (e imposible de quitar cuando captura las imágenes del
ImageList a otros componentes visuales). Si intenta hacerlo
desde el código de programa después de cargar las imágenes,
simplemente borrará la lista de imágenes.
Si desea acceder a los distintos elementos del ImageList,
puede hacerlo llamando, entre otros, al método Draw, como
en:
ImageList1.Draw(Image2.Canvas, 10, 10, 0);
ImageList1.Draw(Image2.Canvas, 10, 30, 1);

Puede pasar parámetros adicionales al método Draw, indi-


cando un estilo de dibujo (para generar las imágenes seleccio-
nadas e imágenes según reciban el foco a partir de base) y ele-
gir así dibujar una máscara o una imagen real:
ImageList1.Draw(Image2.Canvas, 30, 10, 4,
dsSelected, itImage);
ImageList1.Draw(Image2.Canvas, 30, 30, 4,
dsTransparent, itImage);
ImageList1.Draw(Image2.Canvas, 30, 50, 4,
dsFocus, itImage);
ImageList1.Draw(Image2.Canvas, 30, 70, 4,
TDrawingStyle.dsNormal, itImage);

Compruebe como la última llamada hace referencia explícita


al tipo enumerado TDrawingStyle, ya que existe un valor

La guía de Delphi por Marco Cantù


348 - Indice

dsNormal,parte de la enumeración TBandDrawingStyle de la


unidad ExtCtrls, incluida en el proyecto, ya que define el com-
ponente Image.
Los dos fragmentos de código son parte de una rutina utili-
zada para mostrar elementos de un ImageList en un compo-
nente Image, que produce el siguiente resultado:

Como anotación al margen, mientras que al hablar del


componente de imagen, su compañero dataaware el compo-
nente TDBImage, utilizado para la visualización de las imáge-
nes extraídas de base de datos de campos BLOB, ahora so-
porta un “mejor ajuste” de visualización de la imagen cuando
establezca su propiedad Proportional.

El Portapapeles y Unicode
El objeto global Clipboard es un envoltorio bastante simple de
la correspondiente API, definido en la unidad ClipBrd. Una
extensión importante de este componente es la forma en que
maneja las cadenas Unicode. Cuando pegue texto en el porta-
papeles, de hecho, el envoltorio Delphi ahora asocia el
CF_UNICODETEXT al formato de datos del portapapeles, en
lugar del formato clásico CF_TEXT.
Esto queda demostrado en el sencillo programa Uni-
Clipboard, que puede añadir el contenido de un cuadro de
texto al Portapapeles (conteniendo un texto simple o dos ca-
racteres Japoneses) y pegarlos después de comprobar que los

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 349

formatos del portapapeles están actualmente asociados con


los datos disponibles del Portapapeles (ya que pueden ser más
de uno). A continuación muestro el código correspondiente:
procedure TFormUniClipboard.btnCopyClick(
Sender: TObject);
begin
Clipboard.Open;
Clipboard.AsText := Edit1.Text;
Clipboard.Close;
end;

procedure TFormUniClipboard.btnPasteClick(
Sender: TObject);
begin
Clipboard.Open;
if Clipboard.HasFormat(CF_TEXT) then
Memo1.Lines.Add('CF_TEXT');
if Clipboard.HasFormat(CF_UNICODETEXT) then
Memo1.Lines.Add('CF_UNICODETEXT');
Memo1.Lines.Add(Clipboard.AsText);
Clipboard.Close;
end;

Soporte de Vista Ampliado


En este capítulo hemos visto que un buen número de nuevas
propiedades y características sólo se pueden utilizar en la ver-
sión de Vista (o posterior) del sistema operativo Windows.
Creo que vale la pena hacer un breve resumen en esta última
sección.
Antes de hacerlo, sin embargo, observe que la VCL utiliza una
nueva función de soporte especial, CheckWin32Version, que
devuelve True si la versión del sistema operativo es igual o su-
perior a la versión pasada como parámetro (con uno o dos nú-
meros enteros para la versión principal y, opcionalmente, uno
menor). Esta función ya estaba en Delphi 2007, pero la me-
nosprecié en mi libro (y tampoco se ve tanto en el código
fuente VCL).
La guía de Delphi por Marco Cantù
350 - Indice

Como ejemplo, vea el siguiente fragmento, que he añadido al


programa UniClipboard (aunque estén sin relación completa-
mente):
if CheckWin32Version(6) then
ShowMessage ('Running on Windows Vista or later');
if CheckWin32Version(5) then
ShowMessage ('Running on Windows 2000 or later');
Con Delphi 2007 la VCL introdujo el soporte de Vista, proveía
tareas de diálogos (con la redirección de los mensajes están-
dares y el componente TaskDialog), la propiedad GlassFrame,
mejoraba los cuadros de diálogo comunes (de nuevo con nue-
vos componentes y una reorientación parcial de los ya exis-
tentes), las mejoras en soporte de temas en la gestión del for-
mulario principal minimizado, con la propiedad MainFor-
mOnTaskbar de objeto global Application.
En lo más alto de estas características, que ya hacen que los
programas se integren sin problemas con la última versión del
sistema operativo Windows, Delphi 2009 incluye las siguien-
tes características específicas para Vista:
 La propiedad GlowSize del componente Label.
 La elevación, vínculo de comandos, y soporte de menú desple-
gable del control Button.
 El control ProgressBar soporta el movimiento inverso suave.
 Sugerencias (hints) para el componente ComboBox.
 Algunas características ampliadas de agrupación del control
ListView.
 Ventanas de sugerencias temáticas.
Por último, una característica útil para Vista es el nuevo ob-
jeto global Application a fin de establecer la fuente predeter-
minada para ser usada por cada formulario de la aplicación.
Esto es importante ya que Vista tiene una nueva fuente (Segoe
UI), a diferencia de Windows XP (y versiones anteriores).
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 351

A continuación
En esta sección he explorado innumerables mejoras en los
controles clásicos VCL en Delphi 2009, en parte relacionadas
con las nuevas características del sistema operativo. También
se muestra un ejemplo de un nuevo componente, el Cate-
goryPanelGroup.
Este no es el único nuevo componente visual de la VCL, sino
todo lo contrario. Una notable ampliación de la VCL son el
conjunto de controles nativos de Delphi 2009 para dar so-
porte a la interfaz de usuario Ribbon. Este tema es tan impor-
tante que he decidido dedicar un capítulo específico, en lugar
de abordar toda su amplitud en este capítulo de actualizacio-
nes VCL.
Antes de llegar al control Ribbon, sin embargo, existe otra
área de la VCL que fue modificada de manera significativa en
Delphi 2009: El soporte COM. Como veremos en el siguiente
capítulo, hay una introducción de un lenguaje de definición de
interfaz (RIDL), un papel diferente para la librería de archivos
de tipos, un nuevo panel del IDE, y asistentes actualizados.
Para los que usan COM en su arquitectura, estas son unas no-
ticias muy positivas.

La guía de Delphi por Marco Cantù


352 - Indice

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 353

Capítulo 9:
Soporte COM En
Delphi 2009

Durante muchos años, la tecnología de Modelo de Objetos de


Componentes (COM) ha estado en la base del sistema opera-
tivo Windows, como una forma de permitir que las aplicacio-
nes hablasen con el SO y a los programas entre si. COM es
para Windows el único modelo de objetos que funciona de
forma nativa a través de distintos lenguajes. El hecho de que
la programación COM estuviera lejos de ser fácil y que pro-
porcionara unos fundamentos no tan sólidos, ha sido una de
las razones que ha mencionado Microsoft para abandonarla y
pasar a un modelo de objetos gestionados como el de la plata-
forma .NET de Microsoft.
A pesar de que Microsoft dijese que COM estaba obsoleto
cuando anunció .NET, la tecnología COM todavía sigue siendo
muy importante en el uso de muchas aplicaciones Windows y
está lejos de desaparecer. COM proporciona también una
forma sencilla para que las aplicaciones Win32 trabajen junto
con. NET. En cualquier caso, no quiero ahondar aquí en la

La guía de Delphi por Marco Cantù


354 - Indice

historia de COM o en una comparación con .NET. Sólo quiero


centrarme en el hecho de que, aunque no creciera y no se uti-
lice más para la comunicación entre diferentes ordenadores,
COM se encuentra todavía en el corazón de muchos progra-
mas Windows. Además, tradicionalmente Delphi hace muy
fácil escribir servidores COM y utilizar los ya existentes, por lo
que muchos desarrolladores Delphi se basan en esta tecnolo-
gía.
Dada esta introducción, es relevante señalar que existen im-
portantes cambios en el soporte COM proveído por Delphi
2009. En particular, el papel de las bibliotecas de tipos ha
sido minimizado y hay un nuevo tipo de archivos de código
fuente, ficheros restringidos IDL, que asumen un papel cen-
tral para el desarrollo COM en esta versión de Delphi.

IDL, Librerías Tipo, y RIDL


Originalmente, la definición de los objetos COM se basa en
una Interfaz de Definición de Lenguaje (IDL) especificado por
Microsoft (y no muy diferente del CORBA en uso). Con la lle-
gada de Visual Basic, sin embargo, Microsoft necesitó un nivel
superior, más simple, orientado al formato visual, por lo que
comenzó a usar las bibliotecas de tipo, un recurso en formato
binario compilado en servidores al que poder preguntar por
las clases expuestas por el servidor y sus métodos. Las Biblio-
tecas de Tipos se convirtieron, de facto, en un estándar, aun-
que su formato nunca se especificara en detalle. Delphi co-
menzó a confiar en este tipo de bibliotecas desde sus primeros
días, tanto para la importación de la especificación de un ser-
vidor que tiene un tipo de biblioteca como para crear un servi-
dor. En un servidor, las bibliotecas de tipos se utilizan en

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 355

tiempo de ejecución para inicializar los registros internos de


la arquitectura128 de la VCL.
En Delphi 2009 las bibliotecas de tipos se han minimizado,
aunque todavía son parte de la arquitectura. Al crear un
nuevo servidor COM, se utiliza el editor de la biblioteca de
tipos, pero acaba creando un archivo de código fuente Re-
stringido IDL (RIDL). El archivo RIDL se utiliza tanto para la
generación de la biblioteca de tipos (algo que puede hacer ma-
nualmente mediante la invocación de la nueva utilidad Gen-
TLB.exe; disponible en la carpeta bin de Delphi) como para la
generación de los correspondientes interfaces en lenguaje
Delphi (todavía almacenados en un archivo <project-
name>_TLB.pas). Al abrir un proyecto existente o crear un
cliente que se refiera a una biblioteca de tipos, la RIDL se crea
a partir de la biblioteca de tipos (lo que podría lograrse ma-
nualmente utilizando la nueva directiva -I de la utilidad
tlibimp.exe).

Una RIDL Textual


De nuevo, la RIDL es el formato base utilizado como punto de
partida del código de Delphi para la generación de interfaces
COM (tanto en el cliente como en el lado del servidor).
Una de las grandes ventajas de este formato, además de evitar
un error (bug) que ocasionalmente provocaría irrevocable-
mente el archivo binario TLB, es que al usar un archivo de
texto es mucho más sencillo utilizar el archivo RIDL con con-
trol de versiones, comparar diferentes versiones, y (la posible

128Laparte de la VCL, que se centra en el desarrollo COM. fue originalmente lla-


mada entorno DAX, para el framework Direct ActiveX. Ahora este término
es rara vez utilizado, aunque referencias internas lo recuerden.

La guía de Delphi por Marco Cantù


356 - Indice

extensión) soporta proyectos grandes multi-usuario con


múltiples archivos RIDL parciales.
El RIDL, como el IDL, utiliza una sintaxis vagamente parecida
a la sintaxis del lenguaje C++ y muchos decoradores. Por
ejemplo, una interfaz con un método único podría describirse
así129:
interface INumberProp: IUnknown
{
HRESULT _stdcall Increase(void);
};

En C++ l0s 2 puntos (:) después de una clase introducen una


lista de las clases base, lo que significa que INumberProp hereda
de IUnknown. El contenido de la clase se encuentra dentro de
las llaves de inicio y cierre. Cada método tiene un valor de re-
torno, seguido de modificadores opcionales (incluida la con-
vención de llamada), el nombre y los parámetros, si los hu-
biere (void significa que no hay parámetro).
Como se ha mencionado, cada símbolo está generalmente
precedido por algunos atributos, incluyendo el ID de la inter-
faz (IID), un número de versión, una descripción, y otras di-
rectivas:
[
uuid(4D24B32A-DE61-4EBE-AE53-6DF2D3DC80DA),
version(1.0),
helpstring("Interface for NumberProp Object"),
]
interface INumberProp: IUnknown
{
...
}

129Elcódigo fuente completo, del que tomó estos fragmentos de código, se en-
cuentra en la siguiente sección, "El Formato RIDL ".

La guía de Delphi por Marco Cantù | para Desarrollo

sistemas integrados control, SA


Indice - 357

Para una descripción más detallada de la IDL COM puede re-


ferirse a la documentación propia de Microsoft. Puede encon-
trar la especificación de la sintaxis de archivos RIDL (bien
oculta) en la ayuda de Delphi en:
ms-help://embarcadero.rs2009/devwin32/
wtlusingobjectpascaloridlsyntax_xml.html

(Observe que el título de la página de ayuda hace referencia a


la versión anterior de Delphi, ya que ahora editor Type Li-
brary utiliza exclusivamente la sintaxis RIDL.)

El Formato RIDL
(Servidores COM)
Para ver de que se trata este nuevo formato RIDL, vamos a
convertir un proyecto COM ya existente mediante su apertura
en Delphi 2009. Como ejemplo, he tomado el ejemplo COM
SimpleServer de Mastering Delphi 2005 y convertido este a
Delphi 2009. Junto a la norma de conversión del formato del
archivo del proyecto, Delphi me advirtió de la migración de la
biblioteca de tipos:
Converting '...\08\SimpleServer\SimpleServer.tlb'
to .ridl format
Reading 'SimpleServer.tlb'
Writing 'SimpleServer.ridl'
Adding 'SimpleServer.ridl'
Removing 'SimpleServer.tlb'

La guía de Delphi por Marco Cantù


358 - Indice

Si nos fijamos en los archivos del proyecto en el Project Mana-


ger, podemos ver el nuevo fichero RIDL:

Si miramos el archivo de código fuente del proyecto, en lugar


de ello, todavía podemos ver la clásica referencia al archivo de
recursos TLB. La biblioteca de tipos, de hecho, se incluye en el
ejecutable, después de que se compile a partir del archivo
RIDL.
Cómo se convierte el y de que forma se genera el archivo
RIDL depende mayormente de algunas Opciones de las He-
rramientas para la biblioteca de tipos. Estas opciones son bas-
tante diferentes que en el pasado. En particular, ya que este es
un proceso interno en el servidor, para mantener la compati-
bilidad de código (y que esto sea más fácil desde la perspec-
tiva de Delphi) necesitamos habilitar 130 para permitir las

130Usando la convención safecall todas las llamadas COM son asignadas, por lo
que la comprobación de errores en términos de HRESULT en el sistema deben
reasignarse a las excepciones propias de Delphi. Básicamente, cada función
o procedimiento se convierte en una función con un retorno con valor HRE-
SULT, según lo solicitado por la doble interfaz COM, y un parámetro de sa-
lida adicional, posiblemente añadido al valor de retorno real. En el lado del
servidor, Delphi envuelve el código en un bloque try except que eventual-
mente atraparía una excepción y devolvería un código de error. En el lado
del cliente, un código de error en una llamada obligaría a Delphi a lanzar la
correspondiente excepción.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 359

asignaciones (el mapeado) de cualquier interfaz COM, utili-


zando las Opciones del Entorno configuradas como en la pri-
mera imagen de la siguiente página.

Como el valor por defecto safecall es sólo para asignaciones


de doble interfaz, usted tiene que cambiar estos valores, y es
necesario hacerlo antes de abrir el proyecto (o abrir de nuevo
el proyecto después de cambiar los ajustes, como el IDE de
Delphi le sugiere, pero tenga cuidado ya que usted puede te-
ner que abrir el editor de la biblioteca de tipos un par de ve-
ces, antes de que el código se genere de la forma en que usted
lo solicitó).

Si puede reabrir la biblioteca de tipos en el IDE, verá un edi-


tor de biblioteca de tipos muy similar al del pasado:

La guía de Delphi por Marco Cantù


360 - Indice

Actualice la implementación del editor de la biblioteca de ti-


pos (usando el botón Refresh de la barra de herramientas de
este panel) para generar o actualizar el archivo RIDL.
Para el ejemplo SimpleServer, que he actualizado desde una
versión anterior de Delphi, obtengo el siguiente código RIDL,
que importa un tipo estándar de biblioteca Delphi, define la
interfaz INumberProp, con una propiedad & un método, y de-
fine el coclass (construcción de clase) NumberProp para el ob-
jeto servidor implementando la interfaz:
library SimpleServer
{
importlib("stdole2.tlb");
[
uuid(4D24B32A-DE61-4EBE-AE53-6DF2D3DC80DA),
version(1.0),
helpstring("Interface for NumberProp Object"),
oleautomation
]
interface INumberProp: IUnknown
{
[propget, id(0x00000065)]
HRESULT _stdcall Value([out, retval] long* New);
[propput, id(0x00000065)]
HRESULT _stdcall Value([in] long New);
[id(0x00000066)]
HRESULT _stdcall Increase(void);
};
[
uuid(BDC9A273-A973-4DB4-ADE7-8F0A49004D29),
version(1.0),

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 361

helpstring("NumberProp")
]
coclass NumberProp
{
[default] interface INumberProp;
};
};

Esta interfaz es independiente de las decisiones asignadas por


safecall que sólo se reflejan en el correspondiente código de
Delphi. Usted puede ver una primera interfaz basada en
IUnknown, con una propiedad Value definida por un getter y
un setter y un método Increase. En términos de la correspon-
diente Interfaz de Delphi, el código generado es el siguiente
(sigue siendo muy similar con el del pasado) y se coloca en
una unidad SimpleServer_TLB.pas:
type
INumberProp = interface(IUnknown)
['{4D24B32A-DE61-4EBE-AE53-6DF2D3DC80DA}']
function Get_Value: Integer; safecall;
procedure Set_Value(New: Integer); safecall;
procedure Increase; safecall;
property Value: Integer
read Get_Value write Set_Value;
end;

CoNumberProp = class
class function Create: INumberProp;
class function CreateRemote(
const MachineName: string): INumberProp;
end;

Habiendo forzado la generación de las asignaciones de safe-


call, no tengo que tocar más el código fuente original de la
implementación de la clase:
type
TNumberProp = class(TTypedComObject, INumberProp)
private
fValue: Integer;
protected
procedure Increase; safecall;
function Get_Value: Integer; safecall;
procedure Set_Value(New: Integer); safecall;
public
procedure Initialize; override;
destructor Destroy; override;
end;
La guía de Delphi por Marco Cantù
362 - Indice

Cuando todo funciona bien, el importar código existente será


sencillo. Lo que puede ocurrir, sin embargo, es que si se creó
la convención de llamada equivocada (y el IDE recoge la con-
vención de llamada errónea a pesar de su configuración), verá
como la interfaz y la implementación de la clase, con la con-
vención stdcall tienen métodos extras y el valor de retorno
HRESULT es visible directamente. Si esto ocurre, los métodos
adicionales se añaden a la clase, apodados tal como la clase
tenga nombrados métodos similares, y esto puede realmente
desordenar el código, impidiendo la compilación y que restau-
rarlo quede lejos de ser trivial (a menos que, por supuesto, us-
ted vuelva a la versión anterior en el archivo del histórico o,
simplemente, no guarde el archivo modificado).

Registrando y Llamando al
Servidor
Ahora que he recompilado el servidor, puedo registrarlo en el
sistema para poder recurrir al mismo (y también utilizar el
nuevo panel Registered Type Libraries del IDE de Delphi).
Después de compilar, debe utilizar el comando del menú Run
| . La primera vez que lo hice, obtuve el siguiente y terrible
error: Unspecified Error (Error sin especificar). Resulta que,
si está ejecutando el IDE en Vista con el (UAC) activo, no
puede realizar este paso ya que el IDE no tratará de elevar

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 363

esta operación de usuario. Al ejecutar el IDE como adminis-


trador, el registro tiene éxito. Aquí están los dos mensajes:

El nuevo panel de registro


de las “Type Libraries”
Una vez que el servidor esté registrado, usted puede compro-
bar que está en el sistema abriendo la nueva vista Registered
Type Libraries del IDE de Delphi, utilizando el comando co-
rrespondiente situado en el menú View.
El panel de este editor lista todas las bibliotecas de tipos regis-
tradas (o controles ActiveX) del sistema, le permite eliminar
del registro cualquiera de ellas, registrar otras nuevas, buscar
la lista de bibliotecas de tipos. Como se puede ver a continua-
ción, la lista debería incluir nuestra recién registrada biblio-
teca:

La guía de Delphi por Marco Cantù


364 - Indice

Le advierto que no puede usar este cuadro de diálogo para im-


portar una biblioteca de tipos o crear un envoltorio cliente de
la misma. Para este propósito necesita utilizar el renovado im-
portador de biblioteca de tipos. Seleccione el componente del
menú Component | Import Component, y elija la opción Type
Library, como se muestra en la siguiente imagen:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 365

El siguiente paso debería ser el de elegir una de las bibliotecas


de tipos registrada o añadir una nueva. En general, la lista es
muy larga, el nuevo cuadro de búsqueda es muy útil para en-
contrar el tipo específico de biblioteca que está buscando, en
este caso, la que contenga la palabra “simple”:

El siguiente paso le permite agregar el componente a un pa-


quete, se genera e importa una unidad, o se añadirá directa-
mente al proyecto actual. En cualquier caso, usted acaba con
una biblioteca de tipos con archivo de importación casi idén-
tico al generado en el lado del servidor por el editor de la bi-
blioteca de tipos (o, para ser más precisos, tras procesar el ar-
chivo RIDL).
He realizado los pasos anteriores en el proyecto SimpleClient,
que es la nueva edición de otro proyecto que fue parte de Mas-
tering Delphi 2005 y anteriores volúmenes de esta serie. El
programa crea dos objetos COM desde el servidor al iniciarse,

La guía de Delphi por Marco Cantù


366 - Indice

los engancha a botones y spin edits, y tiene un tercer botón di-


námico para la creación de un objeto temporal. (Uno de los
objetivos del proyecto original era mostrar el tiempo de vida
de los objetos COM, esta es la razón por la que cuando un ob-
jeto se destruye el servidor mostrará un cuadro de mensaje).
Este es el fragmento de código hácelo-todo, que genera la Co-
Class:
var
Num3: INumberProp;
begin
// create a new temporary COM object
Num3 := CoNumberProp.Create;
Num3.Value := 100;
Num3.Increase;
ShowMessage ('Num3: ' + IntToStr (Num3.Value));
end;

Una vez más, es muy poco (o nada) lo que tiene que hacer en
el lado de la aplicación cliente COM para pasarlo a Delphi
2009. Sin embargo, si usted está creando un nuevo proyecto
de cliente, como yo he hecho, probablemente le agradarán las
mejoras en el asistente de importación para la biblioteca de ti-
pos.

COM y Unicode
Una de las razones por la que los cambios, en el soporte COM
y clientes COM en particular, están limitados, es debido al he-
cho de que las aplicaciones COM generalmente no utilizan el
sencillo string Delphi sino que se basan en el tipo nativo
WideString habilitado para COM (o BSTR en términos COM).
Como vimos en la Parte I del libro, ya se utilizaba el tipo
WideString de dos-bytes por carácter y no ha cambiado desde
Delphi 2007. La razón de que este tipo de cadena menos efi-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 367

ciente todavía se mantenga es por COM y el soporte a la Auto-


matización, por lo que no debería generar ninguna sorpresa
que esté hablando de ella en esta sección centrada en COM.
En unos pocos términos técnicos más, una de las ventajas del
tipo WideString es que se puede usar en una llamada a un ser-
vidor remoto ya que sus datos se pueden reasignar a un ser-
vidor de Automatización COM. Este no sería el caso para Uni-
codeString, un tipo de datos que sólo tiene sentido en Delphi
y no en otros lenguajes (con la notable excepción de C++ Buil-
der 2009, por supuesto).

Características que
Vuelven: Active Forms
Algunas características de los tiempos de Delphi 7 han vuelto
en Delphi 2009 después de una larga ausencia. Uno de ellos
es el ActiveX Control Wizard (incluyendo Active Forms). Ad-
vierto, no obstante que este asistente, no genera los archivos
de distribución HTML apropiados como lo hizo en el pasado.
En realidad la mayoría de los asistentes COM han mejorado
gráficamente (y parcialmente en su conjunto de característi-
cas) en Delphi 2009, como se puede ver en la sinopsis facili-
tada por Chris Bensen en su blog sneak peek en:
http://chrisbensen.blogspot.com/2008/07/
tiburn-sneak-peek-com-wizards.html

La guía de Delphi por Marco Cantù


368 - Indice

Como ejemplo he creado una nueva biblioteca ActiveX, le he


añadido un Active Form (recogida en el icono de la página Ac-
tiveX del nuevo cuadro de diálogo de elementos):

He añadido a continuación un cuadro Acerca de, generado el


Active Form, guardado todo, y añadido un botón al Active
Form, botón que muestra algunas versiones de la frase "¿Qué
es Unicode?" en diferentes idiomas y alfabetos en un control
Memo.
Ahora puede compilar y registrar la biblioteca de ActiveX,
pero ¿cómo puedo obtener que se muestre este formulario en
Internet Explorer? He abierto un proyecto Active Form muy
antiguo (un proyecto de Delphi 5, para ser precisos), tomo el
código generado por Delphi, y sustituyo el GUID por el GUID
CoClass y el nombre de la biblioteca ActiveX con el actual:
<html>
...
<object
classid="clsid:AE64FD40-25C5-4697-B19C-8CE781695B71"
codebase="./MyActiveForm_Project.ocx"
width=570
height=328
align=center

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 369

>
</object>
</html>

En este punto ya puedo abrir el archivo HTML (en la misma


carpeta que la biblioteca OCX) con Internet Explorer, pa-
sando un par de avisos de seguridad como:

La guía de Delphi por Marco Cantù


370 - Indice

y continuar a la página HTML embebiendo el formulario Acti-


veX:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 371

La creación de un ActiveForm en Delphi 2009 es un ejercicio


interesante para recordar que una vez Microsoft estuvo tra-
tando de imponer un modelo propietario y único de Windows
en la web, una iniciativa provisional que ha fracasado comple-
tamente. Pero ¿tiene algún sentido técnico utilizar esto ahora?
Por mucho que me disgustase la insegura tecnología ActiveX
antes, y con toda la potencia extra (incluyendo las tecnologías
de cliente más abiertas) que existe ahora a nivel navegador, en
general, yo generalmente diría que no.
Sin embargo, hay situaciones específicas en las que un desa-
rrollo simplificado haría las cosas más fáciles... o algunos
clientes no-tan-inteligentes para los que una aplicación web
es buena porque que se ejecuta en el navegador. En situacio-
nes tales, si ya tiene una inversión en esta tecnología, podría
tener sentido usarla... pero cuanto antes pueda distanciarse
de una visión solo-Win32 ó de Internet solo-Explorer, mejor.

A continuación
En este breve capítulo he explorado algunos de los cambios
más significativos del soporte COM en Delphi 2009, incluidos
los nuevos archivos RIDL, los diferentes papeles de las biblio-
tecas de tipos, y algunos de los asistentes y actualizaciones de
herramientas del IDE. Ya no tiene sentido que describa la tec-
nología COM en detalle, ya que he utilizado para ello dos
grandes capítulos de mi serie “Mastering Delphi”.
Ahora que hemos visto todos los cambios en el núcleo VCL y
su subsistema COM, es el momento de centrarse en la nove-
dad visual más interesante de la VCL Delphi 2009, el nuevo
control Ribbon.

La guía de Delphi por Marco Cantù


372 - Indice

Capítulo 10:
Ribbon

A comienzos de los ochenta, junto con la revolución del PC,


IBM decidió tratar de estandarizar las reglas de la interfaz de
usuario (en la época de MS-DOS) de las aplicaciones PC. Esta
especificación se llamó Common User Access (CUA) y Micro-
soft la abanderó durante muchos años en los programas DOS
y en las versiones iniciales de Windows, hasta el punto de que
llego a ser una forma natural de interactuar con la mayoría de
los programas. Se sabe que los comandos de Copiar y Pegar se
encuentran en el menú Edición o que hay que buscar en el
menú Archivo para guardar o cargar un documento, así que al
menos ya sabes algo de antemano de cada nuevo programa.
Incluso aunque hubo muchos agregados a CUA, incluyendo
barras de herramientas, accesos rápidos, barras de comandos
y más, su estructura principal se mantuvo durante alrededor
de 20 años. Microsoft rompió esta regla por primera vez en
Office 2007 (y parcialmente en Windows Vista) con la defini-
ción de un nuevo paradigma de interfaz de usuario, llamado
Fluent User Interface. Este interfaz es generalmente conocido

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 373

como el interfaz “Ribbon131” debido a su elemento visual prin-


cipal.
No quiero profundizar en el debate acerca de si ha sido un
buen movimiento o no (tengo algunas dudas), más bien
quiero centrarme en el hecho de que esta interfaz de usuario y
la serie de reglas rigurosas, no son fáciles de implementar sin
algunos componentes visuales listos para utilizar. Delphi
2009 incluye este conjunto de controles visuales que son el
objeto de este capítulo. Los cuales han sido desarrollados en
Embarcadero por Jeremy North132.

Presentando el “Fluent
User Interface”
Como mencioné anteriormente, el Fluent User Interface fue
inventado por Microsoft, que está buscando una patente133
para él. Esta patente no se centra en el código del interfaz de
usuario (los controles “Ribbon” usados en Office 2007), sino
en el diseño del interfaz de usuario en si mismo. Microsoft
también se refiere a este interfaz como “Microsoft for the Of-
fice UI.”

131Notadel traductor: una posible traducción de la palabra Ribbon es Cinta, si bien se ha


decidido conservar el término a fin de evitar ambigüedades.
132Jeremy North es el autor de varios componentes Delphi, extensiones al IDE y
herramientas para desarrolladores, (incluyendo el cliente extendido de Qua-
lity Central). También participó en el desarrollo de los programas de Com-
pact Framework escritos en Delphi.NET. Si quieres saber más sobre él, visita
http://www.jed-software.com.
133Puedesver más información sobre esta petición de patente y la discusión rela-
cionada en Wikipedia: http://en.wikipedia.org/wiki/Ribbon_(computing)

La guía de Delphi por Marco Cantù


374 - Indice

Esto significa que la patente de Microsoft, si se concede, se


aplicará incluso aunque la implementación de la VCL que está
disponible en Delphi 2009 es una nueva versión de los con-
troles (en ningún sentido relacionada con el código que Mi-
crosoft usa en Office y otras aplicaciones, y que Microsoft no
licencia). Esta es la razón por la cual tenemos que mirar el
lado legal de este componente antes de mirar su uso.
Nótese que a diferencia de otras líneas guía, las guías de di-
seño de Office Fluent UI, que describen como las aplicaciones
basadas en “Ribbon” deben trabajar no son públicas sino in-
formación confidencial de Microsoft.

El lado legal de la “Ribbon”


Cuando instalas Delphi 2009, se te presentará con una apa-
riencia extraña un diálogo (que Microsoft exige):

Como se puede ver, Microsoft pide a cualquiera que quiera


usar su “Fluent User”, que acepte los términos de su licencia
para “Office UI”. Esta licencia está libre de “royalties”, pero
hay pautas de uso y limitaciones relacionadas con lo que se
puede hacer. El asunto más significativo es que no se permite
crear programas que compitan directamente con Microsoft
Office. También hay pautas de uso que hay que seguir, no se
puede adaptar la “Ribbon” según nuestro deseo, sino que
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 375

debe trabajar de una manera consistente con la visión de Mi-


crosoft. Para una información completa puede dirigirse a la
Web mencionada en el diálogo mostrado anteriormente y al
sitio de registro:
http://msdn.microsoft.com/officeui
http://msdn.microsoft.com/en-us/office/aa973809.aspx

Una vez que se subscribe la licencia y se registra la aplicación


se puede descargar la página 119 del PDF con las pautas de
uso de la Office UI.

Una “Ribbon” simple


El ejemplo “My first Ribbon”es una demo muy sencilla de
cómo funciona el componente, aunque realmente no tiene un
interfaz de usuario real. Como veremos en la siguiente sec-
ción, el único camino real de crear un interfaz de usuario ba-
sado en la Ribbon es usar la arquitectura basada en “Action
Manager”. Es posible usar el componente “Ribbon” sin accio-
nes, pero es muy tosco y extremadamente limitado… así que
después de un sencillo ejemplo me moveré en esa dirección.
Podemos, de hecho, comenzar para algunos experimentos ini-
ciales con un componente “Ribbon” sin formato, creando pes-
tañas y grupos, y situando un par de componentes estándar
dentro de ellos. Para seguir mis pasos, simplemente hay que
crear una aplicación y situar un componente “Ribbon” en su
formulario principal. Una vez que se ha situado el compo-
nente se puede usar su menú (seleccionándolo en el formula-
rio o en el panel de estructura) para añadir una nueva pes-
taña. El mismo menú permitirá quitar una pestaña o añadir el
menú de aplicación y una barra de herramientas de Acceso
Rápido, como veremos más tarde. También se puede trabajar
con las pestañas usando la colección Ribbon Tabs del compo-

La guía de Delphi por Marco Cantù


376 - Indice

nente (técnicamente una colección de objetos TribbonTa-


bItem, cada uno de los cuales está conectado a un TRibbon-
Page, un tipo de panel) y el comando relacionado AddItem.
Esto está disponible en la vista de estructura:

La cabecera de una “Ribbon” con dos pestañas y páginas se ve


de esta manera en tiempo de diseño en el IDE de Delphi
2009:

En este caso he mantenido la propiedad ShowHelpButton


(por defecto) que muestra una interrogación en el extremo su-
perior derecho del control. También he mantenido la propie-
dad UseCustomFrame (algo que veremos más adelante).
Aquí hay unas cuantas propiedades del control “Ribbon” en el
ejemplo BareBoneRibbon:
object Ribbon1: TRibbon
Caption = 'Ribbon Caption'
DocumentName = 'Document Name'
Tabs = <
item
Caption = 'RibbonPage1'
Page = RibbonPage1
end
item
Caption = 'RibbonPage2'
Page = RibbonPage2
end>
StyleName = 'Ribbon – Luna'
object RibbonPage1...
object RibbonPage2...
end

Una vez que tenemos una o más pestañas Ribbon, se puede


añadir grupos (o cajas). Otra vez, se puede trabajar con ellos
La guía de Delphi por Marco Cantù | para
Indice - 377

con los menús de contexto de los componentes en el formula-


rio o en el panel de estructura. A continuación muestro como
aparece una página de Ribbon con unos grupos vacíos:

En una página, se puede añadir un grupo, quitar un grupo u


ordenar grupos, a través de un diálogo específico y simple
(que suele ser más fácil de usar que arrastrar grupos dentro
de una página, esperando que se coloquen en su sitio).
¿Qué se puede poner dentro de un grupo? Generalmente se
rellenan con elementos de varios tipos, desde comandos a op-
ciones, que se conectan con Actions de un ActionManager. Si
se quiere agrupar funcionalidades, la mayoría de ellas diver-
gentes de la especificación de la UI de la Ribbon, se pueden
añadir botones sin formato o controles RibbonSpinEdit en los
grupos, como he hecho en la demostración. Otra vez dejamos
de cumplir las normas, aunque el RibbonSpinEdit si encaja
con la especificación “Ribbon UI”.
Aquí se puede ver la primera de las dos páginas publicadas de
la demostración:

La guía de Delphi por Marco Cantù


378 - Indice

Este formulario es diferente del habitual, porque su “caption”


y bordes estándar han sido reemplazados por un marco perso-
nalizado especial, pintado por el control en si mismo. Este es
el estilo por defecto de “Ribbon UI”, con los elementos gráfi-
cos añadidos (como el menú de aplicación), como veremos
posteriormente.
Se puede incluso deshabilitar la propiedad UseCustomFrame.
Yo lo he hecho en ejecución cuando el usuario desmarca un
checkbox, con este código:
procedure TFormBareBoneRibbon.cbShowBorderClick(
Sender: TObject);
begin
Ribbon1.UseCustomFrame := cbShowBorder.Checked;
self.RecreateWnd;
end;

Aunque esto funciona cuando se quita el marco personali-


zado, si se vuelve a habilitar los bordes no se mostrarán co-
rrectamente134. Supongo que tiene sentido cambiar esta pro-
piedad en diseño o cuando el formulario se crea.
Otra configuración importante a recordar es que si el control
“Ribbon” se reduce por debajo de 300x250, se mostrará en
un estado minimizado (otra vez de acuerdo con la especifica-
ción “Ribbon UI”). Si se quiere evitar esto, para evitar que los
usuarios se confundan, se puede indicar una altura y anchura
mínimas para el formulario:
object FormBareBoneRibbon: TFormBareBoneRibbon
Caption = 'BareBoneRibbon'
Constraints.MinHeight = 270
Constraints.MinWidth = 320
...

134Ésteerror será solucionado. Provisionalmente se puede usar el código disponi-


ble en el informe de error: http://qc.code-
gear.com/wc/qcmain.aspx?d=68955

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 379

El tamaño se calcula añadiendo algo de espacio extra para los


bordes, al tamaño mínimo del Ribbon. Esto es algo que se
debe recordar en cada formulario que utilice el “Ribbon”.

Acciones y la “Ribbon”
Como ya he mencionado alguna vez, el control “Ribbon” se
basa en el ActionManager de Delphi. Veremos como crear una
UI basada en “Ribbon” en el siguiente ejemplo. Antes, sin em-
bargo, necesito recapitular una serie de características clave
de esta arquitectura para los que nunca la han usado135. Si ya
ha usado el componente ActionManager anteriormente, se
puede evitar las dos siguientes secciones y saltar a “Acciones y
Ribbon en la práctica”.

De eventos a Acciones
El manejo de eventos de Delphi es muy abierto: se puede es-
cribir un manejador sencillo de evento y conectarlo a los even-
tos OnClick de los botones de una barra de herramientas y un
menú. También puede conectar el mismo gestor de eventos a
diferentes botones o elementos de menú, porque el gestor de
eventos puede usar el parámetro Sender para referirse al ob-
jeto que lanzó el evento. Es un poco más difícil el sincronizar
el estado de los botones de la barra de herramientas y elemen-
tos de menú. Si tiene un elemento de menú y un botón que

135Esta introducción a las acciones y a los componentes ActionList y ActionMa-


nager ha sido extraída del libro “Mastering Delphi 7”. La versión original
tiene también una serie de ejemplos específicos que se han omitido en este
sumario.

La guía de Delphi por Marco Cantù


380 - Indice

comparten la misma funcionalidad, cada vez que una opción


es habilitada, se debe añadir la marca de visto al elemento de
menú y cambiar el estado del botón para mostrarlo pulsado.
Para soslayar estos problemas y otros similares, Delphi in-
cluye una arquitectura basada en acciones. Una action (o co-
mando) juega dos papeles separados al mismo tiempo:
 Indica la operación a realizar, cuando los elementos de la in-
terfaz de usuario (menú, botón), conectados a la acción se ac-
tivan, con su gestor de evento OnExecute.
 Determina el estado de los elementos del interfaz de usuario
(o clientes) conectados a la acción, incluyendo su descripción
textual, estado de habilitación, estado de marcado y demás.
La conexión de una acción con la interfaz de usuario de los
controles asociados es muy importante y no debe ser subesti-
mada, porque es donde puedes obtener las ventajas reales de
esa arquitectura.
En la práctica, una objeto acción tiene propiedades que po-
drán se aplicadas a los controles asociados (llamados clientes
de acción). Estas propiedades incluyen, entre otras, el Cap-
tion, la representación gráfica (ImageIndex), el estado (Checked,
Enabled, y Visible), y la realimentación al usuario (Hint y Help-
Context).

La clase base para todos los objetos acción es TBasicAction, la


cual introduce un comportamiento básico de acción, sin el de
enlace específico o conexión (incluso la de los ítems de menú
o controles). La clase derivada TContainedAction introduce
propiedades y métodos que posibilitan a las acciones aparecer
en una lista de acciones o un “action manager”. La subsi-
guiente clase derivada TCustomAction soporta las propieda-
des y métodos de los ítems de menú y los controles asociados
a los objetos acción. Finalmente hay una clase derivada y lista
para usar llamada TAction.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 381

Cada acción está conectada a uno o más objectos cliente a tra-


vés de un objeto ActionLink. Múltiples controles, posible-
mente de diferentes tipos, pueden compartir la misma acción,
como se indica en su propiedad Action. Técnicamente, los ob-
jetos internos ActionLink mantienen una conexión bidireccio-
nal entre el cliente y la acción.
Los controles cliente conectados a las acciones son normal-
mente ítems de menú y diversos tipos de botones ((botones,
check boxes, botones radio, speed buttons, botones de barra
de herramientas…). Recuérdese que no se pueden establecer
las propiedades de los controles clientes asociados a una ac-
ción, debido a que la acción sobrescribirá los valores de las
propiedades. Por esta razón, se debe generalmente escribir las
acciones primero y luego crear los elementos de menú y boto-
nes a los que se quieren conectar. Cuando se usa una “Ri-
bbon”, este es el único camino a seguir, no hay alternativa a
la “buena practica”.

“ActionList” y “ActionManager”
Cada acción existe en memoria pero no son componentes
VCL. De hecho, son manejados por componentes contenedo-
res, llamados ActionList y ActionManager. Realmente es un
contenedor antiguo y simple, la integración y generación de

La guía de Delphi por Marco Cantù


382 - Indice

interfaz de usuario más tardiamente introducida que está


siendo extendida. El componente ActionList tiene un editor
especial que se puede usar para crear diversas acciones (in-
cluidas muchas predefinidas) y gestionarlas. Las acciones se
agrupan en categorías textuales:
El componente ActionManager, originalmente introducido en
Delphi 6, tiene opciones para permitir la creación y gestión de
las acciones del interfaz de usuario. Además de la colección de
acciones, el ActionManager tiene una colección de barras de
herramientas y menús completamente visual: se pueden
arrastrar acciones desde un editor de componente especial del
ActionManager a las barras de herramientas para acceder a
los botones que se necesiten. Nótese que trabajando con la Ri-
bbon es algo similar; se pueden arrastrar acciones a los gru-
pos de la Ribbon para obtener una “Ribbon” lista para usar.
Los componentes de esta arquitectura incluyen, además del
componente ActionManager en si mismo, un ActionMainMe-
nuBar, un ActionToolBar, un PopupActionBarEx, y un com-
ponente CustomizeDlg usado para que los usuarios finales
personalicen el interfaz de usuario. Estos componentes visua-
les no son usados cuando se trabaja con una “Ribbon”, así que
no quiero cubrirlos en detalle. En vez de eso, permitirme
construir paso a paso un ejemplo usando el ActionManager y
el control “Ribbon”.

Acciones y Ribbon en la práctica


Después de esta introducción acelerada a la arquitectura del
Action Manager de Delphi, vamos a crear un demostración. El
primer paso, desde luego es crear una aplicación VCL y añadir
un ActionManager al formulario principal. Lo siguiente es sol-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 383

tar un control Ribbon en el formulario. El control debería au-


tomáticamente asociarse al action manager, si no es así úsese
la propiedad ActionManager.
Antes de añadir cualquier acción al ActionManager, añada los
controles ImageList y conéctelos a él. El añadir las acciones
estándar, de hecho, automáticamente incluirá las listas de
imágenes. Añada un ImageList para las Images estándares del
ActionManager (las imágenes estándar se usan para los co-
mandos Ribbon) y una para la propiedad LargeImages (usada
por el menú de aplicación y los botones grandes de un Ri-
bbon). Se debería obtener una configuración como:
object RibbonEditorForm: TRibbonEditorForm
Caption = 'RibbonEditor'
Constraints.MinHeight = 300
Constraints.MinWidth = 400
object Ribbon1: TRibbon
ActionManager = ActionManager1
Caption = 'RibbonEditor'
StyleName = 'Ribbon - Luna'
end
object ActionManager1: TActionManager
LargeImages = listLarge
Images = listStandard
StyleName = 'Ribbon - Luna'
end
object listStandard: TImageList...
object listLarge: TImageList...
end

Como mi objetivo es crear un editor sencillo (no un procesa-


dor de textos completo, ya que no pretendo infringir la licen-
cia de la “Ribbon”), básicamente necesito situar un control Ri-
chEdit alineado al área cliente del formulario y añadir la ma-
yoría de las acciones estándar de edición (las 6 acciones es-
tándar de la categoría Edit), el soporte de texto enriquecido
(las 8 acciones estándar de la categoría Format), el soporte de
ficheros (las 8 acciones estándar de la categoría File) y algu-
nas más (la opción Download de la categoría Internet y la ac-
ción Font de la categoría Diálogs).

La guía de Delphi por Marco Cantù


384 - Indice

Grupos y comandos
Ahora que tengo todas esta acciones en su sitio, se puede
crear un interfaz de usuario Ribbon de ellos. Después de crear
dos pestañas y unos cuantos grupos, puedo arrastrar las ac-

ciones en los grupos. Aquí hay un par de ellos:


Estos grupos acogen los comandos directos, así que no hay
nada específico que configurar. Otro grupo tiene un conjunto
de opciones no excluyentes, como establecer el texto en ne-
grita y cursiva. Para los ítems de menú de este grupo es mejor
seleccionar el valor csCheckBox para la propiedad Command-
Style (en vez del valor por defecto csButton). El efecto es el de
tener un conjunto de check boxes que se pueden establecer
seleccionando el área del control o el icono y el texto del co-
mando. Este es un ejemplo de la demo:

La única excepción al arrastre de acciones en grupos es la re-


presentada por la , que puedo establecer como una acción de
diálogo a uno de los grupos, usando su propiedad . Esto
añade un pequeño elemento gráfico en la esquina inferior de-
recha, como en la imagen anterior.
Otra opción es tener opciones alternativas, representadas por
botones radio, estableciendo la propiedad CommandStyle a
csRadioButton, con el efecto visual:

La guía de Delphi por Marco Cantù | para


Indice - 385

Según se selecciona uno de los elementos en un Ribbon, se ve-


rán varias propiedades para los objetos TActionClientItem co-
rrespondientes. Pero ¿cómo se gestionan estos elementos?
Resulta como si el componente ActionManager tuviera una
barra de herramientas para cada grupo de Ribbon, como se
puede ver el editor de componente ActionManager.
Incluso mejor, se puede ver la estructura interna de estos ob-
jetos usando la vista de Estructura y expandir la colección del
componente ActionManager, ¡no los del Ribbon! Aquí se
muestra una pequeña porción de la demostración (y de los

efectos visuales comentados anteriormente):


Esto significa que se puede navegar a través de los elementos
de diversos grupos de “Ribbon” en una forma menos visual y
más detallada, seleccionando los elementos que no son visi-
bles, cogiendo los separadores pequeños e incluso añadiendo
objetos nuevos de tipo ActionClientItem. Se pueden configu-
rar estas nuevas ActionClientItem definiendo los elementos

La guía de Delphi por Marco Cantù


386 - Indice

de texto y separadores, seleccionando acciones o conectándo-


las a los controles visuales.

El Menú de aplicación
Para completar nuestra aplicación, para la cual he enlazado
diversas acciones personalizadas sin tener que escribir código,
debería añadir dos elementos relevantes para el interfaz de
usuario Ribbon. Ambos son añadidos usando comandos al
editor del componente “Ribbon” (el menú de acceso rápido
que aparece en diseño cuando el componente está seleccio-
nado) y puede ser añadido solo si el ActionManager está esta-
blecido.
El primer elemento en el menú de aplicación, el control re-
dondo en la esquina superior izquierda de la “Ribbon” reem-
plaza el menú de aplicación tradicional de Windows. Aquí
está el componente en ejecución en la demo:

Es característico de este elemento un menú desplegable ini-


cialmente vacío. La idea es usarlo para las operaciones rela-
cionadas con ficheros, y se pueden añadir diversas acciones
estándar como Abrir o Guardar, es posible arrastrar acciones
a esta barra de herramientas, pero no es fácil, ya que suele ce-
rrarse. He encontrado más fácil seleccionar en la vista de Es-
tructura, añadir Items y enlazar cada cual con su correspon-
diente acción.
Si el lado izquierdo del menú de la aplicación es simplemente
una lista de acciones orientadas al archivo con iconos gran-
des, el lado derecho podría acoger una lista de los ficheros re-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 387

cientemente usados. El control “Ribbon” tiene un soporte es-


pecífico para manejar este “most recently used” (MRU)136. En
esta demostración simplificada he decidido manejar solo
“Load” y “Save As”. Cada una de las cuales añade una entrada
al MRU llamando al método personalizado, que en turnos, in-
voca el método de control AddRecentItem. Esta operación
añade una nueva entrada al principio de la lista, y eventual-
mente borra una entrada existente referida al mismo fichero.
El evento OnAccept de las acciones FileOpen1 y FileSaveAs1
tiene el siguiente (similar) código, que llama al método
AddToMru mostrado debajo de ellos:
procedure TRibbonEditorForm.FileOpen1Accept(
Sender: TObject);
begin
RichEdit1.Lines.Clear;
RichEdit1.Lines.LoadFromFile(FileOpen1.Dialog.FileName);
Ribbon1.DocumentName := FileOpen1.Dialog.FileName;
AddToMru(FileOpen1.Dialog.FileName);
end;

procedure TRibbonEditorForm.FileSaveAs1Accept(
Sender: TObject);
begin
RichEdit1.Lines.SaveToFile(FileSaveAs1.Dialog.FileName);
Ribbon1.DocumentName := FileSaveAs1.Dialog.FileName;
AddToMru(FileSaveAs1.Dialog.FileName);
end;

procedure TRibbonEditorForm.AddToMru(
const strFilename: string);
begin
Ribbon1.AddRecentItem(strFilename);
end;

136Enel código fuente de la demostración también se encontrará el escrito ma-


nualmente para gestionar la lista MRU, usando una acción común y proce-
sando en Caption en la selección. Considerando la buena calidad del soporte
automático, no creo que sea valioso explorar la manera manual. La razón por
la cual lo dejo en el código es para mostrar como dinámicamente extender el
interfaz de usuario de la “Ribbon” y utilizar acciones compartidas entre dife-
rentes elementos clientes.

La guía de Delphi por Marco Cantù


388 - Indice

Cuando un elemento de la lista MRU se selecciona, el control


“Ribbon” lanza el gestor de evento OnRecentItemClick, el cual
se ha codificado de una manera “naïve”, ya que no com-
prueba si el fichero está actualmente activo en el editor. Ade-
más, esta información no es salvada entre sesiones. Todo lo
que quiero es mostrar cómo se puede manualmente mostrar
los elementos más recientemente usados, obteniendo un
efecto como este:

Este es el gestor de evento para la selección de la lista MRU:


procedure TRibbonEditorForm.Ribbon1RecentItemClick(
Sender: TObject; FileName: string; Index: Integer);
begin
RichEdit1.Lines.Clear;
RichEdit1.Lines.LoadFromFile(FileName);
Ribbon1.DocumentName := FileName;
end;

La página derecha del menú de aplicación puede usarse tam-


bién para mostrar botones estableciendo la propiedad Com-
mandType del objeto ApplicationMenu del Ribbon a ctCommands
(en vez del valor por defecto ctRecent). En este caso, cualquier
elemento añadido a la colección RecentItems se mostrará
como un botón. Esto se demuestra en la demostración del
menú de aplicación que acompaña a Delphi 2009, un ejemplo
interesante ya que funciona de manera similar a Office 2007.

La barra de herramientas de
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 389

acceso rápido
El segundo elemento gráfico de la “Ribbon” es la barra de he-
rramientas de acceso rápido, una barra de herramientas con
operaciones de sistema automáticamente gestionada por el
sistema. Se añade a la derecha del selector redondo del menú
de aplicación, en este caso mostrando un par de acciones
(Save as y Exit):

Al lado de las acciones hay también un botón desplegable para


personalización que permite al usuario añadir comandos ex-
tra a la barra de herramientas, algo bastante poderoso que
puede querer deshabilitar137.
Con estos pasos he construido un editor muy simple pero muy
completo basado en la “Ribbon”. Si se mira en los ejemplos
que acompañan a Delphi se encontrará uno con característi-
cas extra y un aspecto más bonito, pero sin el tratamiento de
los ficheros más recientemente usados.

El soporte de los Key Tips


Después de todos estos pasos tenemos una aplicación comple-
tamente funcional con un agradable interfaz fluido. Los usua-
rios pueden pulsar sobre varios elementos visuales (pestañas,

137Permitir que los usuarios personalicen sus barras de herramientas es discuti-


ble. Un usuario avanzado ciertamente se verá beneficiado pero se puede ge-
nerar una cantidad importante de llamadas a soporte de los novatos que no
puede imaginarse porque los botones de su barra han desaparecido. Como el
80% de los usuario utilizan el 20% de las funcionalidades la personalización
no es siempre buena idea.

La guía de Delphi por Marco Cantù


390 - Indice

controles) y trabajar con ellos. ¿Pero cómo se usa el teclado?


Además del hecho de que se pueda seguir asociando combina-
ciones de teclas a diversas acciones (como el clásico Ctrl-C
para copiar), el control Ribbon tiene su propio interfaz de te-
clado.
Todo lo que hay que hacer es proveer un valor apropiado a la
propiedad KeyTip de cada elemento del interfaz de usuario de
la “Ribbon” (pestañas, grupos, elementos de acción) y será ca-
paz de activarlos usando la tecla Alt. Por ejemplo, en el Rib-
bonEditor he establecido las siguientes “Key Tips” para las pá-
ginas, grupos y acciones:
RibbonPage1 'Editing' KeyTip = 'E'
RibbonPage2 'Advanced' KeyTip = 'A'
RibbonGroup1 'Copy&Paste' KeyTip = 'C'
RibbonGroup2 'Edit' KeyTip = 'E'
RibbonGroup3 'Style' KeyTip = 'S'
RibbonGroup4 'Alignment' KeyTip = 'A'
RibbonGroup5 'Paragraph' KeyTip = 'P'
item Action = EditCopy1 KeyTip = 'C'
item Action = EditPaste1 KeyTip = 'P'
items Action = EditCut1 KeyTip = 'T'
items Action = EditDelete1 KeyTip = 'D'
items Action = EditUndo1 KeyTip = 'U'
items Action = EditSelectAll1 KeyTip = 'S'
El valor de KeyTip debería ser una o más letras mayúsculas138
o números, el efecto es el de visualmente mostrar las “Key
Tips” alternativas de un nivel de sección dado según el usua-
rio pulse la tecla Alt (no la mantenga, simplemente pulse) y
continúe con las diversas selecciones. Mostrando las “Key
tips”, se enseña al usuario las combinaciones correctas poco a
poco. Como ejemplo, cuando se pulsa (y se suelta) la tecla Alt
en la demostración RibbonEditor se ve:

138Nousar letras minúsculas, ya que simplemente no funcionan. En teoría, el


componente Ribbon debe convertir automáticamente a mayúsculas, pero
esto no parece funcionar por ahora (en Delphi 2009 Update 1).

La guía de Delphi por Marco Cantù | para


Indice - 391

Solo se muestra el primer nivel de “Key Tips”, si después se


pulsa la tecla E activando la pestaña de edición, la “Ribbon”
mostrará las teclas de las acciones individuales y grupos, pero
solamente si los grupos tienen un diálogo conectado (lo que

ocurre para el grupo Style):


No hay que olvidar establecer las “Key Tips” para todos los
elementos de interfaz de usuario del “Ribbon” o el uso del
control se verá limitado para aquellos que prefieran la veloci-
dad de la selección de teclado.

Los componentes Ribbon


Ya hemos visto a través de un ejemplo práctico el papel de di-
versos componentes relacionados con la “Ribbon”, desde el
mismo control “Ribbon” hasta pestañas y grupos. Estos nos
aprovisionan de una organización general del interfaz de
usuario y de un amplio rango de opciones las cuales cierta-
mente no pueden ser exploradas en detalle aquí. Incluso sin
pretender una cobertura completa, puedo aportar algunas pis-
tas más.

La guía de Delphi por Marco Cantù


392 - Indice

Centrémonos en el Ribbon. Una de las más importantes ca-


racterísticas visuales de un grupo es la propiedad GroupAlign,
que puede ser vertical u horizontal. Vertical puede ser mejor
para los botones grandes, mientras que horizontal es mejor
cuando se tienen filas de botones pequeños. También se pue-
den usar las propiedades Columns y Rows para cambiar el as-
pecto general. Por defecto, los Ribbon tienen una organiza-
ción vertical con tres columnas, así que tres botones encaja-
rán en un grupo verticalmente. Recuérdese que el Ribbon
puede ser de al menos una altura dada y por lo tanto sus gru-
pos (las barras de desplazamiento nunca se muestran en un
Ribbon).
Por supuesto, si se configura un botón para que sea grande,
rellenará el grupo entero. En este caso (tomado de la demo es-
tándar RibbonDemo que viene en Delphi), el botón es grande
y utiliza un estilo de divisor para mostrar un menú desplega-
ble):

Se puede obtener este efecto estableciendo los estilos del Ac-


tionItem y creando unos cuantos subelementos, asociados con
acciones:
item
Action = EditPaste1
CommandProperties.ButtonSize = bsLarge
CommandProperties.ButtonType = btSplit
Items = <
item
Action = EditPaste1
end
item
Action = EditPasteSpecial
end
item
Action = EditPasteHyperlink
end>
end
end
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 393

En el extremo contrario se puede tener botones pequeños sin


caption utilizando la organización horizontal. En la siguiente
página incluyo un ejemplo de un grupo con dos filas de boto-
nes:

Las propiedades claves de este grupo son:


object RibbonGroup8: TRibbonGroup
Caption = 'Lines'
GroupAlign = gaHorizontal
Rows = 2
end

¿Pero cómo se puede obtener el efecto visual de botones agru-


pados? Esto no es automático, se tienen que modificar los ob-
jetos TActionClientItem de un grupo, quitando el caption
(cambiando la propiedad ShowCaption), estableciendo un valor
específico para su subpropiedad CommandProper-
ties.GroupPosition y utilizando la propiedad NewRow según
se necesite:
Items = <
item
Action = RichEditAlignCenter1
CommandProperties.GroupPosition = gpStart
end
item
Action = RichEditAlignRight1
CommandProperties.GroupPosition = gpMiddle
end
item
Action = RichEditAlignLeft1
CommandProperties.GroupPosition = gpEnd
end
item
Action = RichEditUnderline1
NewRow = True
CommandProperties.GroupPosition = gpStart
end
item
Action = RichEditItalic1
CommandProperties.GroupPosition = gpMiddle

La guía de Delphi por Marco Cantù


394 - Indice

end
item
Action = RichEditBold1
CommandProperties.GroupPosition = gpEnd
end>
ActionBar = RibbonGroup8
end

Esto podría interpretarse como un motón de trabajo manual,


pero permite retener mucho control sobre el lugar exacto de
los elementos, en vez de confiar en algún algoritmo interno
que podría no funcionar como se desea.
Además de las acciones, el posicionado y otros elementos grá-
ficos, lo más importante a decidir es escoger el interfaz y com-
portamiento principal. Si se coge un botón, como en la mayo-
ría de los casos de esta demo, se puede todavía utilizar la es-
tructura TButtonProperties con la propiedad CommandProp-
erties mencionada anteriormente. Esto permite determinar el
tamaño, el tipo, la posición en el grupo y el texto asociado del
botón usando valores de las siguientes enumeraciones (defini-
das como tipos anidados):
type
TButtonSize = (bsSmall, bsLarge);
TButtonType = (btNone, btDropDown, btSplit,
btGallery);
TGroupPosition = (gpNone, gpStart, gpMiddle,
gpEnd, gpSingle);
TTextAssociation = (taImage, taDropdown);

Lo que es muy interesante es percatarse de que el objeto co-


nectado con la propiedad CommandProperties depende del
tipo de comando. Si se escoge por ejemplo, un elemento de
texto, se verán propiedades como Alignment, EllipsisPosition,

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 395

y Font en vez de aquellas listadas anteriormente para un bo-


tón. ¿Pero cuáles son los comandos disponibles para los ele-
mentos clientes de acciones usados por la Ribbon139?
A continuación muestro una lista extraída de la documenta-
ción (esto es, el código fuente de la unidad ActMan):
 csButton - El comando es un botón
 csMenu - El comando es un menú
 csSeparator - El comando es un separador con caption
 csText - El comando solo muestra texto (pulsar no hace nada)
 csGallery - El comando muestra una galería (una caracterís-
tica no soportada en la versión actual de la “Ribbon”)
 csComboBox - El comando es un combo box de Office 2007 (nó-
tese que esto se establece automáticamente cuando se utiliza
un control RibbonComboBox)
 csCheckBox - el comando se muestra como un check box
 csRadioButton - el comando se muestra como un radio button
de Office 2007
 csControl - el comando tiene un TControl asociado con él
 csCustom - permite la expansión de desarrollo de terceros
Un caso interesante es el uso del estilo csControl, el cual per-
mite situar casi cualquier control gráfico de la VCL en una
“Ribbon”. Por ejemplo, en la usual demostración del Rib-
bonEditor, he añadido un grupo con un control ButtonedEdit

139Enteoría, estos tipos de comando podrían ser usados por cualquier otro con-
tenedor de la acción visual de enlaces, ya que esta se define como parte de la
arquitectura de Action Manager que no esté específicamente vinculada al
Ribbon. En este momento sin embargo, los demás visualizadores de acción y
estilos ignoran esta propiedad.

La guía de Delphi por Marco Cantù


396 - Indice

con un botón a la derecha, un TextHint y un microscópico


TreeView:

Todo lo que tuve que hacer fue manualmente añadir un Ac-


tionToolbar al ActionManager (usando la vista de Estructura),
conectada al nuevo y vacío Ribbon, escoger un control de la
paleta de herramientas y seleccionar el grupo “Ribbon” para
establecer una anchura para la acción completa y su label.
Aquí podemos ver la definición textual de uno de los elemen-
tos del ActionToolbar:
item
Items = <
item
Caption = '&Search:'
CommandStyle = csControl
CommandProperties.Width = 150
CommandProperties.ContainedControl = ButtonedEdit1
CommandProperties.LabelWidth = 50
end
item
Caption = '&Pick:'
CommandStyle = csControl
NewRow = True
CommandProperties.Width = 150
CommandProperties.ContainedControl = TreeView1
CommandProperties.LabelWidth = 50
end>
ActionBar = RibbonGroup7
end>

Otra vez, la arquitectura ActionManager y el control “Ribbon”


con sus clases soportadas tienen múltiples características, las
cuales podría ampliar con muchas más páginas. Aquí, en todo
caso, podemos ver unas cuantas sugerencias interesantes más
(no mostradas en la práctica):

La guía de Delphi por Marco Cantù | para


Indice - 397

 Se puede personalizar el menú de aplicación, por ejemplo


cambiando el caption de “Documentos Recientes” o el tamaño
del icono usando la propiedad ApplicationMenu del control
“Ribbon”.
 De manera similar se puede personalizar el comportamiento
del menú de acceso rápido estableciendo varias sub-propie-
dades de la propiedad QuickAccessToolbar del control “Ri-
bbon”.
 La razón por la que no se establece un tamaño para los grupos
de la “Ribbon” pero se les permite adaptarse al tamaño de los
elementos contenidos es debido a un requisito específico de
las líneas de diseño de “Fluent UI”.
 Los usuarios pueden hacer clic derecho sobre los elementos
de la “Ribbon” para añadirlos a la barra de herramientas de
acceso rápido y realizar operaciones relacionadas.

“Ribbons” en aplicaciones
de bases de datos
Es muy obvio ver como el uso de un control Ribbon se adapta-
ría a una aplicación orientada a documentos, pero ¿cómo se
adaptaría a programas completamente diferentes como son
los de base de datos? Considerando que tenemos un conjunto
de acciones básicas relacionadas con base de datos disponi-
bles, podríamos ser tentados de utilizar una “Ribbon” en vez
un DBNavigator, clásico, y esto es de hecho posible (y además
bastante sencillo de lograr). No estoy exactamente seguro de
que esto respete las reglas de uso de Microsoft pero cierta-
mente no infringe la norma de clonación de Office.

La guía de Delphi por Marco Cantù


398 - Indice

Para crear un DataRibbon tuve que situar un ClientDataSet,


con un DataSource y un DBGrid en un formulario para la ges-
tión de la base de datos; añadir un ImageList, un ActionMa-
nager, y una “Ribbon” para el interfaz de usuario. Después
crear unas cuantas acciones personalizadas (básicamente to-
das las acciones personalizadas del DataSet menos el refresco,
además de una acción de deshacer y también las de apertura y
salida de ficheros), crear una pestaña en la “Ribbon” con tres
grupos y arrastrarles algunas acciones. No se añadió ningún
código más. Después todo lo que tuve que hacer fue abrir el
ClientDataSet en el arranque, utilizando el fichero ClientData-
Set como nombre del documento de la “Ribbon” y hacer lo
mismo cuando la acción de la operación de apertura de fi-
chero se ejecute:
procedure TFormDataRibbon.FileOpen1Accept(
Sender: TObject);
begin
ClientDataSet1.Close;
ClientDataSet1.FileName := FileOpen1.Dialog.FileName;
Ribbon1.DocumentName := ClientDataSet1.FileName;
ClientDataSet1.Open;
end;

Esto es casi el código completo del programa, el resto es una


colección de configuraciones almacenadas en el fichero DFM
y obtenidas con operaciones visuales en tiempo de diseño.
¿Quieres echar un vistazo a estas configuraciones? Aquí hay
un pequeño resumen, muy ajustado (esto es una parte de
cerca de 788 líneas de código fuente), valioso para aportar
una vista general de las relaciones entre la “Ribbon” y el Ac-
tionManager:
object FormDataRibbon: TFormDataRibbon
object Ribbon1: TRibbon
ActionManager = ActionManager1
Caption = 'DataRibbon'
Tabs = <item Page = RibbonPage1 end>
StyleName = 'Ribbon - Luna'
object RibbonPage1: TRibbonPage
Caption = 'DBNavigation'
Index = 0

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 399

object RibbonGroup1: TRibbonGroup


Caption = 'Browse'
GroupIndex = 1
end
object RibbonGroup2: TRibbonGroup
Caption = 'Edit'
GroupIndex = 2
end
object RibbonGroup3: TRibbonGroup
Caption = 'File'
GroupIndex = 0
end
end
end
object ImageList1: TImageList...
object ActionManager1: TActionManager
ActionBars = <
item
Items = <
item Action = DataSetFirst1 end
item Action = DataSetPrior1 end
item Action = DataSetNext1 end
item Action = DataSetLast1 end>
ActionBar = RibbonGroup1
end
item
Items = <
item Action = DataSetDelete1 end
item Action = DataSetEdit1 end
item Action = DataSetInsert1 end
item Action = DataSetPost1 end
item Action = DataSetCancel1 end
item Action = ClientDataSetUndo1 end>
ActionBar = RibbonGroup2
end
item
Items = <
item Action = FileOpen1 end
item Action = FileExit1 end>
ActionBar = RibbonGroup3
end>
Images = ImageList1
StyleName = 'Ribbon - Luna'
object DataSetFirst1: TDataSetFirst...
object DataSetPrior1: TDataSetPrior...
object DataSetNext1: TDataSetNext...
object DataSetLast1: TDataSetLast...
object DataSetInsert1: TDataSetInsert...
object DataSetDelete1: TDataSetDelete...
object DataSetEdit1: TDataSetEdit...
object DataSetPost1: TDataSetPost...
object DataSetCancel1: TDataSetCancel...
object ClientDataSetUndo1: TClientDataSetUndo

La guía de Delphi por Marco Cantù


400 - Indice

Caption = 'Undo'
FollowChange = False
end
object FileOpen1: TFileOpen
Caption = '&Open...'
Dialog.Filter = 'CDS|*.cds|XML|*.xml'
Dialog.InitialDir = '...\CodeGear Shared\Data'
OnAccept = FileOpen1Accept
end
object FileExit1: TFileExit...
end
object DBGrid1: TDBGrid
Align = alClient
DataSource = DataSource1
end
object DataSource1: TDataSource
DataSet = ClientDataSet1
end
object ClientDataSet1: TClientDataSet
FileName = '...\CodeGear Shared\Data\customer.cds'
end

Podría haber extendido el ejemplo con las operaciones básicas


de edición (como copiar o pegar) y otras, pero básicamente he

La guía de Delphi por Marco Cantù | para


Indice - 401

alcanzado mi objetivo de probar que, aunque inusual, la “Ri-


bbon” puede ser usada en programas orientados a base de da-
tos. Este el programa en acción:

Utilizar Screen Tips


Otros elementos de la interfaz de usuario de la “Ribbon” son
las grandes y detalladas etiquetas contextuales, conocidas
como “Screen Tips”. Estas son generalmente usadas por las
“Ribbon” para asociarlas a diferentes acciones pero pueden
ser usadas en aplicaciones que no usan la Ribbon, e incluso en
aplicaciones que no usan acciones. Delphi 2009 tiene un so-
porte específico para las “Screen Tips” con dos componentes
diferentes:
 El componente ScreenTipsManager es un manejador general
de “Screen Tips”. Puede manejar detalles de “Screen Tips”
para cada una de las acciones relacionadas con ActionList o
ActionManager, y tiene su propio editor para permitirte gene-
rar “Screen Tips” para cada acción como veremos luego.
 El control ScreenTipsPopup permite un interfaz específico
para que las “Screen Tips” sean asociadas a cualquier control
visual, proveyendo “Screen Tips” para elementos de interfaz
no vinculados a acciones. Este control todavía necesita ser co-
nectado con un componente ScreenTipsManager.
Aquí muestro dos diferentes ejemplos. El primero es un ejem-
plo simple y aislado del uso de “Screen Tips” sin “Ribbon” ni
acciones. El segundo será una extensión de la demostración
integrando “Screen Tips”.

La guía de Delphi por Marco Cantù


402 - Indice

“Screen Tips” sin “Ribbon”


Como mencioné, se pueden usar “Screen Tips” en aplicaciones
que no tengan “Ribbon”. Para mostrar que esta técnica puede
ser añadida a cualquier programa, he tomado un “Hola
mundo” clásico de Delphi , un programa con un list box, un
edit y un botón usados para añadir texto del edit en el list box.
He añadido un ScreenTipsManager (con las configuraciones
por defecto) y tres controles ScreenTipsPopup, uno por cada
control visual. Cada ScreenTipsPopup tiene una propiedad
ScreenTip referida al TScreenTipItem: usted puede personali-
zar estos elementos con una Header, una Image, y una Descrip-
tion, además de otras propiedades. ScreenTipsPopup tiene
una propiedad Associate que se puede usar para referirse al
control visual al cual la “Screen Tip” está conectada (este con-
trol debe tener su propiedad ShowHint activada).
Finalmente si se quiere esconder la pequeña imagen del con-
trol ScreenTipsPopup, se puede establecer la propiedad Visi-
ble a False. Esto es lo que he hecho en la demo número dos
sobre el pequeño gráfico, solo cuando un usuario se mueve so-
bre el control asociado (ocultando el gráfico) o en ambos ca-
sos.
Este es el “Screen tip” del botón añadir de la demo PlainTips:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 403

Nótese la pequeña imagen para la etiqueta conectada con el


list box en el lado izquierdo del control (a la izquierda de
la”Screen Tip” visible). Aquí se muestran las propiedades más
relevantes para los componentes “Screen Tip” de este ejem-
plo:
object FormPlainTips: TFormPlainTips
object ScreenTipsPopup1: TScreenTipsPopup
Associate = ListBox1
ScreenTip.Description.Strings = (
'List of text elements that were added')
ScreenTip.Header = 'List'
ScreenTip.Image.Data = {...}
ScreenTip.ShowImage = True
ScreenTipManager = ScreenTipsManager1
end
object ScreenTipsPopup2: TScreenTipsPopup
Associate = btnAdd
ScreenTip.Description.Strings = (
'Add the text to the list box, avoiding...')
ScreenTip.Header = 'Add Text'
ScreenTip.Image.Data = {...}
ScreenTip.ShowImage = True
ScreenTipManager = ScreenTipsManager1
Visible = False
end
object ScreenTipsPopup3: TScreenTipsPopup
Associate = edText
ScreenTip.Description.Strings = (
'Text to be added to the listbox')
ScreenTip.Header = 'Text'
ScreenTip.Image.Data = {...}
ScreenTip.ShowFooter = False
La guía de Delphi por Marco Cantù
404 - Indice

ScreenTip.ShowImage = True
ScreenTipManager = ScreenTipsManager1
Visible = False
end
object ScreenTipsManager1: TScreenTipsManager
FooterImage.Data = {...}
end
end

Nótese que uno de los controles ScreenTipsPopup está visible,


mientras que el otro no tiene pie. El pie muestra algo de texto
y una imagen opcional compartida por todos los “Screen Tip”,
suministrada por el componente ScreenTipsManager.

ScreenTipsManager y las
acciones
Incluso aunque se usen las “Screen Tips” sin una “Ribbon” y
una ActionList o ActionManager, este sería el escenario más
clásico y en el cual trabajan mejor y para el cual hay un so-
porte específico. De hecho, si se sitúa un componente Screen-
TipsManager en un formulario que usa una ActionList o un
ActionManager (con o sin un control Ribbon), se podrán utili-
zar varias características del editor de componente, que son
los comandos de su menú de contexto:
 El comando “Generate Screen Tips” ayudará a crear un
“Screen Tip” básico para cada acción y conectarlos. Si se tie-
nen ya generados los “Screen Tips” para algunas acciones se
preservaran.
 El comando “Regenerate Screen Tips” trabaja como el co-
mando anterior pero quitará cualquier “Tip” anterior y co-
menzará luego desde el principio.
 El comando “Edit Screen Tips” le permitirá ver y personalizar
todos los “Screen Tips” relacionados, con un editor fácil de
usar.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 405

Como ejemplo he usado esta característica para la aplicación


RibbonEditor, creando la versión del programa. Todo lo que
he tenido que hacer en la primera versión ha sido añadir un
ScreenTipsManager, ir a su colección y añadir un elemento
referido al ActionManager del ejemplo, e invocar el comando
“Generate Screen Tips”. Esto genera “Screen Tips” vacíos para
cada acción.
Para que las “Screen Tips” sean visibles, tuve que acometer
dos pasos más: en el formulario, hube de establecer las pro-
piedades ShowHints (ya que esto no está disponible a nivel de
control); en la “Ribbon” hube de asignar el componente
ScreenTipsManager a la propiedad ScreenTips. Esto es sufi-
ciente para tener un “Tip” básico mostrándose al tiempo que
el ratón se mueve por encima de los elementos del “Ribbon”,
como en el caso siguiente:

La guía de Delphi por Marco Cantù


406 - Indice

Esta es una etiqueta muy básica. Se puede personalizar edi-


tando la “Screen Tip” dada en la colección ScreenTips del

ScreenTipsManager:
Sin embargo, para hacer la cosas mucho más fáciles, se debe-
ría usar el editor de “Screen Tips”, en lugar de hacer doble clic
sobre el ScreenTipsManager.

A continuación muestro el resultado:

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 407

El resumen de las propiedades del ejemplo RibbonEditorTips,


considerando solo las diferencias de la versión anterior se lis-
tan en la siguiente página:
object RibbonEditorForm: TRibbonEditorForm
ShowHint = True
object Ribbon1: TRibbon
ScreenTips = ScreenTipsManager1
end
object ScreenTipsManager1: TScreenTipsManager
FooterImage.Data = {...}
LinkedActionLists = <
item
ActionList = ActionManager1
Caption = 'ActionManager1'
end>
ScreenTips = <
item
Action = EditCut1
Description.Strings = ('Cuts the selection...')
Header = 'Cut'
end
item
Action = EditCopy1

La guía de Delphi por Marco Cantù


408 - Indice

Description.Strings = ('Copies the selection...')


Header = 'Copy'
ShowImage = True
end
...
end
end

A continuación
Después de haber tocado la RTL al final de la parte II del li-
bro, en los tres últimos capítulos he cubierto las nuevas carac-
terísticas de la VCL (Capítulo 8), los cambios en el soporte de
COM (Capítulo 9) y el novísimo componente “Ribbon” basado
en la arquitectura del ActionManager (en el capítulo actual).
En la otra esquina se sitúa el soporte de base de datos de la
VCL. La tecnología de base de datos no ha cambiado desde la
última versión, si no se considera el hecho de ahora se soporta
Unicode. De hecho, la clase TDataSet introdujo el soporte de
Unicode en la versión anterior. Pero se basaba en el tipo
WideString, no en el nuevo tipo UnicodeString. Los cambios
en la arquitectura de acceso a base de datos, incluyendo la li-
brería dbExpress, serán tratados en el siguiente capítulo,
mientras que la ampliamente mejorada arquitectura multi-
capa DataSnap será el tema principal del capítulo número 12.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 409

La guía de Delphi por Marco Cantù


410 - Indice

Capítulo 11:
Datasets Y
dbExpress

Con toda la atención mostrada en Unicode y en las nuevas ca-


racterísticas del lenguaje Delphi, es posible que tenga la im-
presión de que hay pocas novedades en Delphi 2009 para los
desarrolladores de base de datos. Esta impresión es completa-
mente equivocada. No solamente disponemos ahora de base
de datos con pleno soporte Unicode, en comparación con el
apoyo parcial de metadatos de versiones anteriores, sino que
también hay varias novedades en dbExpress, incluido una
nueva versión de DataSnap que voy a cubrir, en el Capítulo 12.
Aquí, en cambio, voy a centrarme en los rasgos esenciales de
TDataSet y en las clases relacionadas, el mejorado dbExpress,
y ahondar en varios temas asociados.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 411

Un ClientDataSet Unicode
Antes de empezar a observar los cambios de los componentes
de bases de datos en Delphi 2009, creo que vale la pena dar
antes un vistazo a un ejemplo de aplicación basada en bases
de datos Unicode. Para mantener la sencillez, por ahora voy a
utilizar un componente ClientDataSet que se rellena dinámi-
camente con las cadenas procedentes de múltiples alfabetos.
En este ejemplo, denominado UniCds, la estructura de datos
del ClientDataSet se define en tiempo de ejecución, en el
evento OnCreate del formulario principal:
procedure TFormUniCds.FormCreate(Sender: TObject);
begin
cds.FieldDefs.Clear;
cds.FieldDefs.Add ('code', ftInteger, 0, True);
cds.FieldDefs.Add ('uni', ftWideString, 30, False);
cds.FieldDefs.Add ('ansi', ftString, 30, False);
cds.CreateDataSet;
cds.Open;
end;

El ClientDataSet está inicialmente vacío. Usted puede escribir


en el DBGrid asociado para añadir datos, o pulsar el botón
btnPopulate que rellena el conjunto de datos con la lista de ca-
denas Unicode con el texto “¿Qué es Unicode?”, escrita en
muchos idiomas, cargada desde el archivo de texto UTF-8 ela-
borado por el ejemplo StreamEncoding del capítulo 2.
Este es el código usado para cargar los datos, haciendo una
conversión entre bastidores de la codificación de archivos
UTF-8 a la codificación UTF-16 utilizada por el tipo Unico-
deString y por el campo WideString:
procedure TFormUniCds.btnPopulateClick(Sender: TObject);
var
I: Integer;
sList: TStringList;
strLine: string;
begin
sList := TStringList.Create;

La guía de Delphi por Marco Cantù


412 - Indice

try
I := 1;
sList.LoadFromFile('utf8text.txt');
for strLine in sList do
begin
cds.InsertRecord([I, strLine, AnsiString(strLine)]);
Inc (I);
end;
finally
sList.Free;
end;
end;

Nada extraordinario sin duda, pero es bueno tener una solu-


ción sencilla y simple para crear una aplicación multilingüe
con un resultado como este:

Lo que es diferente, sin embargo, de versiones anteriores de


Delphi no es el código en el componente . De hecho, se puede
usar el tipo de campo incluso en la versión anterior del IDE
de Embarcadero. Leer los datos desde el archivo y añadirlos a
la base de datos, habría sido sólo un poco más complicado. Lo
qué no es fácil de lograr, es mostrar estos datos en un o en

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 413

cualquier otro componente visual asociado a datos (o no data-


aware), de la VCL.
Sin embargo, el soporte de Unicode se extiende más allá de la
representación visual. Las técnicas utilizadas en el pasado
para el soporte de campos de datos y los nombres de tablas
basados en el juego de caracteres Unicode no se basaban en el
tipo UnicodeString de Delphi 2009, sino en el tipo Wid-
eString, que ha sido considerablemente modificado en Delphi
en 2009.

Unicode en Datasets,
Toma 2
Las clases TDataSet y TField se encuentran entre las pocas
clases que soportaban ya Unicode desde Delphi 2006. Este
soporte Unicode, sin embargo, está basado sobre el tipo Wid-
eString, que todavía está disponible pero que no es la norma
de enfoque utilizada en Delphi 2009 para soportar Unicode.
Como vimos en el Capítulo 2, el tipo WideString representa
un tipo menos optimizado y sin recuento de referencia hospe-
dando caracteres wide, originalmente introducidos para la
compatibilidad COM.
Si usted compara el código fuente de los componentes data-
base de la VCL de Delphi 2006 y Delphi 2009, podrá ver cla-
ramente que la mayoría de las propiedades declaradas como
WideString han vuelto al tipo de cadena predefinido, hasta el
punto de que el código fuente parece más similar al de versio-
nes anteriores, como Delphi 7 y Delphi 2005. Por ejemplo, si
ha mirado el código fuente de la clase TField, en Delphi 2007,
puede observar las siguientes propiedades:
La guía de Delphi por Marco Cantù
414 - Indice

type
TField = class(TComponent)
public
property FullName: WideString read GetFullName;
property DisplayLabel: WideString
read GetDisplayLabel write SetDisplayLabel
property FieldName: WideString
read FFieldName write SetFieldName;
property LookupKeyFields: WideString
read FLookupKeyFields write SetLookupKeyFields;
property LookupResultField: WideString
read FLookupResultField write SetLookupResultField;
property KeyFields: WideString
read FKeyFields write SetKeyFields;

Ahora, en Delphi 2009, todos están declarados ¡como string!


En la mayoría de los casos el cambio es muy compatible,
cuando se desplazan a la versión WideString, y sigue siendo
altamente compatible, tanto si están pasando de las versiones
más recientes de Delphi u otras más antiguas. Hay casos espe-
cíficos, sin embargo, en los que puede experimentar proble-
mas. Además, si por alguna razón usted utiliza explícitamente
el tipo WideString, normalmente deberá reemplazarlo por el
tipo UnicodeString (o incluso mejor, por el tipo genérico de
cadena, string).
No obstante, si el objeto TField y en definiciones relacionadas
en la unidad DB, se han convertido de WideString a
UnicodeString, esto no es cierto en toda la VCL. Los controles
data-aware, de hecho, todavía referencian los nombres de los
campos utilizando el viejo tipo de cadena de 2-bits-por-carác-
ter. Como ejemplo, en la clase TDBEdit podrá ver:
property DataField: WideString
read GetDataField write SetDataField;

Esto dista de ser lo más óptimo, porque este tipo no es de re-


ferencia contada y es menos eficiente que el tipo UnicodeS-
tring. Además, como usted generalmente utilizará el tipo Uni-
codeString en su código fuente, esto implicaría conversiones
entre tipos de cadena.

La guía de Delphi por Marco Cantù | para


Indice - 415

Listas de Cadenas Unicode


Un cambio estrictamente relacionado es el regreso al tipo
TStrings para las listas de cadenas, en lugar de sustituir el tipo
TWideStrings utilizado en las versiones más recientes. En es-
tos casos, por razones de compatibilidad, a menudo existe una
versión sobrecargada para que sea compatible con la imple-
mentación WideString. Por ejemplo, el método GetFieldsList
de la clase TDataSet se define ahora como:
procedure GetFieldNames(List: TStrings);
overload; virtual;
procedure GetFieldNames(List: TWideStrings);
overload; virtual;

El problema potencial con este enfoque es que si usted escri-


bió (o migró) su código a algo similar al siguiente código140,
este no será optimizado, ya que el programa tendrá que con-
vertir los tipos de cadenas:
var
WideList: TWideStringList;
begin
WideList := TWideStringList.Create;
try
cds.GetFieldNames (WideList);
ShowMessage (WideList.Text);
finally
WideList.Free;
end;
end;

Este programa, dejándolo tal como está, debe recopilar y pro-


ducir resultados correctos (mientras que, cuando desplazába-
mos código relacionado con bases de datos que utilizaba listas
de campos de Delphi 7 a Delphi 2006, no siempre compilaba).
Mi sugerencia, sin embargo, es rescribir el código, localizando

140Estefragmento de código se ha extraído de la demo UniCds, , presentada al


comienzo del capítulo.

La guía de Delphi por Marco Cantù


416 - Indice

cualquier concurrencia de las clases TWideStrings y Twid-


eStringList, y sustituirlas por los tipos preferentes TStrings y
TStringList:
var
List: TStringList;
begin
List := TStringList.Create;
try
cds.GetFieldNames (List);
ShowMessage (List.Text);
finally
List.Free;
end;

Por supuesto, si tiene código que sea anterior a Delphi 2006,


usted puede dejarlo como está y será automáticamente actua-
lizado para utilizar las listas de cadena Unicode.

Bookmarks
La clase TDataSet maneja los bookmarks para mantener el
foco a un determinado registro del conjunto de datos y permi-
tir al programa volver a este. Técnicamente, los bookmarks
son punteros a estructuras de datos internas, pero (desde mu-
chas versiones) se declaraban como si se fuesen strings para
aprovechar la ventaja de las cadenas con recuento de referen-
cia:
type
TBookmark = Pointer;
TBookmarkStr = string;

El tipo TBookmarkStr se ha utilizado como tipo de datos de la


propiedad Bookmark de la clase TDataset. Como ya he men-
cionado en la sección “Strings son... strings” del capítulo 3, es-
tas definiciones se han modificado en Delphi

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 417

2009141:
type
TBookmark = TBytes;
TBookmarkStr = AnsiString;
// deprecated use TBookmark instead.
// from SysUtils:
TBytes = array of Byte;

El tipo de datos de la propiedad Bookmark de la clase TDataset


es ahora del tipo TBookmark, que es una matriz (de referencia
contada) de Byte. Esto significa que el código existente que
utilice esta propiedad Bookmark es improbable que compile en
adelante.
Por ejemplo, si usted tiene el siguiente código de una versión
anterior de Delphi:
var
bookm: TBookmarkStr;
begin
// save curent position
bookm := cds.Bookmark;
// move away
cds.First;
// get back
Cds.Bookmark := bookm;

Cuando compile, obtendrá el error:


E2010 Incompatible types: 'AnsiString' and 'TBytes'

Lo que debe hacer es cambiar el código así:


var
bookm: TBookmark;

Una simple búsqueda y reemplazo de TBookmarkStr por TBook-


mark en todo su código fuente, generalmente bastará.

141Conla nueva directiva deprecated teniendo ahora como parámetro una des-
cripción, no estoy realmente seguro de por qué este tipo de datos está co-
mentado como obsoleto. Usando de la directiva adecuada en el caso descrito
aquí, obtendremos una advertencia clara antes del mensaje de error.

La guía de Delphi por Marco Cantù


418 - Indice

Tipos de Campos y Cadenas


Es interesante observar cómo los diferentes tipos de campos
se asignan a los diferentes tipos nativos de cadenas. Me re-
fiero específicamente a los tipos TStringField y TWideString-
Field, por supuesto. La propiedad Value para estos campos ha
cambiado en versiones anteriores de Delphi, pero se mantuvo
la misma hasta Delphi 2007, a pesar de los cambios en el so-
porte de metadatos (incluyendo el nombre de los campos,
mencionado anteriormente):

Delphi 7 Delphi 2007 Delphi 2009

TStringField.Value string string AnsiString

TWideStringField.Value WideString WideString UnicodeString

En el caso que su programa utilice un TWideStringField asig-


nado a un campo de la base de datos de tipo Unicode, los da-
tos se guardan en formato Unicode. ¿Qué sucede, en cambio,
cuando acceda a la propiedad AsString de un objeto TString-
Field? La implementación del método getter correspondiente
asigna este acceso al método AsAnsiString, que obliga a la con-
versión de la cadena búfer al tipo AnsiString.
Observe que un objeto utiliza la misma codificación UTF-16
de un UnicodeString en Delphi 2009, mientras que un objeto
TStringField utiliza el tipo básico AnsiString. Usted tendrá
que escribir código adicional de apoyo (que no será trivial es-
cribirlo) para utilizar un código de página diferente o la codi-
ficación UTF-8 con TStringField.

La guía de Delphi por Marco Cantù | para


Indice - 419

Otras Mejoras del Dataset


Además de los cambios que hemos relacionado con el soporte
Unicode y los cambios en los tipos string y PChar, hay otras
características nuevas en la clase TDataSet, algunas destinadas
a los usuarios finales, y algunas para los desarrolladores de
componentes.

Nuevos Tipos de Campos


Como veremos en el capítulo 12, la nueva arquitectura multi-
capa de Delphi 2009 esta basada en el uso de datasets, re-
cords y fields, tanto para paso de parámetros como para los
resultados. Esta es la razón por la que Delphi 2009 tiene mu-
chos nuevos tipos de campos, que no son estrictamente desti-
nados al procesamiento directo de las bases de datos. Los
nuevos tipos de campo son los siguientes:
ftLongWord, ftShortint, ftByte, ftExtended,
ftConnection, ftParams, ftStream

Si bien estos cuatro primeros tipos representan datos del


idioma, los tres últimos significan claramente un mayor nivel
de transferencia para las estructuras de datos (conexiones de
base de datos, parámetros, y streams).

Un Dataset Más Virtual


Cuando necesite ampliar una clase de una biblioteca como la
VCL, la única manera en que usted puede cambiar el compor-
tamiento de las clases existentes, sin reescribir demasiado có-
digo, es utilizando la herencia. Sin embargo, a menudo los
problemas residen en el hecho de que la clase derivada sólo

La guía de Delphi por Marco Cantù


420 - Indice

puede modificar lo que está declarado como virtual en la clase


base.
Así, por ejemplo el hecho de que el método MoveBy está de-
clarada ahora como virtual en la clase TDataSet significa que
cualquier clase derivada de TDataSet puede modificarlo con
más facilidad.
function MoveBy(Distance: Integer): Integer; virtual;

Otro caso muy interesante es cuando la clase base tiene que


crear soporte interno a objetos y lo hace utilizando una fun-
ción virtual, que permite personalizar el tipo de objeto interno
(utilizando un tipo derivado del de base, en su lugar). Para ha-
cer esto posible, las clases de estos objetos internos también
suelen disponer de un constructor virtual que, opcional-
mente, usted puede reemplazar y modificar.
Este es el ámbito en el que la arquitectura de los datasets ha
visto una útil extensión en Delphi 2009. A continuación in-
cluimos una lista de las clases que ahora soportan los cons-
tructores virtuales:
type
TIndexDef = class(TNamedItem)
constructor Create(Owner: TIndexDefs;
const Name, Fields: string; Options:
TIndexOptions); reintroduce; overload; virtual;

TIndexDefs = class(TDefCollection)
constructor Create(ADataSet: TDataSet); virtual;

TFieldDefList = class(TFlatList)
// from base class
constructor Create(ADataSet: TDataSet); virtual;

TFields = class(TObject)
constructor Create(ADataSet: TDataSet); virtual;

En la clase TDataSet para cada una de estas y otras estructuras


de datos, hay ahora una función virtual que devuelve la refe-
rencia de la clase a crear:
type
TDataSet = class

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 421

protected {indirect creation of internal objects}


function GetFieldDefsClass:
TFieldDefsClass; virtual;
function GetFieldDefListClass:
TFieldDefListClass; virtual;
function GetFieldsClass:
TFieldsClass; virtual;
function GetFieldListClass:
TFieldListClass; virtual;
function GetCheckConstraintsClass:
TCheckConstraintsClass; virtual;
function GetAggFieldsClass:
TFieldsClass; virtual;
function GetIndexDefsClass:
TIndexDefsClass; virtual;
function GetParamsClass:
TParamsClass; virtual;

Lo que hacen estas funciones en su implementación, es devol-


ver el valor de la correspondiente variable global que sostiene
la clase por defecto para el objeto interno dado:
var
DefaultFieldDefsClass: TFieldDefsClass = TFieldDefs;
: TFieldDefClass = TFieldDef;
DefaultLookupListClass: TLookupListClass =
TDefaultLookupList;
DefaultIndexDefClass: TIndexDefClass = TIndexDef;
DefaultCheckConstraintClass: TCheckConstraintClass =
TCheckConstraint;
DefaultParamClass: TParamClass = TParam;
DefaultParamsClass: TParamsClass = TParams;
DefaultFieldsClass: TFieldsClass = TFields;
DefaultFieldListClass: TFieldListClass = TFieldList;
DefaultIndexDefsClass: TIndexDefsClass = TIndexDefs;
DefaultFieldDefListClass: TFieldDefListClass =
TFieldDefList;
DefaultCheckConstraintsClass: TCheckConstraintsClass =
TCheckConstraints;

Esto significa que para personalizar, a nivel global, todos los


datasets en su aplicación, usted puede modificar estas varia-
bles globales, mientras que si usted necesita un conjunto de
datos específicos para devolver un objeto interno diferente,
debe hacerlo derivando una nueva clase y sobrescribiendo
una de las funciones virtuales mencionadas anteriormente.

La guía de Delphi por Marco Cantù


422 - Indice

El caso de la clase TLookupList es ligeramente diferente, con


la clase transformada en una clase abstracta y la clase deri-
vada TDefaultLookupList proporcionando la implementación
real.
¿Cómo podemos aprovechar en la práctica esta nueva fun-
ción? Para demostrar lo fácil que es personalizar estos obje-
tos, he escrito un ejemplo llamado CustomFields. En el ejem-
plo he personalizado la clase del campo y también asignado ti-
pos de campos a una clase personalizada de campo, utilizando
el array público DefaultFieldClasses.
CustomFields es una simple aplicación con un componente ,
un componente DataSource, un DBGrid, una barra de herra-
mientas con algunos botones y un control Memo para el regis-
tro de la información. El programa define una clase para las
definiciones de los campos, en la que he añadido una propie-
dad extra, sólo por el bien de la demo142:
type
TMyFieldDef = class (TFieldDef)
private
FExtraDescription: string;
procedure SetExtraDescription(const Value: string);
public
function ToString: string; override;
property ExtraDescription: string
read FExtraDescription write SetExtraDescription;
end;

function TMyFieldDef.ToString: string;


begin
Result := Name + ' - ' + ExtraDescription +
' [' + ClassName + ']';
end;

142Podría
ser interesante añadir en cada definición de campo una referencia a la
información de metadatos, un diccionario de datos, una definición de
campo, o cualquier cosa que le permita tener un más flexible y potente capa
de acceso a datos.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 423

Esta clase personalizada debe estar instalada antes de que se


cree el ClientDataSet (no antes de que se abra), de modo que
he añadido la siguiente línea a la sección initialization del
formulario principal del programa:
initialization
DefaultFieldDefClass := TMyFieldDef;

Haciendo clic sobre el segundo botón de la barra de herra-


mientas, el programa modifica la propiedad ExtraDescription
para la primera definición del campo y junto con la consabida
información extra sobre la definición de clase del campo:
procedure TFormCustomFields.btnFieldDefClick(
Sender: TObject);
begin
(ClientDataSet1.FieldDefs[0] as TMyFieldDef).
ExtraDescription := 'This is the first column';
Log ('ClientDataSet1.FieldDefs[0].ToString: ' +
ClientDataSet1.FieldDefs[0].ToString);
end;

La salida de esta llamada es:


ClientDataSet1.FieldDefs[0].ToString:
CustNo - This is the first column [TMyFieldDef]

La segunda personalización se basa en la definición de un tipo


de campo de cadena personalizada derivada del tipo AnsiS-
tring:
type
TMyStringField = class (TStringField)
protected
function GetAsString: string; override;
end;

function TMyStringField.GetAsString: string;


begin
Result := inherited GetAsString + ' is not Unicode';
end;

Esta clase hace una adaptación bastante extraña, añadiendo


una cadena fija a la salida de cada campo AnsiString. La
forma más sencilla de conectar este campo personalizado a to-
dos los campos de un tipo dado (cuyo formato interno debe
ser compatible) es usar el array global DefaultFieldClasses:
La guía de Delphi por Marco Cantù
424 - Indice

initialization
DefaultFieldClasses [ftString] := TMyStringField;

Una vez más, hay un botón para pedir la información de la


clase del campo sobre la marcha, pero el efecto de este código
es claramente visible en el DBGrid:

En situaciones más complejas se puede sobrecargar la clase


dataset y redefinir el método virtual143 GetFieldClass. Una vez
más, heredando de la clase CustomDataset usted puede per-
sonalizar el soporte a clases específicamente, mientras que en
este enfoque he utilizado disparadores al cambiar cada con-
junto de datos de la aplicación actual, que es algo que usted
puede querer o no.

143También habría sido posible personalizar la definición de campo mapeando el


campo modificando el método CreateField de la clase TFieldDef, ¡Aunque
sólo sea el método virtual en la clase base!

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 425

Extensiones de Campos
Además de cambiar la propiedad de algunos tipos de Wid-
eString de nuevo a string, como se mencionó anteriormente
en la sección “Unicode en Datasets, Toma 2” la clase TField
ahora tiene soporte para algunos de los nuevos tipos de cam-
pos (además esto diferencia entre AsString, que ahora de-
vuelve un UnicodeString, y el nuevo AsAnsiString):
type
TField = class(TComponent)
public
property AsExtended: Extended
read GetAsExtended write SetAsExtended;
property AsAnsiString: AnsiString
read GetAsAnsiString write SetAsAnsiString;
property AsBytes: TBytes
read GetAsBytes write SetAsBytes;

Compruebe como a pesar de la introducción de Unicode, el


mapeo de las bases de datos no cambia, porque las bases de
datos, por defecto consideran una cadena de texto como An-
siString, y no un texto habilitado-Unicode, para aquellos que
necesiten usar un campo de tipo específico. Es por eso que el
tipo clásico ftString, gestionado por la clase TStringField, se
sigue basando en AnsiString. Si desea tener cadenas Unicode
en su base de datos tiene que utilizar el tipo ftWideString y su
correspondiente clase TwideStringField, exactamente igual
que en versiones anteriores de Delphi. Ya he destacado antes
en la sección “Tipos de Campos y Cadenas” como los dos cam-
pos de tipo cadena manejan los valores internos.
Hay también algunas clases nuevas derivadas de TField, para
el manejo de algunos de los nuevos tipos de campos. Aquí está
la primera línea de la declaración de estas nuevas clases, de
modo que puede ver cual es la clase base:
type
TLongWordField = class(TNumericField)
TShortintField = class(TIntegerField)
TByteField = class(TIntegerField)

La guía de Delphi por Marco Cantù


426 - Indice

TUnsignedAutoIncField = class(TLongWordField)
TExtendedField = class(TNumericField)

Con estas nuevas clases, la jerarquía de la clases TField defini-


das en la unidad DB se hace aún más grande. Para ayudarle a
obtener una imagen completa, me he provisto de un árbol
completo de la clase a continuación:
TField
TStringField
TWideStringField
TGuidField
TNumericField
TIntegerField
TAutoIncField
TSmallintField
TShortintField
TByteField
TWordField
TLongWordField
TUnsignedAutoIncField
TLargeintField // Int64
TFloatField
TCurrencyField
TExtendedField
TBCDField
TFMTBCDField
TBooleanField
TDateTimeField
TDateField
TTimeField
TSQLTimeStampField
TBinaryField
TBytesField
TVarBytesField
TBlobField
TMemoField
TWideMemoField // widestring memo
TGraphicField
TObjectField
TADTField // Abstract Data Type
TArrayField
TDataSetField
TReferenceField
TVariantField
TInterfaceField
TIDispatchField
TAggregateField

La guía de Delphi por Marco Cantù | para


Indice - 427

Campos Considerados ANSI


La mayoría de tipos de campo son totalmente compatibles
con versiones anteriores de Delphi. Esto es particularmente
cierto para los tipos de cadenas, a pesar del nuevo soporte
UnicodeString, porque la gestión de campos de cadenas ANSI
y WideStrings fueron y aún son todavía diferentes. Del mismo
modo, los campos memo conteniendo cadenas ANSI o Uni-
code son asignados a las distintas clases de campo TMemoField
y TWideMemoField. El único escollo potencial podría surgir con
el campo BLOB genérico utilizado para almacenar informa-
ción basada en cadenas. Cuando se accede a un objeto TBlob-
Field como una cadena, ¿cómo debe Delphi 2009 considerar
los datos? Una vez más, la decisión fue a favor de mantener la
compatibilidad hacia atrás, por lo que la cadena de datos den-
tro de un campo BLOB se considera que se basa en ANSI
como en las versiones anteriores de Delphi. Usted debe usar
TWideMemoField para datos Unicode en un BLOB como se
sugiere en este comentario en el método GetAsString de la
clase TBlobField:
// For backwards compatibility, read untyped data as Ansi.
// Use TWideMemoField for blobs associated with Unicode
// string data.

Extensiones de Parámetros
Como los campos, los parámetros tienen algunas propiedades
nuevas, pero en este caso puede no sólo ver el soporte para los
nuevos tipos de campo, sino también para los nuevos tipos de
parámetros utilizados para DataSnap:
type
TParam = class(TCollectionItem)
public
property AsShortInt: LongInt;
property AsByte: LongInt;

La guía de Delphi por Marco Cantù


428 - Indice

property AsLongWord: LongWord;


property AsLargeInt: LargeInt;
property AsAnsiString: AnsiString;
property AsBytes: TBytes;
// Used by TSQLServerMethod
property AsDataSet: TDataSet;
property AsParams: TParams;
property AsStream: TStream;

Encontrará más información sobre estos nuevos tipos de pa-


rámetros en el Capítulo 12, donde voy a tratar lo nuevo de
DataSnap. Observe que el núcleo de la implementación de es-
tos parámetros, sin embargo, agrega soporte para una interfaz
IParamImplementation, que se utiliza para asignar objetos al va-
lor Variant de un objeto TParam. Hay algunas clases predefini-
das que implementan esta interfaz: la clase TParamObject (para
los parámetros genéricos), TParamDataSetObject (para los pará-
metros de datos), la clase TParamParamsObject (para los pará-
metros que representan parámetros), y TParamStreamObject
(para los parámetros de flujo).

DataSets Internos
Usted probablemente ya sabe que la clase TDataset es una
clase base abstracta que define los fundamentos de acceso a
la base de datos de Delphi. Puede que no sepa, sin embargo,
que esta clase tiene un gran número de métodos virtuales que
requieren una puesta en práctica de nivel de implementación
bastante bajo para su administración a nivel-buffer, que en el
pasado se basaron todos en punteros PChar.
Huelga decir que, en Delphi 2009 esto ya no es cierto. La ma-
yoría de punteros a bajo nivel están declarados como PByte o
TBytes (es decir, array of Byte). Para aclarar y simplificar el

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 429

código, la unidad DB introduce y usa el nuevo tipo TRecord-


Buffer y modifica la lista de búferes tipo (que solía ser una
matriz de PChar):
type
TRecordBuffer = PByte;
TBufferList = array of TRecordBuffer;

Por ejemplo, esta es la definición de las funciones de gestión


de registro de memoria intermedia (buffer) en todas las ver-
siones desde Delphi 3 a Delphi 2007:
function AllocRecordBuffer: PChar; virtual;
procedure FreeRecordBuffer(
var Buffer: PChar); virtual;
procedure GetBookmarkData(Buffer: PChar;
Data: Pointer); virtual;
function GetBookmarkFlag(
Buffer: PChar): TBookmarkFlag; virtual;

Este es el mismo conjunto de métodos del TDataSet en Delphi


2009:
function AllocRecordBuffer: TRecordBuffer; virtual;
procedure FreeRecordBuffer(
var Buffer: TRecordBuffer); virtual;
procedure GetBookmarkData(Buffer: TRecordBuffer;
Data: Pointer); overload; virtual;
function GetBookmarkFlag(
Buffer: TRecordBuffer): TBookmarkFlag; virtual;

Podría listar una docena de otros métodos con las mismas di-
ferencias. Es importante señalar que muchos de estos son mé-
todos virtuales, los métodos que necesita aplicar para definir
un dataset personalizado. Los métodos de la interfaz pública
de TDataSet, en cambio, sufren cambios muy limitados. En
otras palabras, los cambios en la clase de TDataSet tienen
poco efecto sobre los usuarios de clases datasets, pero afectan
de forma significativa a los que escribieron una clase persona-
lizada de dataset.

Portando un (simple) dataset


La guía de Delphi por Marco Cantù
430 - Indice

personalizado
Para Mastering Delphi 7 escribí144 un conjunto de datos per-
sonalizados sobre la base de una arquitectura registro-a-
stream. Así que pensé que la adaptación de este dataset perso-
nalizado a Delphi 2009 sería una buena prueba del esfuerzo
necesario para dicho proceso.
Una vez hecha esta importación (sin tener que pasar toda la
extensión a soporte de cadenas Unicode en mi base de datos),
tengo que decir que fue fácil. El CustomDataset está dividido
en dos unidades de código fuente: la unidad MdDsCustom,
que define la clase TMdCustomDataSet, de alto nivel abstracto, y
la unidad MdDsStream, que define la implementación actual
de la clase TMdDataSetStream.
Abrí las dos unidades, hice un Buscar / Reemplazar de PChar a
TRecordBuffer encontrando 19 apariciones en la búsqueda de
la primera unidad y 4 en la segunda, aceptando el cambio en
todos ellos. La mayoría de las referencias a PChar estaban en la
declaración de los métodos virtuales que se han modificado,
un par eran variables locales de carácter temporal que se uti-
lizan para almacenar el buffer en uso, obtenidas con el mé-
todo ActiveBuffer de la clase TDataSet que ahora devuelve un
TRecordBuffer.
Hasta este punto, el componente compilará. En el programa
principal tuve que modificar el código para probar book-
marks, declarando la variable local como:

144Elcódigo original se remonta de hecho a mi Delphi Developer Handbook, es-


crito para Delphi 3, y fue modificado para el libro de Delphi 7. No tengo es-
pacio aquí para cubrir en detalle lo que se necesita para escribir un conjunto
de datos personalizados, que está lejos de ser una tarea fácil. Si usted está in-
teresado en conocer los detalles de cómo escribir un conjunto de datos según
sus necesidades puede consultar uno de mis dos libros anteriores.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 431

// bm: TBookmarkStr; // old


bm: TBookmark; // new

¡Eso fue todo! En cuestión de minutos, el conjunto de datos


personalizado se crea y se ejecuta, y puedo incluso reabrir un
archivo anterior de demostración, con algunos datos. El có-
digo fuente del Dataset y el programa de demostración está
disponible en la carpeta StreamDsDemo. Aquí está la muestra del
resultado:

dbExpress en Delphi 2009


Delphi 2007 aportó una importante actualización de la arqui-
tectura de dbExpress, con el lanzamiento de la versión dbEx-
press IV. Delphi 2009 incorpora algunas mejoras, pero pue-
den ser consideradas como menores... si no en piensa en las
imágenes, ni en el soporte para varios niveles de datos que se
ha denominado DataSnap 2009, pero es técnicamente parte
de la arquitectura dbExpress, por lo menos en el lado del
cliente. DataSnap 2009 es el tema del siguiente capítulo, por
lo que no lo voy a cubrir aquí.
Centrándose sólo en aplicaciones cliente/servidor, ¿cuáles son
los cambios en dbExpress? Nada sorprendente, pero si hay al-
gunas mejoras interesantes (y un par de problemas). Voy a ex-
plorar estos cambios trabajando en un programa dbExpress
La guía de Delphi por Marco Cantù
432 - Indice

muy sencillo prestado de Delphi 2007 Handbook (y original-


mente escrito para “Mastering Delphi 6”, creo). La nueva ver-
sión del programa se llama DbxMulti2009.

Ajustes de Conexión y Cadenas


de Conexión
Tal cómo cree una nueva aplicación dbExpress (o abra una ya
existente), y seleccione el editor de componente SQLConnec-
tion (el menú contextual de los componentes) inmediata-
mente usted verá algo distinto del pasado:

El editor de conexión (Editar Propiedades de conexión) y el


correspondiente cuadro de diálogo han desaparecido, y tam-
bién lo ha hecho el cuadro de diálogo con la lista de controla-
dores instalados. Ahora tiene dos opciones para modificar la
configuración de dbExpress: La primera consiste en modificar
manualmente el dbxdrivers.ini y dbxconnections.ini, y la se-
gunda es utilizar el componente SqlConnection, sus propieda-
des, y su nuevo componente editor de comandos. Particular-
mente, después de que ha seleccionado una conexión, puede
elegir entre:
 Reload connection parameters (Volver a cargar los paráme-
tros de conexión) copiará la configuración dbxconnections.ini

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 433

al componente SqlConnection para la configuración actual.


Observe que cuando los valores se han modificado, compa-
rado con la versión original, la propiedad ConnectionName en
el Object Inspector tendrá un asterisco después de su nombre,
lo que indica una modificación en el conjunto de datos de co-
nexión:
 Por otra parte, Save connection parameters (Guardar los pa-
rámetros de conexión) actualizará el dbxconnections.ini con
los ajustes de la configuración actual
 Add ConnectionString Param (Añadir Parámetro a Cadena de
Conexión) añadirá una propiedad extra al componente, con la
configuración completa guardada en una propiedad de cadena
única, algo muy útil para la configuración del status sobre la
base de un archivo INI exterior o algún otro ajuste de configu-
ración.
 Una vez que haya agregado una cadena de conexión, este co-
mando se sustituye con los comandos Refresh ConnectionS-
tring Param (Actualizar Parámetros de Cadena de Conexión)
y Remove ConnectionString Param (Quitar Parámetros de
Cadena Conexión).
En la demo, añadiendo el parámetro de cadena de conexión se
añadirá una línea ConnectionString a la propiedad Params, con
el siguiente contenido (que he dividido en varias líneas para
facilitar la lectura, si bien tiene que estar en una sola línea,
porque es parte de un TStringList):
DelegateConnection=dbxpoolconnection,
DriverName=INTERBASE,
DRIVERUNIT=DBXDynalink,
DRIVERPACKAGELOADER=TDBXDynalinkDriverLoader,
DRIVERPACKAGE=DBXCommonDriver110.bpl,
DRIVERASSEMBLYLOADER=
Borland.Data.TDBXDynalinkDriverLoader,
DRIVERASSEMBLY=Borland.Data.DbxCommonDriver,
Version=11.0.5000.0,
Culture=neutral,
PublicKeyToken=a91a7c5705831a4f,

La guía de Delphi por Marco Cantù


434 - Indice

GETDRIVERFUNC=getSQLDriverINTERBASE,
DATABASE=..\CodeGear Shared\Data\Employee.GDB,
ROLENAME=RoleName,
USER_NAME=sysdba,
PASSWORD=masterkey,
SERVERCHARSET=,
SQLDIALECT=3,
BLOBSIZE=-1,
COMMITRETAIN=False,
WAITONLOCKS=True,
ERRORRESOURCEFILE=,
LOCALECODE=0000,
INTERBASE TRANSISOLATION=ReadCommited,
TRIM CHAR=False

Observe que esta única-línea de configuración incluye una op-


ción estándar de ajustes de base de datos (en la última parte),
los ajustes de configuración del controlador, la base de datos
actual para conectarse, e incluso información para el driver
delegado.
Este nuevo enfoque se basa en una nueva estructura de datos
interna, la clases persistente TConnectionData, a la que se
puede acceder utilizando la nueva propiedad de sólo lectura
ConnectionData del componente SqlConnection. Esta clase
almacena todos los ajustes de configuración en una lista in-
terna TBDXProperties que se puede referir a una nueva es-
tructura TConnectionData con una conexión delegada, y tiene
métodos de soporte como:
procedure UpdateProperties(NewProperties: TStrings);
procedure AddProperties(NewProperties: TStrings);
procedure ReloadProperties;
procedure RefreshProperties;

Como ejemplo, he utilizado esta propiedad para extraer la ca-


dena de conexión de ConnectionData (información tam-
bién disponible en la propiedad de conexión Params). He es-
crito este código en la acción del manipulador de evento OnE-
xecute que he añadido al componente ActionManager del pro-
grama:
procedure TForm1.ActionGetInfoExecute(Sender: TObject);
begin
ShowMessage (SQLConnection1.ConnectionData.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 435

Properties ['ConnectionString']);
end;

Configuración de propiedades del


controlador y Controladores
Delegado
Otra diferencia notable se relaciona con la configuración de
los controladores (drivers) y controladores delegado. Cuando
en tiempo de diseño se selecciona un valor para la propiedad
del controlador en Delphi 2009, podrá ampliar esta propiedad
y establecer unos valores. Entre ellas hay una referencia al
driver delegado, que puede ampliar así como configurarlo. En

otras palabras, si necesita configurar un archivo de segui-


miento, por ejemplo, ahora puede hacerlo directamente en la
configuración del controlador. En tal caso, en la siguiente pá-
gina puede ver como queda el Inspector de Objetos:

La guía de Delphi por Marco Cantù


436 - Indice

Despliegue y Archivos INI


Delphi 2009 tiene un efecto secundario no deseado relacio-
nado con el despliegue de archivos de configuración dbEx-
press. Este problema esta resuelto en Delphi 2009 Update 2.
Usted probablemente puede hacer caso omiso de lo siguiente,
si tiene instalada esta actualización. En la versión inicial de
Delphi 2009, si configura el componente estableciendo una
conexión y un driver, su aplicación tendrá que leer los archi-
vos y dbxconnections.ini en tiempo de ejecución, lo que se no
sucedía anteriormente. Si los archivos no están disponibles
verá un error como este145:

Esto se debe al hecho de que aunque las diversas opciones de


configuración del controlador se han cargado en las propieda-
des de conexión adecuadas (incluidas las LibraryName, Ven-
dorLib, y GetDriverFunc) y parámetros (Parámetros almacena-
dos en la lista de cadenas), el código intenta actualizarlas de
todos modos. La línea que provoca el error es la siguiente (a
partir del método SetDriverName de la clase TSQLConnec-
tion):
DriverProperties := TDBXConnectionFactory.
GetConnectionFactory.GetDriverProperties(FDriverName);

145Elmensaje de error es un poco confuso, ya que el archivo no falta en el regis-


tro del sistema, por supuesto, pero si en la carpeta de referencia del registro
del sistema, que en mi sistema es C:\Users\Public\Documents\RAD Stu-
dio\dbExpress

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 437

Una solución temporal es crear e instalar un gestor de cone-


xiones en memoria146 antes que la propiedad se asigne, es de-
cir, antes de que el formulario se haya cargado (como lo he
hecho en el ejemplo dbxMulti2009):
procedure SetConnectionManager;
var
ConnFact: TDBXConnectionFactory;
begin
ConnFact := TDBXMemoryConnectionFactory.Create;
ConnFact.Open;
TDBXConnectionFactory.SetConnectionFactory(ConnFact);
end;

initialization
SetConnectionManager;

De nuevo, probablemente no necesitará este código, después


de la aplicación de Update 2 de Delphi 2009. Como solución
alternativa se puede incluso considerar el despliegue de archi-
vos dbxdrivers.ini y dbxconnections.ini vacíos en el mismo di-
rectorio que la aplicación.

Drivers en el Ejecutable
Por último, para que la aplicación funcione tiene que agregar
la unidad DbxInterBase a su cláusula uses, ya que contiene el
controlador y la información de metadatos de la base de datos
específica.
Esta unidad se añade generalmente automáticamente al con-
figurar el controlador con el componente SQLConnection,
pero he visto situaciones en las que esto no era gestionado
adecuadamente, lo que produce el siguiente error:

146Elcódigo original, y lo relacionado tras todo ello, se puede encontrar en el blog


de Chau Chee Yang, en la dirección http://chee-
yang.blogspot.com/2008/09/delphi-2009-usingdbx4-framework.html.

La guía de Delphi por Marco Cantù


438 - Indice

Si desea manejar múltiples bases de datos en tiempo de ejecu-


ción, cambiando la configuración dinámicamente o cargando
de los archivos de configuración, usted debe añadir manual-
mente las unidades diferentes adecuadas para las bases de da-
tos de la aplicación: DbxOracle, DbxMSSQL, DbxMySql,
DbxBlackfishSQL, DbxDb2, DbxInformix, los dos conductores
de Sybase, y así sucesivamente. En el pasado este controlador
y su información de metadatos se añadía a cada proyecto, en
caso de que fueran necesarias, mientras que ahora sólo puede
incluir el apoyo a las bases de datos que va a utilizar en el eje-
cutable.
Tenga en cuenta que estas unidades añaden el controlador y
su información de acceso a metadatos a la configuración de
dbExpress, pero no se integran al driver147 real dbExpress
(en este caso dbxint.dll), de modo que todavía tiene que im-
plementarlo junto con su aplicación y el proveedor de la libre-
ría.

Soporte de Metadatos Extendidos


Una característica de los últimos dbExpress, aunque no es
nuevo en Delphi 2009, es el soporte extendido de metadatos.

147Estoes diferente que en versiones anteriores de Delphi, en el que fue posible


incluir una unidad especialmente compilada que contiene el código del
driver, embebiendo de manera efectiva la librería del driver en el archivo
ejecutable.

La guía de Delphi por Marco Cantù | para


Indice - 439

Esta característica se introdujo en Delphi 2007, pero no se in-


cluyó en Delphi 2007 Handbook (ya que el libro apareció an-
tes de la actualización de Delphi 2007 que lo introdujo).
El nuevo soporte a metadatos es ampliamente utilizado por el
panel Explorador de Datos del IDE de Delphi, pero también
puede ser utilizado por cualquier aplicación. En resumen, us-
ted no sólo podrá navegar por la estructura de base de datos,
sino que también utilizará sus clases y objetos para modifi-
carla, en lugar de basarse directamente en comandos SQL na-
tivos para la creación y modificación de la estructura de datos.
No sólo la apariencia del código parece más orientada a obje-
tos, sino que será más fácil trabajar con diferentes servidores
de bases de datos con el mismo código, ya que dbExpress abs-
trae las capacidades de metadatos de cada servidor.
No quiero ahondar en los detalles de los metadatos de dbEx-
press que en realidad es bastante complejo de manejar, sólo le
mostraré un sencillo ejemplo que puede utilizar para añadir
nuevas tablas a una base de datos. Antes de hacer cualquier
operación sobre metadatos con dbExpress, necesita inicializar
un proveedor específico y mantener una referencia en torno a
este objeto (en el ejemplo sobre un campo privado del formu-
lario):
type
TFormMetaCreateTable = class (TForm)
private
metaProv: TDBXDataExpressMetaDataProvider;

procedure TFormMetaCreateTable.InitMetaProvider;
begin
if not Assigned (metaProv) then
begin
metaProv := TDBXDataExpressMetaDataProvider.Create;
metaProv.Connection := SqlConnection1.DBXConnection;
metaProv.Open;
end;
end;

La guía de Delphi por Marco Cantù


440 - Indice

Observe que los metadatos y están conectados y dependen de


la conexión real y su configuración, básicamente, el controla-
dor sobre el que está trabajando. La unidad que define el pro-
veedor de metadatos, la cual tiene usted que incluir en su
cláusula uses,es DBXDataExpressMetaDataProvider.
Con esta configuración disponible, este es el código utilizado
para crear la tabla:
procedure TFormMetaCreateTable.btnCreateClick(
Sender: TObject);
var
MetaDataTable: TDBXMetaDataTable;
begin
InitMetaProvider;

MetaDataTable := TDBXMetaDataTable.Create;
MetaDataTable.TableName := edTableName.Text;
MetaDataTable.AddColumn(
TDBXInt32Column.Create('id'));
MetaDataTable.AddColumn(
TDBXDecimalColumn.Create('amount', 10, 2));
MetaDataTable.AddColumn(
TDBXUnicodeCharColumn.Create('city', 32));

metaProv.QuoteIdentifierIfNeeded('');
metaProv.CreateTable(MetaDataTable);
Log ('Table ' + MetaDataTable.TableName + ' created');
end;

Además de la creación de tablas, puede agregar otros ajustes,


a partir de los índices de vistas a las restricciones de la integri-
dad referencial. Puede utilizar también los metadatos para
consultar la estructura de la base de datos. Este es el código
tradicional que puede usar en dbExpress para acceder a las ta-
blas disponibles, utilizando el método GetTableNames del
componente SQLConnection:
procedure TFormMetaCreateTable.btnTableListOldClick(
Sender: TObject);
var
sl: TStringList;
str: string;
begin
sl := TStringList.Create;
try
SqlConnection1.GetTableNames(sl);

La guía de Delphi por Marco Cantù | para


Indice - 441

for str in sl do
begin
Log (str);
end;
finally
sl.Free;
end;
end;

Utilizando el proveedor de metadatos usted puede acceder a


mucho más que a los nombres de las tablas, aunque este frag-
mento de ejemplo hace básicamente solamente esto, filtrando
y descartando las tablas del sistema:
procedure TFormMetaCreateTable.btnTableListMetaClick(
Sender: TObject);
var
dbxTable: TDBXTablesTableStorage;
begin
InitMetaProvider;

dbxTable := metaProv.GetCollection (
TDBXMetaDataCommands.GetTables)
as TDBXTablesTableStorage;
while dbxTable.Next do
if not (dbxTable.TableType = 'SYSTEM TABLE') then
Log (dbxTable.TableName);
end;

Compruebe la utilización de la clase TDBXMetaDa-


taCommands, que cuenta con una colección de constantes pú-
blicas utilizadas para ayudar a escribir los distintos comandos
de metadatos, que son básicamente cadenas de comandos. El
resultado del anterior evento (después de crear una nueva ta-
bla con el nombre predeterminado) se muestra a continua-
ción:

La guía de Delphi por Marco Cantù


442 - Indice

En forma muy parecida al programa lee los nombres y tipos


de las columnas de la nueva tabla (o de la tabla con el nombre
en el cuadro de edición):
procedure TFormMetaCreateTable.btnColumnsListClick(
Sender: TObject);
var
dbxTable: TDBXColumnsTableStorage;
begin
InitMetaProvider;

dbxTable := metaProv.GetCollection (
TDBXMetaDataCommands.GetColumns + ' ' +
edTableName.Text) as TDBXColumnsTableStorage;
while dbxTable.Next do
Log (dbxTable.ColumnName +
' [' + dbxTable.TypeName + ']');
end;

En este caso, usted tiene que combinar un comando de texto


(GetColumns) con información específica de la tabla en cues-
tión, lo que es bastante extraño. Una función de ayuda con un
parámetro probablemente habría tenido más sentido.
Observe que las dos llamadas a del proveedor de metadatos,
en los últimos dos fragmentos de código, devuelven objetos de
clases diferentes que heredan de (en última instancia, un per-
sonalizado TDBXValueList). En el último caso, la clase devuelta
ha sido TDBXColumnsTableStorage, mientras que en el frag-
mento de código anterior ha sido TDBXTablesTableStorage.
La guía de Delphi por Marco Cantù | para
Indice - 443

Bombeo de Datos para dbExpress


Cuando se trabaja con múltiples bases de datos a menudo es
necesario migrar datos de un servidor a otro, y, como las defi-
niciones de tablas no son exactamente idénticas entre los dife-
rentes servidores, esta operación puede necesitar algún
tiempo. Siguiendo enfoque del antiguo DataPump disponible

La guía de Delphi por Marco Cantù


444 - Indice

para el BDE, el equipo de servicios de Internet de Embarca-


dero148 ha creado una aplicación de bombeo de datos con
dbExpress que puede ver en la siguiente página:
Este programa utiliza ampliamente los metadatos de dbEx-
press para migrar las definiciones y los datos reales entre los
servidores para los cuales existe un driver dbExpress con so-
porte a metadatos. La aplicación no es parte de Delphi, ya que
se produjo por un grupo diferente dentro de Embarcadero,
pero está disponible entre los ejemplos de bases de datos de
Delphi 2009. En mi instalación, el proyecto se encuentra en:
C:\Users\Public\Documents\
RAD Studio\6.0\Demos\database\projects\dbxdatapump\

El uso de esta herramienta es relativamente simple, pero lo


genial es tener la totalidad del código fuente a tu disposición.

Controles Data-Aware
Al primer vistazo, teniendo en cuenta los controles de datos
de la VCL, le puede parecer que hay muy pocos cambios. En
efecto, es cierto que se han limitado las nuevas características,
si no considera el hecho de que todos los controles de datos
están habilitados para Unicode.
También vimos en el último capítulo cómo crear un control
“navegador” de base de datos basado en el control Ribbon. Sin
embargo, sería bueno tener algunas de las características que

148Estaherramienta fue desarrollada por Jon Benedicto, Yorai Aminov, y John


Kaster, quien es el jefe de los servicios de Internet del equipo de Embarca-
dero. John Kaster es bien conocido por el Comunidad Delphi, ya que se vio
involucrado con el producto y la tecnología MIDAS, entre otras cosas. John
está en el blog: http://blogs.codegear.com/johnk.

La guía de Delphi por Marco Cantù | para


Indice - 445

se añadieron a otros controles de la VCL. Por ejemplo, la clase


TDBEdit hereda de TCustomEdit, pero no expone propieda-
des como NumbersOnly o TextHint. ¿Será difícil hacer dispo-
nibles estas otras características, incluso sin heredar un com-
ponente custom de la clase TDBEdit? Podemos hacerlo bien pi-
rateando la protección, o (la solución que prefiero), con una
clase intercalador local. Esta es la clase que he añadido al for-
mulario principal del ejemplo DbEditPlus, para hacer las dos
propiedades adicionales disponibles:
type
TDbEdit = class (DBCtrls.TDBEdit)
public
property NumbersOnly;
property TextHint;
end;

El programa tiene un sencillo formulario con una serie de


cuadros de edición (obtenidos automáticamente arrastrando
los campos desde el editor de campos). Ahora, el primero de
estos editores está conectado a un campo numérico de la base
de datos, lo que limita su entrada a números, separadores de-
cimales, valores exponenciales, y algunos más que me gusta-
ría eliminar. Podemos hacerlo fácilmente (e incluso obtener el
mensaje de error específico de Windows) escribiendo en el
evento OnCreate del formulario:
procedure TFormDbEditPlus.FormCreate(Sender: TObject);
begin
ClientDataSet1.Open;
DbEdit1.NumbersOnly := True;
end;

Gestionar el TextHint no es fácil, porque si todo lo que usted


hace es establecer esta propiedad, usted tiene que mover ma-
nualmente el foco a la casilla de edición dada para conseguir
que empiece a funcionar. Podría haber una forma mejor, pero
que he resuelto simular este comportamiento en el código.
Como el establecimiento del foco no funcionará hasta que el
formulario esté creado y visible, he decidido ejecutar el código

La guía de Delphi por Marco Cantù


446 - Indice

cuando un botón se presione, aunque no sería difícil automa-


tizarlo:
procedure TFormDbEditPlus.btnTextHintClick(
Sender: TObject);
var
aControl: TWinControl;
begin
aControl := ActiveControl;
DbEdit4.TextHint := 'Enter second address line';
DbEdit4.SetFocus;
aControl.SetFocus;
end;

El efecto combinado de las características de esta demo (el


error al entrar una letra y el texto del hint cuando un campo

está vacío) es visible en esta captura de pantalla:

Desde DBImage hasta el Viejo y


Pobre DBGrid
Una nueva característica de control de la DBImage es su
nueva propiedad Proportional. Lo que también es relevante

La guía de Delphi por Marco Cantù | para


Indice - 447

es que el DBImage hereda la capacidad de control para mane-


jar los nuevos formatos de archivo, como PNG, a partir del
control Image.
A pesar de los rumores, el control DBGrid no se ha renovado
en Delphi 2009 y sigue siendo un viejo control con una capa-
cidad limitada. Lo que ha hecho Embarcadero en cambio, es
facilitar a los usuarios registrados de Delphi 2009149, la rejilla
InfoPower Grid Essentials, comercializada en la península
ibérica por Danysoft. Esta edición especial del control (con al-
guna característica limitada en comparación con el control co-
mercial) no forma parte de la instalación de Delphi, sino que
tiene que descargarse por separado por parte de los usuarios
registrados de Delphi desde:
http://cc.embarcadero.com/reg/

Esto sólo es posible si ha comprado y registrado Delphi 2009,


por supuesto. Consulte con su comercial habitual en Danysoft
para obtener más detalles sobre la versión comercial de la re-
jilla o visite:
http://www.danysoft.com/embarcadero

A continuación
Este capítulo me centre en las nuevas características de arqui-
tectura para base de datos de Delphi, una parte importante de
la VCL, tanto en términos de tamaño como en importancia.

149Por lo menos durante algún tiempo, no está claro si la oferta tendrá una dura-
ción indefinida.

La guía de Delphi por Marco Cantù


448 - Indice

He detallado la forma en que el soporte de Unicode se ha mo-


dificado y cubierto algunas de las nuevas características en la
arquitectura dbExpress.
El cambio más importante de la arquitectura de base de datos
de Delphi es, sin embargo, su renovado soporte multi-capa,
llamado DataSnap y presentado originalmente en Delphi 5
con el nombre de MIDAS. La nueva arquitectura se asemeja a
la original, y utiliza algunos de esos componentes, pero ya no
está vinculada a COM, usando una capa de transporte dife-
rente, y abriendo nuevas e interesantes posibilidades. Es por
ello por lo que me he decidido a incluir en este libro todo un
capítulo dedicado a DataSnap 2009.

La guía de Delphi por Marco Cantù | para


Indice - 449

La guía de Delphi por Marco Cantù


450 - Indice

Capitulo 12:
DataSnap 2009

Durante mucho tiempo Delphi ha incluido una tecnología


para crear aplicaciones de datos “multi-tier”. Inicialmente co-
nocida como MIDAS y más tarde como DataSnap, esta tecno-
logía estaba basada en COM, incluso aunque la conectividad
remota se estableciese a través de sockets o HTTP, en vez de
DCOM. Durante algún tiempo incluso soportó CORBA. Un
versión ligeramente modificada, que se incluía con la conecti-
vidad SOAP.
Delphi 2009 todavía incluye el clásico, pero además aporta
una nueva y reluciente tecnología de aplicaciones remotas y
“multi-tier” también. Está parcialmente basada en la arquitec-
tura dbExpress. Esta nueva tecnología todavía se conoce como
DataSnap, pero para evitar confusiones normalmente es refe-
rida como “DataSnap 2009”.

Construyendo la primera
demostración de
La guía de Delphi por Marco Cantù | para
Indice - 451

DataSnap 2009
Antes de entrar en detalles, permitame que comience por una
demostración orientada a datos en el modelo “tres capas”.
Esto ayudará a aclarar algunos puntos y también a ilustrar las
diferencias con las versiones previas de la tecnología.

Construyendo el servidor
El primer paso es construir una aplicación servidora DataS-
nap 2009. Esta puede ser una aplicación estándar VCL, a la
cual añadimos un módulo de servidor (que se puede encon-
trar en la página “Delphi Files” en el diálogo “New Items”).
Al módulo de servidor (aunque también podríamos haber
usado un “Data Module” estándar) generalmente añadimos
los componentes dbExpress para conectar al servidor de base
de datos, además de un “DataProvider” para exponer los “Da-
taSets” dados:
object IBCONNECTION: TSQLConnection
ConnectionName = 'IBCONNECTION'
DriverName = 'Interbase'
LoginPrompt = False
Params.Strings = (
'DriverName=Interbase'
'Database=C:\Program Files\...\Data\Employee.GDB')
end
object EMPLOYEE: TSQLDataSet
CommandText = 'EMPLOYEE'
CommandType = ctTable
SQLConnection = IBCONNECTION
end
object DataSetProviderEmployee: TDataSetProvider
DataSet = EMPLOYEE
end

Este módulo está construido de una manera muy similar a


como se realizaba en el pasado. Lo que es nuevo es la necesi-
dad de incluir en el programa tres nuevos componentes que
La guía de Delphi por Marco Cantù
452 - Indice

aportan la configuración y la conectividad en lugar del soporte


COM (que se ha retirado completamente). Estos componentes
son:
 DSServer, el componente principal de la configuración de
servidor, que se necesita para enlazar el resto de componentes
DataSnap 2009.
 DSServerClass, se necesita un componente de este tipo por
cada clase que queramos exponer. Este componente no es la
clase que quieres hacer disponible, pero actúa como una fá-
brica de clases que crea los objetos de la clase que se pretende
invocar desde cliente. En otras palabras, el componente
DSServerClass se referirá a la clase que tiene el interfaz pú-
blico.
 DSTCPServerTransport, un componente que define el proto-
colo de transporte que se va a usar (este es el único protocolo
disponible directamente en Delphi 2009) y su configuración ,
como por ejemplo que puerto TCP/IP se va a usar.
En la demo estos componentes están en el formulario princi-
pal del servidor, estando configurados como sigue:
object DSServer1: TDSServer
AutoStart = True
HideDSAdmin = False
OnConnect = DSServer1Connect
OnDisconnect = DSServer1Disconnect
end
object DSTCPServerTransport1: TDSTCPServerTransport
PoolSize = 0
Server = DSServer1
BufferKBSize = 32
end
object DSServerClass1: TDSServerClass
OnGetClass = DSServerClass1GetClass
Server = DSServer1
LifeCycle = 'Session'
end

La guía de Delphi por Marco Cantù | para


Indice - 453

Veremos algunos detalles de estas propiedades más adelante.


La razón de que no se vea el valor de puerto TCP/IP en el lis-
tado anterior es que no se ha modificado su valor por defecto
de 211.
El único código que necesitamos escribir es el de “fabrica de
clases” que se necesita para conectar el componente DSServer-
Class1 al módulo de servidor exponiendo los proveedores:
procedure TFormFirst3Tier2009Server.
DSServerClass1GetClass(DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
PersistentClass := TDSFirst3TierServerModule;
end;

Esto es todo lo que se necesita para el servidor. En el ejemplo


actual he añadido una instrucción de log al método anterior,
así como dos manejadores de evento de los eventos OnCon-
nect y OnDisconnect del componente DSServer.
De nuevo, no hay ninguna necesidad de registro en ningún
sentido. Simplemente ejecútelo, por ejemplo usando el co-
mando Run | Run Without Debugging del IDE, para que po-
damos construir el cliente que conecte con el servidor incluso
en tiempo de diseño.

El primer cliente
Ahora que tenemos el servidor disponible, podemos continuar
y construir el primer cliente. En la aplicación cliente DataS-
nap 2009 necesitamos un componente SQLConnection aso-
ciado con el nuevo driver DataSnap dbExpress, configurado
con el puerto TCP/IP.
A continuación necesitamos un componente , usado para
identificar la clase de servidor, con la propiedad . Esta no es
una clase intermediara a la fábrica de clases del servidor

La guía de Delphi por Marco Cantù


454 - Indice

(DSServerClass1), sino el objetivo actual de dicha fábrica, en


mi ejemplo TDSFirst3TierServerModule.
Como en las aplicaciones DataSnap tradicionales, el provee-
dor puede ser usado por un componente ClientDataSet para
obtener (y actualizar) los datos remotos. Primero se ha de
asignar la propiedad RemoteServer del ClientDataSet, esco-
giendo el componente DSProviderConnection1 de la lista des-
plegable. Lo siguiente es seleccionar del desplegable de la pro-
piedad ProviderName el proveedor DataSetProviderEmployee
expuesto con el resto de componentes DataSetProvider por el
módulo remoto.
Este es un resumen de las propiedades de estos componentes,
más un DataSource usado para mostrar la tabla en un
DBGrid:
object SQLConnection1: TSQLConnection
DriverName = 'Datasnap'
end
object DSProviderConnection1: TDSProviderConnection
ServerClassName = 'TDSFirst3TierServerModule'
SQLConnection = SQLConnection1
end
object ClientDataSet1: TClientDataSet
ProviderName = 'DataSetProviderEmployee'
RemoteServer = DSProviderConnection1
end
object DataSource1: TDataSource
DataSet = ClientDataSet1
end

La guía de Delphi por Marco Cantù | para


Indice - 455

Esto es todo lo que engloba la demostración introductoria.


Ahora si se ejecuta el servidor primero y luego el cliente, se
puede pulsar el botón “Open” del cliente y ver los datos de la
base de datos. También hay que fijarse en el log producido
por el servidor, como se muestra en la siguiente captura de

ambos programas combinados:

De DataSnap a DataSnap 2009


Comparada con una aplicación DataSnap tradicional, hay al-
gunas diferencias significativas, más relacionadas con la ar-
quitectura y el despliegue que con el código que hay que escri-
bir:
 No hay tecnología COM involucrada en el desarrollo del servi-
dor. Incluso si un cliente usaba sockets en el pasado, se reque-
ría un servicio de mapeo socket a COM en el servidor. Ahora
el cliente y el servidor se pueden comunicar directamente so-
bre TCP/IP.

La guía de Delphi por Marco Cantù


456 - Indice

 Un efecto colateral, no se necesita registrar el servidor, ni eje-


cutar ningún servicio de ayuda. Todo lo que el servidor ha de
proporcionar a cliente es un puerto TCP/IP abierto que sea
accesible.
 Se puede ejecutar la aplicación manualmente en el servidor o
crear un servicio para ello. En el pasado el soporte COM im-
plicaba que la aplicación servidora se ejecutaba según se nece-
sitara.
 La implementación del servidor es ligeramente más compli-
cada en terminos de componentes, pero sigue habiendo muy
poca cantidad de código como en las versiones COM.
 La implementación del cliente es casi idéntica, ya que necesi-
tamos un componente SQLConnection estándar, en lugar de
un objeto de conexión específica.
 En el lado de servidor, la clase TDSServerModule hereda de
TDataModule, incluyendo el interfaz IAppServer (el mismo
usado en el pasado por el TremoteDataModule basado en
COM) y habilitar la directiva de compilador $MethodInfo.
 Debido a que el driver de dbExpress es 100% puro Delphi, no
se necesita desplegar ninguna DLL en el ordenador cliente,
incluso si se está usando dbExpress como conectividad.
 Hay que prestar mucha atención cuando se cierra la aplica-
ción servidora. Al contrario que la arquitectura COM, que pre-
venía de la existencia de conexiones pendientes, un servidor
de DataSnap 2009 parecerá cerrarse, sin hacerlo realmente
hasta que no existan conexiones pendientes. Sin embargo,
aun cuando las conexiones se hayan cerrado se mantendrá
ejecutándose en memoria, aun cuando el formulario principal
se haya cerrado. Se necesitará usar el gestor de tareas (o el ex-
plorador de procesos) para terminar el servidor. Se podría
pensar que con cerrar todas las aplicaciones cliente debería
ser suficiente, pero no es así; el IDE de Delphi, de hecho,
La guía de Delphi por Marco Cantù | para
Indice - 457

puede abrir una conexión al servidor automáticamente, para


explorar las clases expuestas y sus métodos. Asegúrate de que
todas y cada una de las SQLConnection están cerradas antes
de parar el servidor.

Añadiendo métodos al servidor


Como en el pasado, se puede escribir métodos en el servidor
que podrán ser invocados desde el cliente. En el pasado, esto
estaba basado en COM, así que había que añadir interfaces a
la librería de tipos e implementarlos luego en los objetos de
servidor e invocar los métodos usando interfaces de envío en
el cliente. En DataSnap 2009 las llamadas a métodos remotos
o llamadas a métodos de servidor están basados en la tecnolo-
gía RTTI de Delphi. Nótese, sin embargo, que los parámetros
de paso están basados en los tipos de parámetros dbExpress y
no en los tipos del lenguaje Delphi.
Se pueden tener múltiples clase de servidor que expongan
métodos, pero por continuar con el proyecto simple que ya
hemos creado, he añadido un método extra a la clase de mó-
dulo de servidor (en la aplicación servidora), usando el si-
guiente código:
type
TDSFirst3TierServerModule = class(TDSServerModule)
IBCONNECTION: TSQLConnection;
EMPLOYEE: TSQLDataSet;
DataSetProviderEmployee: TDataSetProvider;
private
{ Private declarations }
public
function GetHello: string;
end;

function TDSFirst3TierServerModule.GetHello: string;


begin
Result := 'Hello from TDSFirst3TierServerModule at '
+ TimeToStr (Now);
end;

La guía de Delphi por Marco Cantù


458 - Indice

Para habilitar la invocación remota hay que conectar la clase a


través de la cual se quieren exponer métodos a una fábrica
DSServerClass. (En este caso, ya lo hemos hecho en la parte
de base de datos de la demo). El segundo requisito es usar una
clase que está compilada con la directiva $MethodInfo habili-
tada, pero esto ya ocurre en la declaración de la clase base
TDSServerModule. Esto significa que, en la práctica, todo lo
que tenemos que hacer es añadir un método público al mó-
dulo de servidor, y todo lo demás funcionará.
¿Cómo podemos invocar este método de servidor desde una
aplicación cliente? Hay básicamente dos alternativas. Una es
usar el nuevo componente SqlServerMethod y llamar el mé-
todo de servidor como si fuera un procedimiento almacenado.
El segundo es generar una clase proxy en la aplicación cliente
y usar este proxy para realizar la llamada.
En el cliente de la demo First3Tier2009 he implementado
ambas aproximaciones. Para la primera, he añadido un com-
ponente SqlServerMethod al formulario del cliente, asociado
a la conexión, escogiendo un valor para la propiedad Server-
MethodName a través del Object Inspector (de entre todos los
disponibles, ya que los métodos del interfaz IAppServer se lis-
tan también) y comprobado el valor de la propiedad Params.
Esta es una copia de la configuración del componente (la cual
actualmente incluye el resultado de la llamada de ejemplo
realizada cuando se comprueban los parámetros):
object SqlServerMethod1: TSqlServerMethod
GetMetadata = False
Params = <
item
DataType = ftWideString

150Extrañamente el componente SqlServerMethod se nombre con mayúsculas y


minúsculas mezcladas “Sql”, mientras que el resto de los componentes de
dbExpress usan mayúsculas “SQL”. No es un gran problema pero merece la
pena prestarle atención.

La guía de Delphi por Marco Cantù | para


Indice - 459

Precision = 2000
Name = 'ReturnParameter'
ParamType = ptResult
Size = 2000
Value = 'Hello from TDSFirst3TierServerModule...'
end>
SQLConnection = SQLConnection1
ServerMethodName = 'TDSFirst3TierServerModule.GetHello'
end

Nótese que el tipo string native esta mapeado a un parámetro


string de 2.000 caracteres. Después de configurar el compo-
nente SqlServerMethod, el programa puede llamarlo usando
los parámetros de entrada (ninguno en este caso) y el paráme-
tro de resultado (resultado) como en un procedimiento alma-
cenado o la llamada a un query:
procedure TFormFirst3Tier2009Client.btnHelloClick(
Sender: TObject);
begin
SqlServerMethod1.ExecuteMethod;
ShowMessage (SqlServerMethod1.Params[0].Value);
end;

Para hacer más fácil de escribir el código de llamada se puede


usar una segunda aproximación que mencioné antes, creando
una clase proxy local en la aplicación cliente. Para conse-
guirlo, podemos solicitar al IDE de Delphi que “parsee” el in-
terfaz de la clase servidora para crear la clase proxy local para
él, pulsando sobre el componente SQLConnection y seleccio-
nando Generate Datasnap client classes. En este ejemplo, Del-
phi generará una unidad con la siguiente clase (de la cual he
omitido el código de los constructores y destructores):
type
TDSFirst3TierServerModuleClient = class
private
FDBXConnection: TDBXConnection;
FInstanceOwner: Boolean;
FGetHelloCommand: TDBXCommand;
public
constructor Create(
ADBXConnection: TDBXConnection); overload;
constructor Create(
ADBXConnection: TDBXConnection;
AInstanceOwner: Boolean); overload;

La guía de Delphi por Marco Cantù


460 - Indice

destructor Destroy; override;


function GetHello: string;
end;
function TDSFirst3TierServerModuleClient.GetHello: string;
begin
if FGetHelloCommand = nil then
begin
FGetHelloCommand := FDBXConnection.CreateCommand;
FGetHelloCommand.CommandType :=
TDBXCommandTypes.DSServerMethod;
FGetHelloCommand.Text :=
'TDSFirst3TierServerModule.GetHello';
FGetHelloCommand.Prepare;
end;
FGetHelloCommand.ExecuteUpdate;
Result := FGetHelloCommand.Parameters[0].
Value.GetWideString;
end;

Nótese que el código generado no usa el componente de alto


nivel SqlServerMethod, sino llamadas directas a bajo nivel a
la implementación de objetos dbExpress, como la clase
TDBXCommand151.
Teniendo esta clase proxy disponible, el programa cliente
puede ahora llamar al método de servidor de una manera mu-
cho más amigable, aunque necesitemos crear una instancia de
la clase proxy cada vez (o crear una y mantenerla). Este có-
digo hace exactamente lo mismo que el código anterior ba-
sado en el componente SqlServerMethod:
procedure TFormFirst3Tier2009Client.btnHelloClick(
Sender: TObject);
begin
with TDSFirst3TierServerModuleClient.Create(
SQLConnection1.DBXConnection) do
try
ShowMessage (GetHello);
finally
Free;
end;
end;

151Hay
una demo sobre las clases a bajo nivel de dbExpress en la sección “Utili-
zando las clases de DBXCommon” en el capítulo 10 de mi libro Delphi 2007
Handbook.

La guía de Delphi por Marco Cantù | para


Indice - 461

Si el código es en este caso más largo que el anterior, es de-


bido a que el método que estamos llamando no tienen pará-
metros, lo cual convierte al código de invocación en menos re-
levante. Si hubiéramos tenido un objeto previamente instan-
ciado el código se hubiera limitado a:
ShowMessage (ServerProxyObject.GetHello);

Sesiones e hilos con un


servidor DataSnap no
dirigido a base de datos
Si usar el interfaz IAppServer directamente va a ser el camino
más común para usar DataSnap 2009, es igualmente posible
usar la tecnología “muti-tier” para la invocación de métodos
fuera del contexto de la base de datos. Se puede usar la misma
tecnología para acceder a los datos de base de datos o realizar
operaciones en la base de datos usando el interfaz IAppServer,
lo cual es perfecto si lo que se pretende es leer datos del servi-
dor. Si lo que se pretende es que la aplicación cliente modifi-
que los datos y los envíe al servidor, el usar método propios
puede ser un camino más tedioso comparado con el interfaz
IAppServer, que está listo para ser usado, implementado por
los componentes ClientDataSet y el DataSetProvider.
En cualquier caso, en este segundo ejemplo, quiero crear un
servidor mínimo exponiendo un par clases simples. En las si-
guientes secciones usaré ese servidor simple para explorar un
par de asuntos relevantes, como la gestión de memoria y los
hilos del servidor (y el cliente).

La guía de Delphi por Marco Cantù


462 - Indice

La primera clase de servidor (sin métodos) que quiero publi-


car en el proyecto DsnapMethodServer es la siguiente:
{$MethodInfo ON}
type
TSimpleServerClass = class(TPersistent)
public
function Echo (const Text: string): string;
function SlowPrime (MaxValue: Integer): Integer;
end;
{$MethodInfo OFF}
El código del primer método simplemente realiza un eco de la
entrada, repitiendo su última parte, mientras que el segundo
realiza el proceso más clásico de mis libros (ya usé el ejemplo
ParallelFor en el capítulo 6, entre otros). Este es el código
para los dos métodos:
function TSimpleServerClass.Echo(
const Text: string): string;
begin
Result := Text + '...' +
Copy (Text, 2, maxint) + '...' +
Copy (Text, Length (Text) - 1, 2);
end;

function TSimpleServerClass.SlowPrime(
MaxValue: Integer): Integer;
var
I: Integer;
begin
// counts the prime numbers below the given value
Result := 0;
for I := 1 to MaxValue do
begin
if IsPrime (I) then
Inc (Result);
end;
end;

Usaré el último método para tratar los hilos en la práctica. He


omitido las instrucciones extra usadas para crear un log de las
operaciones del servidor en el código anterior.
La aplicación de servidor solo contiene una unidad, la cual de-
fine el formulario principal y dos clases en el lado servidor. El
formulario contiene los componentes DataSnap habituales,

La guía de Delphi por Marco Cantù | para


Indice - 463

un DSTCPServerTransport, más dos componentes DSServer-


Class, uno para cada una de las clases que quiero exponer.
Después de compilar y ejecutar el servidor, dejé que Delphi
creara la clase proxy usando un componente SQLConnection
en una nueva aplicación cliente. Esta es la clase proxy de
cliente:
type
TSimpleServerClassClient = class
private
FDBXConnection: TDBXConnection;
FInstanceOwner: Boolean;
FEchoCommand: TDBXCommand;
FSlowPrimeCommand: TDBXCommand;
public
constructor Create(
ADBXConnection: TDBXConnection); overload;
constructor Create(
ADBXConnection: TDBXConnection;
AInstanceOwner: Boolean); overload;
destructor Destroy; override;
function Echo(Text: string): string;
function SlowPrime(MaxValue: Integer): Integer;
end;

En el programa cliente, el evento OnClick del botón llama al


método Echo del servidor, después de crear una instancia del
proxy, si es necesario:
procedure TFormDsnapMethodsClient.btnEchoClick(
Sender: TObject);
begin
if not Assigned (SimpleServer) then
SimpleServer := TSimpleServerClassClient.Create (
SQLConnection1.DBXConnection);
Edit1.Text := SimpleServer.Echo(Edit1.Text);
end;

En el ejemplo, pulsando el botón de “Marco”, se transforma


por la llamada al servidor en “Marco...arco...co”. Este es un
ejemplo completo de cómo se puede crear un servidor com-
pletamente personalizado, sin ningún tipo de acceso a base de
datos ni uso del interfaz IAppServer. Esta no es la única téc-
nica de invocación de métodos disponible en Delphi, ya que se

La guía de Delphi por Marco Cantù


464 - Indice

puede usar SOAP, en aplicaciones basadas en Sockets, o he-


rramientas de terceros… pero mantener esta característica ex-
tra en la cúspide del acceso a datos remoto es ciertamente un
plus.
Una de las razones por la cual me estoy centrando en este
ejemplo es que puede sernos de ayuda para clarificar alguna
de las características relevantes de DataSnap 2009. Una de
ellas es relacionar los objetos de lado de servidor relacionados
con los “proxies” de cliente o la invocación de métodos de
servidor. Esto se demuestra mejor con un objeto de servidor
que se enfoca en su propio estado, tal y como se demuestra en
la segunda clase de servidor del proyecto de prueba:
{$MethodInfo ON}
type
TStorageServerClass = class(TPersistent)
private
FValue: Integer;
public
procedure SetValue(const Value: Integer);
function GetValue: Integer;
function ToString: string; override;
published
property Value: Integer read GetValue write SetValue;
end;
{$MethodInfo OFF}
Mientras los métodos “get” y “set” simplemente leen y escri-
ben en el campo local, la función ToString devuelve el valor y
un identificador del objeto basado en su código “hash”:
function TStorageServerClass.ToString: string;
begin
Result := 'Value: ' + IntToStr (Value) +
' - Object: ' + IntToHex (GetHashCode, 4);
end;

Usaré este método para adivinar cómo se comporta el ciclo de


vida de los objetos de servidor. En esta clase la definición de
propiedades solo tiene sentido para el servidor, ya que no esta
expuesta al cliente. El interfaz del proxy correspondiente se
transforma (después de quitar los campos privados, construc-
tores estándar y destructor):
La guía de Delphi por Marco Cantù | para
Indice - 465

type
TStorageServerClassClient = class
public
procedure SetValue(Value: Integer);
function GetValue: Integer;
function ToString: string;

Nótese que la compilación de esta clase produce muchos


“warnings”, a menos que manualmente se marquen los méto-
dos como override:
Method 'ToString' hides virtual method of base type
'TObject'

El objetivo de este ejemplo es adivinar que es lo que pasa


cuando múltiples aplicaciones de cliente usan el mismo servi-
dor. El comportamiento de un servidor DataSnap 2009 en
este caso depende del valor de la propiedad LifeCycle en el
componente DSServerClass que se está usando.

Ciclo de vida de los objetos de


servidor
El ciclo de vida de los objetos de un servidor DataSnap 2009
depende de la configuración del componente DSServerClass.
La propiedad LifeCycle de este componente puede asumir los
tres valores de cadena siguientes152 (que son leídos de los
componentes DSServerClass cuando el objeto DSServer se
abre, ignorando cualquier cambio en tiempo de ejecución):
 Session indica que el servidor creará un objeto diferente por
cada conexión de cliente, esto es, un objeto de servidor por
cliente. Los objetos de servidor se liberan cuando la conexión
se cierra. Los múltiples clientes tendrán un estatus indepen-
diente y un acceso separado a la base de datos en caso de que

152Los tres valores para esta propiedad son tres constantes de la clase
TDSLifeCycle definida en la unidad DSNames.

La guía de Delphi por Marco Cantù


466 - Indice

el objeto de servidor sea un DataModule, con quizás su propio


componente de conexión a la base de datos. Este es el valor
por defecto.
 Invocation indica que un nuevo objeto de servidor se crea (y
destruye) cada vez que un método de servidor es invocado.
Este es el clásico comportamiento sin estado, permitiendo que
el servidor sea extremadamente escalable, pero también su-
jeto de obtener los mismos datos una y otra vez.
 Server indica un objeto de servidor compartido, un objeto
“Singleton”. Cada cliente usará la misma instancia de objeto
de servidor, los mismo datos y potencialmente estará sujeto a
problemas de sincronización (ya que diferentes invocaciones
de cliente se realizan sobre diferentes hilos de servidor). El ac-
ceso a los objetos de servidor compartido deben ser protegi-
dos por técnicas de sincronización (por ejemplo utilizando en
nuevo registro TMonitor).
Además de utilizar las configuraciones por defecto, se puede
personalizar la creación y destrucción de los objetos de servi-
dor usando los eventos OnCreateInstance y
OnDestroyInstance del componente DSServerClass. Esto se
puede usar por ejemplo, para implementar un “pooling” de
objetos de servidor.

Un cliente que arranca un


servidor y abre múltiples
conexiones
Como un ejemplo práctico, el proyecto DsnapMethods per-
mite crear múltiples conexiones de una instancia de aplica-
ción cliente (usando múltiples instancias que producirán el
mimo resultado), creando múltiples instancias del formulario

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 467

que tiene el componente SQLConnection y almacena una ins-


tancia local del proxy de cliente, creado la primera vez que se
usa. No solo puede el cliente crear múltiples conexiones de
cliente, sino que además puede arrancar el programa con el
parámetro de configuración de ciclo de vida adecuado. Esto es
muy fácil de lograr porque el cliente y el servidor se encuen-
tran en la mima máquina.
Para producir este efecto he añadido a la unidad del formula-
rio principal del servidor una variable global, usada para esta-
blecer la propiedad LifeCycle DSServerClass:
var
ParamLifeCycle: string;

procedure TFormDsnapMethodsServer.DSServerClass2GetClass(
DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
DSServerClass2.LifeCycle := ParamLifeCycle;
Log ('LifeCycle: ' + DSServerClass2.LifeCycle);
PersistentClass := TStorageServerClass;
end;

El valor de la variable global ParamLifeCycle se inicializa


usando un parámetro de línea de comandos de la aplicación
de servidor, que tiene el siguiente código al principio del ar-
chivo de código de proyecto:
begin
if ParamCount > 0 then
ParamLifeCycle := ParamStr(1);
Application.Initialize;

Con este código disponible en el servidor, el formulario prin-


cipal de la aplicación cliente (el cual no tiene conexión, ya
que esta está configurada en el formulario secundario) tiene
un RadioGroup con los siguientes valores:
object rgLifeCycle: TRadioGroup
ItemIndex = 0
Items.Strings = (
'Session'
'Invocation'
'Server')
end
La guía de Delphi por Marco Cantù
468 - Indice

Cuando se pulsa un botón, el programa cliente lee el valor ac-


tual y lo pasa como parámetro al servidor153 ( obsérvese que
no se puede ejecutar el servidor dos veces, ya que no se puede
tener más de un mismo “socket” a la escucha en el mismo
puerto abierto por dos aplicaciones en el mismo equipo):
procedure TFormDsmcMain.btnStartServerClick(
Sender: TObject);
var
aStr: AnsiString;
begin
Log (rgLifeCycle.Items[rgLifeCycle.ItemIndex]);
aStr := 'DsnapMethodsServer.exe ' +
rgLifeCycle.Items[rgLifeCycle.ItemIndex];
WinExec (PAnsiChar (aStr), CmdShow);
end;

Ahora el formulario principal de la aplicación cliente tiene un


botón usado para crear instancias de un formulario secunda-
rio, que son destruidas cuando se cierran ( en el manejador de
evento de OnClose), cerrando la conexión específica al servi-
dor. Se usa otro botón para obtener un log de los formularios
del cliente:
procedure TFormDsmcMain.btnUpdateStatusClick(
Sender: TObject);
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I].ClassType = TFormDsmcClient then
Log (IntToStr (I) + ': ' +
Screen.Forms[I].ToString);
end;

Cuando llamamos a ToString para alguno de los formularios


secundarios, este devuelve el status del objeto de servidor co-
nectado, llamando a su método público ToString:
function TFormDsmcClient.ToString: string;

153Recuerde que el API WinExec utiliza un parámetro PAnsiChar y no tiene la


versión wide, ya que los nombres de módulo en Windows no están habilita-
dos con Unicode, como los nombres de las funciones expuestas por las DLLs
y referenciadas en la API GetProcAddress.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 469

begin
InitStorageServer;
Result := StorageServer.ToString;
end;

Como un ejemplo de primera ejecución, he creado un servidor


con la configuración por defecto Session, he abierto dos for-
mularios de cliente, he establecido los valores a 3 y 4 y he soli-
citado el status general, con el resultado:
Session
1: Value: 3 - Object: 1C38400
2: Value: 4 - Object: 1C384E0

En una segunda ejecución, he establecido el tipo de servidor


con un ciclo de vida Invocation, y he solicitado el estado ge-
neral dos veces con la siguiente salida:
Invocation
1: Value: 0 - Object: 1D185B0
2: Value: 0 - Object: 1D18490
1: Value: 0 - Object: 1D185C0
2: Value: 0 - Object: 1D185D0

Nótese que se está obteniendo un nuevo objeto para cada eje-


cución, y que el status de los objetos es siempre cero (cual-
quier cambio se perderá inmediatamente ya que el objeto se
destruye inmediatamente después de la invocación). No es
necesario decir que esto solo tiene sentido para operaciones
que no necesiten estado.
Finalmente, he repetido los mismos pasos (estableciendo los
valores a 3 y 4) con el ciclo de vida Server, esta vez los formu-
larios cliente usan el mismo objeto de servidor, con el mismo
conjunto de valores:
Server
1: Value: 4 - Object: 1E08490
2: Value: 4 - Object: 1E08490

En otras palabras, la práctica muestra… ¡que la teoría es co-


rrecta! Mientras exploramos la configuración del ciclo de vida
en la demo, también podemos observar un cliente arrancado

La guía de Delphi por Marco Cantù


470 - Indice

el servidor (local) que necesita mientras establece múltiples


conexiones concurrentes al servidor.

Gestión de memoria
La gestión de la memoria de los objetos de servidor está ligada
a las conexiones cliente y al ciclo de vida del servidor. Los ob-
jetos de servidor se mantienen en memoria generalmente
hasta que la conexión (Session) se cierra o hasta que el servi-
dor se cierra (Server) aun a pesar de cualquier conexión ac-
tiva.
La situación es diferente con el ciclo de vida Invocation, ya
que en este caso el objeto de servidor (TStorageServerClass en
el ejemplo) se crea en cada invocación y debería ser inmedia-
tamente destruido. Lo que ocurre, sin embargo, es que (in
Delphi 2009 con el Update 1 instalado) para cada invocación
de un método de servidor hay una pérdida con cada objeto de
servidor. Por ejemplo, usando el ciclo Invocation, creando
una conexión de cliente y llamando al método de servidor dos
veces, se produce el siguiente error cuando el servidor se cie-
rra:

Para arreglar el problema se puede manualmente liberar el


objeto de servidor manejando el evento OnDestroyInstance
del componente DSServerClass relacionado (que recibe un
único parámetro, con un nombre muy largo y un nombre de

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 471

clase, con la información de la clase de servidor y la instancia


de servidor asociadas):
procedure TFormDsnapMethodsServer.DSSC2DestroyInstance(
DSDestroyInstanceEventObject:
TDSDestroyInstanceEventObject);
begin
// only if LifeCycle = 'Invocation'?
DSDestroyInstanceEventObject.ServerClassInstance.Free;
end;

Gestión de hilos
Otro tema relacionado es la gestión de hilos en el servidor (y
en algunos casos en el cliente). El gestor de hilos es el compo-
nente de transporte TCP/IP, el cual puede usar un “pooling”
de hilos para mejorar la eficiencia de la llamada. Se puede
configurar el “pooling” de hilos de servidor usando la propie-
dad PoolSize del componente de transporte del servidor (y es-
tablecer un límite de hilos usando la propiedad MaxThreads).
De acuerdo con la documentación (en el código fuente, no en
el fichero de ayuda) el valor de la propiedad PoolSize debe ser
10 por defecto, pero parece como si esta estuviera establecida
a 0 por defecto, lo que deshabilita el “pooling” de hilos. Esta-
blécela al valor que desees, pero no a 10 ya que sería reinicia-
lizada por un aparente error en la definición del compo-
nente154.
Los hilos en el servidor se crean según una base por conexión
y no por petición, y se mantienen mientras las conexiones es-
tán abiertas. Esto significa que el modelo de “pooling” de hilos
y la configuración requieren adaptarse a la configuración de

154Lapropiedad PoolSize, de hecho, tiene un valor por defecto declarado de 10,


pero como no se establece en su constructor, el efecto es que el valor 10 no se
salva en el fichero DFM.

La guía de Delphi por Marco Cantù


472 - Indice

ciclo de vida. Nótese también que un cliente no debería reali-


zar dos llamadas concurrentes usando la misma conexión, ya
que esto puede liar el tratamiento con el hilo de servidor155.
Ya que el servidor es una aplicación multihilo, cada vez que
un objeto de servidor se referencia a un recurso global (como
el formulario usado para el “log” en algunos ejemplos) se debe
monitorizar mediante secciones críticas. El nuevo registro
Tmonitor de Delphi 2009 explicado en el capítulo 7 nos debe-
ría proporcionar un mecanismo de sincronización simple y
“ligero”.
Como en el ejemplo de cómo funciona el modelo de hilo, en la
aplicación DsnapMethods he añadido una operación muy
lenta. Ahora podemos tratar de imaginarnos cual es su efecto.
Se afina la operación lenta pasando al método de servidor
SlowPrime un parámetro diferente, en la demo extraída del co-
rrespondiente control UpDown.
Activando dos conexiones cliente (en dos aplicaciones cliente
separadas), ralentizándolas (usando un valor grande como
100.000), o acelerándolas, se puede ver el siguiente “log” en
el servidor:
Client connected
Client connected
Starting SlowPrime for 1BA8490
Starting SlowPrime for 1BA84E0
Done SlowPrime for 1BA84E0
Done SlowPrime for 1BA8490

Los métodos en el servidor son ejecutados efectivamente en


hilos paralelos, y podría ocurrir (como en el log expuesto

155Bien,el hecho de que las llamadas concurrentes del cliente usen una única co-
nexión es la teoría, como informa la limitada documentación disponible. En
la práctica, si trata de invocar al servidor usando hilos, y se realiza una se-
gunda petición antes de que la primera se complete, el servidor encolará las
peticiones, por lo menos cuando se está usando el ciclo de vida sesión. Vere-
mos un ejemplo más adelante sobre ello en esta sección.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 473

arriba) que el segundo hilo arrancado finalizase después del


primero. Nótese que estas cosas pueden ser peligrosas en el
caso de un objeto de servidor con un estado compartido a tra-
vés de las conexiones, e incluso el “log “ sin protección que es-
toy usando en el ejemplo puede causar problemas.
Si se prueba esta aplicación, inmediatamente se notará que
mientras está esperando a la respuesta del servidor, la aplica-
ción cliente está bloqueada sin responder a las peticiones de
usuario. En este caso el culpable es el tiempo requerido por el
servidor en procesar la petición, pero podría también ser un
paquete muy grande a devolver o una conexión terriblemente
mala.
A menos que se tenga un control estricto (una red local rá-
pida, un servidor realizando unas operaciones relativamente
cortas, el ClientDataSet pidiendo un número relativamente
pequeño de datos…) podría ser bueno añadir algún soporte
extra en el lado de cliente para producir una aplicación con
mejor respuesta. Careciendo de la opción de genera un proxy
basado en hilo, para el proyecto DsnapMethodsClient he
creado un hilo manualmente. La clase del hilo necesita refe-
rencias al proxy, el valor del parámetro, y el formulario donde
mostrar la salida:
type
TPrimesClientThread = class (TThread)
private
FMaxValue: Integer;
FSimpleServer: TSimpleServerClassClient;
FCallingForm: TFormDsmcClient;
protected
procedure Execute; override;
public
constructor Create (MaxValue: Integer;
SimpleServer: TSimpleServerClassClient;
CallingForm: TFormDsmcClient);
end;

La guía de Delphi por Marco Cantù


474 - Indice

Mientras que el constructor simplemente almacena sus pará-


metros, el método Execute hace el trabajo (utilizando un mé-
todo anónimo):
procedure TPrimesClientThread.Execute;
var
nResult: Integer;
begin
nResult := FSimpleServer.SlowPrime(FMaxValue);
Synchronize (procedure ()
begin
FCallingForm.lblPrimesTh.Caption :=
IntToStr (nResult);
// FCallingForm.btnPrimesTh.Enabled := True;
end);
end;

Esto se invoca a través de un nuevo botón añadido al formula-


rio cliente:
procedure TFormDsmcClient.btnPrimesThClick(
Sender: TObject);
begin
// btnPrimesTh.Enabled := False;
if not Assigned (SimpleServer) then
SimpleServer := TSimpleServerClassClient.Create (
SQLConnection1.DBXConnection);
TPrimesClientThread.Create(
SpinEdit2.Value, SimpleServer, self);
end;

Las dos lineas comentadas en el código superior (usado para


deshabilitar y habilitar el botón) pueden ser usadas para evi-
tar las llamadas concurrentes de la misma conexión cliente
(ya que ahora se pueden crear dos formularios de cliente e in-
vocar la operación lenta en cada uno de ellos), pero como ya
mencioné en una nota anterior, incluso si se dejan comenta-
das y tratamos de ejecutar una petición antes de que la ante-
rior se termine, esta se encolará en el servidor, como demues-
tra el “log”. Sin embargo, observé que cuando se encolan las
peticiones es cuando puede haber pérdidas de memoria en el
servidor, así que no recomiendo esta aproximación para am-
pliar el servidor DataSnap, es mejor tratar de evitar múltiples

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 475

peticiones simultáneas en una conexión de un cliente multi-


hilo.

Portando un ejemplo
anterior de DataSnap
Habiendo explorado algunas alternativas de uso de DataSnap
2009, permítaseme volver a un escenario de uso más clásico,
que son las aplicaciones de datos “multi-capa”. Como ya he-
mos podido ver los pasos para crear una nueva aplicación de
datos, vamos a centrarnos en un elemento igual de relevante:
portar una aplicación DataSnap (o MIDAS) a esta nueva ar-
quitectura.
Como ejemplo práctico, he decidido portar la aplicación lla-
mada ThinPlus156 que se recogía en Mastering Delphi 2005, la
cual muestra algunas de las capacidades de DataSnap, y que
me permitirá cubrir un ejemplo más completo, además de en-
focarnos en lo necesario para portar un servidor COM invo-
cado desde un cliente usando un “socket” a lo que es una ar-
quitectura puramente “socket”. El nuevo ejemplo (con los
proyectos del servidor y del cliente) se encuentra en la carpeta
ThinPlus2009.
Nótese que portar aplicaciones DataSnap a la nueva arquitec-
tura es una opción interesante, pero no debe ser obligatoria.

156Elprograma se describe en el libro Mastering Delphi 2005, pero también en


ediciones previas como “Mastering Delphi 7”. Aquí solo se aporta una visión
general de algunas de estas características. Estos libros aportan una visión
más amplia de las características originales de DataSnap (anteriormente Mi-
das) que en su mayoría todavía están disponibles en la versión 2009 de Del-
phi.

La guía de Delphi por Marco Cantù


476 - Indice

Los servidores y clientes DataSnap tradicionales pueden toda-


vía ser compilados y trabajar propiamente en Delphi 2009.

Portar el servidor
Para portar el servidor he seguido estos pasos:
 Quité la sección de inicialización de la unidad de “remote data
module” llamada AppsRDM. El código sustraído fue la lla-
mada al constructor de la clase TComponentFactory.
 También quité el método de clase UpdateRegistry de la clase
TAppServerPlus de la misma unidad.

 En este punto pude eliminar de la cláusula “uses” del “remote


data module” las unidades relacionadas con COM y ActiveX:
ComServ, ComObj, VCLCom, y StdVcl.
 Lo siguiente fue quitar la referencia al interfaz personalizado
IappServerPlus, usado por el proyecto para aprovisionar mé-
todos de servidor personalizados (el interfaz se definía en el
proyecto de “type library”).
 Borré la “type library” y el fichero RIDL (que se creó cuando el
proyecto fue abierto con Delphi 2009) del proyecto y del
disco. También quité la cláusula uses referida a la unidad de
la “type library”.
 Trasladé el único método de servidor (Login) de la sección
protegida a la sección pública de la clase del “remote data mo-
dule”, retirando el modificador safecall. Ya que la clase
TRemoteDataModule está compilada con $MethodInfo acti-
vado, no hay necesidad de añadir esta declaración a la unidad
de proyecto .
 Finalmente, añadí al formulario principal del programa el trío
usual de componentes (server, clase de servidor y transporte

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 477

de servidor), asociándolos y devolviendo el TAppServerPlus


en el evento OnGetClass del componente del servidor.
Esto es todo lo el esfuerzo que se necesita para actualizar un
servidor DataSnap clásico a la versión Delphi 2009. Podría
parecer mucho, pero realmente fue muy rápido. Ahora es el
momento de centrarse en la aplicación cliente, la que acomete
unas cuantas operación propias.

Actualizando el cliente
Portar la aplicación cliente a DataSnap 2009 es generalmente
más fácil que portar el servidor. El paso fundamental es quitar
los componentes de conexión (el ejemplo tiene tres, ya que
permite a los usuarios experimentar con varias opciones de
conexión) y reemplazarlos con una conexión “SQLConnec-
tion” y un DSProviderConnection, y referir el componente
ClientDataSet a este nuevo componente de conexión remota.
El único código específico que he tenido que cambiar fue la
llamada al método Login del servidor. Esto tiene lugar en el
OnAfterConnection de la conexión, moviéndose ahora al
evento correspondiente del componente “SQLConnection”:
procedure TClientForm.SQLConnection1AfterConnect(
Sender: TObject);
begin
// was: ConnectionBroker1.AppServer.
// Login (Edit2.Text, Edit3.Text);
SqlServerMethod1.ParamByName('Name').AsString :=
Edit2.Text;
SqlServerMethod1.ParamByName('Password').AsString :=
Edit3.Text;
SqlServerMethod1.ExecuteMethod;
end;

Esta llamada sirve para pasar al servidor información de vali-


dación de cliente. El servidor comprueba la información y en
caso de que sea válida, permitirá al “provider” exponer sus da-

La guía de Delphi por Marco Cantù


478 - Indice

tos. La comprobación de la contraseña es trivial, pero la apro-


ximación podría ser interesante. Este es el método Login en el
servidor:
procedure TAppServerPlus.Login(
const Name, Password: WideString);
begin
if Password <> Name then
raise Exception.Create (
'Wrong name/password combination received');
ProviderDepartments.Exported := True;
ServerForm.Add ('Login:' + Name + '/' + Password);
end;

Nótese que en el caso de que el servidor devuelva una excep-


ción está será claramente mostrada en el cliente (indicando de

donde viene Remote error):

Características avanzadas de
ThinPlus2009
He actualizado las aplicaciones cliente y servidor de ThinPlus
a DataSnap 2009 siguiendo los pasos mencionados anterior-
mente, incluso aunque estos sean programas DataSnap bas-
tante más complicados y con diversas personalizaciones. Esto
incluye la transmisión de paquetes de datos manualmente,
usando estructuras maestro/detalle, ejecutando consultas pa-
rametrizadas, transfiriendo datos extra en los paquetes de da-
tos y la validación de usuario ya tratada.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 479

Es valioso echar un vistazo a esta características, incluso bre-


vemente, ya que puede ayudar a aquellos que no hayan traba-
jado nunca con DataSnap (o no mucho) a apreciar su poder.
Aquellos que ya lo hayan usado, por otro lado, pueden ver lo
fácil que es portar el código a la nueva arquitectura. La aplica-
ción servidor define una estructura maestro/detalle , basada
en la siguiente configuración de los (respectivos) proveedores,
el conjunto maestro de datos, el “data source” usado para ex-
ponerlo y los detalles del “data set” referido por el “data
source”:
object ProviderDepartments: TDataSetProvider
DataSet = SQLDepartments
end
object SQLDepartments: TSQLDataSet
CommandText = 'select * from DEPARTMENT'
SQLConnection = SQLConnection1
end
object DataSourceDept: TDataSource
DataSet = SQLDepartments
end
object SQLEmployees: TSQLDataSet
CommandText =
'select * from EMPLOYEE where dept_no = :dept_no'
DataSource = DataSourceDept
Params = <
item
Name = 'dept_no'
ParamType = ptInput
end>
SQLConnection = SQLConnection1
end

En el cliente, el programa usa un primer ClientDataSet conec-


tado con el proveedor y un segundo ClientDataSet que se re-
fiere a un campo de datos especial del primero:
object cds: TClientDataSet
FetchOnDemand = False
PacketRecords = 5
ProviderName = 'ProviderDepartments'
RemoteServer = DSProviderConnection1
object cdsDEPT_NO: TStringField...
object cdsDEPARTMENT: TStringField...
...
object cdsSQLEmployees: TDataSetField
FieldName = 'SQLEmployees'

La guía de Delphi por Marco Cantù


480 - Indice

end
end
object cdsDet: TClientDataSet
DataSetField = cdsSQLEmployees
end

Los datos de los dos componentes ClientDataSet se muestran


en dos controles DBGrid. Nótese como el programa transmite
solo 5 registros (como se indica en la propiedad Pack-
etRecords) de cada paquete, y parará de transmitir datos des-
pués del primer paquete (cuando la propiedad FetchOnDe-
mand es False), incluso si la “rejilla” no está llena. Como se

puede ver en la siguiente pantalla del interfaz de usuario de la


aplicación cliente justo después de abrir la conexión:
Siguiendo los paquetes de datos transmitidos manualmente, a
medida que el usuario pulsa en el botón correspondiente:

procedure TClientForm.btnFetchClick(Sender: TObject);


begin
btnFetch.Caption := IntToStr (cds.GetNextPacket);
end;

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 481

El programa muestra en el “caption” del botón cuantos regis-


tros se transmiten en cada paquete. Esto será igual a 5 mien-
tras haya suficientes registros, luego el número de registros
restantes y finalmente cero cuando todos los registros han
sido finalmente devueltos. A cada petición de transmisión el
DBGrid de cliente mostrará más datos, y su “scrollbar” será
actualizado. También se puede usar el botón bntRecCount para
preguntar cuantos registros han sido enviados en cada mo-
mento.
El programa cliente tiene un segundo formulario, mostrado
cuando se pulsa el botón Query, que contiene otro ClientData-
Set. Este ClientDataSet está conectado con una consulta para-
metrizada definida en el servidor como:
object SQLWithParams: TSQLDataSet
CommandText =
'select * from employee where job_code = :job_code'
Params = <
item
DataType = ftString
Name = 'job_code'
ParamType = ptInput
Value = 'Eng'
end>
SQLConnection = SQLConnection1
end

El programa de cliente tiene una “list box”, que se rellena en


diseño con los nombres de los departamentos, que se usa para
pasar el parámetro correcto al servidor. Nótese que para es-
cribir este código primero se tiene que actualizar la definición
de los parámetros, una operación que se puede realizar en
tiempo de diseño usando el comando correspondiente del edi-
tor de componente en el ClientDataSet. Esta es la llamada
usada para ejecutar la consulta parametrizada remota:
procedure TFormQuery.btnParamClick(Sender: TObject);
begin
cdsQuery.Close;
cdsQuery.Params[0].AsString := ComboBox1.Text;
cdsQuery.Open;
...

La guía de Delphi por Marco Cantù


482 - Indice

En el servidor, cuando esta consulta se ejecuta, el evento


OnGetDataSetProperties del proveedor añade información
extra al paquete devuelto:

procedure TAppServerPlus.
ProviderQueryGetDataSetProperties(Sender: TObject;
DataSet: TDataSet; out Properties: OleVariant);
begin
Properties := VarArrayCreate([0,1], varVariant);
Properties[0] := VarArrayOf(['Time', Now, True]);
Properties[1] := VarArrayOf([
'Param', SQLWithParams.Params[0].AsString, False]);
end;

Nótese que el uso de parámetros “variant array” todavía fun-


ciona, incluso si el mecanismo de transporte usado por
DataSnap 2009 es ahora diferente. En el cliente, el gestor de
evento btnParamClick tiene dos líneas más de código para de-
volver estas propiedades extras del paquete de datos:
Caption := 'Data sent at ' + TimeToStr (
TDateTime (cdsQuery.GetOptionalParam('Time')));
Label1.Caption := 'Param ' +
cdsQuery.GetOptionalParam('Param');

Hay algunas características más en DataSnap que han sido


trasladadas a la nueva versión, pero esta introducción al pro-
grama ThinPlus2009 (mayormente sin cambios de su versión
original escrita en Delphi 6) debería ser suficiente para mis
objetivos: mostrar el poder de DataSnap y lo fácil que es mi-
grar incluso aplicaciones complejas a la nueva versión de Da-
taSnap de Delphi 2009 basada en “sockets” (y libre de COM).

El interfaz administrativo

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 483

de DataSnap
Cuando se escribe un servidor DataSnap 2009, se puede eje-
cutar y conectar con el IDE en tiempo de diseño con el fin de
ayudar a escribir el código de cliente. Esta ayuda se materia-
liza en listas de los métodos y proveedores disponibles y tam-
bién en términos de generación de las clases proxy. Para lo-
grar esto cada servidor tiene un interfaz extra, llamado inter-
namente DSAdmin. En un servidor en producción se puede
deshabilitar, para evitar que otros escriban aplicaciones
cliente usando Delphi (o al menos hacérselo más difícil). Esto
se logra cambiando el valor de la propiedad HideDSAdmin en
el componente DSServer lo que generalmente está recomen-
dado cuando se despliega una aplicación.
Si el interfaz DSAdmin157 está activo, se puede usar para ex-
plorar el servidor dinámicamente. He escrito un ejemplo lla-
mado “bare-bones” que hace exactamente eso. Puede ser am-
pliado aportando un interfaz genérico de llamada e incluso
dejándole generar clases proxy extendidas.
El programa tiene un SQLConnection que conecta con un ser-
vidor disponible. Se podría querer personalizar el código para
conectar al servidor disponible en una IP y puertos dados (el
ejemplo usa los valores por defecto, escritos en código). Para
probar esta aproximación, he llamado inicialmente el método
GetPlatformName, usando un componente SqlServerMethod
configurado como:
object smGetPlatformName: TSqlServerMethod
GetMetadata = False
Params = <
item

157Para más información acerca del interfaz DSAdmin puede dirigirse a la clase
DSAmin (sin T al comienzo) en la unidad DSCommonServer.

La guía de Delphi por Marco Cantù


484 - Indice

DataType = ftWideString
Precision = 2000
Name = 'ReturnParameter'
ParamType = ptResult
Size = 2000
end>
SQLConnection = SQLConnection1
ServerMethodName = 'DSAdmin.GetPlatformName'
end

Como el método no tiene parámetros y un valor de retorno de


cadena, el código usado para invocarlos es muy simple:
procedure TFormAdmin.btnGetPlatformNameClick(
Sender: TObject);
begin
smGetPlatformName.ExecuteMethod;
ShowMessage (smGetPlatformName.ServerMethodName + ': ' +
smGetPlatformName.ParamByName('ReturnParameter').
AsString)
end;

La mayoría de los métodos de DSAdmin simplemente devuel-


ven un DataSet. Se pueden abrir y explorar estos “dataset” con
simples bucles que realizan un procesado personalizado:
smGetMethods.Open;
while not smGetMethods.EOF do
begin
strLog := '';
for I := 0 to smGetMethods.FieldCount - 1 do
strLog := strLog +
smGetMethods.Fields[I].AsString + ' | ';
ShowMessage (strLog);
smGetMethods.Next;
end;

Sin embargo, si el objetivo es simplemente mostrar informa-


ción en la pantalla, ¿qué mejor que conectar un DBGrid al da-
taset resultante? El componente SqlServerMethod, de hecho
puede ser abierto directamente como una base de datos. To-
davía no se puede conectar al interfaz de usuario ya que es un
“dataset” unidireccional. La aproximación clásica para mos-
trar datos es añadir un ClientDataSet y un DataSetProvider a
la aplicación y conectarlos normalmente.

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 485

El primer método de servidor llamado de esta manera es Get-


ServerClasses:
object smGetServerClasses: TSqlServerMethod
Params = <
item
DataType = ftDataSet
ParamType = ptResult
Value = 'TDataSet'
end>
SQLConnection = SQLConnection1
ServerMethodName = 'DSAdmin.GetServerClasses'
end

El resultado de la llamada puede ser mostrado asociándole un


componente DataSetProvider, reabriendo el ClientDataSet y
reduciendo el tamaño de las columnas del DBGrid:
procedure TFormAdmin.btnGetClassesClick(Sender: TObject);
var
I: Integer;
begin
ClientDataSet1.Close;
smGetServerClasses.Open;
DataSetProvider1.DataSet := smGetServerClasses;
ClientDataSet1.Open;
for I := 0 to DbGrid1.Columns.Count - 1 do
DbGrid1.Columns[I].Width := 150;
end;

Finalmente, el último método es GetServerMethods del inter-


faz DSAdmin que es el más útil de todos, ya que devuelve los
métodos de todas las clases disponibles en el Servidor:
object smGetMethods: TSqlServerMethod
Params = <
item
DataType = ftDataSet
ParamType = ptResult
Value = 'TDataSet'
end>
SQLConnection = SQLConnection1
ServerMethodName = 'DSAdmin.GetServerMethods'
end

El código que conecta el resultado al “dataset provider” es pa-


recido a la llamada anterior. El efecto visual, en la aplicación
cliente, será similar a este:

La guía de Delphi por Marco Cantù


486 - Indice

Otra vez este ejemplo solo aporta una idea de lo que se puede
hacer usando el interfaz administrativo de DataSnap. Otros
métodos del interfaz DSAdmin le permitirán devolver los pa-
rámetros de los métodos y otros elementos. Finalmente se
puede usar un componente SqlServerMethod y establecer su
nombre y parámetros dinámicamente para invocar un método
de servidor.

Conclusión
En este capítulo he cubierto una de las más significativas ac-
tualizaciones en términos de la librería de componentes de
Delphi 2009, la nueva arquitectura DataSnap para construir
aplicaciones “multi-tier” sin tener que recurrir a COM. Se
puede usar DataSnap 2009 para la programación orientada a
bases de datos pero también para invocar fácilmente métodos
de servidor.
Ya que este es el último capítulo del libro, no hay sección “A
continuación” solo una pequeña conclusión acerca del pro-

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 487

ducto. No tengo mucho más que añadir al material presen-


tado, lo que espero es que le ayude a comprender las nuevas
características de Delphi 2009 y a apreciar esta nueva versión
del producto.
Delphi 2009 es seguramente una versión fuera de serie, con
un increíble número de nuevas características que me hizo es-
cribir más de 400 páginas. Con un nuevo propietario (Em-
barcadero Technologies) y una buena versión con soporte
para cualquier idioma (gracias a Unicode), podemos esperar
que Delphi tenga una larga vida y una grata codificación, ¡en
cualquier parte del mundo!

La guía de Delphi por Marco Cantù


488 - Indice

Indice

1995 ..................................................................................................................................... 13
211 ..................................................................................................................................... 391
Acción del diálogo de fuente............................................................................................. 333
Acciones ............................................................................................................................ 329
Acciones estándar ............................................................................................................. 332
Action ................................................................................................................................330
Action Manager ......................................................................................................291, 331p.
ActionItem ........................................................................................................................340
ActionLink ........................................................................................................................330
ActionList.......................................................................................................................... 331
ActionManager ................................................................................................................. 331
Actions .............................................................................................................................. 327
Active Form ...................................................................................................................... 319
ActiveX Control Wizard .................................................................................................... 319
ActMan.............................................................................................................................. 342
AddRecentItem ................................................................................................................. 335
AJAX .......................................................................................................................... 217, 221
Alertas de Conversión de Cadenas ..................................................................................... 94
Alineación ......................................................................................................................... 277
Allen Bauer ....................................................................................................................... 213
Anders Melander ............................................................................................................. 300
AnsiChar ......................................................................................46, 48, 50, 73, 75, 90p., 93
AnsiString ............................................................................................................... 52p., 58p.
AnsiStrings........................................................................................................... 102pp., 264
AnsiStrings unit ................................................................................................................ 263
Application................................................................................................................ 297, 304
ApplicationMenu ..............................................................................................................344
Apply Option Set............................................................................................................... 130
Archivos DFM ..................................................................................................................... 86
Asistentes de Componentes............................................................................................... 141
Associate ........................................................................................................................... 348
AsString ....................................................................................................................362, 368
BabelFish ............................................................................................................................ 76
BackgroundColor .............................................................................................................. 287
BalloonHint ...................................................................................................................... 269
BarColor ............................................................................................................................ 287
Barry Kelly ................................................................................................................. 187, 195
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 489

BaseException .......................................................................................................... 257, 259


Basic Multilingual Plane ..................................................................................................... 57
BCM_SETSHIELD ........................................................................................................... 273
BDE ........................................................................................................................... 257, 383
Bibliotecas de tipo ........................................................................................................... 308
BitBtn ................................................................................................................................ 272
BLOB ........................................................................................................................ 303, 369
Blog ............................................................................................................. 17, 220, 247, 250
BOM ........................................................................................................... 77p., 98, 253, 290
Bombeo de Datos ..............................................................................................................383
Bookmark .................................................................................................................100, 360
Borland ............................................................................................................................... 13
Botones de pulsación ........................................................................................................ 272
BS_COMMANDLINK ...................................................................................................... 273
BS_SPLITBUTTON .......................................................................................................... 273
BSTR ................................................................................................................................. 318
Bucle For Paralelo ............................................................................................................ 213
ButtonedEdit ......................................................................................................... 281p., 343
Byte Order Mark ................................................................................................................. 35
ByteLength .......................................................................................................................... 54
C++ ................................................................................................................................... 147
C++Builder ......................................................................................................................... 68
Cada punto .......................................................................................................................... 56
Cadena literal ...................................................................................................................... 75
Capacity ............................................................................................................................ 249
Carácter literal .................................................................................................................... 75
CategoryPanel ................................................................................................................293p.
CategoryPanelGroup ........................................................................................................ 292
CF_UNICODETEXT ........................................................................................................303
Character ............................................................................................................................ 92
Character unit ................................................................................................................... 263
Characters ........................................................................................................................... 51
CharCase .......................................................................................................................... 280
CharInSet ...................................................................................................................... 47, 91
Chau Chee Yang ................................................................................................................ 378
CheckBoxes .......................................................................................................................289
CheckWin32Version ................................................................................................. 297, 304
Chevron .......................................................................................................................... 293p.
Chinos ......................................................................................................................... 93, 144
Chris Bensen ............................................................................................................. 229, 319
Chris Hesik ....................................................................................................................... 145
Cirílico................................................................................................................................. 62
Clase interceptor97 ...........................................................................................................205
Classes.......................................................................................................................235, 263
ClientDataSet ................................. 180, 345, 356p., 365p., 392, 398, 408, 411, 413pp., 418
ClipBrd ..............................................................................................................................303
Coclass .............................................................................................................................. 313
Code point ........................................................................................................................... 85
CodeGear ............................................................................................................................ 13
La guía de Delphi por Marco Cantù
490 - Indice

ColorDepth .......................................................................................................................302
Columns ............................................................................................................................340
COM .......................................................................................................................307, 393p.
ComboBox................................................................................................................ 280, 305
CommandLinkHint .......................................................................................................... 273
CommandProperties ..................................................................................................... 341p.
CommandStyle ................................................................................................................. 333
CommandType ................................................................................................................. 337
Common User Access ....................................................................................................... 323
Compilador de Recursos .................................................................................................. 136
Compilador de Recursos de Borland................................................................................ 136
Compiler directive ..................................................................................................................
INLINE ........................................................................................................................ 122
M .................................................................................................................................. 123
MethodInfo.................................................................................................................. 394
POINTERMATH ......................................................................................................... 109
STRINGCHECKS ........................................................................................................ 124
VARPROPSETTER ...................................................................................................... 229
Z ................................................................................................................................... 123
$HIGHCHARUNICODE ............................................................................................... 76
$STRINGCHECKS ........................................................................................................ 69
Compiler option ......................................................................................................................
--string-checks ...............................................................................................................68
-$M .............................................................................................................................. 123
-$Z ............................................................................................................................... 123
ConnectionData ................................................................................................................ 375
ConnectionName .............................................................................................................. 374
ConnectionString .............................................................................................................. 374
Construcción de Configuraciones .................................................................................... 129
Construct .......................................................................................................................... 176
Contnrs .......................................................................................................................171, 177
Control de cuentas de usuario .......................................................................................... 315
Conversión de Cadenas ...................................................................................................... 65
ConvertFromUtf32 ....................................................................................................... 50, 56
CreatePanel ....................................................................................................................... 293
Cuenta de referencia ........................................................................................................... 53
CustomHint .............................................................................................................. 269, 271
CustomizeDlg.................................................................................................................... 331
Danysoft ............................................................................................................................ 387
Danysoft ............................................................................................................................ 1p.
Danysoft. ..............................................................................................................................17
DataSetProvider ....................................................................................................... 392, 418
DataSnap ............................................................................................................... 389p., 416
DBError ............................................................................................................................ 257
DbExpress ........................................................................................................ 373, 380, 394
DBGrid ................................................................... 345, 357, 365, 367, 386, 392, 414p., 418
DBImage ...........................................................................................................................386
DBNavigator ..................................................................................................................... 344
Dbxdrivers.ini ................................................................................................................... 377
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 491

DbxInterBase .................................................................................................................... 379


De la biblioteca de tipos ...................................................................................................309
Default ....................................................................................................................... 159, 161
DefaultFieldClasses .......................................................................................................... 365
DefaultFieldDefClass ........................................................................................................ 364
DefaultFont ....................................................................................................................... 297
DefaultParamClass ........................................................................................................... 364
Delphi................................................................................................................................ 137
Delphi 2007 Handbook ........................................................................ 4, 101, 296, 373, 380
Delphi Explorer ................................................................................................................ 137
Deprecated ................................................................................................................ 227, 361
Depurador ......................................................................................................................... 144
Description ....................................................................................................................... 348
DExplorer .......................................................................................................................... 117
DialogAction ..................................................................................................................... 333
Dialogo de Opciones de Proyecto .................................................................................... 136
Dictionary .......................................................................................................................... 177
DoubleBuffered.................................................................................................................268
Draw..................................................................................................................................302
DrawText ......................................................................................................................... 41p.
DropDownMenu ............................................................................................................... 273
DSAdmin................................................................................................................ 416, 418p.
DSCommonServer ............................................................................................................ 417
DSNames ..........................................................................................................................402
DSProviderConnection ..............................................................................................392, 411
DSServer ........................................................................................................................ 391p.
DSServerClass.................................................................................. 391, 395, 400, 402, 406
DSTCPServerTransport ............................................................................................ 391, 400
Edit.................................................................................................................................... 277
Editor ................................................................................................................................ 143
Editor ........................................................................................................................... 309p.
Editor de la biblioteca de tipos .................................................................................. 311, 317
El Menú de aplicación ...................................................................................................... 335
ElevationRequired ............................................................................................................ 273
Embarcadero Technologies ........................................................................................ 13, 420
EndOfStream .................................................................................................................... 253
Enlaces .............................................................................................................................. 275
EnsureUnicodeString ................................................................................................. 68, 124
EProgrammerNotFound .................................................................................................. 262
Equals ....................................................................................................................... 235, 237
ER/Studio ........................................................................................................................... 13
ES_NUMBER ................................................................................................................... 278
Essential Pascal .................................................................................................................... 4
Example ..................................................................................................................................
AnonAjax ................................................................................................................ 217pp.
AnonymFirst................................................................................................ 195, 198, 200
AppFont .................................................................................................................... 297p.
BareBoneRibbon ......................................................................................................... 327
ButtonEdits .................................................................................................................282
La guía de Delphi por Marco Cantù
492 - Indice

ButtonsDemo............................................................................................................... 273
CategoryPanels ............................................................................................................ 293
CharTest ........................................................................................................... 50p., 90p.
CharTroubles ................................................................................................................. 92
CheckBoxHeader ........................................................................................................ 288
ClassContraint ............................................................................................................. 162
CustomEncoding ........................................................................................................... 84
CustomerDictionary .................................................................................................... 178
CustomFields ............................................................................................................... 365
DataRibbon ................................................................................................................. 345
DbEditPlus .................................................................................................................. 385
DbxMulti2009 ..................................................................................................... 373, 378
DfmTest ......................................................................................................................... 86
DsnapMethods ................................................................................................... 403, 407
DsnapMethodsClient.................................................................................................. 408
DsnapMethodServer ................................................................................................... 399
EditFamilyDemo ................................................................................................ 278, 280
ExceptionsTest ................................................................................................. 257p., 261
First3Tier2009 ............................................................................................................ 396
GenericCodeGen.......................................................................................................... 156
GenericInterface ....................................................................................................... 182p.
GenericTypeFunc ..................................................................................................... 159p.
GraphicsTest....................................................................................................... 300, 302
GraphicTest ................................................................................................................. 299
GroupingList ............................................................................................................... 285
HighCharTest ................................................................................................................ 75
HintsDemo ............................................................................................................ 269pp.
IntfConstraint ............................................................................................ 164, 167p., 183
IntfContraints .............................................................................................................. 182
KeyValueClassic .......................................................................................................... 148
KeyValueGeneric ...................................................................................................... 150p.
LabelsDemo ................................................................................................................. 275
LatinTest ............................................................................................................... 59, 61p.
ListDemoMd2005 .................................................................................................... 172p.
ListMonitor..................................................................................................................242
MiniPack ........................................................................................................................ 85
MiniSize ......................................................................................................................... 85
MinorLang ..............................................................................................................226pp.
MoveStrings................................................................................................................ 96p.
MyTrayIcon ................................................................................................................. 296
MyTrayIconClick ......................................................................................................... 296
PlainTips ...................................................................................................................... 349
PointerMath ................................................................................................................ 107
PointerMathD2007 ......................................................................................................110
ProjManagerTest .................................................................................................. 127, 131
RadioGroupDemo ....................................................................................................... 277
RawTest ..........................................................................................................................71
ReaderWriter ....................................................................................................... 252, 254
ResourceTest ............................................................................................................ 134p.
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 493

RibbonEditor ................................................................................................... 338p., 343


RibbonEditorTips ..................................................................................................... 351p.
SimpleClient ................................................................................................................ 317
SimpleServer ....................................................................................................... 310, 313
SmartPointers............................................................................................................... 191
StreamDsDemo ........................................................................................................... 373
StreamEncoding ...................................................................................................... 79, 81
StringBuilder ................................................................................................ 245p., 248p.
StringConvert ................................................................................................ 66, 144, 207
StringTest ................................................................................................................ 54, 57
StringTest, ..................................................................................................................... 56
SuperProgress ............................................................................................................. 287
SystemObject ............................................................................................................... 237
TestChar ........................................................................................................................ 47
ThinPlus2009 ............................................................................................... 410, 413, 416
TypeCompRules ................................................................................................ 151, 154p.
UniApiSpeed: ................................................................................................................ 38
UniCds ................................................................................................................. 356, 359
UniClipboard ............................................................................................................303p.
UnicodeConsoleTest .................................................................................................... 106
UniRichEdit .................................................................................................................290
Utf8Test ......................................................................................................................63p.
VariantOver .............................................................................................................. 231p.
VarProp........................................................................................................................230
WebFind ........................................................................................................... 210p., 219
Excepción .......................................................................................................................... 262
Exception .............................................................................................................. 256p., 260
Execute..............................................................................................................................409
ExpandCapacity ................................................................................................................249
ExtCtrls ..................................................................................................................... 281, 302
ExtTextOut...................................................................................................................... 40p.
Fabrica de clases ............................................................................................................... 391
FastCode ........................................................................................................................... 264
FetchOnDemand .............................................................................................................. 414
Ficheros de Configuración de Proyecto............................................................................. 119
FillChar ............................................................................................................................... 92
Fluent User Interface..................................................................................................... 323p.
FreeOnTerminate ............................................................................................................. 216
FtWideString ............................................................................................................ 357, 368
Fuente ............................................................................................................................... 305
Funciones de Tipo Genérico ............................................................................................. 158
Generate Datasnap client classes ..................................................................................... 397
Generics.Collection unit ................................................................................................... 263
Generics.Collections ................................................................................................... 171, 177
Generics.Default ............................................................................................................ 184p.
Generics.Default unit ....................................................................................................... 263
Generics.Defaults ............................................................................................................. 173
GenTLB.exe ......................................................................................................................309
Gestores De Eventos .........................................................................................................204
La guía de Delphi por Marco Cantù
494 - Indice

GetCategoryPanelClass..................................................................................................... 295
GetCollection .................................................................................................................... 383
GetFieldsList ..................................................................................................................... 359
GetHashCode .................................................................................................................... 236
GetNextPacket .................................................................................................................. 415
GetPlatformName............................................................................................................. 417
GetPreamble ....................................................................................................................... 77
GetProcAddress ........................................................................................................ 105, 404
GetServerClasses .............................................................................................................. 418
GetServerMethods ............................................................................................................ 419
GetTableNames ................................................................................................................ 381
GetTickCount .................................................................................................... 209, 214, 243
GetTypeName ................................................................................................................... 159
GetUserName ..................................................................................................................... 39
GetWindowText .................................................................................................................. 36
GIF ................................................................................................................................ 300p.
GlassFrame .......................................................................................................................304
GlowSize ................................................................................................................... 275, 305
GlyFX ................................................................................................................................ 301
Google ............................................................................................................................... 210
GridPanel .......................................................................................................................... 294
GroupAlign .......................................................................................................................340
GroupHeaderImages ........................................................................................................284
GroupPosition................................................................................................................... 341
Groups ..............................................................................................................................284
GroupView ........................................................................................................................284
Grupo de Google ................................................................................................................. 18
Gustavo Daud .................................................................................................................. 300
HeaderControl ................................................................................................................. 288
Helper ................................................................................................................................. 81
HideDSAdmin................................................................................................................... 417
Hint ................................................................................................................................... 271
HRESULT ......................................................................................................................... 314
HTML........................................................................................................................ 276, 320
HTTP ................................................................................................................................. 218
IAppServer ............................................................................................... 394, 396, 398, 400
IComparer<T>................................................................................................... 173, 175, 184
ID de interfaz .................................................................................................................... 182
IdHttp ............................................................................................................................... 210
IDL ................................................................................................................................... 308
IEqualityComparer<T> .................................................................................................... 184
ImageList ......................................................................................................... 284, 299, 332
Images ............................................................................................................................... 332
Implicit ..................................................................................................................... 189, 232
Indy ............................................................................................................................210, 217
InfoPower Grid Essentials ................................................................................................ 387
InnerException ...................................................................................................256p., 259p.
Instalacion ......................................................................................................................... 116
InstallAware....................................................................................................................... 116
La guía de Delphi por Marco Cantù | para
Indice - 495

InstanceSize ...................................................................................................................... 162


Int8 ................................................................................................................................... 234
Interceptor de clase ..........................................................................................................205
Interfaz de Definición de Lenguaje ................................................................................. 308
InterlockedIncrement....................................................................................................... 214
Invocation .........................................................................................................................402
Invoke ...............................................................................................................................202
IsLeadChar.................................................................................................................... 51, 92
IsSurrogate ......................................................................................................................... 51
IUnknown ......................................................................................................................... 313
Jan Goyvaerts ............................................................................................................... 69, 71
Japoneses..............................................................................................................76, 86, 303
JavaScript ......................................................................................................................... 194
Jeremy North .................................................................................................................... 324
John Kaster ....................................................................................................................... 383
Jon Benedicto ................................................................................................................... 383
JPEG ..............................................................................................................................299p.
JQuery........................................................................................................................194, 217
Key Tips ............................................................................................................................ 338
La barra de herramientas de acceso rápido ..................................................................... 337
Lado divertido de Delphi .................................................................................................. 262
LargeImages ..................................................................................................................... 332
Latin-1 ........................................................................................................................... 59, 66
LeadBytes............................................................................................................................ 92
LeftButton ......................................................................................................................... 281
Lenguaje C++.................................................................................................................... 149
Lenguajes Específicos de Dominio ................................................................................... 247
LifeCycle ....................................................................................................................... 402p.
LinkedActionList .............................................................................................................. 351
LinkLabel .......................................................................................................................... 275
ListView ................................................................................................... 180p., 284pp., 305
Los métodos Anónimos .................................................................................................... 194
Los tipos anidados ............................................................................................................ 273
Maestro/detalle ................................................................................................................ 413
MainFormOnTaskbar .......................................................................................................304
Marquesina ....................................................................................................................... 287
Mastering Delphi ...............................................................4, 16, 171, 193, 310, 317, 329, 410
Math .................................................................................................................................. 264
MaxThreads ...................................................................................................................... 407
Memo ............................................................................................................................... 280
MessageDlg .......................................................................................................................298
MessageFont .....................................................................................................................298
Métodos ............................................................................................................................ 193
Métodos al servidor .......................................................................................................... 395
Métodos de Encadenamiento ........................................................................................... 247
Micro ISV ............................................................................................................................ 15
Microsoft ............................................................................................15, 117, 136, 307, 324p.
Modelo de Objetos de Componentes................................................................................ 307
Most recently used ............................................................................................................ 335
La guía de Delphi por Marco Cantù
496 - Indice

Move ................................................................................................................................... 96
MoveBy ............................................................................................................................. 363
MSBuild ......................................................................................................................... 118p.
MultiByteToWideChar .......................................................................................................60
NewRow ............................................................................................................................ 341
NumbersOnly ........................................................................................................... 278, 385
Número primo .................................................................................................................. 213
Office 2007 ....................................................................................................................... 324
Office Fluent UI ................................................................................................................ 325
OnAccept........................................................................................................................... 336
OnAfterConnection............................................................................................................ 411
OnBalloonClick ................................................................................................................. 296
OnClose .............................................................................................................................404
OnConnect ........................................................................................................................ 392
OnCreateInstance .............................................................................................................403
OnDestroyInstance .................................................................................................. 403, 406
OnDisconnect ................................................................................................................... 392
OnExecute.........................................................................................................................330
OnGetDataSetProperties .................................................................................................. 416
OnItemChecked ................................................................................................................286
OnLinkClick ...................................................................................................................... 276
OnMouseEnter .................................................................................................................270
OnSectionCheck ...............................................................................................................289
Opciones de Configuración .............................................................................................. 129
Open Arrays ....................................................................................................................... 111
Ordenando ........................................................................................................................ 173
Overloading ......................................................................................................................230
PacketRecords .................................................................................................................. 414
Paleta de Herramientas .................................................................................................... 140
Panel ................................................................................................................................. 292
PAnsiChar ............................................................................................................. 36, 46, 105
Parámetros Variants .......................................................................................................... 111
ParentCustomHint ........................................................................................................... 269
ParentDoubleBuffered......................................................................................................268
ParentFont ........................................................................................................................ 297
PasswordChar ................................................................................................................... 278
PByte ..........................................................................................................................109, 371
PChar .................................................................................................... 40, 46, 105, 107, 109
Peek ................................................................................................................................... 254
Philippe Kahn ................................................................................................................... 147
PInteger .................................................................................................................... 107, 109
PNG ....................................................................................................................... 300p., 386
Polimorfismos................................................................................................................... 185
PoolSize............................................................................................................................. 407
PopupActionBar ............................................................................................................... 291
PopupActionBarEx ........................................................................................................... 331
Portapapeles .....................................................................................................................303
Potential data loss............................................................................................................... 67
Prime number ................................................................................................................... 399
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 497

ProcessMessages............................................................................................................... 214
ProgressBar............................................................................................................... 287, 305
Project Manager ........................................................................................................ 126, 133
Proportional ......................................................................................................................303
Protección ......................................................................................................................... 385
ProviderName................................................................................................................... 392
Proxy ......................................................................................................................... 179, 396
Puntero Matemático ......................................................................................................... 107
Punteros inteligentes ........................................................................................................ 185
Punto de código .................................................................................................................. 57
Put by ref........................................................................................................................... 229
PWideChar ............................................................................................................ 36, 46, 105
QueryPerformanceCounter ..............................................................................................209
QuickAccessToolbar ......................................................................................................... 344
RadioGroup ..............................................................................................................276, 404
RaiseOuterException ........................................................................................................ 258
RaisingException ..................................................................................................... 260, 262
RawByteString ........................................................................................................ 58, 70, 97
ReadFromFile ..................................................................................................................... 78
RecentItems ...................................................................................................................... 337
Recursos............................................................................................................................ 133
Register ActiveX Server .................................................................................................... 314
Registered Type Libraries ................................................................................................ 315
RemoteServer ................................................................................................................... 392
ReportMemoryLeaksOnShutdown .................................................................................. 188
ResemblesText .................................................................................................................. 103
Resourcestring .................................................................................................................. 137
Restringido IDL ................................................................................................................309
ReverseString.................................................................................................................... 103
Ribbon .......................................................... 326p., 329, 331pp., 338p., 342, 344, 348, 350
RibbonComboBox ............................................................................................................ 343
RibbonSpinEdit ................................................................................................................ 327
RichEdit ....................................................................................................................289, 332
RIDL ............................................................................................................................. 309p.
RightButton ...................................................................................................................... 281
RoundTo ........................................................................................................................... 264
Rows..................................................................................................................................340
Safecall ................................................................................................................ 311, 313, 411
Salidas ...............................................................................................................................228
Screen ...............................................................................................................................298
Screen Tips ....................................................................................................................... 348
ScreenTipsManager .............................................................................................. 348, 350p.
ScreenTipsPopup ........................................................................................................ 348pp.
Server ................................................................................................................................402
ServerClassName .............................................................................................................. 392
ServerMethodName ......................................................................................................... 396
Session ..............................................................................................................................402
Set of Char .................................................................................................................... 47, 90
SetCodePage ...................................................................................................... 55, 59, 66, 71
La guía de Delphi por Marco Cantù
498 - Indice

SetWindowText .................................................................................................................. 39
ShellExecute ..................................................................................................................... 276
ShortString ................................................................................................................. 53, 240
ShowCaption ..................................................................................................................... 292
ShowHelpButton .............................................................................................................. 326
ShowMessage....................................................................................................................298
Sincronización de Procesos ..............................................................................................209
Singleton ........................................................................................................................... 185
SizeOf ......................................................................................................................... 159, 162
SmoothReverse ................................................................................................................. 287
Soporte de Metadatos Extendidos .................................................................................. 380
SQLConnection ................................................................. 374, 379, 381, 392, 394, 400, 417
SqlServerMethod .............................................................................................. 395pp., 417p.
SqlServerMethod150 ........................................................................................................ 396
Stdcall ............................................................................................................................... 314
Steve Tendon .................................................................................................................... 147
StringCodePage ............................................................................................................ 54, 60
StringElementSize .....................................................................................................54p., 60
StringOfChar....................................................................................................................... 93
StringRefCount ................................................................................................................... 54
Style .................................................................................................................................. 273
SupportsPartialTransparency .......................................................................................... 299
Synchronize .............................................................................................................. 210, 409
SyncObjs unit .................................................................................................................... 263
System ......................................................................................... 48, 53, 60, 233p., 242, 263
System unit ....................................................................................................................... 263
System.Object ................................................................................................................... 234
System.Text.Encoding ........................................................................................................ 77
SysUtils .................................................................................. 54, 77, 91, 97, 203, 256, 262p.
SysUtils unit...................................................................................................................... 264
TAction..............................................................................................................................330
TActionClientItem .................................................................................................... 334, 341
TBalloonHint .................................................................................................................... 269
TBasicAction .....................................................................................................................330
TBDXProperties ............................................................................................................... 376
TBitmap ............................................................................................................................ 299
TBlobField ........................................................................................................................ 370
TBookmark ........................................................................................................... 100, 360p.
TButton ............................................................................................................................. 272
TButtonProperties ............................................................................................................342
TButtonStyle ..................................................................................................................... 273
TBytes ................................................................................................................................. 97
TCategoryPanel................................................................................................................. 295
Tcharacter .................................................................................................................. 49, 55p.
TComboBox ..................................................................................................................... 280
TComparer<T> .......................................................................................................174p., 184
TConnectionData .............................................................................................................. 375
TControl .................................................................................................................... 269, 343
TCP/IP ................................................................................................................... 391p., 394
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 499

TCustomAction .................................................................................................................330
TCustomButton ................................................................................................................ 272
TCustomEdit ..................................................................................................................... 385
TCustomHint .................................................................................................................... 269
TDataSet ........................................................................................................... 358pp., 371p.
TDBEdit .................................................................................................................... 359, 385
TDBImage .........................................................................................................................303
TDBXCommand ............................................................................................................... 397
TDBXMetaDataCommands.............................................................................................. 382
TDBXTable ....................................................................................................................... 383
TDictionary<TKey,TValue> .............................................................................................. 171
TDrawingStyle ..................................................................................................................302
TDSServerModule .........................................................................................................394p.
Teclado .............................................................................................................................. 338
Tecnologías Web 2.0 ...........................................................................................................17
TEdit ...................................................................................................................... 277, 279p.
TEditButton ...................................................................................................................... 281
TEncoding............................................................................................................ 64, 77p., 81
TEqualityComparer<T> ................................................................................................... 184
TextHint ................................................................................................... 278, 280, 343, 385
TextOut ........................................................................................................................41, 105
TField ........................................................................................................................358, 368
TFieldDef .......................................................................................................................... 367
TFileStream .............................................................................................................. 106, 253
TFunc<TResult> ..............................................................................................................203
TGraphic ..................................................................................................................... 299pp.
Threading.......................................................................................................................... 241
Threads Status .................................................................................................................. 145
Tiburón ..............................................................................................................................118
TInterfacedObject ............................................................................................................. 182
Tipos de procedimiento .................................................................................................... 193
Tipos Enteros .................................................................................................................... 233
TJPEGImage ..................................................................................................................... 301
TLabel ............................................................................................................................... 275
TLB..................................................................................................................................... 311
Tlibimp.exe .......................................................................................................................309
TList<T> ......................................................................................................................... 171p.
TLookupList...................................................................................................................... 365
TMonitor .................................................................................................................. 242, 402
TObject......................................................................................................................234, 239
TObjectDictionary<TKey, TValue>................................................................................... 177
TObjectList<T> ................................................................................................................. 177
ToString ............................................................................ 234, 237, 246, 257, 259, 401, 405
ToUpper .............................................................................................................................. 50
TParam ............................................................................................................................. 370
TProc .................................................................................................................................203
TQueue<T>........................................................................................................................ 171
TrayIcon ............................................................................................................................ 296
TRecordBuffer ............................................................................................................... 371p.
La guía de Delphi por Marco Cantù
500 - Indice

TreeView ...................................................................................................................284, 343


TRemoteDataModule ................................................................................................394, 411
TRibbonPage .................................................................................................................... 326
TribbonTabItem ...............................................................................................................326
TScreenTipItem ................................................................................................................348
TSingletonImplementation .............................................................................................. 185
TSQLConnection ................................................................................................... 377p., 390
TStack<T> ......................................................................................................................... 171
TStreamReader .............................................................................................................. 252p.
TStreamWriter.................................................................................................................. 252
TStringBuilder ...............................................................................................99, 245pp., 253
TStringField ..............................................................................................................362, 368
TStringReader................................................................................................................ 252p.
TStringWriter ........................................................................................................ 252p., 255
TSysCharSet ........................................................................................................................ 91
TTextReader ............................................................................................................. 252, 254
TTextWriter ...................................................................................................... 106, 252, 254
TThread ............................................................................................................ 210, 241, 408
TUnicodeEncoding ............................................................................................................. 77
Turbo Pascal ....................................................................................................................... 13
TVarRec ............................................................................................................................. 111
TWideMemoField ............................................................................................................. 370
TWideStringField .............................................................................................................362
TwideStringField, .............................................................................................................368
TWinControl .....................................................................................................................268
Type Library ..................................................................................................................... 310
TypeInfo ..................................................................................................................... 159, 161
TypInfo ................................................................................................................ 85, 159, 241
UCS4Char ............................................................................................................... 48, 57, 82
UCS4String ............................................................................................................. 49, 57, 83
UnicodeString ............................................................................................................... 53, 56
Unidad Classes.................................................................................................................. 252
UnitName ......................................................................................................................... 236
UpCase ................................................................................................................................ 50
UpdateRegistry ................................................................................................................. 410
UpDown ............................................................................................................................ 407
UpperCase................................................................................................................... 50, 104
UseCustomFrame .....................................................................................................326, 328
UseLatestCommonDialogs ...............................................................................................298
UseVisualStyle .................................................................................................................. 276
UTF-16 .......................................................................................................................... 29, 35
UTF-32 .................................................................................................................... 29, 35, 81
UTF-8....................................................................................................... 29, 35, 63, 80, 240
UTF8String ........................................................................................... 58, 63, 65p., 73p., 99
VER200 ...................................................................................................................... 13, 226
Versión del compilador .............................................................................................. 13, 226
Visual C++ .......................................................................................................................... 15
VmtParent......................................................................................................................... 239
WideChar ................................................................................................................ 46, 52, 91
La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA
Indice - 501

WideCharToMultiByte ....................................................................................................... 63
WideString ............................................................................................................ 38, 42, 318
WideStrUtils ......................................................................................................... 43, 64, 104
Wikipedia .......................................................................................................................... 324
Win32.................................................................................................................................. 14
Windows 9x ........................................................................................................................40
Windows Vista .................................................................................. 273, 275, 278, 281, 287
Windows XP ................................................ 40, 272, 275, 277p., 280p., 284, 287, 291, 305
WinExec ............................................................................................................................404
WriteToFile ......................................................................................................................... 78
Yorai Aminov .................................................................................................................... 383
ZLib unit ........................................................................................................................... 264
Explorer ........................................................................................................................... 137
Genéricos ......................................................................................................................... 147
puntero............................................................................................................................. 193
.DPROJ ...........................................................................................................................119p.
.OPTSET ............................................................................................................................ 131
.RES .................................................................................................................................. 133
€ .......................................................................................................................................... 75

La guía de Delphi por Marco Cantù


502 - Indice

Sitios Web relacionados


Incluyo a continuación una lista de Webs tanto miás como re-
lacionadas con el mundo Delphi que podrá encontrar intere-
santes:
http://www.marcocantu.com
http://blog.marcocantu.com
http://dev.newswhat.com
http://delphi.newswhat.com
http://ajax.marcocantu.com

Las webs de Danysoft en castellano:


http://www.danysoft.com/embarcadero
http://www.danysoft.com

Por último, aquí hay páginas personales en sitios de la comu-


nidad (no todos los actualizan con frecuencia) y micro-sitios
de blogs:
http://www.linkedin.com/in/marcocantu
http://www.facebook.com/people/Marco_Cant/600881813
http://www.librarything.com/profile/MarcoCantu
http://twitter.com/marcocantu
http://marcocantu.myplaxo.com/

La guía de Delphi por Marco Cantù | para Desarrollo sistemas integrados control, SA

También podría gustarte

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy