Con el fin de amenizar este, ya de por si, árido tema voy a escribir con vosotros una aplicación salvapantallas en delphi que será totalmente funcional y útil.
El bucle de mensajes:
En primer lugar ya sabemos que Windows se maneja a base de mensajes y que lo hace a través de un bucle de mensajes. Esos mensajes son tratados en una función especial de tratamiento de mensajes. Cuando creamos una ventana tenemos de definir una de las clases preexistentes o una nueva definida por usuario.
Todo bucle de mensajes en Windows tiene mas o menos este aspecto:
while(GetMessage(Msg, 0, 0, 0)) do begin TranslateMessage(Msg); DispatchMessage(Msg); end;
GetMessage: Espera hasta recibir un mensaje entrante.
TranslateMessage: Traduce los mensajes del teclado de Virtual-Key a caracteres produciendo un mensaje tipo WM_CHAR o WM_DEADCHAR.
DispatchMessage: envía el mensaje a la función de tratamiento de mensajes de la ventana destinataria.
En nuestro primer proyecto vamos a crear una ventana que ocupe toda la pantalla y la vamos a pintar de un solo color tratando el mensaje WM_PAINT y la vamos a destruir con WM_DESTROY.
Algunos habréis visto algo de código de ejemplo en las páginas de ayuda de Microsoft y os resultará familiar una función principal como esta:
function WinMain(hInstance, hPrevInstance: THISTANCE; lpCmdLine: LPSTR; nCmdShow: integer): cardinal; stdcall;
En Delphi esta función no está predefinida en ningún proyecto, así que para poder acercar nuestro código de ejemplo a lo que podáis ver en la web, nos la vamos a currar. El siguiente código va a ser común en todos los ejemplos y constituye el inicio de nuestro programa:
program BanScr_1; uses Windows, Unit1 in 'Unit1.pas'; var info: STARTUPINFO; CmdLine: PCHAR; n: integer; begin GetStartupInfo(info); CmdLine:= GetCommandLine(); n:=0; //Navegamos por CmdLine a través de comillas y espacios //para aislar solo los parámetros pasados a nuestra App. if CmdLine^ = #34 then repeat inc(n); until(CmdLine[n] = #34); while(CmdLine[n]<>#32) do inc(n); inc(n); if n = lstrlen(CmdLine) then CmdLine:= nil else CmdLine:= CmdLine+n; ExitProcess(WinMain(GetModuleHandle(nil), 0, CmdLine, info.wShowWindow)); end.
El código anterior se encarga de crear los parámetros para la función main, hInstance de la aplicación, la línea de comandos que se le pasa (eliminando el nombre del ejecutable y la ruta) y la visibilidad de la misma. Vemos el uso exclusivo de la API incluso para el tratamiento de cadenas de texto. Este proyecto base puede servir para cualquier aplicación Windows a bajo nivel que posteriormente queramos escribir.
La función WinMain y el resto del programa la escribiremos en un módulo aparte: Unit1.pas
function WinMain(hInstance, hPrevInstance: THISTANCE; lpCmdLine: LPSTR; nCmdShow: integer): cardinal; stdcall; var Wnd: HWND; WScreen, HScreen: integer; WinClass: WNDCLASS; Msg: TMsg; begin WScreen:= GetSystemMetrics(SM_CXSCREEN); HScreen:= GetSystemMetrics(SM_CYSCREEN); ZeroMemory(@WinClass, sizeof(WinClass)); WinClass.lpfnWndProc:= @WindowProc; WinClass.lpszClassName:= 'WindowsScreenSaverClass'; // Registramos nuestra clase de ventana RegisterClass(WinClass); //Creamos la ventana Wnd:= CreateWindowEx(0, WinClass.lpszClassName, 'SCR', WS_VISIBLE + WS_OVERLAPPEDWINDOW, 0, 0, WScreen, HScreen, HWND_DESKTOP, 0, 0, nil); // El bucle de mensajes while(GetMessage(Msg, 0, 0, 0)) do begin TranslateMessage(Msg); DispatchMessage(Msg); end; Result:= 0; end;
En este caso estamos creando una nueva clase de ventana que llamamos SCR y a la que asociamos una función de tratamiento de mensajes WindowProc. En principio creamos una ventana de esa clase que va a ocupar toda la pantalla y va a tener caption y botones de sistema. Esa ventana va a responder a dos mensajes: WM_PAINT y WM_DESTROY. En ella sólo vamos a dibujar un rectángulo blanco que ocupa toda su área cliente.
Función de tratamiento de mensajes:
Esta función tendrá la siguiente forma para ir respondiendo a los diferentes mensajes que le correspondan. Cada ventana estará diseñada para responder a los que nos parezcan.
function WindowProc(Wnd: HWND; uMsg: Cardinal; wParam, lParam: Integer): Integer; stdcall; begin case uMsg of WM_PAINT: .… WM_CHAR: .…. ..... WM_DESTROY: PostQuitMessage(0); //Destruimos la ventana else // Función por defecto de tratamiento de mensajes. Result:= DefWindowProc(Wnd, uMsg, wParam, lParam); end; end; end;
Para asociar la función con la ventana tenemos la API RegisterClass Esta API usa como parámetro una estructura WNDCLASS que, entre otros valores, porta un puntero a la función de tratamiento de mensajes.
Este es el código completo:
unit Unit1; interface uses Windows, Messages; type THISTANCE = HMODULE; function WinMain(hInstance, hPrevInstance: HMODULE; lpCmdLine: LPSTR; nCmdShow: integer): cardinal; stdcall; implementation function WindowProc(Wnd: HWND; uMsg: Cardinal; wParam, lParam: Integer): Integer; stdcall; var ps: PAINTSTRUCT; DC: HDC; Brush: HBRUSH; Rect: TRect; begin Result := 0; case uMsg of WM_PAINT: begin DC:= BeginPaint(Wnd, ps); //Comenzamos GetWindowRect(Wnd, Rect); Brush:= CreateSolidBrush($FFFFFF); //brocha blanca SelectObject(DC, Brush); Rectangle(DC, 0, 0, Rect.right, Rect.bottom); //Pintamos un rectángulo DeleteObject(Brush); //Destruimos la brocha EndPaint(Wnd, ps); end; WM_DESTROY: PostQuitMessage(0); //Destruimos la ventana else // Función por defecto de tratamiento de mensajes. Result:= DefWindowProc(Wnd, uMsg, wParam, lParam); end; end; function WinMain(hInstance, hPrevInstance: THISTANCE; lpCmdLine: LPSTR; nCmdShow: integer): cardinal; stdcall; var Wnd: HWND; WScreen, HScreen: integer; WinClass: WNDCLASS; Msg: TMsg; begin WScreen:= GetSystemMetrics(SM_CXSCREEN); HScreen:= GetSystemMetrics(SM_CYSCREEN); ZeroMemory(@WinClass, sizeof(WinClass)); WinClass.lpfnWndProc:= @WindowProc; WinClass.lpszClassName:= 'WindowsScreenSaverClass'; // Registramos nuestra clase de ventana RegisterClass(WinClass); //Creamos la ventana Wnd:= CreateWindowEx(0, WinClass.lpszClassName, 'SCR', WS_VISIBLE + WS_OVERLAPPEDWINDOW, 0, 0, WScreen, HScreen, HWND_DESKTOP, 0, 0, nil); // El bucle de mensajes while(GetMessage(Msg, 0, 0, 0)) do begin TranslateMessage(Msg); DispatchMessage(Msg); end; Result:= 0; end; end.
Para no agobiar mas esta parte paramos en este punto. En la siguiente añadiremos mas funcionalidades a nuestro embrión de salvapantallas.
Saludos.
Edito para resubir el archivo de código