Tutorial 26: Splash Screen
Nun, da wir wissen, wie man Bitmaps benutzt, können wir zu einer etwas kreativeren Benutzung dieser kommen. Splash Screen.
Laden Sie das Beispiel herunter.
Theorie
Ein Splash Screen ist ein Fenster, dass keine Titelleiste, keine System-Menü-Box, kein Rand hat und ein Bitmap für einige Zeit anzeigt und dann automatisch wieder verschwindet. Sie werden für gewöhnlich während des Ladens benutzt, um das Logo anzuzeigen oder um den Benutzer abzulenken, während das Programm einige längere Ladevorgänge bearbeitet. Wir werden in diesem Tutorial ein Splash Screen implementieren.
Der erste Schritt ist, das Bitmap in eine Ressource-Datei einzufügen. Wenn Sie darüber nachdenken, ist es Verschwendung wertvollen Speichers, ein Bitmap zu laden, was nur einmal angezeigt wird und dann bis zur Schließung des Programms im Speicher zu behalten. Eine bessere Lösung wäre eine *Ressource* DLL zu erstellen, die das Bitmap enthält und die einzige Aufgabe hat, den Splash Screen anzuzeigen und wieder aus dem Speicher zu werfen, wenn es nicht mehr benötigt wird. Demnach haben wir zwei Module: das Hauptprogramm und die Splash DLL. Wir werden das Bitmap in der DLL-Ressource unterbringen.
Das generelle Schema ist wie folgt:
-
Speichern Sie das Bitmap in eine DLL als Bitmap-Ressource
-
Das Haupt-Programm ruft LoadLibrary auf, um die DLL in den Speicher zu laden.
-
Die DLL Einsprungs-Funktion der DLL wird aufgerufen. Sie wird einen Timer erzeugen und die Dauer der Zeit, die der Splash Screen angezeigt wird, festlegen. Als nächstes wird ein Fenster registriert und erzeugt, ohne Titelleiste und Umrahmung und das Bitmap wird in der Client Area angezeigt.
-
Wenn das spezifizierte Zeit-Intervall verstrichen ist, wird der Splash Screnn vom Bildschirm entfernt und die Kontrolle zurück an das Haupt-Programm gegeben.
-
Das Hauptprogramm ruft FreeLibrary auf, um die DLL aus dem Speicher zu entfernen und fährt mit dem fort, was immer seine Aufgabe auch sein mag.
Wir werden diese Mechanismen im Detail untersuchen.
Laden/Entfernen einer DLL
Sie können eine DLL mit der LoadLibrary Funktion dynamisch laden. Die Funktion hat folgende Syntax:
LoadLibrary proto lpDLLName:DWORD
Sie benötigt lediglich einen Parameter: die Adresse des Namens der DLL, die Sie in den Speicher laden möchten. Wenn der Aufruf erfolgreich war, wird das Modul-Handle der DLL zurückgegeben, ansonsten NULL.
Um die DLL zu entfernen, rufen Sie FreeLibrary auf:
FreeLibrary proto
hLib:DWORD
Sie benötigt auch nur einen Parameter: das Modul-Handle der DLL, die Sie entfernen möchten. Normalerweise, bekommen Sie das Handle von der LoadLibrary Funktion.
Wie man einen Timer benutzt
Als erstes müssen Sie einen Timer mit SetTimer erzeugen:
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD
hWnd
ist das Handle des Fensters, dass die Timer-Benachrichtigungs-Nachrichten erhält. Dieser Parameter kann gleich NULL sein, wenn kein Fenster dem Timer zugehörig sein soll.
TimerID
ist ein benutzerdefinierter Wert, der als ID für den Timer benutzt wird.
uElapse
ist Intervallangabe in Millisekunden.
lpTimerFunc
ist die Adresse der Funktion, die ausgeführt wird, wenn eine Timer-Benachrichtigungs-Nachricht geschickt wird. Wenn Sie NULL als Parameter übergeben, werden die Timer-Nachrichten an das im hWnd Parameter spezifizierte Fenster gesendet.
SetTimer gibt die ID des Timers zurück, wenn die Ausführung erfolgreich war, ansonsten NULL. Demnach wäre es ganz gut, nicht 0 als Timer ID zu benutzen.
Sie können einen Timer auf zwei Arten erzeugen:
-
Wenn Sie ein Fenster haben und dieses die Nachrichten für den Timer empfangen soll, müssen Sie alle vier Parameter SetTimer übergeben (lpTimerFunc muss dabei NULL sein)
-
Wenn Sie kein Fenster haben oder Sie nicht möchten, dass die Nachrichten in der Fenster-Prozedur abgearbeitet werden, müssen Sie der Funktion NULL als Fenster-Handle übergeben. Außerdem müssen Sie die Adresse der Timer-Funktion spezifizieren, die die Timer Nachrichten abarbeiten soll.
Wir werden die erste Möglichkeit in unserem Beispiel verwenden.
Wenn das Zeitintervall überschritten wurde, wird eine WM_TIMER Nachricht an das Fenster gesendet, dass dem Timer zugehörig ist. Wenn Sie zum Beispiel 1000 bei uElapse angeben, wird Ihr Fenster jede Sekunde eine WM_TIMER Nachricht erhalten.
Wenn Sie den Timer nicht mehr benötigen, zerstören Sie ihn mit KillTimer:
KillTimer
proto hWnd:DWORD, TimerID:DWORD
Beispiel:
;-----------------------------------------------------------------------
; Das Hauptprogramm
;-----------------------------------------------------------------------
.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
;--------------------------------------------------------------------
; Die Bitmap DLL
;--------------------------------------------------------------------
.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
Analye:
Wir werden uns als erstes den Code im Hauptprogramm anschauen.
invoke LoadLibrary,addr Libname
.if eax!=NULL
invoke FreeLibrary,eax
.endif
Wir rufen LoadLibrary auf, um die DLL names "splash.dll" zu laden. Und danach entfernen wir sie wieder aus dem Speicher mittels FreeLibrary. LoadLibrary kehrt solange nicht zurück, bis die DLL mit ihrer Initialisierung fertig ist.
Das ist alles was das Hauptprogramm macht. Der interessante Teil ist die DLL.
.if reason==DLL_PROCESS_ATTACH ; Wenn die DLL geladen ist
push hInst
pop hInstance
call ShowBitMap
Wenn die DLL geladen ist, ruft Windows die Einsprungs-Funktion mit dem DLL_PROCESS_ATTACH
Flag auf. Wir nehmen diese Gelegenheit war, um den Splash Screen anzuzeigen. Als erstes speichern wir das Instanz-Handle der DLL für den späteren Gebrauch. Dann rufen wir die Funktion namens ShowBitMap auf, um die wirkliche Arbeit zu verrichten. ShowBitMap registriert die Fenster-Klasse, erzeugt ein Fenster und betritt die Message-Loop wie immer. Der interessante Part ist im CreateWindowEx Aufruf:
INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\
WS_POPUP,CW_USEDEFAULT,\
CW_USEDEFAULT,250,250,NULL,NULL,\
hInstance,NULL
Beachten Sie, dass der Fenster-Stil nur WS_POPUP ist, damit das Fenster Rahmenlos und ohne Titel bleibt. Wir beschränken auch die Breite und Höhe des Fensters auf 250x250 Pixel.
Nun, da das Fenster erzeugt wurde, bewegen wir während der WM_CREATE Nachricht das Fenster in die Mitte des Bildschirms, mit folgendem Code:
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
Er holt die Dimensionen des Desktops und Fensters ein und berechnet dann die obere linke Koordinate des Fensters, damit es mittig ist.
invoke LoadBitmap,hInstance,addr BitmapName
mov hBitMap,eax
invoke SetTimer,hWnd,1,2000,NULL
mov TimerID,eax
Als nächstes wird das Bitmap aus der Ressource mit LoadBitmap geladen und erzeugt einen Timer mit der Timer ID gleich 1 und dem Zeit-Intervall von 2 Sekunden. Der Timer sendet eine WM_TIMER Nachricht alle 2 Sekunden.
.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
Wenn das Fenster die WM_PAINT Nachricht erhählt, wird ein Speicher DC erzeugt, das Bitmap im Speicher DC selektiert, die Größe des Bitmaps mit GetObject ermittelt und dann wird das Bitmap ins Fenster mit der StretchBlt-Funktion gezeichnet, welche genauso arbeitet, wie BitBlt, nur dass sie das Bitmap strecken oder aber auf die gewünschte Größe stauchen kann. In diesem Fall wollen wir das Bitmap auf die Fenstergröße anpassen, weswegen wir StretchBlt statt BitBlt benutzen. Danach löschen wir den Speicher DC.
.elseif uMsg==WM_LBUTTONDOWN
invoke DestroyWindow,hWnd
Es wäre frustrierend, wenn der Benutzer darauf warten müsste, dass der Splash Screen verschwindet. Wir können dem Benutzer eine Möglichkeit anbieten. Wenn er auf den Splash Screen klickt, verschwindet dieser. Das ist der Grund, warum wir die WM_LBUTTONDOWN Nachricht in der DLL bearbeiten müssen. Wenn diese Nachricht empfangen wird, wird das Fenster mit einem DestroyWindow Aufruf zerstört.
.elseif uMsg==WM_TIMER
invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL
invoke KillTimer,hWnd,TimerID
Wenn der Benutzer lieber wartet, verschwindet der Splash Screen nach dem spezifizierten Zeit (in unserem Beispiel sind es 2 Sekunden). Wir können das steuern, indem wir die WM_TIMER Nachricht bearbeiten. Wenn wir die Nachricht empfangen, schließen wir das Fenster, indem wir eine WM_LBUTTONDOWN Nachricht an das Fenster senden. Das vermeidet unnötigen Code. Wir haben danach keinen weiteren Gebrauch für den Timer, so dass wir ihn mit KillTimer zerstören können.
Wenn das Fenster geschlossen ist, kehrt die DLL zurück zum Hauptprogramm.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage