25 abril 2008

Programar videojuegos con la librería SDL (1)

Hace mucho tiempo leí en una página web que uno no se hace programador profesional hasta que ha programado uno o más videojuegos (o por lo menos lo ha intentado). Por mi experiencia personal creo que tienen razón. En un videojuego no hay trucos de lenguajes, librerías o componentes. Todo hay que hacerlo a pelo. Nuestras funciones y procedimientos se ejecutan de 25 a 30 veces por segundo y ante cualquier error nuestro (bucles cerrados, objetos creados y no liberados, etc.) nos podemos cargar la pila de nuestra aplicación o el heap de datos mejor que el virus más maléfico del mercado.

Peor era en mis tiempos, cuando programaba en ensamblador con mi MSX que tenía un procesador Z80 de 8 bits. Cuando se colgaba el juego había que resetear la máquina, rebobinar la cinta de casete y esperar dos minutos a que se cargue el ensamblador y el debugger. Eso si que eran buenos tiempos.

Aunque no os preocupéis, la programación de videojuegos en PC no es tan traumática. Vamos a ver paso a paso como crear un nuevo proyecto en Delphi y adaptarlo para añadirle la librería SDL.

SDL (Simple DirecMedia Layer) es una librería multiplataforma escrita en el lenguaje de programación C/C++ y disponible para todos los sistemas operativos (Windows, Linux, BeOS, NetBSD, etc.). Permite controlar todo lo referente al video 2D y 3D con OpenGL, reproduce archivos multimedia y controla el teclado, ratón o joystick.

DONDE CONSEGUIR LA LIBRERÍA SDL PARA DELPHI

Como todos sabéis, existe la página Project JEDI Portal encargada de traducir librerías y utilidades en C/C++ al Pascal/Delphi. Dentro de esa inmensa cantidad de librerías se encuentra el proyecto JEDI-SDL que es el que nos interesa:

Es en esta dirección donde está lo que nos interesa:

http://sourceforge.net/project/showfiles.php?group_id=43805

El archivo es un zip de 5.5 MB. Lo que tenemos que hacer es crear una carpeta donde alojar la librería de tal modo que varios proyectos puedan tirar de ella, por ejemplo:

D:\desarrollo\librerias\sdl\

Ahí es donde tenemos que descomprimir el archivo JEDI-SDLv1.0.zip que nos hemos descargado. Nos va a extraer todas estas carpetas:


Una vez tenemos nuestra librería instalada ya podemos ponernos manos a la obra.

CREANDO UN JUEGO DELPHI CON LA LIBRERÍA SDL

Los pasos para crear un nuevo juego serían los siguientes:

1. Creamos un nuevo proyecto y lo guardamos en la carpeta donde vamos a crear el juego, por ejemplo:

D:\desarrollo\delphi\juego\

2. Eliminamos el formulario principal Form1 (si, habéis leído bien, nos vamos a cargar todos los formularios del proyecto. Hay que olvidarse de formularios y componentes).

3. Guardamos el proyecto con el nombre juego.dpr

4. Abrimos la única unidad que tiene el proyecto: CTRL + F12 y Juego.

Para que el código fuente quede lo más limpio posible sólo vamos a insertar en la unidad juego.dpr el núcleo central del programa. Todo lo referente a las rutinas de inicialización y manejo de la SDL la vamos a añadir en otra unidad.

CREANDO UNA UNIDAD PARA RUTINAS GENÉRICAS

Vamos a crear la unidad UJuego.pas la cual va a contener todas las rutinas genéricas que puede tener un juego. Cuando se diseña un programa siempre hay que pensar en reutilizar el código para un futuro.

Lo primero que hay que añadir en esta unidad es una referencia a la librería SDL. Para ello primero vamos a vincular al proyecto las unidades SDL que necesitamos. En el menú superior de Delphi seleccionamos Proyect -> Options y en la pestaña Directories/Conditionals vamos a añadir al campo Search Path la carpeta:

D:\Desarrollo\Librerias\sdl\SDL\Pas\

De este modo ya podemos vincular la librería SDL a nuestra unidad:

También he añadido las unidades Windows, SysUtils y Dialogs porque las vamos a necesitar para llamar a algunas funciones y procedimientos estándar.

INICIALIZAR LA LIBRERÍA SDL

El primer procedimiento que vamos a crear se va a llamar InicializarSDL, el cual va a encargarse de inicializar el modo de video en 2D para poder dibujar:

procedure InicializarSDL;
begin
// Inicializamos la librería SDL
if SDL_Init( SDL_INIT_VIDEO or SDL_DOUBLEBUF ) < 0 then
begin
ShowMessage( 'Error al inicializar la librería SDL.' );
SDL_Quit;
bSalir := True;
Exit;
end;

bSalir := False;
end;

bSalir es una variable global que vamos a declarar también en la unidad UJuego.pas para controlar si hay que salir del juego inmediatamente:

Var
bSalir: Boolean;

La función SDL_Init se encarga inicializar la librería SDL, controlando todos los dispositivos que vamos a inicializar: video, sonido, ratón, joysticks, etc. Tiene los siguientes parámetros:

function SDL_Init( flags : UInt32 ) : Integer;

