Bueno, ¿Recuerdan que les dije que si ocurre un error en el código probablemente sea de sus compiladores mal configurados? pues, tengan eso presente desde ahora ya que no lo volveré a repetir, usare el proyecto que hice en la introducción con el compilador GCC para windows, MinGw y Notepad++ como ide. Por si se preguntaban que usare en esta sección pues, ahí lo tienen.
El crear una ventana no es una tarea fácil ni mucho menos difícil, pero si es aquella tarea que te dejan en casa para estudiar y luego exponer, no es de memorizar cada estructura que se va a decir (Perderán el tiempo) sino entender que hace esta estructura y porque las cosas van en ese lugar y no en otro, les mostrare la plantilla que genera Code::blocks para explicar el funcionamiento de una ventana:
Les dejare todo el código completo en Repl: código.
Explicación del código
Probablemente vean algo diferente en la linea 7 a lo que mostré anteriormente en el código y es porque de momento no vamos a trabajar con TCHAR (Lo explicare mas adelante a fondo), porque para eso debemos definir primero UNICODE o ANSI, es sencillo, hasta mas recomendable que usar cadenas estrechas con char[i], pero para abarcar mejor ciertos temas lo dejare para un blog próximo mas detallado, así que optaremos de momento por otro camino, lo que hace esta linea es almacenar el nombre «Sombra.exe» a la variable global «zsClassName» que por así decirlo es nuestra Ventana en si, esta clase Ventana sera la que va a hacer instancia con otras ventanas que compartan cosas como el icono, cursor, entre otros.
Entrando al WinMain..
-
HWND hwnd: Este tipo de dato es para manipular o manejar nuestra ventana.
- MSG messages: Este tipo de variable es la que usaremos para manipular los mensajes que llegaran a nuestro programa. (Ej. Tenemos una Ventana y queremos que haga cierta acción, para esto debemos enviarle cierto mensaje)
- WINDCLASSEX wincl: Lo primero que tendrá en cuenta winmain es registrar la estructura wincl para de nuestra «Clase Ventana»
- hInstance: Este hace una instancia o referencia para nuestra aplicación, ya depende de como lo hayan asignado en el WinMain.
- lpszClassname: El nombre que usaremos para identificar la clase ventana en la estructura.
- lpfnWndProc: Es un puntero que almacena la dirección del procedimiento de la ventana principal (Window Procedure), esta función es la que se encarga de manejar todos los eventos que van pasando…
Espera, ¿No sabes qué es un Evento? Un evento es aquel que el programa detecta, y hace uso o no de este, algunos dicen que estan dormidos, pero en realidad lo sub-procesos nunca lo están simplemente son elegidos por nosotros, los programadores, por ejemplo, ahora mismo ocupas de muchos eventos y uno de ellos son; presionamos una tecla del teclado, cuando movemos el mouse, etcétera. Esto es así, porque Windows al ser orientados a Evento debe repartir todas las tareas a todos los hilos existente y cada uno hace uso de los eventos de manera correspondiente (Por eso lo de Multitarea), sino fuera así y su funcionamiento seria linea, es decir, que espera a que cierto hilo se termine los demás eventos no serán posible de usar. Como un amigo diría, mucho texto.
- style: Le damos estilo a nuestra clase ventana (CS*)
- cbSize: Tamaño de nuestra estructura.
Asignaciones por DEFAULT:
- hIcon: Handle que usaremos para cargar nuestro icono (32x32) a la clase ventana grande (LoadIcon (NULL, IDI_APPLICATION);, IDI_APPLICATION es el identificado del recurso icono)
- hIconSm: Handle que usaremos para cargar nuestro icono (16x16) a la clase ventana pequeña (LoadIcon (NULL, IDI_APPLICATION);, IDI_APPLICATION es el identificado del recurso icono)
- hCursor: Handle que usaremos para cargar nuestro cursor a la clase ventana (LoadIcon (NULL, IDC_ARROW);, IDC_ARROWes el identificado del recurso icono)
- ** lpszMenuName** La mantendremos en NULL ya que no tenemos un menu para agregar de momento.
- cbClsExtra: Bytes que le asignamos a la memoria para datos de la clase ventana. Normalmente: 0.
- **cbWndExtra: ** Bytes que le asignamos a la memoria a cada ventana. Normalmente: 0.
- **hbrBackground: ** Pincel que usaremos para pintar el fondo de nuestra ventana. Usar: wincl.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); y nos pondrá una ventana blanca en vez de gris.
Después de registrar o llenar nuestra estructura llamamos la función RegisterClassEx( ) la cual nos dirá si hubo un error o no, si existe uno se detendrá el programa mostrando un mensaje y luego retornando todo esto a winmain. Pueden omitirla, pero la verdad es que ayuda para encontrar errores que probablemente tu no encontraras
A este punto ya podemos crear nuestra primera ventana:
Para crear nuestra primera ventana hacemos uso de la función CreateWindowsEx, en el primer parámetro esta 0, este es el estilo que le damos a nuestra ventana de extendida, por ejemplo, podemos usar cosas como WS_EX_CLIENTEDG para darle un borde mas definido, pero por default tieneWS_EX_LEFT, existen muchos mas y pueden ir probando. sZClassname es el nombre de la clase que le dimos desde la variable global, la encargada de decirle a la ventana que tipo de Ventana sera.
“Sombra.exe” es el nombre de la ventana, y WS_OVERLAPPEDWINDOW es el estilo que le daremos a nuestra ventana igual que en WS_EX_LEFT ir probando cuales les gusta mas entre mejor estilo su ventana se va a ver superior a muchas otras, CW_USEDEFAULT y CW_USEDEFAULT, son ordenadas de la esquina superior a izquierda desde variable X y, Y. 544 es el ancho de nuestra ventana mientras que 375 es la altura de esta.
HWND_DESKTOP, este parámetro es nuestro identificador a la ventana principal, ventana padre en otras palabras. Como no hay mas ventanas y esta es la padre, pongan esto como NULL. En este otro parámetro definimos como NULL puesto que no tenemos menu o identificador a menu. hThis, es la instancia que hacemos a la ventana, en el ultimo parámetro.. Aquí aplicamos lo de antes del procedimiento a la ventana o datos a la ventana, sino tenemos nada que trasmitir en este caso es NULL.
Aun con todo esto la ventana no es visible e.e
Antes de mostrar la ventana debemos comprobar de que no hayan errores que probablemente nosotros ni podamos ver, ¿Por qué? porque no miramos los jodidos valores de retorno!!, aun así esto no esta en el codigo por defecto de Code::Blocks y pasa y acontece de que esto es opcional, porque Code::blocks ya sabe que algun dia o que ya podemos identificar errores así solo deben poner esto:
1
2
3
4
5
6
7
if(hwnd == NULL){
MessageBox(NULL, "Hubo un error! :frowning: ", "¡Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
Para que digan que soy bueno y no tengan que reescribir todo eso de la imagen.
Ahora si, es hora de mostrar la ventana o hacer que esta ya sea visible con ShowWindow puesto que el handle es valido, solamente nos queda usar el parámetro nCmdShow para poder controlar nuestra ventana (Ej. Maximizar o minimizarla, la ventana, entre otras cosas), aunque Windows recomienda usar SW_SHOWDEFAULT, recomiendo nCmdShow. Como plus, pongan abajo de ShowWindows la funcion: UpdateWindow, esta hace que podamos actualizar la ventana cada vez que enviemos un mensaje.
¡Ahora seguimos con la parte que le da vida a nuestra ventana!
Ahora de darle poweeeer -w-
GetMessage retorna siempre 0 solamente cuando en es todo los mensajes sean correctos o hasta que recibe un mensaje que haga que pare, por ejemplo, si retorna -1 es porque encontró un error, e igual con 1 que en este caso es FALSE, 0 es TRUE. Esto pasa ya que aunque retorne en booleano este retorna otros valores por el simple hecho de que esta definido como un unsigned int, (El bucle esta mal ya luego les digo, porque y como corregirlo, de momento piensen.. ¿Cual es el error?) Este bucle apunta a la estructura messages la cual es como una cola, o mejor dicho lo es, ya que se va llenando de mensajes (Cada mensajes es TRUE hasta que pase lo mencionado anteriormente) para luego ser removidos y ya después ser procesados, y mostrados o retornado.
¿Ahora si cual es error?, pues como os dije antes este retorna 3 resultados TRUE(0), FALSE(1), ERROR(-1), si retorna error o -1, el bucle no para y sigue esto hace que la ventana nunca termine y no queremos eso, para evitarlo podemos usar uno de estos dos bucles:
1
2
3
4
5
while(TRUE == GetMessage(&messages, 0, 0, 0)){
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
O tambien pueden usar:
1
2
3
4
5
while(GetMessage(&messages, NULL, 0, 0) > 0){
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
El return solo nos devuelve el mensaje que contiene wParam de la estructura messages. TranslateMessage hace procesos en el evento del teclado para traducir mensajes o teclados virtuales a caracteres, que luego serán apuntara a la estructura de cola messages. Tomando esas traducciones por DispatchMessage y enviado al procedimiento de ventana, es decir, que puede ser tomado por otra ventana o hilo donde sabrá procesarlo y tratarlo, luego de todo eso hace nos retorna un valor.
Ahora nos queda que nuestro frankenstein piense mediante el winprock
Winprock o WindowsProcedure, es la función con la cual manejaremos mensajes que Windows va enviado a esta función es la que tanto mencionamos en el blog, «Procedimiento de ventana», responsable de que cada mensaje se ocupe de manera correcta para cada “Ventana” o “Clase Ventana”:
- hwnd: Este es un manipulador para la ventana con la que estemos tratando ciertos mensajes los cuales seran procesados por el winprock, esta puede ser diferente ya dependiendo de la ventana.
- ms: identificador de mensaje
- wp: depende del WM es que se puede usar este argumento e igual lp, wp para tratar con información que pasa por los mensajes, mientras que lp es usado para tratar con manejadores, controles, punteros, en otras palabras lp es un HWD que controla el envio del mensaje, pero como no siempre es así y posiblemente el mensaje no proviene de un control o manejador así que se declararía como NULL.
Pasamos por switch, le damos un valor que luego sera tomado por alguno de los case o tipo de mensaje que va a tratar de momento solo tenemos uno como ejemplo, WM_DESTROY, el cual es el encargado de enviar un mensaje (WM_QUIT ) cuando se trata de cerrar una ventana mediante el icono de aspa u otros métodos, este mensaje es enviado a PosQuitMessage(0) el cual enviara a WM_QUIT a la cola de GetMessage retornando FALSE, el cual para el bucle y simplemente llena la estructura MSG para luego retornar el valor 0 (return messages.wParam;
) como les dije depende del WM para argumentos como wParam, a veces ni siquiera toma wp o lp, pues windows trabaja con mas argumentos.
Pasamos a return DefWindowProc(hwnd, ms, wParam, lParam);
prácticamente DefWindowProc solo tiene el uso por default para cada mensaje y ya lo demás la API lo trata