Iczelion - 26 - Splash Screen

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:
  1. Speichern Sie das Bitmap in eine DLL als Bitmap-Ressource
  2. Das Haupt-Programm ruft LoadLibrary auf, um die DLL in den Speicher zu laden.
  3. 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.
  4. Wenn das spezifizierte Zeit-Intervall verstrichen ist, wird der Splash Screnn vom Bildschirm entfernt und die Kontrolle zurück an das Haupt-Programm gegeben.
  5. 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