donde flags puede contener a la vez todos estos valores:

SDL_INIT_TIMER: Prepara el temporizador de la librería SDL.
SDL_INIT_AUDIO: Prepara la tarjeta de sonido.
SDL_INIT_VIDEO: Prepara el modo de video.
SDL_INIT_CDROM: Prepara el CD-ROM.
SDL_INIT_JOYSTICK: Inicializa el joystick.
SDL_INIT_EVERYTHING: Inicializa todos lo que hemos visto anteriormente.

También vamos a crear otro procedimiento para finalizar la ejecución de la librería SDL:

procedure FinalizarSDL;
begin
SDL_Quit;
end;

Este procedimiento es importante para que la librería SDL suelte los dispositivos que tiene cogidos en Windows (teclado, ratón, tarjeta de sonido, etc.).

CAMBIAR EL MODO DE VÍDEO EN SDL

El siguiente procedimiento que vamos a crear se va a encargar de cambiar el modo de video:

procedure ModoVideo( iAncho, iAlto, iProfundidadColor: Integer; bModoVentana: Boolean );
begin
// Pasamos a modo de video de la ventana especificada
if bModoVentana then
Pantalla := SDL_SetVideoMode( iAncho, iAlto, iProfundidadColor, SDL_HWSURFACE )
else
Pantalla := SDL_SetVideoMode( iAncho, iAlto, iProfundidadColor, SDL_HWSURFACE
or SDL_FULLSCREEN );

if Pantalla = nil then
begin
ShowMessage( 'Error al cambiar de modo de video.' );
SDL_Quit;
Exit;
end;
end;

Los parámetros son los siguientes:

iAncho -> Resolución horizontal en pixels (320, 640, 800, 1024)
iAlto -> Resolución vertical en pixels (200, 480, 600, 768)
iProfundidadColor -> número de bits por píxel (8, 16, 32)
bModoVentana -> True: en ventana False: Pantalla completa

Cuando se desarrolla un juego por primera vez lo mejor es hacerlo en modo ventana, porque si se produce un error va a costar mucho volver al escritorio de Windows e incluso puede dejarnos el modo de video en una resolución inferior a la que deseamos (desordenando todos los iconos del escritorio). Cuando el juego esté terminado y funcione bien entonces activamos el modo a pantalla completa.

La resolución de los modos de video estándar son los siguientes:

320 x 200
640 x 480
800 x 600
1024 x 768

La profundidad de color se refiere al número de bits por pixel pudiendo seleccionar los valores: 8, 16 o 32. Estos son los valores estándar en la creación de videojuegos, pero hay muchos modos de vídeo más según la tarjeta (nVidia, ATI, Matrox, Intel, etc.). Si vamos a hacer un juego normal que no consuma muchos recursos tenemos dos opciones:

ModoVideo( 640, 480, 16, False );
ModoVideo( 800, 600, 16, False );

Si la tarjeta de video es buena (una aceleradora 3D) los valores serían:

ModoVideo( 640, 480, 32, False );
ModoVideo( 800, 600, 32, False );

Estas son las resoluciones que más se utilizan en el desarrollo de juegos Indie (los juegos Indie son los realizados por desarrolladores independientes pero que suelen tener buena calidad y se venden a bajo precio: de 10 a 20 €).

La variable Pantalla que hemos utilizado en el procedimiento ModoVideo la vamos a declarar también como global (al lado de bSalir):

var
bSalir: Boolean;
Pantalla: PSDL_Surface;

PSDL_Surface es el puntero a un objeto SDL_Surface. Para poder dibujar en SDL se necesita una superficie, la cual puede estar en la memoria de video (lo recomendable) o en la memoria RAM (sólo para guardar sorites y texturas cuando son muchas).

El puntero Pantalla que yo he creado representa directamente la pantalla de video donde vamos a dibujar. Para crear esta pantalla he utilizado la siguiente función de la librería SDL:

function SDL_SetVideoMode(width, height, bpp: Integer; flags: UInt32): PSDL_Surface;

Los parámetros son los siguientes:

Width -> Ancho de la superficie
Height -> Altura de la superficie
Bpp -> Número de bits por píxel
Flags -> Que tipo de superficie vamos a crear

Los tipos de superficie que admite el parámetro flags son los siguientes:

SDL_SWSURFACE: la superficie estará en la memoria RAM
SDL_HWSURFACE: la superficie estará en la memoria de video (recomendado)

También se puede combinar con los siguientes valores:

SDL_ANYFORMAT: admite cualquier resolución o profundidad de color
SDL_HWPALETTE: tendrá una paleta de color exclusiva (sólo se utiliza en modos de 8 bits, esto ya está obsoleto).
SDL_DOUBLEBUF: tiene doble buffer (ya lo explicaré más adelante)
SDL_FULLSCREEN: la superficie estará a pantalla completa
SDL_OPENGL: va a utilizar la libería OpenGL para dibujar polígonos en 3D
SDL_RESIZABLE: el modo de video puede ser redimensionado
SDL_NOFRAME: la ventana no tendrá ni título ni bordes

En la siguiente parte de este artículo crearemos el bucle principal de la aplicación y veremos como cargar las figuras gráficas (sprites) para darles movimiento.

