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