Tutorial 26: Splash Screen

Ahora que sabemos cómo usar un bitmap, podemos progresar hacia un uso más creativo de él. Splash screen. Baja el ejemplo.

Teoría

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:

  1. Poner el bitmap dentro de la DLL como un recurso bitmap
  2. El programa principal llama a LoadLibrary para cargar la dll en memoria
  3. Se llama a la función del punto de entrada de la DLL. Se creará un temporizador y se establecerá cuánto tiempo permanecerá desplegada la splash screen. Luego se registrará y creará una ventana sin encabezado y sin borde, y desplegará el bitmap en el área cliente.
  4. Cuando se cumple el lapso de tiempo establecido, la splash screen es removida de la pantalla y el control es regresado a la ventana principal
  5. El programa principal llama a FreeLibrary para descargar la DLL de la memoria y luego se dirigirá a realizar la tarea que se supone que hará.

Examinaremos los mecanismos en detalle.

Cargar/Descargar una DLL

Puedes cargar dinámicamente una DLL con la función LoadLibrary que tiene la siguiente sintaxis:

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:DWORD

Toma un parámetro: el manejador del módulo de la DLL que quieras descargar. Normalmente, obtienes el manejador a partir de LoadLibrary

Cómo usar un temporizador [timer]

Primero, debes crear un temporizador con SetTimer:

SetTimer  proto  hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

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.

Puedes crear un temporizador de dos maneras :

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

Ejemplo:

;-----------------------------------------------------------------------
;                         El programa principal
;-----------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

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

Análisis:

Primero examinaremos el código en la ventana principal.

 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.


Index

Next

[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

www.000webhost.com