Pruebas realizadas en Delphi 7.

18 abril 2008

Formateando código automáticamente con DelForExp

Esta es otra herramienta que con el tiempo se me ha ido haciendo imprescindible junto con Delphi. DelForExp es un experto (y gratuito) que se puede instalar en cualquier versión de Delphi y que formatea el código fuente automáticamente según nuestras preferencias.

Con esta utilidad ya no será necesario utilizar las combinaciones de teclas CTRL + K + I y CTRL + K + U para identar las líneas de código cada vez que copiamos un trozo de código de un lugar a otro. Otro de los “desastres” que suele solucionar es cuando se trabaja en equipo y cada programador escribe el código a su manera (los begin, end, los espacios, los paréntesis, etc.).

Para descargar esta interesante utilidad hay que irse a la siguiente dirección:

http://www.aew.wur.nl/UK/Delforexp/

La instalación va comprimida en un zip de 564 KB. Al no llevar realmente una instalación como Dios manda hay que descomprimirlo en alguna carpeta donde sepamos que no se va a borrar (ya que contiene librerías DLL que se van a enganchar a Delphi como lapas). Después ejecutamos el archivo SetupEx.exe y comenzará a instalarse en todos los Delphi que tengamos instalados:

Es una instalación algo cutre pero efectiva, ya que si abrimos Delphi y miramos en el menú superior la opción Tools aparecerá una opción nueva llamada Source Formatter:

Al ejecutar esta opción nos aparece esta ventana:

Mucho cuidado de pulsar los primeros tres botones (Current File, All Open Files y Whole Project) antes de establecer el tipo de formateo de texto. Antes de hacer experimentos recomiendo hacer una copia de seguridad de nuestro proyecto, sobre todo para evitar sustos.

ESTABLECIENDO EL FORMATO DE TEXTO

Para seleccionar nuestro propio estilo de programación debemos pulsar el botón Options:

En la primera pestaña (ident) establecemos los espacios que vamos a meter para identar nuestro código. Lo ideal es dejar el campo Number of spaces per ident a 2, que es lo estándar en Delphi. También es conveniente identar los comentarios mediante la opción Ident comments para que la alineación de nuestros comentarios no se quede descolocada respecto al código.

Igual se puede hacer con las directivas del compilador, los bloques de código después del comando else, etc. Lo mejor es dejarlo como se ve en la foto que he mostrado, con los comentarios es suficiente.

En la siguiente pestaña (Spacing) le indicamos cuantos espacios queremos alrededor de los símbolos (paréntesis, comas, etc.):

A mí por ejemplo me gusta que deje un espacio después de abrir un paréntesis y otro antes de cerrarlo:

procedure TForm1.FormCreate( Sender: TObject );

Para ello debemos seleccionar en la línea Left Parenthesis el valor Alter only (sólo después) y en la línea Right Parenthesis el valor Befote Only (sólo antes). También se puede hacer lo mismo con los corchetes.

En la tercera pestaña (Line breaks) le podemos indicar cuando debe hacer un Intro para pasar a la línea siguiente. En el siguiente ejemplo lo he puesto para procedimientos y funciones:

La cuarta pestaña (Capitalization) se utiliza para pasar a mayúsculas la primera letra de las palabras reservadas o comandos:

Por defecto, están ambas opciones desactivadas. Si activamos la opción Reserved words pasará a mayúsculas la primera letra de todas la palabras reservadas (While, Begin, etc.).

La quinta pestaña (Align) se utiliza para alinear los comentarios a una línea que solemos poner detrás de nuestro código para dejarlos alineados:

Por ejemplo, en este bloque de código hay comentarios detrás de cada línea:

procedure TFPrincipal.Inicializar( Sender: TObject; var Done: Boolean );
begin
Done := True; // El proceso se ha realizado correctamente
Application.OnIdle := nil; // Liberamos el uso de la aplicación
Liberar_Memoria; // Liberamos memoria
Bloquear_Ventana( True ); // Impedimos que usuario modifique los datos
end;

Como puede verse los comentarios quedan sin alinear respecto al código. Para corregir ese problema activo la opción Aling simple comments alter code con el valor 40 y así quedaría al formatear el código:

procedure TFPrincipal.Inicializar( Sender: TObject; var Done: Boolean );
begin
Done := True; // El proceso se ha realizado correctamente
Application.OnIdle := nil; // Liberamos el uso de la aplicación
Liberar_Memoria; // Liberamos memoria
Bloquear_Ventana( True ); // Impedimos que usuario modifique los datos
end;

Como puede verse el código queda mucho más elegante y nos evita machacar la barra de espacio o el tabulador.

La sexta pestaña (Misc.) se utiliza para dar formateo a las directivas del compilador. También podemos establecer qué combinación de teclas vamos a utilizar para formatear el código fuente de la unidad que estamos tocando en ese momento (que por defecto es la combinación de teclas CTRL + D):

Y en la última pestaña (Preview) podemos ir viendo en tiempo real cómo quedaría el código fuente según apliquemos unas opciones u otras:

También podemos pulsar el botón Borland Style para que lo deje al estilo clásico de Borland (por si la hemos fastidiado con alguna opción y no sabemos dejarlo como estaba).

