Download Informática Industrial. 3º ITIET. STR 1 . Windows CE: Introducción a

Document related concepts
Transcript
Informática Industrial. 3º ITIET.
STR 1 . Windows CE: Introducción a la interfase con el
usuario.
En este documento tratamos los fundamentos de la interfase de usuario de Windows CE, y algunas
operaciones para manejar ventanas, controles y diálogos. Es importante señalar que estas funciones pueden
verse como un subconjunto de las funciones de Windows 2000. Para limitar el tamaño del sistema operativo,
en Windows CE sólo se han incorporado las funciones más básicas y necesarias. Por tanto, aquellas personas
que han programado con la interfase gráfica de Windows 2000 apenas encontrarán diferencias al hacerlo con la
de Windows CE. De hecho, la mayor parte del código que aparece aquí puede compilarse sin cambios para
cualquiera de los dos sistemas operativos.
1. Programación dirigida por eventos en Windows CE. El módulo GWE....................... 3
2. Algunas operaciones previas. El bucle de mensajes. ................................................ 4
2.1. Pintar texto. .........................................................................................................................5
2.2. Más información..................................................................................................................6
3. Entrada de datos desde el usuario. Controles............................................................ 7
3.1. Comunicación entre una ventana y sus controles. .............................................................8
3.2. Más información..................................................................................................................9
4. Diálogos...................................................................................................................... 9
4.1. El editor de diálogos y recursos de Embedded Visual C++. ............................................10
4.2. Crear un diálogo. ..............................................................................................................10
4.3. Los recursos en el proceso de compilación......................................................................13
4.4. Crear y usar un diálogo.....................................................................................................13
4.5. Más información................................................................................................................14
5. Listados. ...................................................................................................................14
Listado 1. TextoSimple: muestra una ventana con texto. .......................................... 15
Listado 2. HacerBeep: una ventana con control........................................................ 17
Listado 3. Pitador: una ventana con diálogo.............................................................. 18
Las configuraciones Pocket PC de Windows CE suponen que el computador dispone de una pantalla táctil
para mostrar información al usuario. También sirve, junto con algunos pulsadores, para recoger información:
Fig. 1.
1
La pantalla táctil es el equivalente a la pantalla y el ratón en un ordenador convencional.
Muchos sistemas operativos usan el concepto de “consola” para interactuar con el usuario. Una consola no
es más que una construcción1 del sistema operativo que permite entrada y salida de datos por parte del usuario.
A nivel hardware suele estar formada por un teclado y una pantalla alfanumérica2. Las aplicaciones usan la
consola en lugar de acceder directamente al hardware que la implementa.
Otros sistemas operativos, como Windows CE3, pueden usar un tipo de construcción diferente para manejar
la interacción con el usuario: la ventana. Las ventanas facilitan dos características fundamentales:
•
Interfase con el usuario en entornos multitarea, ya que varias aplicaciones pueden mostrar información
simultáneamente en la misma pantalla usando ventanas distintas.
•
Se puede mostrar y recibir información con formatos complejos. Mucho más complejos que en una
consola.
El que una aplicación use ventanas no sólo implica una interfase de usuario con un aspecto diferente al que
tendría si usara una consola; también la estructura interna de la aplicación es diferente.
La interacción con el usuario en una consola suele ser simple. Cuando se necesitan datos se muestra
información en la pantalla y se espera a que el usuario responda de alguna forma:
Fig. 2.
A esta forma de programación se le conoce como programación procedural.
Las ventanas permiten interfases más complejos. En el ejemplo de la Fig. 3, el usuario puede introducir
datos mediante distintos elementos o áreas dentro de la ventana. Aquí también se puede usar programación
procedural, aunque el código resultante sería muy complejo: hay que dibujar todos los elementos de la ventana,
esperar cualquiera de las posibles respuestas del usuario e interpretarlas.
Una alternativa es la programación dirigida por eventos4. Consiste, básicamente, en enumerar todas las
posibles "acciones" que tengan interés para la aplicación y escribir el código que reacciona ante ellas. En el
caso de la ventana anterior, las acciones de interés serían del tipo "se ha pulsado el botón Predeterminar", "se
quiere cerrar la ventana", etc., que dan como resultado una serie de eventos que el sistema operativo "envía" a
la aplicación. En este sentido, un evento no es más que un aviso que el sistema operativo hace a la aplicación,
indicándole que ha ocurrido algo que puede ser de su interés.
1
Que incluye, normalmente, una serie de estructura de datos dentro del kernel, un conjunto de funciones API (o
equivalentes) y un conjunto de drivers.
2
Hay otras alternativas a la hora de implementar en hardware una consola: un terminal conectado por línea serie o por
red LAN, una ventana dentro de una pantalla gráfica, etc.
3
Algunas configuraciones de Windows CE pueden incluir módulos para gestionar una o más consolas.
4
Event driven programming.
2
Fig. 3.
Una característica importante de los eventos es que, en principio, pueden ocurrir en cualquier orden, por lo
que la aplicación debe estar preparada para procesarlos en el orden en que los recibe. En la ventana anterior, el
usuario puede pulsar el botón Predeterminar, Aceptar o Cancelar, pasar a otro menú, cerrar la ventana, etc.
El sistema operativo da una serie de utilidades que permiten definir los eventos a los que responde la
aplicación y asociar código a esos eventos.
1. Programación dirigida por eventos en Windows CE. El módulo
GWE.
Hay que tener en cuenta que la programación procedural es la forma natural de programar un procesador:
su hardware está diseñado para ejecutar programa en forma de secuencia de instrucciones. Lo único que
recuerda a la programación orientada a eventos es la forma en que se procesan las interrupciones hardware:
cuando se activa la indicación de interrupción el procesador responde ejecutando el código que trata la
interrupción.
Implementar en hardware la programación orientada a eventos implicaría que se generara una interrupción
distinta cuando el usuario pulsa los distintos botones de la ventana anterior, por ejemplo. Algo inviable en la
actualidad, por lo que se necesitan una serie de estructuras de datos y funciones que soporten por software la
programación orientada a eventos.
Esto quiere decir que los programas orientados a eventos son, en realidad, programas procedurales que
utilizan servicios del sistema operativo para "simular" un comportamiento orientado a eventos.
En Windows CE el software necesario para la programación orientada a eventos está implementado en el
módulo GWE (Graphic, Windowing, Events), que integra, además, software para manejar el sistema gráfico y
las ventanas.
Vamos a usar una aplicación muy sencilla para ilustrar cómo se utiliza el GWE de Windows CE. Nuestra
aplicación imprime un mensaje en la pantalla y termina cuando pulsamos el botón que hay en la parte superior
derecha de la ventana:
Fig. 4.
3
Cuando se pulsa el botón , el sistema operativo crea un mensaje5 y lo envía a la aplicación que ha creado
la ventana. El término "envía" no es más que una abstracción: significa que el sistema operativo llama a una
función determinada de la aplicación para que procese el mensaje. Cada ventana6 puede tener su propia
función o proceso de ventana, con el siguiente formato:
BOOL CALLBACK ProcesoVentana( //CALLBACK es una función ejecutada por
// el sistema operativo.
HWND HandleVentana,
// Ventana a la que se envía el mensaje
UINT Mensaje,
// Identificador del mensaje
WPARAM wParam, // Primer parámetro. Depende del tipo de mensaje
LPARAM lParam) // Segundo. También depende del tipo de mensaje
El sistema operativo traduce la estructura de datos que forma el mensaje en parámetros y hace la llamada a
la función de ventana:
Una llamada por mensaje
Cola de mensajes de la aplicación (una por trhead)
Mensaje
...
Mensaje
Mensaje
Mensaje
Sistema operativo
Aplicación
BOOL CALLBACK ProcesoVentana (HWND HandleVentana, UINT Mensaje, WPARAM wParam, LPARAM lParam) {
...
}
Fig. 5.
El segundo parámetro identifica el tipo de mensaje. Si se pulsa el botón , la aplicación recibe un mensaje
con Mensaje= WM_CLOSE7. Para procesarlo, ProcesoVentana debe analizar el valor de Mensaje y
ejecutar el código correspondiente (ver el Listado 1, al final de este documento). De forma general, una
función de proceso de ventana se implementa con, al menos, una estructura switch-case para reconoce los
mensajes enviados por el sistema operativo y actúar en consecuencia. En nuestro ejemplo sólo hay dos
mensajes a los que responder;
•
WM_CLOSE: la aplicación responde indicando al sistema operativo que tiene que dejar de procesar
mensajes.
•
WM_PAINT. El sistema operativo indica que puede ser necesario "refrescar" el contenido de la
ventana. La aplicación responde ejecutando la función que pinta el texto en la ventana.
2. Algunas operaciones previas. El bucle de mensajes.
Antes de que una aplicación pueda recibir mensajes debe realizar algunas operaciones previas. Los
mensajes son "enviados" a una función asociada a una ventana, por tanto, primero hay que crear al menos una,
a la que denominaremos ventana principal de la aplicación. El proceso tiene dos pasos:
5
Que no es más que una simple estructura de datos.
A primera vista, puede parecer que una ventana es simplemente un elemento grafico. Sin embargo, su principal
función no es gráfica: define la función que procesa los eventos asociados a una ventana.
7
Es decir, el sistema operativo hace una llamada a ProcesoVentana con Mensaje igual a 0x10. El valor del símbolo
WM_CLOSE está definido en uno de los ficheros asociados a windows.h.
6
4
•
Crear una clase8 para nuestra ventana principal usando la función RegisterClass (ver
IniciaAplicacion en el Listado 1). Una clase es una estructura de datos interna del sistema
operativo donde almacena algunas características comunes a las ventanas definidas con la misma clase:
todas tendrán el mismo color de fondo, el mismo procedimiento de ventana, etc.
•
Crear una ventana con el tipo que acabamos de registrar usando la función CreateWindow (ver
WinMain en el Listado 1).
Al crear la primera ventana el sistema operativo también crea una cola de mensajes para la aplicación: una
estructura donde se almacenan, de forma ordenada, los mensajes enviados a su ventana.
La aplicación puede crear nuevas ventanas, pero todas usarán la misma cola.
El siguiente paso es realizar el bucle de mensajes. Un mensaje que llega a la cola no es “enviado” a la
función de ventana hasta que explícitamente le indicamos al sistema operativo que lo saque de la cola y se lo
envíe. El bucle de mensajes se suele hacer con el siguiente código:
while (GetMessage(&Mensaje, NULL, 0, 0)){
TranslateMessage(&Mensaje);
DispatchMessage(&Mensaje);
}
•
GetMessage coge el primer mensaje de la cola. Si la cola está vacía, el programa se suspende hasta
que llegue un mensaje.
•
TranslateMessage siempre se debe colocar para que el sistema operativo preprocese los mensajes
del teclado (para más detalles, mirar la ayuda del compilador).
•
DispatchMessage hace que el sistema operativo envíe el mensaje a la función de ventana
correspondiente.
El bucle continúa hasta que GetMessage devuelve 0 al coger un mensaje WM_QUIT.
2.1. Pintar texto.
Una de las operaciones gráficas más simples es pintar texto en una ventana.
Para ver cómo se hace usaremos función PintaTextoCentro, del programa del Listado 3, que pinta
una cadena de texto en el centro de su ventana principal (Fig. 4).
Lo primero que hay que considerar no es cómo, sino cuándo se pinta. Qué eventos hacen necesario pintar el
texto y que mensaje o mensajes generan esos eventos. Los eventos pueden ser muy diversos:
•
La ventana que contiene el texto se crea por primera vez.
•
Se tiene que mostrar el valor de algún dato, que ha cambiado desde la última vez que se mostró.
•
Otra ventana que la tapaba, en parte o completamente, se ha cerrado.
•
Etc.
Todos ellos hacen que el sistema operativo envié un mensaje WM_PAINT9 a las ventanas que puedan verse
involucradas. Por tanto, el código que pinta el texto debe ser la respuesta del proceso de ventana a un mensaje
WM_PAINT.
La función gráfica que usamos para pintar texto es DrawText. El significado de casi todos sus parámetros
es sencillo: la ventana donde se pintará el texto, el recuadro que lo contendrá, el número máximo de caracteres
8
El término clase de ventana (window class) no tiene nada que ver con una clase en la terminología de programación
orientado a objeto. Aquí tiene, más bien, el sentido de "tipo de ventana". Es un termino anterior al auge de la programación
orientada
9
Es importante señalar que un evento puede generar varios mensajes de distinto tipo. La creación de una ventana, por
ejemplo, genera mensajes WM_CREATE y WM_PAINT.
5
y sus características de formato. El primer parámetro, sin embargo, es menos familiar: el dispositivo de
contexto (device context).
El dispositivo de contexto es una estructura de datos del sistema operativo que contiene las características
por defecto de las operaciones gráficas: color del fondo, tipo, tamaño y color de la fuente, características
adicionales de la fuente (subrayado, negrita, etc.). La existencia de esta estructura hace que se tenga que seguir
una especie de "protocolo" para dibujar sobre una ventana:
•
Se debe pedir el dispositivo de contexto al sistema operativo. En nuestro ejemplo usamos la función
BeginPaint.
•
Se realizan las operaciones gráficas necesarias. Antes de cada operación podemos modificar las
características gráficas que queramos: podemos, por ejemplo, cambiar el tamaño de la fuente antes de
pintar un texto.
•
Se restauran las características que hayamos cambiado.
•
Se "libera" el dispositivo de contexto. En nuestro ejemplo usamos la función EndPaint.
El protocolo BeginPaint - EndPaint es necesario para evitar que modifiquemos alguna
característica gráfica en mitad de una operación gráfica de otra aplicación10. Si una aplicación ejecuta
BeginPaint antes de que otra libere el dispositivo de contexto la primera se queda suspendida hasta que la
segunda ejecute EndPaint.
2.2. Más información.
Como se dijo al principio, la programación con ventanas en casi idéntica en Windows CE y 2000, e incluso
en Windows 95/98, por lo que podemos usar cualquier libro que trate el tema en estos sistemas operativos.
Uno de los mejores es:
•
Programacion en Windows 95. Charles Petzold. 1996.
En la ayuda del Embedded Visual C++ hay varios temas interesantes, todos incluidos en la sección
Microsoft Windows CEÆUser Interface Services. Dentro de esta sección, las más interesantes son:
•
...ÆWorking with Windows and MessagesÆWindows Overview es una introducción a los elementos
gráficos que componen una ventana: barras de títulos, menús, …Tambien introduce las funciones para
manejar mensajes.
•
...Æ Creating a Window habla sobre las opciones de CreateWindow y CreateWindowEx.
La información de referencia de las funciones para el manejo de ventanas esta en Microsoft Windows
CEÆAPI ReferenceÆWindows User Interface ServicesÆWindow, aquí hay dos secciones interesantes:
•
...ÆFunctions contiene las funciones para manejar ventanas (crear, cerrar, mover, etc.)
•
...ÆMessages contiene los mensajes directamente relacionados con el manejo de ventanas.
10
Hay que recordar que estamos en un sistema multitarea: el sistema operativo puede cortar una aplicación en mitad de
un DrawText, por ejemplo.
6
Fig. 6.
3. Entrada de datos desde el usuario. Controles.
La forma más básica de recoger datos desde el teclado o el ratón es usando controles. Un control es una
ventana con una clase predefinida por el sistema operativo. En el 0 se muestra un ejemplo sencillo de cómo se
usan los controles. El programa tiene una ventana principal con un botón:
Fig. 7.
Los controles asociados a una ventana se crean en su función de ventana (ver ProcesoVentana en el 0).
El sistema operativo indica el momento idóneo para crearlo, enviándole un mensaje WM_CREATE11. Nuestra
aplicación responde con:
case WM_CREATE:
HControlBoton= CreateWindow(_T("BUTTON"), // Nombre de la clase
_T("Pulsa para Beep"),
// Nombre de la ventana
WS_CHILD |BS_PUSHBUTTON | WS_VISIBLE | BS_VCENTER | BS_CENTER, // Estilo
10, // Coordenada X de la esquina superior izquierda
15, // Coordenada Y de la esquina superior izquierda
200,
// Ancho de la ventana (botón)
20,
// Alto de la ventana (botón)
HVentana, // Ventana a la que pertenece el boton (ventana padre)
(MENU) ID_BOTON1, // Identificador que asociamos al control
GlobalHInstance,
// Manejador de la instancia
NULL);
// Siempre a NULL
La función usada para crear un control es exactamente la misma que la que crea cualquier ventana, ya que
los controles son ventanas, aunque su tipo esté predefinido por el sistema operativo. Hay, sin embargo, algunos
puntos a señalar en los parámetros de CreateWindow:
11
El GWE envía un WM_CREATE a la ventana cuando termina de crear todas las estructuras internas necesarias crearla,
pero antes de mostrarla en pantalla.
7
•
La clase BUTTON no ha sido registrada por nosotros, sino que la registra el GWE cuando arranca.
•
El título de la ventana, _T("Pulsa para Beep")), se usa como etiqueta asociada al control.
•
Hay algunas características de estilo nuevas:
o
WS_CHILD indica que estamos creando una ventana hija, es decir, que forma parte de la
estructura de una ventana superior (su ventana padre)
o
BS_PUSHBUTTON indica que el botón se comporta como un pulsador.
o
BS_VCENTER y BS_CENTER lo centran vertical y horizontalmente.
•
Se le indica a la ventana creada cuál es su padre, es decir, qué ventana la ha creado, lo que permite que
el control pueda comunicarse con su ella.
•
Se le pasa a la ventana creada un identificador, ID_BOTON1 en este caso, que servirá para que la
ventana padre pueda identificar de qué control provienen los mensajes recibidos.
3.1. Comunicación entre una ventana y sus controles.
La comunicación con los controles se hace, principalmente, a través de mensajes. Hay que tener en cuenta
que cada ventana que creamos tiene asociada su función para procesar mensajes. Todos los controles de la
clase _T"BUTTON")) tienen una función de ventana12 que procesa los mensajes recibidos, tanto desde el
sistema operativo como desde otras ventanas. La función del control puede, a su vez, enviar mensajes a otras
ventanas. Aunque en teoría cualquier ventana puede intercambiar mensajes con cualquier otra, en la práctica la
mayoría de los intercambios se producen entre padre e hijas.
Un control se comunica con su padre usando lo que se conoce como mensajes de notificación (o
simplemente notificaciones). El mensaje de notificación más usado es el WM_COMMAND. Un control de tipo
_T(“BUTTON") envía este mensaje cuando el usuario lo ha pulsado. El mensaje debe ser procesado en la
función de la ventana padre. En nuestro ejemplo (ver función ProcesoVentana del 0):
case WM_COMMAND:
switch (WParam) { // Solo necesario si tenemos más de un control
// ID_BOTON1 ha sido pulsado
case ID_BOTON11:
MessageBeep(MB_OK); // Da un pitido
SendMessage(HControlBoton, WM_SETTEXT, 0, _T(“BEEEEEP");
Sleep(500);
// espera 500 ms
SendMessage(HControlBoton, WM_SETTEXT, 0, _T(“Pulsa para Beep");
break;
}
break;
Nótese que puede haber más de un control en la ventana padre. Para poder reconocer el control del que
viene un mensaje, dicho control envía como parámetro el identificador que se le asignó en el momento de
creación.
La ventana padre también se comunica con sus controles mediante mensajes. La función SendMessage
envía un mensaje a cualquier ventana. En este caso, el mensaje indica al control que debe cambiar su título. La
respuesta al mensaje dependerá del tipo de control. En este caso, el botón cambia el texto que hay dentro de él.
De igual forma, podemos eliminar un control simplemente enviándole un mensaje WM_QUIT.
Por último, señalar de nuevo que un control se comporta como una ventana más, con su propio proceso de
mensajes. Esto hace que en la ventana principal de nuestro ejemplo no sea necesario procesar los mensajes
WM_PAINT, a pesar de que la ventana contiene texto, ya que el texto no le pertenece directamente. Pertenece
a una ventana hija, que procesará sus propios mensajes WM_PAINT, repintando el texto que contiene.
12
Es equivalente a las funciones de las ventanas de usuario, pero no podemos ver su código fuente al formar parte del
GWE.
8
3.2. Más información.
También en la sección Microsoft Windows CEÆUser Interface Services de la ayuda del Embedded Visual
C++ hay varios temas donde se puede encontrar más información sobre controles. Concretamente en las
secciones:
•
...ÆCreating ControlsÆWorking with Window Controls es una introducción a los controles. Las
secciones que le siguen tratan los distintos tipos de controles. En esta asignatura sólo vamos a usar los
dos más simples: botones, que ya hemos visto, y editores, que son ventanas en las que se puede
introducir texto. Su función de ventana implementa un editor de una o varias líneas.
•
...Æ Creating ControlsÆCreating a Button habla sobre las características de los botones y cómo
manejarlos.
•
...Æ Creating ControlsÆCreating an Edit Control trata de los editores.
La información de referencia de las funciones y mensajes para el manejo de controles está en Microsoft
Windows CEÆAPI ReferenceÆWindows User Interface Services, donde se puede encontrar la información
sobre funciones y mensajes para manejar de botones y editores.
4. Diálogos.
Ya hemos visto que los controles son la forma básica de entrada de datos desde el usuario. Son sencillos de
manejar si se conocen los mensajes más usados para cada tipo de control. Sin embargo su configuración
gráfica es bastante engorrosa de manejar, sobre todo si hay que mostrar varios al mismo tiempo. Supongamos,
por ejemplo, que queremos mostrar los controles que aparecen en la Fig. 3, que repetimos a continuación:
Fig. 8.
Si usamos el manejo de controles que hemos visto hasta ahora, nos encontramos ante dos "fuentes de
engorro":
•
En la función CreateWindow de cada control hay que especificar su posición, de forma que el
programador debe ir colocando manualmente su tamaño y posición, y ejecutar el programa para ver el
aspecto que tiene en la ventana.
•
Cada vez que se pulse una de las opciones del menú superior (Márgenes, Tamaño del papel, Fuentes
del Papel, Diseño), se deben destruir los controles actuales, enviándoles a cada uno un mensaje
WM_CLOSE, y generar los controles de la nueva opción.
9
Para facilitar estas tareas el sistema operativo proporciona unas construcciones denominadas diálogos. Un
diálogo es una ventana que contiene un grupo de controles, de forma que el usuario puede manejarlo como un
todo, o cada control de forma independiente.
Una de sus características más interesantes es que el los controles pueden posicionarse dentro de un diálogo
usando una herramienta gráfica: el editor de diálogos, incluido en el editor de recursos del compilador.
4.1. El editor de diálogos y recursos de Embedded Visual C++.
Además del código y datos, un fichero ejecutable puede contener los recursos de una aplicación. Un
recurso es una estructura de datos que la aplicación necesita para que el sistema operativo le de un servicio
determinado. El ejemplo más sencillo es el icono asociado al fichero ejecutable de una aplicación. La
estructura de datos que lo define, incluida en el fichero ejecutable, contiene un mapa de bits que el sistema
operativo usa para dibujarlo cuando se está mostrando el contenido de un directorio, por ejemplo. En el caso de
un diálogo, también hay una serie de estructuras que definen sus características y que se incorporan al fichero
ejecutable para que el sistema operativo pueda dar los servicios asociados a un diálogo.
Embedded Visual C++ dispone de una herramienta, el editor/compilador de diálogos, que permite construir
diálogos e incorporarlos a una aplicación, y que forma parte del editor/compilador de recursos.
El ejemplo con el que mostraremos las operaciones básicas con diálogos aparece en el programa del
Listado 3. Muestra la siguiente ventana inicial:
Fig. 9.
Cuando se pulsa Empezar el programa responde:
•
Comenzando a emitir pitidos a una frecuencia de 1.00 pitidos por segundo.
•
Cambiando la cadena "Timer parado" por "Timer funcionando".
•
Cambiando el color del botón circular a la misma frecuencia que los pitidos.
Cuando se pulsa Parar el programa deja de pitar y vuelve a colocar el texto "Timer parado". Se puede
cambiar la frecuencia de los pitidos escribiendo una nueva frecuencia en el editor.
En la pantalla de la Fig. 9 hay 6 controles:
•
Dos botones de tipo Push Button y uno de tipo Radio Button.
•
Un editor para cambiar la frecuencia y otro (no modificable por el usuario), para mostrar el estado del
timer.
•
Un texto estático (no se modifica nunca): "Pitidos/segundo".
4.2. Crear un diálogo.
El formato de un diálogo estará definido en un fichero de recursos. Para crearlo, nos situamos en la ventana
que contiene los ficheros del proyecto (Workspace) y creamos un nuevo fichero con extensión .rc. En nuestro
ejemplo, hemos creado el fichero Diálogo.rc:
10
Fig. 10.
Si hacemos un doble clic sobre el nombre de fichero se ejecuta automáticamente el editor de recursos, que
crea el fichero de recursos. El editor tiene la siguiente ventana inicial:
Fig. 11.
Si pulsamos Control-R (insertar recurso) aparecen los tipos de recursos que podemos incorporar a un
ejecutable:
Fig. 12.
Podemos utilizar cualquiera de los 3 tipos de diálogos disponibles. Seleccionaremos el más simple:
IDD_PROPPAGE_SMALL:
11
Fig. 13.
A continuación borramos el texto central (TODO: Place controls ...) y modificamos el tamaño y las
características del diálogo. Para modificar las propiedades solo hay que seleccionarlo, pulsar el botón derecho
del ratón y seleccionar Properties. Aparecerá la ventana de propiedades:
Fig. 14.
La característica más importante es el identificador (ID), que nos va a servir para utilizarlo desde la
aplicación. También se puede modificar características adicionales: fuente, incorporar o no barra de título,
texto de la barra de título (Caption), borde, botón de cerrar, etc... Las modificaciones realizadas en nuestro
ejemplo son:
•
ID ha sido cambiado a IDD_DIALOGO
•
Se han activado las opciones Title Bar y System Menu, de forma que la ventana de diálogo tendrá barra
de título y botón de cerrar.
•
Se ha colocado el título de la ventana en Caption: Título Diálogo.
Por último se insertan los controles hasta conseguir la siguiente ventana de diálogo:
Fig. 15.
Se puede modificar el tamaño y posición de cada control, así como sus propiedades. La más importante
siempre es el identificador del control, ya que permite comunicarnos con él. Cualquier modificación que
realicemos con el editor gráfico se refleja en el fichero Dialogo.rc, que no es más que una trascripción a texto
de las características (posición, tamaño y propiedades) del diálogo y sus elementos (Ver el fichero Dialogo.rc
al final del Listado 3)
12
Una vez terminada la edición, se puede compilar el fichero Dialogo.rc. La compilación también se hace
automáticamente cada vez que se compila el proyecto completo con los comandos Build o Rebuild Proyect.
4.3. Los recursos en el proceso de compilación.
La compilación de Dialogo.rc da como resultado dos ficheros: Dialogo.res y resource.h. Dialogo.res es
una especie de fichero objeto, que se enlazará (link) con el resto de ficheros objetos para formar el ejecutable,
y del que no tenemos que volver a preocuparnos.
El fichero resource.h es la llave para poder usar el diálogo desde la aplicación. Está compuesto por
definiciones de tipo #difine:
#define
#define
#define
#define
#define
#define
#define
IDD_DIALOGO
IDC_EDICION
IDC_BOTON1
IDC_BOTON2
IDC_EDIT2
IDC_RADIO1
IDC_TEXTO
101
1000
1001
1002
1015
1023
-1
Estos símbolos son los que aparecen en la entrada ID de las ventanas de propiedades del diálogo y sus
controles. Por tanto, para poder usar el diálogo la aplicación debe tener la siguiente línea include:
#include "resource.h"
4.4. Crear y usar un diálogo.
Un diálogo no es más que una ventana que contiene controles, por lo que su manejo es similar al de
cualquier ventana o control, con algunas diferencias.
La primera es que debe usar alguna de las siguientes funciones para crearlo (en lugar de CreateWindow):
•
DialogBox :Crea un diálogo modal, que quiere decir que la ventana que lo ha creado no recibe
mensajes hasta que el diálogo termina. El efecto es que la aplicación se queda "parada" hasta que se le
proporciona los datos que pide el diálogo. Un ejemplo de este comportamiento son los diálogos que
piden el nombre de un fichero antes de realizar un proceso sobre él.
•
CreateDialog: Crea un diálogo no modal. La aplicación sigue funcionando "en paralelo" con la
entrada de datos del diálogo.
En nuestro ejemplo se ha creado un diálogo no modal en respuesta a la creación de la ventana principal:
case WM_CREATE:
// Ha sido creada la ventana principal de la aplicación.
HDialogo=CreateDialog(GlobalHInstance, // Instancia de la aplicación
MAKEINTRESOURCE (IDD_DIALOGO), // Macro que crea un Id. para el
diál.
HVentana,
// Ventana a la que pertenece el diálogo
ProcesoDialogo);
// "Proceso de ventana" asociado al diálogo
ShowWindow(HDialogo, SW_SHOW);
// Muestra el diálogo recien creado
break;
CreateDialog crea automáticamente tanto la ventana que contiene los controles del diálogo como los
propios controles, que empiezan a interactuar con él a través de mensajes. La mayor parte de los mensajes son
entre la ventana del diálogo y sus controles, lo que libera a la ventana principal de tener que manejarlos
directamente.
La función que maneja los mensajes del diálogo, ProcesoDialogo, en nuestro ejemplo, es similar a una
función de proceso de ventana, aunque con algunas diferencias:
13
•
El sistema operativo le puede enviar algunos tipos de mensajes propios de los diálogos:
WM_INITDIALOG por ejemplo.
•
La función devuelve TRUE si es ella la que ha procesado el mensaje, y FALSE si no lo ha procesado.
Al devolver FALSE, el sistema operativo realiza el proceso por defecto asociado al mensaje.
•
Aunque los controles son ventanas, no se manejan con handles de ventana sino con sus identificadores
(incluidos en resource.h). Para coger el texto del primer editor se usa la función
GetDlgItemText(HDialogo, IDC_EDICION, ...
Para enviar un mensaje al botón circular, la función:
SendDlgItemMessage(HDialogo, IDC_RADIO1, …
4.5. Más información.
En la sección Microsoft Windows CEÆUser Interface ServicesÆUsingResourcesÆCreating Dialog Boxes
de la ayuda del Embedded Visual C++ se puede encontrar más información sobre diálogos.
La información de referencia de las funciones y mensajes para el manejo de diálogos está en Microsoft
Windows CEÆAPI ReferenceÆWindows User Interface ServicesÆDialog Boxes.
Hay que tener cuidado de no confundir los diálogos tal como lo hemos visto aquí (Application Dialog
Box), con lo que se denomina en la ayuda Common Dialogs. Los Common Dialogs son diálogos prediseñados
por el sistema operativo que se utilizan para algunas tareas muy concretas, como, por ejemplo, abrir ficheros o
imprimir. En esta asignatura no los vamos a usar.
5. En interfase de usuario en aplicaciones de tiempo real.
6. Listados.
Pasos para construir los ejemplos (desde el menú principal de Embedded Visual C++):
•
Seleccionar File Æ New (Control-N).
•
Aparece la ventana NEW, en la pestaña Projects. Seleccionar:
•
o
Windows CE Application (tipo de aplicación por defecto).
o
En Location modificar, si es necesario, el directorio de donde cuelga el directorio de
aplicación.
o
Dar un nombre a la aplicación en Project name. El compilador creará un directorio con este
nombre para almacenar los ficheros fuente y ejecutables.
o
Seleccionar la CPU para la que se va a compilar: x86em (si usamos el simulador).
Aparece la ventana Windows CE Application – Step 1 of 1.
o
Seleccionar An empty project y Finish.
•
Copiar los ficheros fuente al directorio de la aplicación.
•
Añadirlos al proyecto. En la ventana WorKspace, en la pestaña FileView, seleccionar sobre Source
Files el botón derecho del ratón, Add Files to Folder, y añadir todos los ficheros.
14
Listado 1. TextoSimple: muestra una ventana con texto.
Ficheros: TextoSimple\Principal.c y TextoSimple\Definiciones.h
Este es un programa muy sencillo, pero que refleja la estructura elemental de cualquier aplicación
Windows CE que use el interfase GWE. Crea y muestra una ventana con un mensaje de texto. Para salir de la
aplicación solo hay que pulsar el botón en la esquina superior derecha de la ventana.
/*/////////////////////////////////////////////////////////////////
Para CONSEGUIR MAS INFORMACION sobre las funciones y lo que significan
exactamente los parametros solo hay que seleccionar con el ratón el nombre de la
función y pulsar F1 (ayuda del compilador).
*//////////////////////////////////////////////////////////////
#include <windows.h>
// Definiciones.h contiene los prototipos de las funciones de este fichero
#include "Definiciones.h"
HINSTANCE GlobalHInstance; // El manejador de instancia se almacena en una
// vble. global porque es necesario en varias funciones.
//////////////////////////////////////////////////////////////////////////////////
//
WinMain: Programa principal
//////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow) {
HWND HVentanaPrincipal;
// Handle para manipular la ventana que vamos a usar.
MSG Mensaje; // Estructura para almacenar los mensajes del sistema operativo
if (IniciaAplicacion(hInstance, _T("TipoVentanaConMensaje"))==FALSE)
return FALSE;
// Si todo es correcto en el inicio de la aplicación, se crea la ventana principal
HVentanaPrincipal= CreateWindow(
_T("TipoVentanaConMensaje"), // Nombre de la clase.
_T("Ventana con mensaje"), // Titulo de la ventana
WS_VISIBLE | WS_SYSMENU | WS_OVERLAPPED,
// Estilo inicial
CW_USEDEFAULT,
// Posicion horizontal
CW_USEDEFAULT,
// Posición vertical
CW_USEDEFAULT, // Ancho inicial de la ventana
CW_USEDEFAULT, // Alto inicial de la ventana
NULL,
// Handle a la ventana padre
NULL,
// Handle al identificador de menu
hInstance,
// Handle de la instancia de la aplicación
NULL);
// No se usa (puntero a datos adicionales
// de la creación de la ventana
if (HVentanaPrincipal==0)
return 0; // Esto le dice al SO que no se ha podido entrar en el bucle de
mensaje
// Bucle de mensajes
while (GetMessage(&Mensaje, NULL, 0, 0)){ /* Coge un mensaje de la cola de
mensajes. Si no hay ninguno, la aplicacion se
queda bloqueada hasta que el SO tenga
uno para ella. */
TranslateMessage(&Mensaje); // Hace que el sistema operativo
// pre-procese los mensajes
// asociados al proceso teclado
DispatchMessage(&Mensaje);
// Hace que la aplicación procese sus mensajes
}
return Mensaje.wParam;
// Se sale del bucle de mensaje cuando se recibe un
// mensaje de salida
}
15
/*///////////////////////////////////////////////////////////////////////////////
ProcesoVentana: Una vez que se abre una ventana del tipo que estamor
registrando,
el SO llama a esta función cada vez que se produce un evento.
*////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK ProcesoVentana (// Un CALLBACK es una función ejecutada por el SO.
HWND HVentana, // Ventana a la que se envía el mensaje
UINT Mensaje,
// Identificador del mensaje
WPARAM WParam, // Primer parametro del mensaje.
LPARAM LParam) // Segundo parámetro. Ambos dependen del tipo de mensaje
{
// Estas funciones suelen hacerse con instruccines SWITCH-CASE que procesan
// el tipo de mensaje y los parámetros.
switch (Mensaje) {
case WM_PAINT: /* El SO genera este evento cuando "considera" que la
aplicación tiene que pintar el contenido de una ventana (totalmente
o sólo en parte). Ocurre, por ejemplo, cuando se crea la ventana
actual, se maximiza, se cierra otra ventana que estaba sobre la
actual, etc. */
PintaTextoCentro(HVentana, _T("MENSAJE DE PRUEBA"));
break;
case WM_CLOSE: /* El SO genera este evento cuando el usuario pulsa el
boton de cierre de la ventana. */
PostQuitMessage(0); // Esto le dice al sistema operativo que debe
// terminar la aplicación.
break;
default:
// Los mensajes que no se van a procesar se pasan al SO
return DefWindowProc(HVentana, Mensaje, WParam, LParam);
}
return 0;
// 0 indica que todo ha ido bien
}
/*///////////////////////////////////////////////////////////////////////////////
IniciaAplicacion: registra el el tipo de ventana que vamos a utilizar.
En aplicaciones más complejas se coloca aqui el codigo necesario para las
operaciones de inicialización: reservar memoria, iniciar variables globales,
cargar parametros iniciales, etc.
*////////////////////////////////////////////////////////////////////////////////
BOOL IniciaAplicacion ( HINSTANCE HInstancia, TCHAR * PNombreTipoDeVentana ) {
WNDCLASS TipoDeVentana;
// Estructura necesaria para usar RegisterClass
GlobalHInstance= HInstancia;
// Coloca en TipoDeVentana algunas caracteristicas de la ventana
// que usaremos. El significado de los campos puede leerse seleccionando
// WNDCLASS y pulsando F1
TipoDeVentana.style
= 0;
TipoDeVentana.lpfnWndProc
= (WNDPROC) ProcesoVentana;
// Función asociada a
la ventana
TipoDeVentana.cbClsExtra
= 0;
TipoDeVentana.cbWndExtra
= 0;
TipoDeVentana.hIcon
= NULL;
TipoDeVentana.hInstance
= NULL;
TipoDeVentana.hCursor
= NULL;
TipoDeVentana.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
TipoDeVentana.lpszMenuName = NULL;
TipoDeVentana.lpszClassName = PNombreTipoDeVentana;
// Registra el tipo de ventana y vuelve
return RegisterClass(&TipoDeVentana);
}
/*///////////////////////////////////////////////////////////////////////////////
16
PintaTexto: Pinta texto en la ventana HVentana, en la posición PosX, PosY,
relativas
a la esquina superior izquierda del area de cliente
*////////////////////////////////////////////////////////////////////////////////
void PintaTextoCentro(HWND HVentana, TCHAR * Cadena)
{
PAINTSTRUCT ps;
// Estructura necesaria para usar BeginPaint y EndPaint
RECT Rectangulo;
// Contendrá las dimensiones del area de cliente de la vent.
HDC hDeviceContext; // Handle del dispositivo de contexto.
hDeviceContext = BeginPaint(HVentana, &ps);
GetClientRect (HVentana, &Rectangulo); // Rectangulo = area de cliente de la v.
DrawText (hDeviceContext, // Dispositivo de contexto donde vamos a pintar
Cadena,
// Cadena de texto
-1,
// Numero maximo de caracters
&Rectangulo, // Rectangulo que contendra el texto
DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_CENTER); // Formato
// Libera el dispositivo de contexto
EndPaint(HVentana, &ps);
Listado 2. HacerBeep: una ventana con control.
Ficheros: HacerBeep\Principal.c y HacerBeep\Definiciones.h. El programa es igual que el anterior, excepto
en las partes que se ven a continuación.
ƒ
ƒ
ƒ
BOOL CALLBACK ProcesoVentana (
ƒ
ƒ
ƒ
switch (Mensaje) {
case WM_CREATE:
HControlBoton= CreateWindow(
_T("BUTTON"), // Clase (predefinida por el SO)
_T("Pulsa para Beep"),
// Nombre de la ventana: en los
// botones, es el texto que aparece en el boton.
WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE|BS_VCENTER|BS_CENTER,
// Estilo. Definen el aspecto y comportamiento
// del boton
10, // Coordenada X de la esquina superior izquierda
15, // Coordenada Y de la esquina superior izquierda
200,
// Ancho de la ventana (boton)
20,
// Alto de la ventana (boton)
HVentana, // V. a la que pertenece el boton (v. padre)
(HMENU) ID_BOTON1, // Identificador que asociamos al
// control. Declarado en Definiciones.h
GlobalHInstance, // Manejador de la instancia
NULL);
// Siempre a NULL
break;
case WM_COMMAND: /* Este evento no es generado por el SO, sino por
cualquiera de los controles "hijos" creados en esta
17
ventana. El primer parametro del mensaje es el
identificador que asignamos al control cuando lo
creamos */
switch (WParam) {//solo es necesario si hay más de un control
case ID_BOTON1:
// ID_BOTON1 ha sido pulsado
MessageBeep(MB_OK); // Da un pitido
// Mandamos un mensaje al contro para que cambie
// el texto que contiene
SendMessage(HControlBoton,WM_SETTEXT,0,_T("BEEEEEP"));
Sleep(500); // espera 500 ms antes de restaurar
// el texto original
SendMessage(HControlBoton, WM_SETTEXT, 0,
_T("Pulsa para Beep"));
break;
}
break;
case WM_CLOSE: /* El SO genera este evento cuando el usuario pulsa el
ƒ
ƒ
ƒ
Listado 3. Pitador: una ventana con diálogo.
Ficheros: Pitador\Diálogo.rc, Pitador\resource.h, Pitador\Principal.c y Pitador \Definiciones.h.
Fichero Pitador\Principal.c:
ƒ
ƒ
ƒ
BOOL CALLBACK ProcesoVentana (// Un CALLBACK es una función ejecutada por el SO.
ƒ
ƒ
ƒ
switch (Mensaje) {
// Decodificamos los mensajes
case WM_TIMER: // El proceso de los timeout siempre debe ir al principio.
MessageBeep(0xFFFFFFFF); // Da un pitido
// Cambia el estado del botón IDC_RADIO1, de marcado (BST_CHECKED) a
// sin marcar (BST_UNCHECKED), o viceversa, enviándole un mensaje
// de chambio de estado (BM_SETCHECK)
if (Marca==BST_UNCHECKED)
Marca=BST_CHECKED;
else
Marca=BST_UNCHECKED;
SendDlgItemMessage(HDialogo, IDC_RADIO1, BM_SETCHECK, Marca, 0);
break;
case WM_CREATE:
// Creamos el dialogo IDD_DIALOGO. La funcion es similar a CreateWindow
HDialogo=CreateDialog(GlobalHInstance, // Instancia de la aplicación
MAKEINTRESOURCE (IDD_DIALOGO), // Esto es una macro que crea un
18
// identificador para la el dialogo
HVentana,
// Ventana a la que pertenece el dialogo
ProcesoDialogo);
// "Proceso de ventana" asociado al dialogo
ShowWindow(HDialogo, SW_SHOW);
// Muestra el dialogo recien creado
break;
default:
return DefWindowProc(HVentana, Mensaje, WParam, LParam);
}
return 0;
}
// 0 indica que todo ha ido bien
/*///////////////////////////////////////////////////////////////////////////////
ProcesoDialogo: Una vez que se abre el dialogo, el SO llama a esta funcion
cuando ocurre algo que afecta al dialogo o alguno de sus controles (cada vez que
se envía un mensaje al dialogo). ProcesoDialogo es equivalente a un CALLBACK de
proceso de ventana, aunque no es exactamente igual.
*////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK ProcesoDialogo (// Un CALLBACK es una función ejecutada por el SO.
HWND HDialogo,
// Ventana a la que se envía el mensaje
UINT Mensaje,// Identificador del mensaje
WPARAM WParam, // Primer parametro del mensaje.
LPARAM LParam) // 2º parámetro. Ambos dependen del tipo de mensaje
{
static float TicsPorSegundo= 1.0;
// Velocidad inicial de los pitidos
static UINT NroTimer=0;
// Identificador del timer que estamos usando
TCHAR CadenaEntrada[50];
edición
float TicsPorSegundoTmp;
// Aqui vamos a meter lo que se teclee en el control de
switch (Mensaje) {
case WM_INITDIALOG:
// Colocamos el valor de TicsPorSegundo como cadena inicial en IDC_EDICION
swprintf(CadenaEntrada, _T(“%.2f"), TicsPorSegundo);
SetDlgItemText(HDialogo, IDC_EDICION, CadenaEntrada);
// Imprimimos texto en IDC_EDIT2
SetDlgItemText(HDialogo, IDC_EDIT2, _T(“Timer parado"));
// Centramos el dialogo en el area de cliente de la ventana padre
CentraVentana(HDialogo);
return TRUE; // Devolviendo TRUE indicamos que se ha procesado el mensaje
case WM_COMMAND:
// Cuando se activa un control del dialogo recibimos un mens. WM_COMMAND
// En WParam está el identificador del control sobre el que se ha actuado
switch (WParam) { // Miramos qué control se ha activado
case IDC_BOTON1:
// Se ha pulsado el boton "Empezar"
// cogemos el texto de IDC_EDICION
GetDlgItemText(HDialogo, IDC_EDICION, CadenaEntrada,
sizeof(CadenaEntrada));
// Convertimos a float la cadena de texto
if (swscanf(CadenaEntrada,_T(“%f"), &TicsPorSegundoTmp)!=1) {
MessageBeep(MB_ICONEXCLAMATION); // Error en la
// conversión: restauramos el valor de inicial
// en el texto
swprintf(CadenaEntrada, _T(“%.2f"), TicsPorSegundo);
SetDlgItemText(HDialogo, IDC_EDICION, CadenaEntrada);
}
else {
// Redondeamos todo lo que pase de dos digitos decimales
TicsPorSegundo= ((int) (TicsPorSegundoTmp*10.0))/10.0;
// Mostramos el valor redondeado
swprintf(CadenaEntrada, _T(“%.2f"), TicsPorSegundo);
SetDlgItemText(HDialogo, IDC_EDICION, CadenaEntrada);
// Arrancamos el timer (o lo rearrancamos si ya estaba
// funcionando.
SetDlgItemText(HDialogo, IDC_EDIT2,
_T(“Timer funcionando"));
if (NroTimer!=0)
KillTimer(GetParent(HDialogo), ID_TIMER1);
NroTimer= SetTimer(GetParent(HDialogo), ID_TIMER1,
19
1.0/TicsPorSegundo*1000.0, NULL);
}// El timer se asocia a la ventana padre para que se
// procese antes el mensaje WM_TIMER. Si se asocia a
// HDialogo, el mensaje llega aquí despues de pasar
// Por el procedimiento de ventana principal.
return TRUE;
case IDC_BOTON2:
// Se ha pulsado el boton "Terminar"
if (NroTimer!=0)
KillTimer(GetParent(HDialogo), ID_TIMER1); // Para timer
SetDlgItemText(HDialogo, IDC_EDIT2, _T(“Timer parado"));
// Se escribe el estado del timer en IDC_EDIT2
// Desactiva la marca del boton IDC_RADIO1
Marca= BST_UNCHECKED;
SendDlgItemMessage(HDialogo,IDC_RADIO1,BM_SETCHECK, Marca, 0);
return TRUE;
case IDC_RADIO1:
// Repone estado del boton si se intenta cambiar
SendDlgItemMessage(HDialogo,IDC_RADIO1,BM_SETCHECK, Marca, 0);
return TRUE;
default:
return TRUE;
}
case WM_CLOSE: //Evento Æ activacion del boton de cierre del diálogo*/
PostQuitMessage(0); // Esto dice al SO que debe terminar la aplicación.
return TRUE;
default:
return FALSE; // Esto indica al SO que el mensaje no ha sido procesado
}
}
/*///////////////////////////////////////////////////////////////////////////////
CentraVentana: Coloca una ventana hija en el centro del area de cliente de
su padre.
*////////////////////////////////////////////////////////////////////////////////
void CentraVentana(HWND HVentana)
{
HWND HVentanaPadre;
RECT Rectangulo;
UINT PosX, PosY, Ancho, Alto;
GetClientRect(GetParent(HVentana), &Rectangulo);
// Se centra en el area de
// cliente de su padre.
PosX= Rectangulo.left;
PosY= Rectangulo.top;
Ancho= Rectangulo.right - Rectangulo.left;
Alto= Rectangulo.bottom - Rectangulo.top;
// Mueve la ventana, modificando su ancho y alto. El parametro a TRUE indica al SO
// que debe repintarla despues de moverla.
MoveWindow(HVentana, PosX, PosY, Ancho, Alto, TRUE);
}
BOOL IniciaAplicacion ( HINSTANCE HInstancia, TCHAR * PNombreTipoDeVentana ) {
ƒ
ƒ
ƒ
20