| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Ventanas Este capítulo supone que tienes conocimientos previos en. . . 3.1 Preparativos para un buen inicioCrear una aplicación en Windows puede parecer complicado, ¡y vaya que lo es al principio! Las codificaciones te parecerán interminables y confusas, se topará con tipos de datos disfrazados ( Cuando consiga ganar experiencia, se dará cuenta de que lo complicado no es mas que una mera apariencia, irá reconociendo funciones y sabrá qué usar y dónde buscar para realizar lo que tiene en mente. Con el tiempo, irá creando sus propias librerías para facilitar programar cosas cotidianas (como crear una ventana en una línea). La experiencia se gana superando esa prueba mental que impone la programación en Windows. En este capítulo se tratará algo un tanto complejo pero con lo que ya está familiarizado: las ventanas. Es un tema básico de la programación en Windows, pues como ya sabe, Windows traducido al español quiere decir "ventanas" y no deberá extrañarle saber que dedicaremos capítulos enteros a ellas, eso sí, enfocado desde el punto de vista del programador. Sé que ahora tiene mucha duda sobre conceptos, tipos de datos, constantes... todo un mundo de información que necesito mostrarle en tan poco tiempo; no se preocupe, a medida que avance en el curso vendrá resolviéndolas. Por ahora solo me enfoco en las bases, en lo principal, lo realmente útil; los detalles vendrán en su momento. Bueno, voy a hacer costumbre esto de iniciar con un código. Para esto, debe seguir los 5 sencillos pasos del capítulo anterior
#include <windows.h> /* La función ProcedimientoVentana() es necesaria porque es la LRESULT CALLBACK ProcedimientoVentana(HWND hwnd, UINT mensaje,encargada de recibir los eventos (movimientos del ratón, tecla, clics a un botón, etc). En este caso, solo monitorea el momento el que el usuario decide cerrar la ventana para descargar la aplicación de memoria */ WPARAM wParam, LPARAM lParam) { switch (mensaje) { case WM_DESTROY: PostQuitMessage (0); return 0; } return DefWindowProc (hwnd, mensaje, wParam, lParam); } /* Esta cadena se ocupa para la clase de ventana y la función char szNombreAplicacion[] = "Ejercicio2";CreateWindowEx() se refieran a un mismo nombre */ int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev, LPSTR lpLineaCmd, int nEstadoVentana) { //Los 4 pasos para crear una ventana... //1. Establecer los 12 campos de la "clase de ventana" WNDCLASSEX ventana; ventana.cbSize = sizeof(WNDCLASSEX); ventana.style = CS_HREDRAW | CS_VREDRAW; ventana.lpfnWndProc = ProcedimientoVentana; ventana.cbClsExtra = 0; ventana.cbWndExtra = 0; ventana.hInstance = hInstancia; ventana.hIcon = LoadIcon (NULL, IDI_APPLICATION); ventana.hCursor = LoadCursor (NULL, IDC_ARROW); ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1); ventana.lpszMenuName = NULL; ventana.lpszClassName = szNombreAplicacion; ventana.hIconSm = LoadIcon (NULL, IDI_APPLICATION); //2. Dar de alta en el sistema a la "clase de ventana" if( !RegisterClassEx(&ventana) ) { MessageBox(NULL, "No fue posible dar de alta a la ventana, abortando ejecución", "¡Error!", MB_ICONEXCLAMATION | MB_OK); return FALSE; } //3. Crear la ventana en base a la "clase de ventana" anteriormente registrada HWND hwnd; hwnd = CreateWindowEx( 0, szNombreAplicacion, //Nombre de clase de la ventana a la que pertenece "Programa de ejemplo", //Título de la ventana WS_OVERLAPPEDWINDOW, //Tipo de ventana CW_USEDEFAULT, //Posición de la ventana en pantalla en "x" CW_USEDEFAULT, //Posición de la ventana en pantalla en "y" 300, //Ancho 200, //Alto HWND_DESKTOP, NULL, hInstancia, NULL); if(hwnd == NULL) { MessageBox(NULL, "Error al crear la ventana, abortando ejecución", "¡Error!", MB_ICONEXCLAMATION | MB_OK); return FALSE; } //4. Mostrar la ventana en pantalla ShowWindow(hwnd, SW_SHOWDEFAULT); //Necesario para manipular los mensajes o eventos de la ventana MSG mensaje; while(GetMessage(&mensaje, 0, 0, 0) > 0) { DispatchMessage(&mensaje); } return mensaje.wParam; }
¡Es mucho código para solo poder generar esto! No es cierto, hacer una ventana como esa sin usar las funciones API le habría tomado mucho tiempo. Hacer un programa para consola es relativamente más fácil que hacerlo para Windows, pero hoy en día un programa que no tiene interfase gráfica amigable no se usa ni se vende (salvo extraordinarias excepciones). En capítulos posteriores verá cómo crear sus propias librerías que facilitarán las cosas (estilo MFC o .NET). 3.2 Tipos de datos en WindowsComo seguramente lo habrá notado en el código anterior, las funciones API de Windows no hacen uso de los tipos de datos estándar en C++ ( Los tipos de datos más comúnes son:
Como puede ver no hay nada de que asustarse o que no conozca. En la programación Windows también es muy común lidiar con manipuladores, que no son más que simples números que sirven para identifican y referirse a ciertos elementos. Un manipulador se define como:
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name; DECLARE_HANDLE(HINSTANCE); DECLARE_HANDLE(HWND); DECLARE_HANDLE...
La lista de manipuladores puede continuar. Lo importante es conocer su existencia y su uso. Todos los tipos de manipuladores empiezan con H. Por medio de las funciones API es que se establecen los valores de los manipualdores según Windows asigne los recursos en cada sistema. Adicionalmente, Windows define estrucuras como 3.3 Cómo funciona una aplicación WindowsUna aplicación bajo un sistema gráfico como Windows trabaja de una forma muy diferente a las tradicionales. No podemos precisar el momento en que el usuario decida mover el puntero del ratón, dar un clic sobre un botón o minimizar la ventana. Por ello, el programa debe contar con un código que se encargue de recibir estos eventos en todo momento. Windows está al pendiente de cualquier evento ya sea un movimiento del ratón, la pulsación de una tecla, el cambio de aplicación activa, la alteración del tamaño de una ventana, etc. Conforme se van presentando, los almacena en una lista que se le conoce como cola de mensajes. Existen varias funciones API que en visitan la cola de mensajes ( He introducido este tema de la cola de mensajes y los eventos porque en el código del Programa2, y en cualquier otro programa común de Windows, se utilizan estos conceptos. Pueda que el Programa2 aparente no hacer nada, pero en realidad se encuentra monitoreando la cola de mensajes con un ciclo Nota aclaratoria: Verá, cerrar una ventana y finalizar la aplicación son dos cosas completamente distintas. En Programa2 se presentan al mismo tiempo (cuando se cierra una ventana se finaliza la aplicación), pero ello no implica que deba ser así. Imagine que se tiene un documento el cual no ha sido guardado, el programa puede monitorear cuando el usuario desee cerrar la ventana y mostarle al usuario un mensaje "¡Si cierra la ventana perderá la información! ¿guardar cambios?". Calma, ¡ya tendremos tiempo para aprenderlo! 3.4 Crear una ventanaNo quisiera extenderme en el tema de eventos sin antes ser capaces de responder a la siguiente pregunta... ¿cómo crear una ventana? La explicación pudiera ser complicada, pero ponerlo en práctica no lo es. Ya sabe algunas cosas importantes: deberá incluir el archivo de cabecera Ahora bien, para crear una ventana hay que seguir 4 pasos. Primero diré cuáles son y en qué consisten para después aclarar las dudas sobre conceptos, tipos de datos, etc. Los pasos son los siguientes:
1. Establecer los 12 campos de la clase de ventana. Antes de crear una ventana es obligatiorio establecer 12 campos correspondientes a la estructura clase de ventana (la palabra clase no tiene nada que ver con la programación orientada a objetos). Verá, una clase de ventana almacena atributos que definen el aspecto y comportamiento de una ventana. Algunos ejemplos sobre la información que contiene son el color de fondo de la ventana, ícono, menú y el puntero de la función que será encargada de procesar los mensajes de la ventana. Examinando los archivos de cabecera de
typedef struct _WNDCLASSEXA { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; HICON hIconSm; } WNDCLASSEXA,*LPWNDCLASSEXA,*PWNDCLASSEXA; typedef WNDCLASSEXA WNDCLASSEX,*LPWNDCLASSEX,*PWNDCLASSEX; Observe como se declaran los campos con tipos de datos que no existen en C++ estándar ( Por ahora solo hay que concentrarse en los campos involucrados ya que almacenarán información reelevante para la creación de una ventana (o varias de ellas). Por medio de la notación húngara vamos a deducir lo siguiente:
Como puede ver, la finalidad de algunos campos es muy descriptiva con solo leer su nombre (¡la ventaja de utilizar la notación húngara!), en otros quizás alcance a tener una vaga idea del significado porque no nunca ha utilizado esta estructura. Le introduciré al uso de cada uno de ellos en su momento. Mientras tanto, le mostraré una sección de código del Programa2 para observar cómo se precisa esta "configuración" de la ventana. Le advierto que aparecen nuevas funciones, constantes y, por si fuera poco, lo que parecieran tipos de datos que no corresponden al C++ estándar. No se preocupe por entender cada línea, solamente intento darle una vista muy general:
//1. Establecer los 12 campos de la "clase de ventana" WNDCLASSEX ventana; ventana.cbSize = sizeof(WNDCLASSEX); ventana.style = CS_HREDRAW | CS_VREDRAW; ventana.lpfnWndProc = ProcedimientoVentana; ventana.cbClsExtra = 0; ventana.cbWndExtra = 0; ventana.hInstance = hInstancia; ventana.hIcon = LoadIcon (NULL, IDI_APPLICATION); ventana.hCursor = LoadCursor (NULL, IDC_ARROW); ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1); ventana.lpszMenuName = NULL; ventana.lpszClassName = szNombreAplicacion; ventana.hIconSm = LoadIcon (NULL, IDI_APPLICATION); Así es como se ha decidido iniciar los campos de la estructura Como comentario adicional, puede observar que en el campo 2. Dar de alta en el sistema a la clase de ventana:
//2. Dar de alta en el sistema a la "clase de ventana" if( !RegisterClassEx(&ventana) ) { MessageBox(NULL, "No fue posible dar de alta a la ventana, abortando ejecución", "¡Error!", MB_ICONEXCLAMATION | MB_OK); return FALSE; } A grandes rasgos, lo anterior llama a la función Cuando las configuraciones no son aceptadas por el sistema, la función regresa el valor 3. Crear la ventana en base a la clase de ventana anteriormente registrada Se manda llamar a la función
//3. Crear la ventana en base a la "clase de ventana" anteriormente registrada HWND hwnd; hwnd = CreateWindowEx( 0, szNombreAplicacion, //Nombre de clase de la ventana a la que pertenece "Programa de ejemplo", //Título de la ventana WS_OVERLAPPEDWINDOW, //Tipo de ventana CW_USEDEFAULT, //Posición de la ventana en pantalla en "x" CW_USEDEFAULT, //Posición de la ventana en pantalla en "y" 300, //Ancho 200, //Alto HWND_DESKTOP, NULL, hInstancia, NULL ); if(hwnd == NULL) { MessageBox(NULL, "Error al crear la ventana, abortando ejecución", "¡Error!", MB_ICONEXCLAMATION | MB_OK); return FALSE; } Quizás el parámetro más importante de la función Si el nombre de la clase de ventana no existe o no ha sido aceptada por el sistema, no se permitirá crear la ventana y en ese caso la función devuelve el valor Y por último, 4. Mostrar la ventana:
//4. Mostrar la ventana en pantalla ShowWindow(hwnd, SW_SHOWDEFAULT); La función 3.5 Cambiar el color de fondo de la ventanaEl objetivo de este apartado es que interactúe con el código y adquiera confianza. No se preocupe por entrar en detalles que por ahora no necesita saber, los cómos y los porqués vendrán después. Hasta el momento, ya ha logrado crear una ventana con un color que viene configurado en Windows (por defecto es gris claro como en la fig. 3.1.1), es decir, el color que el usuario ha configurado en "Panel de Control / Propiedades de pantalla / Apariencia". Como ya sabe, en el paso 1 se definieron doce campos de la clase de ventana; uno de los campos es el que fija el color del fondo de la ventana ( En el Programa2, la clase de ventana se declara con el identificador
//Hay varias formas de cambiar el color... //======================================== //Utilizando los colores definidos por el usuario en "Propiedades //de pantalla" (como el gris claro que usamos originalmente) ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); ventana.hbrBackground = (HBRUSH) (COLOR_BTNTEXT + 1); ventana.hbrBackground = (HBRUSH) (COLOR_BTNHIGHLIGHT + 1); ventana.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); ventana.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1); ventana.hbrBackground = (HBRUSH) (COLOR_HIGHLIGHT + 1); ventana.hbrBackground = (HBRUSH) (COLOR_MENU + 1); /* Puede probar con estas otras constantes: COLOR_ACTIVEBORDER COLOR_ACTIVECAPTION COLOR_BACKGROUND COLOR_BTNSHADOW COLOR_CAPTIONTEXT COLOR_GRAYTEXT COLOR_HIGHLIGHTTEXT COLOR_INACTIVEBORDER COLOR_INACTIVECAPTION COLOR_MENUTEXT COLOR_SCROLLBAR COLOR_WINDOW COLOR_WINDOWFRAME COLOR_WINDOWTEXT */ //Utilizando colores básicos ventana.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH ); ventana.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH ); //Creando un color específico producto de la mezcla de los 3 colores básicos // RGB(rojo, verde, azul) donde los valores para rojo, verde y azul //tienen un rango de 0x00 a 0xFF ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0xFF, 0xFF) ); //blanco ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0x00) ); //negro ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0x00, 0x00) ); //rojo ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0xFF, 0x00) ); //verde ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0xFF) ); //azul ventana.hbrBackground = CreateSolidBrush( RGB(0xC0, 0xC0, 0xC0) ); //gris //No olvide borrar la brocha creada por CreateSolidBrush() antes de //finalizar la aplicación con la función DeleteObject(ventana.hbrBackground) No creo que tenga problemas para cambiar la línea de código y observar el cambio en la ventana al compilar nuevamente. Si así lo desea, puede seguir practicando con cada una de las líneas anteriormente mostradas. Si utiliza la función 3.6 La función CreateWindowEx()La función
HWND CreateWindowEx( DWORD dwExStyle, // un tal "estilo extendido" LPCTSTR lpClassName, // puntero al nombre de la clase a la que pertenece LPCTSTR lpWindowName, // p. a la cadena que contiene el título de ventana DWORD dwStyle, // estilo de ventana (características) int x, // posición horizontal de la ventana int y, // posición vertical de la ventana int nWidth, // Ancho de la ventana int nHeight, // Alto de la ventana HWND hWndParent, // manipulador de ventana padre HMENU hMenu, // manipulador del menú HINSTANCE hInstancia, // manipulador de Instancia (2do param. WinMain()) LPVOID lpParam // puntero al valor lpParam ); Por ahora puedes modificar los parámetros Como puede observar en el prototipo de la función, se regresa un tipo de dato HWND. Es el manipulador de ventana del que se ha estado hablando. Ese dato es muy útil para identificar en todo momento a la ventana que se crea. Ya sabemos que el manipulador devuelto será igual a 3.7 Ejercicios1. Crear una ventana. Modifique el código del Programa2 para crear una ventana de 400x200 (ancho x alto), con el color de fondo rojo (
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|