Después de haber establecido nuestro estilo de código, ahora podemos aplicarlo pulsando los botones:

Current File -> Formatea el código fuente de la unidad donde estamos situados.

All Open Files -> Formatea el código de todas las pestañas de código que tengamos abiertas en el proyecto.

Whole Proyect -> Formatea el código de todas las unidades del proyecto.

Como he dicho anteriormente, antes de pulsar los botones All Open files o Whole Proyect sería aconsejable hacer copia de seguridad del proyecto (o por lo menos de los archivos .pas).

De todas formas, llevo más de una año utilizando esta herramienta y jamás me ha jodido el código fuente o me ha provocado ningún problema. Es fiable 100%.

Cuando estemos escribiendo código fuente y queramos formatear el código sólo hay que pulsar la combinación de teclas CTRL + D (aparecerá la ventana de DelForExp) y luego la tecla Intro (ya que estará enfocado el botón Current File).

Con herramientas como esta se ahorra mucho tiempo y además se puede compartir el código fuente con otros usuarios sin que hayan quejas sobre los estilos de escritura de cada programador.

Pruebas realizadas en Delphi 7.

11 abril 2008

Cazando errores con EurekaLog

Cuando uno encuentra herramientas como esta, a veces le dan ganas de darse cabezazos contra la pared después de haber sufrido los castigos del debugger de Delphi. EurekaLog es un experto que se acopla perfectamente a cualquier versión de Delphi y que permite capturar excepciones, access violation y pérdidas de memoria todo ello en una sóla herramienta (y además sin modificar para nada las opciones de Delphi o de nuestro proyecto).

Funciona incluso con las últimas versiones de Delphi (Delphi for Win32 y RadStudio 2007). Para versiones muy antiguas de Delphi, como por ejemplo Delphi 5 a veces requiere tener instalado algún Update que otro.

Aunque sea un programa comercial podemos bajar la demo de su página oficial:

http://www.eurekalog.com/downloads.php

La última versión a fecha de este artículo es la 6.0.12, teniendo el archivo de instalación un tamaño de 12.4 MB. Antes de ejecutar la instalación es conveniente tener todos los Delphi cerrados. La instalación no tiene muchas complicaciones:

Vamos pulsado el botón Next hasta que finalice la instalación. En uno de estos pasos reconoce las distintas versiones de Delphi que tenemos instaladas, permitiendo seleccionar las que nos interesen:

Cuando termina la instalación nos da la posibilidad de visualizar un videotutorial online sobre cómo funciona EurekaLog, lo cual facilita mucho las cosas.

Ahora arrancamos Delphi y abrimos un proyecto cualquiera para probarlo. Para activar EurekaLog en nuestro proyecto hay que ejecutar las opciones Proyect -> Eureka Options:

En la ventana que aparece debemos activar la opción Activate EurekaLog. Esto hará que intercepte los errores en tiempo de ejecución sin necesidad de tener activada la opción Integrated Debugging del propio Delphi. Si también queremos interceptar los errores de pérdida de memoria debemos irnos a la sección llamada Avanced Options y seleccionar la opción Catch Memory Leaks:

Después pulsamos el botón Ok y ya tenemos EurekaLog listo para cazar bichos. Vamos a verlo con unos ejemplos.

CAPTURANDO ACCESS VIOLATIONS

Uno de los errores que más solemos cometer es el utilizar objetos que no hemos creado. Por ejemplo, voy a crear un formulario con un botón en el cual si hago clic intentaré añadir un elemento a un objeto StringList sin haberlo creado anteriormente:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S.Add( 'añadiendo elemento' );
end;

Veamos como se comporta EurekaLog en este caso. Al hacer clic sobre el botón nos aparece esta ventana en vez de la típica de Access Violation:

Si pulsamos la opción click here nos mostrará esta otra ventana:

En la primera pestaña (General) nos informa de la excepción que ha provocado la aplicación así como todas las características del equipo donde se está ejecutando. Esta información es muy interesante para averiguar en que máquina esta corriendo nuestra aplicación.

En la segunda pestaña (Call Stack) podemos ver en que línea de código se ha producido el error:

Y ahora viene lo mejor de todo. Si hacemos doble clic sobre la línea azul saltará directamente a Delphi y se irá a la línea de código donde se ha producido el error. Vosotros os preguntareis ahora, ¿y que tiene de especial si el depurador de Delphi ya lo hace? Pues porque EurekaLog caza errores donde Delphi no llega. ¿Cuántos access violations os ha llevado Delphi al depurador en ensamblador? ¿Cuántas veces os ha explotado Delphi sin tener ni idea donde se ha producido el error? A mi por lo menos se me ha quedado el puntero del depurador en Application.Run y se ha quedado como Dios.

Con EurekaLog he llegado a cazar el 95% de errores de Delphi donde el debugger no llegaba. Además me sitúa el cursor exactamente en la línea de código donde se ha producido el error.

En la tercera pestaña (Modules) podemos ver nuestro ejecutable y cuantas librerías DLL hay cargadas en memoria:

En la cuarta pestaña (Processes) se ve los ejecutables que estaban corriendo en el sistema en el momento del error:

