Estudiaremos estos dos elementos cogiendo un código general de un programa que utilice la API de Windows:
#include <windows.h> LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { HWND hWnd; MSG Message; WNDCLASS WndClass; WndClass.style = CS_HREDRAW | CS_VREDRAW; WndClass.lpfnWndProc = WindowProcedure; WndClass.cbClsExtra=0; WndClass.cbWndExtra = 0; WndClass.hbrBackground = (HBRUSH) GetStockObject (LTGRAY_BRUSH); WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hIcon= LoadIcon (NULL, IDI_APPLICATION); WndClass.hInstance = hInstance; WndClass.lpszClassName = "NUESTRA_CLASE"; WndClass.lpszMenuName = NULL; RegisterClass (&WndClass); hWnd = CreateWindow ( "NUESTRA_CLASE", "Ventana de ejemplo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 320, 200, HWND_DESKTOP, NULL, hInstance, NULL ); ShowWindow(hWnd, SW_SHOWDEFAULT); while (TRUE==GetMessage(&Message, 0,0,0)) { TranslateMessage(&Message); DispatchMessage(&Message); } return Message.wParam; } LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc (hwnd, msg, wParam, lParam); } return DefWindowProc(hwnd,message,wParam,lParam); }
En el código anterior aparecen dos funciones: WinMain y WindowProcedure. El WinMain es lo que es el main a un programa convencional en C: el punto de entrada, la primera función que se ejecuta. La WndProc es la función encargada de recibir y procesar todos los mensajes que el SO manda a tu ventana (teclas presionadas, cambio de tamaño…).
Recuerda incluir el windows.h cuando programes con la Win32.
Es posible que si desarrollas con Visual Studio el código generado dé problemas (en la WndClass o en la CreateWindow) debido a conversiones de strings, esto es debido al juego de carácteres configurado como Unicode, hay que configurarlo como Multibyte (en las propiedades generales del proyecto).
Su declaración recibe los siguientes parámetros:
LoadIcon (NULL, IDI_APPLICATION); → Hará que se cargue el icono por defecto.LoadCursor(NULL, IDC_ARROW); → Hará que se cargue el cursor por defecto.(HBRUSH)GetStockObject (WHITE_BRUSH);.sizeof(WNDCLASSEX);.Es la función de tu aplicación a la que le llegan los mensajes enviados por el sistema operativo, generalmente contiene un switch que es donde se elige qué mensaje ha llegado, y por tanto, como gestionarlo. El mensaje viene como un int, y su valor correspondrá a uno de los defines declarados en windows.h, junto a él vienen dos parámetros wParam y lParam, estos dan más información sobre el mensaje. Algunos de los mensajes existentes son los siguientes:
bool rightButtonDown=wParam & MK_RBUTTON;Si lParam contiene varios valores utilizaremos las macros: LOWORD y HIWORD:
xPos=LOWORD(lParam); yPos=HIWORD(lParam);
En windows toda ventana que se crea tiene asociado un thread y una función para procesar mensajes (wndproc). Una vez se crea la ventana a esta le empiezan a llegar mensajes, estos se van enconlando en una cola de mensajes global del sistema e identifican la ventana por su hwnd.
Todas estas funciones manipulan una estructura denominada MSG. Generalmente se les pasa un puntero a una variable de este tipo y estas funciones dan los valores concretos.
true si ha recogido algo.
GDI es el conjunto de funciones que se utilizan para pintar sobre una ventana. Lo mejor es usarlas en el WndProc, cuando le llegue el mensaje: WM_PAINT, ya que es cuando el sistema pide un repintado. Recuerda, para utilizarlas tendrás que linkar la gdi32.lib.
Para dibujar necesitamos declarar una PAINTSTRUCT. El dibujo lo definiremos entre las llamadas a BeginPaint y EndPaint, estas reciben el HWND y el PAINTSTRUCT.
PAINTSTRUCT ps; BeginPaint (hwnd, &ps); ... EndPaint (hwnd, &ps);
Generalmente, el elementos que necesitaremos para hacer nuestros dibujos será la propiedad hdc del PAINTSTRUCT.
MoveToEx.MoveToEx(ps.hdc, 40,40,NULL);
LineTo(ps.hdc, 50,50);
CreatePen (int estilo, int anchura, COLOREF color);. El estilo puede ser: PS_SOLID, PS_DASH, PS_DOT, PS_DASHOOT, PS_DASHDOTDOT. Devuelve una variable HPEN.HPEN RedPen = CreatePen (PS_SOLID, 2, RGB(255,0,0));
SelectObject. Esta función devuelve el objeto pluma, fuente, brocha… (según lo asignado) anterior.HPEN OldPen = (HPEN) SelectObject(hdc, RedPen);
DeleteObject, de esa forma liberaremos los recursos asociados a este.SetPixel(hdc, x, y, COLORREF);. O recoger el color de un pixel concreto: COLORREF GetPixel (hdc, x, y);CreateSolidBrush (color sólido), CreateHatchBrush (con adornos (HS_BDIAGONAL, HS_CROSS…) O CreatePatternBrush (relleno a partir de una imágen).Rectangle(hdc, x1, y1, x2, y2);Ellipse (hdc, x1, y1, x2, y2);Polygon (hdc, array de POINT, el número de vertices que se dibujarán);, el array de POINT corresponde a la posición de cada vértice del polígono:POINT vertices[3]; vertices[0].x =25; vertices[0].y =25; vertices[1].x =50; vertices[1].y =25; vertices[2].x =AnchoVent; vertices[2].y =AltoVent; Polygon(hdc, vertices, 3);
SetBkColor(hdc, COLORREF); y el tipo de fondo (OPAQUE o TRANSPARENT): SetBkMode(hdc, modo);TextOut (hdc, X, Y, puntero a la string o la string misma, número de caracteres de la string);, para darle color tendremos que llamar antes a la función: SetTextColor(hdc, COLORREF);.PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); TextOut(hdc,10,10,"Hello World",11); EndPaint(hWnd, &ps);
PostMessage(hwnd, WM_DESTROY, 0,0);SetWindowText (hWnd, <cadena de caracteres>);PlaySound(“window_open.wav”, NULL, SND_FILENAME | SND_ASYNC); Esta función recibe como parámetros:ShowCursor(FALSE);GetCursorPos al cual le pasas una referencia a una variable POINT.ScreenToClient o ClientToScreen; a las dos has de pasarle el HWND y un POINT.PostQuitMessage(0);, esto envia el mensaje WM_QUIT al WndProc, lo que es una finalización definitiva.GetModuleHandle a la que si le pasamos NULL nos devolverá una instancia válida para nuestro programa.WM_ACTIVATE, cuando la HIWORD del wParam viene a true es que ha recibido el foco, cuando viene a false es que se ha minimizado o perdido el foco:switch (uMsg) { case WM_ACTIVATE: if (!HIWORD(wParam)) active = TRUE; else active = FALSE; break; ...
A las dos se les pasa como parámetro el nombre de la tecla: VK_LSHIFT, VK_RSHIFT, VK_LCONTROL…
if (GetAsyncKeyState(VK_LSHIFT)) MessageBox(NULL, "hol", NULL, NULL);
Para crear un Timer convencional utilizamos la función SetTimer, esta recibe los siguientes parámetros:
SetTimer(hWnd, 101, 1, NULL);
Para recibir los mensajes del timer lo haremos desde el WndProc, llegará un mensaje WM_TIMER. En el wParam vendrá el identificador del timer:
case WM_TIMER: switch (wParam) { case IDT_TIMER1: ... break; case IDT_TIMER2: ... break; }
Para destruir un timer utilizamos KillTimer, pasándole:
Este tipo de timers requieren linkar la librería winmm.lib.
Construiremos un timer haciendo uso de las funciones QueryPerformanceFrequency y QueryPerformanceCounter, a las dos se les pasa un puntero a referencia a LARGE_INTEGER, la primera coloca en este la frecuencia del contador de alta resolución (si no puede porque el sistema no acepta este tipo de contadores coloca un 0), la segunda devuelve el valor actual del contador.
Para construir nuestro timer utilizaremos la siguiente estructura:
struct { __int64 frequency; // Frecuencia float resolution; // Resolución bool performance_timer; // Indica si es un timer de alta resolución unsigned long mm_timer_start; // Momento en el que inicia (baja resolución) unsigned long mm_timer_elapsed; // Momento en el que finaliza (baja resolución) __int64 performance_timer_start; // Momento en el que inicia (alta resolución) __int64 performance_timer_elapsed; // Momento en el que finaliza (alta resolución) } timer;
Usaremos la función que inicia el timer y la que recoge el momento actual. La que inicia el timer recogerá la frecuencia del contador de alta resolución, si este es soportado configurará el timer con los parámetros adecuados para este. Si no, conficurará un timer de baja resolución. Un timer de baja resolución utilizará la función timeGetTime, esta devuelve en milisegundos la hora del sistema (su frecuencia por tanto será de 1000 en un segundo y su resolución de 0.0001). Para el de alta esto vendrá según el parámetro dado por el sistema.
void TimerInit() { memset(&timer, 0, sizeof(timer)); if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency)) { timer.performance_timer = FALSE; timer.mm_timer_start = timeGetTime(); timer.resolution = 1.0f/1000.0f; timer.frequency = 1000; timer.mm_timer_elapsed = timer.mm_timer_start; } else { QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start); timer.performance_timer = TRUE; timer.resolution = (float) (((double)1.0f)/((double)timer.frequency)); timer.performance_timer_elapsed = timer.performance_timer_start; } }
Ahora, recogemos el tiempo actual y lo restamos del inicial:
float TimerGetTime() { __int64 time; if (timer.performance_timer) { QueryPerformanceCounter((LARGE_INTEGER *) &time); return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f; } else { return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f; } }
Puedes sacar algo de cambios de resolución o de convertir la aplicación a una de pantalla completa si lees el artículo de OpenGL que habla de ello, encontrarás información sobre un DEVMODE, la función ChangeDisplaySettings y EnumDisplaySettings.
Existen otras funciones que pueden serte de utilidad si haces aplicaciones de este estilo:
MessageBox(NULL, “Mensaje”, “Título”, NULL);
El primer parámetro corresponde al hwnd. Luego viene el mensaje y el título. En el último parámetro podremos elegir que icono y que botón queremos que aparezcan (icono | boton).
Luego podrá devolver distintos valores: IDNO, IDOK, IDCANCEL…
Tenemos que crear el menú como resource y luego asignarlo a la WNDCLASS.
winclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
case WM_COMMAND: switch (wParam) { case ID_MENU1_MOSTRARCUADRO: ... break; case ID_MENU2_CONFIG: ... break; } break;
Las variables que necesitamos:
HDC hdcBackBuffer; HDC hdc; HBITMAP BMPantes; HBITMAP BMPahora;
Para conseguir un hdc compatible con el de la ventana utilizamos el método CreateCompatibleDC.
hdcBackBuffer = CreateCompatibleDC(NULL); hdc = GetDC(hwnd); BMPahora = CreateCompatibleBitmap(hdc, AnchoVent, AltoVent); BMPantes = (HBITMAP) SelectObject(hdcBackBuffer, BMPahora);
Utilizaremos la función BitBlt para pintar sobre los hdc:
BitBlt(hdcBackBuffer, 0, 0, AnchoVent, AltoVent, NULL, NULL, NULL, WHITENESS); RectX += velX; RectY += velY; SelectObject(hdcBackBuffer, Brocha); Ellipse(hdcBackBuffer, RectX, RectY, RectX+tam,RectY+tam); BitBlt(ps.hdc, 0, 0, AnchoVent, AltoVent, hdcBackBuffer, 0, 0, SRCCOPY);
Si la ventana cambia de tamaño tendremos que hacer los cambios adecuados en los bitmaps creados:
SelectObject(hdcBackBuffer, BMPantes); DeleteObject(BMPahora); hdc = GetDC(hwnd); BMPahora = CreateCompatibleBitmap(hdc,AnchoVent,AltoVent); ReleaseDC(hwnd, hdc); SelectObject(hdcBackBuffer, BMPahora);
La función BitBlt hace un volcado pixel a pixel de un hdc a otro, recibe los siguientes parámetros:
Para ver las flags y más información sobre la función: MSDN - BitBlt
void CALLBACK TimerProc(hwnd, uMsg, idEvent, dwTime) {...
MAKEINRESOURCE, a la que le pasamos el identificador del elemento deseado; esta devolverá un objeto que tendremos que castear.LoadIcon y para cargar un cursor la LoadCursor.WndClass.hIcon= LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1)); WndClass.hCursor = LoadCursor (GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_CURSOR1));
ZeroMemory llena un bloque de memoria con ceros (lo limpia). Se le pasa un puntero y un tamaño en int (con el sizeof ya va bien). Perfecto para inicializar estructuras y muy parecido al memset.