Una splash screen [pantalla de salpicadura] es una ventana que no tiene barra de título, ni caja de menú de sistema, ni borde, que despliega un bitmap por un lapso de tiempo y luego desaparece automáticamente. Usualmente es usada durante el inicio de un programa, para desplegar el logo del programa o distraer la atención del usuario mientras el programa hace alguna inicialización extendida. En este tutorial implementaremos un splash screen.
El primer paso es incluir el bitmap en el archivo de recursos. Sin embargo, si piensas un poco en esto, verás que hay un consumo precioso de memoria cuando se carga un bitmap que será usado sólo una vez y se mantiene en la memoria hasta que el programa es cerrado. Una mejor solución es crear una DLL de *recursos* que contenga el bitmap y que tenga el único propósito de desplegar la splash screen. De esta manera, puedes cargar la DLL cuando quieras desplegar la splash screen y descargarla cuando ya no sea necesaria. Así que tendremos dos módulos: El programa principal y la DLL con el splash. Pondremos el bitmap dentro de los recursos de la DLL.
El esquema general es como sigue:
LoadLibrary proto lpDLLName:DWORD
Toma sólo un parámetro: la dirección del nombre de la DLL que quieres cargar en memoria. Ssi la llamada es satisfactoria, regresa el manejador del módulo de la DLL sino regresa NULL.
Para descargar una DLL, llama a FreeLibrary:
FreeLibrary proto hLib:DWORDToma un parámetro: el manejador del módulo de la DLL que quieras descargar. Normalmente, obtienes el manejador a partir de LoadLibrary
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORDPuedes crear un temporizador de dos maneras :hWnd es el manejador de una ventana que recibirá el mensaje de notificación del temporizador. Este parámetro puede ser NULL para especificar que no hay ventana asociada con el temporizador.
TimerID es un valor definido por el usuario empleado para el ID del temporizador.
uElapse es el valor del lapso de tiempo en milisegundos.
lpTimerFunc Es la dirección de una función que procesará los mensajes de notificación del temporizador. Si pasas NULL, los mensajes del temporizador serán enviados a la ventana especificada por el parámetro hWnd.SetTimer regresa el ID del temporizador si tiene éxito. De otra manera regresa NULL. Así que es mejor usar el ID del temporizador de 0.
Usaremos la primera aproximación en este ejemplo.
Cuando se cumple el período de tiempo, se envía el mensaje WM_TIMER a la ventana asociada con el temporizador. Por ejemplo, si especificas un uElapse de 1000, tu ventana recibirá un mensaje WM_TIMER cada segundo.
Cuando ya no necesites el temporizador, lo destruyes con KillTimer:
KillTimer proto hWnd:DWORD, TimerID:DWORD
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SplashDemoWinClass",0
AppName db "Splash Screen Example",0
Libname db "splash.dll",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke LoadLibrary,addr Libname
.if eax!=NULL
invoke FreeLibrary,eax
.endif
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
;--------------------------------------------------------------------
;
La DLL Bitmap
;--------------------------------------------------------------------
.386
.model flat, stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
BitmapName db "MySplashBMP",0
ClassName db "SplashWndClass",0
hBitMap dd 0
TimerID dd 0
.data
hInstance dd ?
.code
DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH ; When the dll is loaded
push hInst
pop hInstance
call ShowBitMap
.endif
mov eax,TRUE
ret
DllEntry Endp
ShowBitMap proc
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF
WNDCLASSEX
mov wc.style, CS_HREDRAW
or CS_VREDRAW
mov wc.lpfnWndProc,
OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET
ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,0
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr
wc
INVOKE CreateWindowEx,NULL,ADDR
ClassName,NULL,\
WS_POPUP,CW_USEDEFAULT,\
CW_USEDEFAULT,250,250,NULL,NULL,\
hInstance,NULL
mov hwnd,eax
INVOKE ShowWindow, hwnd,SW_SHOWNORMAL
.WHILE TRUE
INVOKE GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
ShowBitMap endp
WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL ps:PAINTSTRUCT
LOCAL hdc:HDC
LOCAL hMemoryDC:HDC
LOCAL hOldBmp:DWORD
LOCAL bitmap:BITMAP
LOCAL DlgHeight:DWORD
LOCAL DlgWidth:DWORD
LOCAL DlgRect:RECT
LOCAL DesktopRect:RECT
.if uMsg==WM_DESTROY
.if hBitMap!=0
invoke DeleteObject,hBitMap
.endif
invoke PostQuitMessage,NULL
.elseif uMsg==WM_CREATE
invoke GetWindowRect,hWnd,addr DlgRect
invoke GetDesktopWindow
mov ecx,eax
invoke GetWindowRect,ecx,addr DesktopRect
push 0
mov eax,DlgRect.bottom
sub eax,DlgRect.top
mov DlgHeight,eax
push eax
mov eax,DlgRect.right
sub eax,DlgRect.left
mov DlgWidth,eax
push eax
mov eax,DesktopRect.bottom
sub eax,DlgHeight
shr eax,1
push eax
mov eax,DesktopRect.right
sub eax,DlgWidth
shr eax,1
push eax
push hWnd
call MoveWindow
invoke LoadBitmap,hInstance,addr BitmapName
mov hBitMap,eax
invoke SetTimer,hWnd,1,2000,NULL
mov TimerID,eax
.elseif uMsg==WM_TIMER
invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
invoke KillTimer,hWnd,TimerID
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemoryDC,eax
invoke SelectObject,eax,hBitMap
mov hOldBmp,eax
invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
invoke StretchBlt,hdc,0,0,250,250,\
hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
invoke SelectObject,hMemoryDC,hOldBmp
invoke DeleteDC,hMemoryDC
invoke EndPaint,hWnd,addr ps
.elseif uMsg==WM_LBUTTONDOWN
invoke DestroyWindow,hWnd
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
End DllEntry
invoke LoadLibrary,addr Libname
.if eax!=NULL
invoke FreeLibrary,eax
.endif
Llamamos a LoadLibrary para cargar la DLL llamada "splash.dll". Y después de eso, descargarla de la memoria con FreeLibrary. LoadLibrary no regresará hasta que la DLL haya terminado con su inicialización.
Eso es todo lo que hace le programa principal. La parte interesante está en la DLL.
.if reason==DLL_PROCESS_ATTACH ; Cuando la dll es cargada
push hInst
pop hInstance
call ShowBitMap
Cuando la DLL es cargada, Windows llama a su punto de entrada con la bandera DLL_PROCESS_ATTACH. Aprovechamos esta oportunidad para desplegar la splash screen. Primero almacenamos el manejador de instacia de la DLL para usarla luego. Luego llamamos a una función llamada ShowBitMap para realizar la tarea. ShowBitMap registra una clase de ventana, crea una ventana e introduce el bucle de mensaje como es usual. La parte interesante está en la llamada a CreateWindowEx:
INVOKE CreateWindowEx,NULL,ADDR
ClassName,NULL,\
WS_POPUP,CW_USEDEFAULT,\
CW_USEDEFAULT,250,250,NULL,NULL,\
hInstance,NULL
Nota que el estilo de la ventana es sólo WS_POPUP lo cual hará que la ventana no tenga bordes ni tampoco encabezamiento [caption]. También limitamos el ancho y la altura de la ventana a 250x250 pixeles.
Ahora cuando la ventana es creada durante en el manejador del WM_CREATE, movemos la ventana al centro del monitor con el siguiente código.
invoke GetWindowRect,hWnd,addr DlgRect
invoke GetDesktopWindow
mov ecx,eax
invoke GetWindowRect,ecx,addr DesktopRect
push 0
mov eax,DlgRect.bottom
sub eax,DlgRect.top
mov DlgHeight,eax
push eax
mov eax,DlgRect.right
sub eax,DlgRect.left
mov DlgWidth,eax
push eax
mov eax,DesktopRect.bottom
sub eax,DlgHeight
shr eax,1
push eax
mov eax,DesktopRect.right
sub eax,DlgWidth
shr eax,1
push eax
push hWnd
call MoveWindow
Regresan las siguientes dimensiones del escritorio y la ventana luego calcula la coordenada apropiada de la esquina izquierda superior de la ventana para convertirse en centro.
invoke LoadBitmap,hInstance,addr BitmapName
mov hBitMap,eax
invoke SetTimer,hWnd,1,2000,NULL
mov TimerID,eax
Lo siguiente es cargar el bitmap desde el recurso con LoadBitmap y crea un temporizador con el ID de temporizador de 1 y el intervalo de tiempo de 2 segundos. El temporizador enviará mensajes WM_TIMER a la ventana cada 2 segundos.
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemoryDC,eax
invoke SelectObject,eax,hBitMap
mov hOldBmp,eax
invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap
invoke StretchBlt,hdc,0,0,250,250,\
hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
invoke SelectObject,hMemoryDC,hOldBmp
invoke DeleteDC,hMemoryDC
invoke EndPaint,hWnd,addr ps
Cuando la ventana recibe el mensaje WM_PAINT, crea un DC de memoria, selecciona el bitmap dentro del DC de memoria, obtiene el tamaño del bitmap con GetObject luego pone el bitmap en la ventana llamando a StretchBlt que se ejecuta como BitBlt pero puede estrechar o comprimir el bitmap a la dimensión deseada. En este caso, queremos que el bitmap se fije dentro de la ventana así que usamos StretchBlt en vez de BitBlt. Después de eso, borramos el DC de memoria.
.elseif uMsg==WM_LBUTTONDOWN
invoke DestroyWindow,hWnd
Sería frustrante para el usuario tener que esperar hasta que la splash screen desaparezca. Podemos suministrarle al usuario un elección. Cuando haga click sobre la splash screen, desaparecerá. Por eso es que necesitamos procesar el mensaje WM_LBUTTONDOWN en la DLL. Durante la recepción del mensaje, la ventana es destruida por la llamada a DestroyWindow.
.elseif uMsg==WM_TIMER
invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
invoke KillTimer,hWnd,TimerID
Si el usuario elige esperar, la splash screen desaparecerá cuando el lapso de tiempo especificado se haya cumplido (en nuestro ejemplo, es 2 segundos). Podemos hacer esto procesando el mensaje WM_TIMER. Al recibir este mensaje, cerramos la ventana enviando el mensaje WM_LBUTTONDOWN a la ventana. Esto es para evitar duplicación de código. No tenemos que emplear luego el temporizador, así que lo destruimos llamando a KillTimer.
Cuando la
ventana es cerrada, la DLL regresará el control al programa principal.
[Iczelion's Win32 Assembly Homepage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r