En la quinta pestaña (Assembler) nos muestra el punto de ejecución en ensamblador donde se ha quedado el procesador:

Y en la última pestaña (CPU) tenemos un volcado de los registros del procesador, la pila y la memoria del segmento actual:

Si pulsamos el botón Ok, la aplicación seguirá su curso y devolverá el control a Delphi.

AVERIGUANDO DONDE SE PRODUCEN LAS PERDIDAS DE MEMORIA

Este es otro de los agujeros negros que sufrimos los programadores de Delphi, las pérdidas de memoria (Memory Leaks). Yo hasta ahora había utilizado la unidad MemCheck.pas para cazar los errores, pero es muy incómoda su instalación y seguimiento. En este caso EurekaLog también es insuperable, cazando las perdidas de memoria al vuelo. Veamos otro ejemplo.

Voy a crear un StringList y a salirme del programa sin hacer Free del objeto:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S := TStringList.Create;
S.Add( 'añadiendo elemento' );
end;

Al finalizar el programa saltará la siguiente ventana de EurekaLog:

Cuando pulsemos sobre la opción click here y nos vayamos a la pestaña Call Stack nos dirá exactamente que línea ha creado algo en memoria y no lo ha liberado:

Haciendo doble clic sobre la misma saltará a la línea de código responsable del problema:

S := TStringList.Create;

Esto deja a Delphi 2007 en ridículo en cuestión de cazar pérdidas de memoria, ya que este último IDE de CodeGear no dice ni en que unidad ni en que línea se ha producido la perdida de memoria, sólo nos dice que objetos han quedado sin liberar. Yo no se vosotros, pero para mi, eso y nada es lo mismo.

ENVIANDO LOS ERRORES POR CORREO ELECTRONICO

¿Cuántas veces os ha ocurrido que a un cliente vuestro le salta un Access Violation y a vosotros os funciona bien con la misma base de datos? Eso a mi me ha pasado un día si y otro también. Pues en ese caso enviamos el ejecutable de nuestra aplicación a nuestros clientes con EurekaLog activado y compilado, y en momento que se produzca la explosión a ellos también les aparecerá la misma ventana de EurekaLog con el error, con lo cual podrán decirnos en que unidad y línea de código se ha producido el error.

Pero no sólo eso, si no queremos complicarnos la vida, EurekaLog permite enviar por correo electrónico toda la información perteneciente al error que se ha producido. Para ello volvemos a la ventana de opciones de EurekaLog y nos situamos en la primera sección (Email & WebSend):

Seleccionamos la opción SMTP Client dentro del campo Send Mode y rellenamos el resto de campos con la cuenta de correo que vamos a utilizar para enviar los errores. Esa cuenta se puede utilizar para enviarse errores así misma, gastando pocos recursos de nuestro servidor SMTP.

Cuando a nuestro cliente le ocurra un error con nuestra aplicación le aparecerá esta ventana:

Cuando pulse el botón Send Error Report comenzará a enviar el error por correo electrónico con los datos de la cuenta que le hemos dicho (si os fijáis en la imagen se puede enviar a otro correo opcional):

Junto con el mensaje de correo llevará adjunto un archivo comprimido con zip llamado BugReport.zip. Este a su vez tiene dos archivos dentro:

Screenshot.png que contiene una imagen capturada de todo el escritorio de Windows cuando se produjo el error.

ProbandoEureka.elf (Igual que el nombre de nuestro ejecutable pero con extensión .elf) que al doble clic sobre el mismo y nos abrirá un visor con el error:

Es decir, nos manda la pantalla capturada (para saber que estaba haciendo el usuario antes de saltar el error) y el archivo de depuración con la línea de código que provocó el error.

Aunque EurekaLog sea una herramienta comercial (cuesta 99 €) sin duda merece la pena comprarla si eso va a mejorar la productividad y ahorrarnos muchos dolores de cabeza. En resumen, otra herramienta imprescindible para Delphi.

Pruebas realizadas en Delphi 7.

04 abril 2008

El componente ValueListEditor

Hace bastante tiempo mostré como guardar valores dobles (nombre=valor) dentro de una lista (StringList), concrétamente fue en este artículo:

El Objeto StringList (I)

Lo que vamos a ver ahora es cómo almacenar esos mismos valores pero utilizando el componente visual de la clase TValueListEditor, el cual está alojado en la pestaña Additional:

Al insertar este componente en un formulario se muestra de este modo:


La columna Key contendrá el nombre de los valores que deseamos insertar y la columna Value el valor asociado al nombre. Supongamos que quiero que el usuario pueda modificar los datos principales de un cliente. Lo primero que vamos a hacer el cambiar el título de las columnas a través de la propiedad TitleCaptions. Vamos a sustituir Key y Value por Campo y Valor, de manera que así quedaría:

Lo siguiente que toca es introducir los campos del cliente. Para ello utilizamos la propiedad Strings del componente e introducimos los nombres de los campos en la columna Key:

Para crear más de una línea tenemos que pulsar en el teclado la tecla del cursor hacia abajo (la verdad es que este editor de valores es algo chapucero). Una vez introducidos los valores y pulsado el botón Ok dejaría este resultado:

Al ejecutar el programa se puede apreciar que este componente funciona de manera similar al inspector de objetos de Delphi (Object inspector). La primera columna se halla en modo lectura dejando que el usuario sólo pueda modificar los valores. Pero tenemos que ir un paso más allá: hay que conseguir que dependiendo del campo, el usuario sólo pueda meter valores de cierto tipo.

ESTABLECIENDO MASCARAS PARA CADA VALOR

El problema radica en que no se puede establecer el tipo de valor que puede introducirse en un campo en tiempo de diseño utilizando el inspector de objetos. Hay que hacerlo en el evento OnCreate del formulario mediante código. Por ejemplo, vamos a hacer que en el campo ID sólo se puedan introducir valores numéricos:

procedure TFPrincipal.FormCreate( Sender: TObject );
var
PropNumerica: TItemProp;
begin
PropNumerica := TItemProp.Create( ValueListEditor );
PropNumerica.EditMask := '###0';
ValueListEditor.ItemProps[0] := PropNumerica;
end;

Lo que hemos hecho es crear el objeto PropNumerica de la clase TItemProp y posteriormente hemos establecido la regla de edición asignándola al primer elemento de la lista (el campo ID).

También podemos crear otro tipo de propiedad que permita escoger el valor sólo entre una lista predeterminada. Por ejemplo, voy a hacer que el campo ESTADO sólo pueda contener los estados PENDIENTE o PAGADO:

procedure TFPrincipal.FormCreate( Sender: TObject );
var
PropNumerica, PropEstado: TItemProp;
begin
PropNumerica := TItemProp.Create( ValueListEditor );
PropNumerica.EditMask := '###0';
ValueListEditor.ItemProps[0] := PropNumerica;

PropEstado := TItemProp.Create( ValueListEditor );
PropEstado.PickList.Add( 'PENDIENTE' );
PropEstado.PickList.Add( 'PAGADO' );
ValueListEditor.ItemProps[5] := PropEstado;
end;

Al ejecutar el programa el campo ESTADO quedaría del siguiente modo:

También podemos modificar la forma en cómo se guardan los valores dentro de la rejilla a través de la propiedad KeyOptions en el inspector de objetos:

Aunque todas estas opciones hacen que este componente sea bastante útil, se echan en falta algunas características tales como valores para selección de color, máscaras para valores numéricos en coma flotante y la selección de fuentes de texto.

Pruebas realizadas en Delphi 7.

28 marzo 2008

El objeto Application

Todas las aplicaciones Win32 que se crean con Delphi tienen definido un objeto global accesible desde cualquier formulario o unidad llamado Application, de la clase TApplication, el cual esta definido dentro de la unidad Forms. Este objeto se encarga de encapsular todo lo referente a nuestra aplicación, evitando el tener que procesar el envío y recepción de mensajes como ocurre cuando se programa en C/C++ mediante las funciones PeekMessage y SendMessage.

De hecho, si miramos la unidad DPR de cualquier proyecto de Delphi podemos ver como el objeto Application se incializa, después llama al formulario principal y posteriormente comienza la ejecución de la aplicación mediante el método Run:

program Project1;

uses
Forms,
Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

Este código fuente cambia dependiendo de las preferencias que tengamos a través del menú Project -> Options (pestaña Forms). Todos los formularios que pongamos en la sección Auto-create forms serán los que se creen después de Application.Initialize.

Windows funciona internamente como un buzón donde van llegando mensajes y los va procesando en el orden de llegada, consiguiendo así el efecto de multitarea, aunque internamente sólo puede procesar un mensaje a la vez (menos en los últimos sistemas con procesador de dos o más núcleos).

El objeto Application nos abstrae de toda esta complejidad interna y se encarga de controlar todos los eventos que se producen en nuestra aplicación. Si queremos modificar el comportamiento de nuestra aplicación para ciertos eventos, tenemos en la pestaña Additional el componente ApplicationEvents que podemos colocar en el formulario principal de nuestra aplicación, controlando así eventos tan comunes como OnActivate, OnRestore, OnMessage, etc.

Por ejemplo, se puede utilizar el evento OnIdle para que nuestro programa realice cálculos complejos en segundo plano cuando el procesador esté desocupado, aprovechando al máximo el rendimiento de nuestro PC. También se puede utilizar el evento OnMessage para interceptar mensajes de Windows que incluso Delphi no tiene definidos en el objeto Application.

MOSTRANDO MENSAJES

Esta es una de las funciones que más se suelen utilizar del objeto Application:

function MessageBox( const Text, Caption: PChar; Flags: Longint = MB_OK ): Integer;

Esta función muestra al usuario un mensaje dentro de un cuadro de dialogo. Para mostrar un mensaje informativo sería así:

Application.MessageBox( 'Mensaje informativo.', 'Atención',
MB_ICONINFORMATION );

se vería así:


Otro mensaje sería para informar al usuario que se ha interrumpido un proceso:

Application.MessageBox( 'No ha rellenado el nombre del proveedor.',
'Acceso denegado', MB_ICONSTOP );

este sería el resultado:


También se puede hacer una pregunta al usuario por si desea realizar algo:

if Application.MessageBox( '¿Desea continuar?', 'Guardando factura',
MB_ICONQUESTION OR MB_YESNO ) = ID_YES then
....

este cuadro de diálogo tendría dos botones:

El parámetro flags de esta función permite que aparezcan los siguientes botones:

MB_ABORTRETRYIGNORE Anular, Reintentar y Omitir.
MB_OK Aceptar (por defecto).
MB_OKCANCEL Aceptar y Cancelar.
MB_RETRYCANCEL Reintentar y Cancelar.
MB_YESNO Si y No.
MB_YESNOCANCEL Si, No y Cancelar.

Entre los posibles mensajes que devuelve esta función (según lo que hagamos con flags) tenemos:

Constante Valor numérico Resultado
--------- -------------- ---------
IDOK 1 El usuario ha pulsado Aceptar.
IDCANCEL 2 El usuario ha pulsado Cancelar.
IDABORT 3 El usuario ha pulsado Abortar.
IDRETRY 4 El usuario ha pulsado Reintentar.
IDIGNORE 5 El usuario ha pulsado Ignorar.
IDYES 6 El usuario ha pulsado Si.
IDNO 7 El usuario ha pulsado No.

CONTROLANDO EL ESTADO DE LA APLICACIÓN

Tenemos los siguientes procedimientos disponibles para controlar el estado global de la aplicación:

procedure Minimize;

Este procedimiento minimiza la aplicación en la barra de tareas. Es importante tener bien claro cual es el formulario principal de la aplicación, ya que si minimizamos otra ventana provocará un efecto no deseado: se minimiza en la esquina inferior izquierda del escritorio. Para evitar esto siempre tenemos que procurar que el formulario que se pueda minimizar sea siempre el formulario principal. Si no fuera así, entonces recomiendo que toda la aplicación sea de tipo MDI.

procedure Restore;

Vuelve a mostrar la ventana principal de la aplicación si ha sido minimizada por el usuario o por el propio programa con el método Minimize.

procedure BringToFront;

Este procedimiento enfoca de nuevo nuestra aplicación superponiendo la ventana principal de nuestra aplicación a otras aplicaciones que se estén visualizando en este momento en Windows. Puede ser interesante cuando tenemos que informar al usuario de algún evento que se ha producido en nuestra aplicación (como hacen los antivirus).

procedure CreateForm( FormClass: TFormClass; var Reference );

Este es otro de los procedimientos más utilizados por el objeto Application. Crea el formulario que le pasemos como parámetro. El primer parámetro es la variable de la instancia que se va a crear y el segundo es la clase de la variable. Por ejemplo:

Application.CreateForm( FCliente, TFCliente );

procedure Terminate;

Hay que estar muy seguro para llamar a este procedimiento, ya que cierra toda la aplicación por la fuerza. Si tenemos algún formulario que haya instanciado algunos objetos que no ha liberado de memoria se podría quedar memoria desperdiciada en todo Windows.

procedure ProcessMessages;

Es importante llamar a este procedimiento cuando estamos haciendo un proceso largo que gasta bastante procesador, ya que si no lo hacemos así el usuario tiene la sensación de que la aplicación se ha colgado. Hay que llamar a este procedimiento en cada iteración del bucle.

LEYENDO DATOS DE NUESTRA APLICACION

El objeto Application también es muy útil para obtener información. Veamos que funciones pueden utilizarse para esto:

property Active: Boolean;

Esta propiedad nos dice si la aplicación esta activa y enfocada.

property ExeName: string;

Nos devuelve la ruta y nombre completo de donde se está ejecutando nuestra aplicación. Si nos interesa saber sólo la ruta se podría hacer esto:

ExtractFilePath( Application.ExeName );

property MainForm: TForm;

Nos devuelve la referencia al formulario principal de la aplicación. Si el formulario principal cambiara, no haría falta modificar el código de nuevo.

property ShowMainForm: Boolean;

Si desactivamos esta propiedad, cuando arranque el programa por primera vez no mostrará la ventana principal. Esto puede ser útil para mostrar la típica ventana de presentación antes de mostrar la pantalla principal. También puede utilizarse si vamos a crear un control de usuarios con clave de acceso y necesitamos que no muestre la ventana principal antes de que se haya validado el usuario.

property Title: string;

Modificando esta propiedad podemos cambiar en tiempo de ejecución el título de nuestra aplicación según su estado (se ve sobre todo cuando está minimizada en la barra de tareas).

property Handle: HWND;

Un handle es un identificador único que Windows da a todas las aplicaciones y ventanas que crea. Este handle (que en realidad es un valor numérico) es muy utilizado por funciones de la API de Windows. Por ello, esta propiedad es muy interesante para leer el handle de nuestra aplicación (cada vez que se ejecuta una aplicación Windows le da un handle distinto).

Aunque el objeto Application tiene más funciones y propiedades creo que hemos cubierto aquí las más importantes.


Pruebas realizadas en Delphi 7.

21 marzo 2008

El componente ActionMaganer

Uno de los problemas que suele presentarse a un programador de aplicaciones es cuando tiene que rediseñar de nuevo toda la interfaz del programa. Si tenemos el código vinculado directamente a botones, menús genéricos o menús contextuales nos toca de nuevo trasladar el código que había en los eventos de los componentes viejos a los componentes nuevos (haciendo copy-paste a base de bien).

Supongamos hemos programado un pequeño bloc de notas:




Para cada opción de menú tenemos definido su código correspondiente:

procedure TForm1.NuevoClick( Sender: TObject );
begin
Memo.Lines.Clear;
end;

procedure TForm1.AbrirClick( Sender: TObject );
begin
if OpenDialog.Execute then
Memo.Lines.LoadFromFile( OpenDialog.FileName );
end;

procedure TForm1.GuardarComoClick( Sender: TObject );
begin
if SaveDialog.Execute then
Memo.Lines.SaveToFile( SaveDialog.FileName );
end;

.....

Supongamos que quiero eliminar el componente de la clase TMainMenu y crear el su lugar un componente nuevo. El código que hemos visto anteriormente quedaría abandonado y habría que cortar el código de cada opción para llevárselo al nuevo componente.

Para evitar estos problemas tenemos el componente ActionMaganer que esta situado dentro de la paleta de componentes Aditional. Este componente esta diseñado específicamente para contener acciones de menús o botones pero sin estar vinculados a ningún componentes visual en concreto.

De manera que vamos a centralizar todas las acciones de nuestro formulario dentro de este componente para desvincularlo de la interfaz. Sería algo así como una programación visual dividida en dos capas: capa lógica (la cual contiene el código fuente relacionado con las acciones del usuario) y la capa visual (los componentes con los que va a interactuar el usuario).

Veamos cuales son los pasos para realizar esta tarea:

1. Insertamos un componente ActionManager en nuestro formulario:

2. Hacemos doble clic sobre dicho componente y nos aparecerá esta ventana:

Es esta ventana vamos a definir las acciones genéricas del formulario.

3. Pulsamos el botón New Action:
4. Nos aparecera en la lista Action1. Hacemos doble clic sobre el mismo y guardamos el código asociado al menú (Archivo -> Nuevo):

procedure TForm1.Action1Execute( Sender: TObject );
begin
Memo.Lines.Clear;
end;

Igualmente vamos a crear dos acciones más para guardar los eventos Archivo -> Abrir y Archivo -> Guardar como...

procedure TForm1.Action2Execute( Sender: TObject );
begin
if OpenDialog.Execute then
Memo.Lines.LoadFromFile( OpenDialog.FileName );
end;

procedure TForm1.Action3Execute( Sender: TObject );
begin
if SaveDialog.Execute then
Memo.Lines.SaveToFile( SaveDialog.FileName );
end;

5. Ahora vamos a añadir a nuestro formulario una barra de menús de la clase TActionMainMenuBar situada en la pestaña de componentes Additional:


Esto creará una barra vacía en la parte superior de nuestro formulario:


Para añadir opciones a esta barra vamos a hacer lo siguiente:

6. Hacemos doble clic sobre el componente ActionMaganer.

7. Arrastramos el objeto de la lista Action1 hacia la barra vacía que hemos insertado en el formulario:


8. Copiamos también las acciones 2 y 3.

9. Si queremos cambiar el nombre de las acciones hacemos doble clic en el objeto ActionMaganer, pulsamos Action1 y en el inspector de objetos modificamos la propiedad Caption y ponemos Nuevo. Igual hacemos para las acciones 2 y 3 que vamos a llamarlas Abrir y Guardar como. Como podemos apreciar no sólo cambian los nombres de las acciones en la lista del objeto ActionManager sino que también las situadas en la barra superior del formulario:


10. Por último y no menos importante, sería conveniente cambiar también la propiedad Name de cada una de las acciones, de modo que en el código fuente los procedimientos pasen a llamarse:

procedure TForm1.NuevoExecute( Sender: TObject );
procedure TForm1.AbrirExecute( Sender: TObject );
procedure TForm1.GuardarComoExecute( Sender: TObject );

en vez de:

procedure TForm1.Action1Execute( Sender: TObject );
procedure TForm1.Action2Execute( Sender: TObject );
procedure TForm1.Action3Execute( Sender: TObject );

De este modo tenemos separadas las acciones internas del formulario y su parte visual. Si eliminamos por error la barra superior del formulario seguimos teniendo todos nuestros eventos vinculados al objeto ActionMaganer. Así nuestro programa queda más flexible y elegante.

La mayor parte del código fuente de este artículo puede sustituirse mediante acciones predefinidas que lleva el componente ActionMaganer. Cuando creamos una acción, en vez de pulsar botón New Action tenemos que pulsar la flecha hacia abajo que hay a su derecha y seleccionar New Standar Action. En la ventana que aparece tenemos todas las típicas acciones que suelen realizarse en un bloc de notas, tales como copiar, cortar, abrir archivos, etc. También incorpora algunas funciones para recorrer los registros de las bases de datos, acelerando de este modo la implementación del programa.

Otra ventaja de utilizar este método es que mientras un programador puede dedicarse a la parte visual del formulario (creando menús, botones, iconos, etc), otro programador puede dedicarse a crear el código asociado al formulario distribuyendo de esa manera el trabajo en equipo.

Pruebas realizadas en Delphi 7.

Publicidad