Rozdział XXVI - Ekran startowy


Teraz, gdy już poznaliśmy sposoby wykorzystania grafiki rastrowej, możemy przejść dalej - do bardziej twórczego jej zastosowania. Oto ekran startowy.

 

 

Załaduj {ten przykład}

 

Teoria

Ekran startowy (splash screen) jest oknem, które nie posiada paska tytułowego, przycisków systemowych, brzegu i wyświetla grafikę rastrową przez pewien czas, a później znika automatycznie. Zwykle stosuje się go podczas startu programu do wyświetlenia logo lub do odwrócenia uwagi użytkownika, gdy program dokonuje przydługawej inicjalizacji. Na tej lekcji zaimplementujemy w tworzonym programie ekran startowy.

Pierwszym krokiem jest dołączenie grafiki rastrowej do pliku zasobów. Jednakże, gdy przemyślisz to, okaże się, iż marnujemy cenną pamięć do załadowania grafiki używanej w programie tylko jeden raz i przechowywanie jej aż do jego zakończenia. Lepszym rozwiązaniem jest utworzenie biblioteki DLL "zasobów", która zawiera grafikę rastrową i jest przeznaczona tylko do wyświetlenia ekranu startowego. W ten sposób możesz załadować bibliotekę DLL, gdy chcesz wyświetlić ekran startowy i usunąć ją, gdy przestanie już być potrzebna. Zatem będziemy posiadali dwa moduły: program główny i bibliotekę DLL ekranu startowego. Grafikę rastrową umieścimy w zasobach biblioteki DLL.

Ogólny schemat jest następujący:

  1. Umieść grafikę rastrową w bibliotece DLL jako zasób graficzny.
  2. Główny program wywołuje LoadLibrary do załadowania tej biblioteki DLL do pamięci.
  3. Wywołana zostaje funkcja wejścia biblioteki DLL. Utworzy ona licznik czasu i ustawi okres, przez który będzie wyświetlany ekran startowy. Następnie zarejestruje i utworzy okno bez paska tytułowego i brzegu i wyświetli grafikę rastrową w jego obszarze roboczym.
  4. Gdy minie zadany okres czasu, ekran startowy zostanie usunięty i sterowanie jest zwracane do programu głównego.
  5. Główny program wywoła FreeLibrary, aby usunąć z pamięci bibliotekę DLL i przejść do swoich dalszych zadań.

Szczegółowo zbadamy te mechanizmy.

 

Ładowanie i usuwanie biblioteki DLL

Przy pomocy funkcji LoadLibrary można dynamicznie załadować bibliotekę DLL. Funkcja ta ma następującą składnię:

 

LoadLibrary PROTO lpDLLName:DWORD

 

Przyjmuje ona tylko jeden parametr: adres nazwy biblioteki DLL, którą chcemy załadować do pamięci. Jeśli wywołanie to się powiedzie, zwróci uchwyt modułu biblioteki DLL, w przeciwnym wypadku zwracana jest wartość NULL.

Aby usunąć bibliotekę DLL, wywołujemy FreeLibrary:

 

FreeLibrary PROTO hLib:DWORD

 

Przyjmuje ona jeden parametr: uchwyt modułu biblioteki DLL, którą chcemy usunąć. Zwykle uchwyt ten otrzymujesz jako rezultat wywołania funkcji LoadLibrary

 

Jak używać licznik czasu

Najpierw musisz stworzyć licznik czasu (timer) za pomocą funkcji SetTimer:

 

SetTimer PROTO hWnd:        DWORD,\
               TimerID:     DWORD,\
               uElapse:     DWORD,\
               lpTimerFunc: DWORD

Jeśli wywołanie funkcji SetTimer się powiedzie, to zwróci ona numer ID utworzonego licznika czasu. W przeciwnym wypadku zwróci NULL. Zatem nie powinieneś na numer licznika czasu używać liczby 0. Licznik czasu można utworzyć na dwa sposoby:

W naszym przykładzie użyjemy pierwszego rozwiązania.

Gdy minie zadany okres czasu, zostaje wysłana wiadomość WM_TIMER do okna powiązanego z licznikiem czasu. Na przykład, jeśli określisz uElapse na 1000, twoje okno będzie otrzymywało co każdą sekundę wiadomość WM_TIMER.

Gdy licznik czasu przestanie ci już być potrzebny, usuń go za pomocą funkcji KillTimer:

 

KillTimer PROTO hWnd:DWORD, TimerID:DWORD

 

Przykład

Plik SPLASH.ASM

 

.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 "Ekran startowy", 0
Libname   DB "splashdll.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

 

Plik SPLASHDLL.ASM

 

.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     ;Gdy DLL jest ładowana
        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, 320, 400, 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                           ;Część późniejszego wywołania MoveWindow
        mov    eax, DlgRect.bottom         ;Pobieramy spód okna dialogowego
        sub    eax, DlgRect.top            ;Obliczamy wysokość okna
        mov    DlgHeight, eax              ;Wynik zapamiętujemy w zmiennej
        push   eax                         ;Na stos dla wywołania MoveWindow
        mov    eax, DlgRect.right          ;Prawy bok naszego okna dialogowego
        sub    eax, DlgRect.left           ;Obliczamy szerokość okna
        mov    DlgWidth, eax               ;Zapamiętujemy wynik
        push   eax                         ;Na stos dla wywołania MoveWindow
        mov    eax, DesktopRect.bottom     ;Spód okna pulpitu
        sub    eax, DlgHeight              ;Odejmujemy wysokość naszego okna
        shr    eax, 1                      ;Dzielimy przez 2, co daje środek ekranu
        push   eax                         ;Na stos dla MveWindow
        mov    eax, DesktopRect.right      ;Pobieramy prawy bok  okna pulpitu
        sub    eax, DlgWidth               ;Odejmujemy szerokość okna
        shr    eax, 1                      ;Dzielimy wynik przez 2
        push   eax                         ;Na stos dla MoveWindow
        push   hWnd                        ;Na stos uchwyt okna
        call   MoveWindow                  ;Przesuwamy okno
        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 BitBlt, hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight,\
                       hMemoryDC, 0, 0, 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

 

Plik SPLASHDLL.DEF

 

LIBRARY SPLASHDLL.DLL

 

Plik SPLASHDLL.RC

 

MySplashBMP BITMAP "JOURNEY.BMP"

 

Analiza

Najpierw zbadajmy kod w głównym programie.

 

start:
    INVOKE LoadLibrary, ADDR Libname
    .IF eax!=NULL
       INVOKE FreeLibrary, eax
    .ENDIF

 

Wywołujemy funkcję LoadLibrary do załadowania biblioteki DLL o nazwie splashdll.dll. A po tym fakcie usuwamy bibliotekę z pamięci przy pomocy FreeLibrary. Funkcja LoadLibrary nie powróci, aż biblioteka DLL zakończy swoją inicjalizację.

To wszystko co robi program główny. Interesujący fragment znajdziemy w bibliotece DLL.

 

DllEntry PROC hInst:DWORD, reason:DWORD, reserved1:DWORD

    .IF reason==DLL_PROCESS_ATTACH  ;Gdy DLL jest ładowana
        push hInst
        pop  hInstance 
        call ShowBitMap      
    .ENDIF
    mov eax, TRUE
    ret

DllEntry ENDP

 

Gdy biblioteka DLL zostaje załadowana, system Windows wywołuje jej funkcję wejściową ze znacznikiem DLL_PROCESS_ATTACH. Korzystamy z tej okazji do wyświetlenia ekranu startowego. Najpierw zachowujemy do późniejszego wykorzystania uchwyt egzemplarza biblioteki DLL. Następnie wywołujemy funkcje o nazwie ShowBitMap do przeprowadzenia właściwej operacji. Funkcja ShowBitMap rejestruje klasę okna, tworzy okno i wchodzi jak zwykle do pętli wiadomości. Oto interesujący fragment w wywołaniu CreateWindowEx:

 

    INVOKE CreateWindowEx, NULL, ADDR ClassName, NULL,\
                           WS_POPUP, CW_USEDEFAULT,\
                           CW_USEDEFAULT, 320, 400, NULL, NULL,\
                           hInstance, NULL

 

Zwróć uwagę, iż styl okna to tylko WS_POPUP, co utworzy okno bez brzegów i paska tytułowego. Rozmiary okna ustalamy na 320x400, aby obszar roboczy obejmował w całości nasz obrazek. Teraz gdy okno zostało utworzone, w sekcji obsługi wiadomości WM_CREATE przenosimy okno na środek ekranu za pomocą poniższego kodu:

 

    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

 

Pobiera on wymiary pulpitu i okna, a następnie oblicza odpowiednie współrzędne lewego górnego narożnika okna, aby je umieścić na środku. Stosujemy następujące wzory:

 

x = (szerokość pulpitu - szerokość okna) / 2y = (wysokość pulpitu - wysokość okna) / 2

 

    INVOKE LoadBitmap, hInstance, ADDR BitmapName
    mov    hBitMap, eax
    INVOKE SetTimer, hWnd, 1, 2000, NULL
    mov    TimerID, eax

 

Następnie ładuje grafikę rastrową z zasobów za pomocą funkcji LoadBitmap i tworzy licznik czasu z numerem identyfikacyjnym ID równym 1 o okresie czasu powiadamiania 2 sekundy. Licznik czasu będzie co 2 sekundy wysyłał do tego okna wiadomości WM_TIMER.

 

    .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 BitBlt, hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hMemoryDC, 0, 0, SRCCOPY
        INVOKE SelectObject, hMemoryDC, hOldBmp
        INVOKE DeleteDC, hMemoryDC
        INVOKE EndPaint, hWnd, ADDR ps
 

 

Gdy okno otrzyma wiadomość WM_PAINT, tworzy ono kontekst urządzenia w pamięci, wprowadza grafikę do tego kontekstu, odczytuje jej wymiary przy pomocy funkcji GetObject, a następnie umieszcza grafikę w obszarze roboczym okna wywołując funkcje BitBlt. Po wykonaniu tych operacji usuwamy kontekst urządzenia w pamięci.

 

    .ELSEIF uMsg==WM_LBUTTONDOWN               
        INVOKE DestroyWindow, hWnd

 

Użytkownik może się zdenerwować, jeśli będzie musiał czekać bezczynnie, aż ekran startowy zniknie z pulpitu. Dlatego dajemy mu wybór. Gdy kliknie lewym przyciskiem myszki na ekranie startowym, zniknie on. Dlatego właśnie obsługujemy wiadomość WM_LBUTTONDOWN w bibliotece DLL. Po odebraniu tej wiadomości okno jest usuwane za pomocą wywołania DestroyWindow.

 

    .ELSEIF uMsg==WM_TIMER
        INVOKE SendMessage, hWnd, WM_LBUTTONDOWN, NULL, NULL
        INVOKE KillTimer, hWnd, TimerID

 

Jeśli użytkownik zdecyduje się poczekać, ekran startowy zniknie po upływie zadanego okresu czasu (w naszym przykładzie jest to 2 sekundy). Osiągamy ten efekt obsługując wiadomość WM_TIMER. Po jej odebraniu zamykamy okno wysyłając do niego wiadomość WM_LBUTTONDOWN. To w celu uniknięcia dublowania kodu. Nie korzystamy już dalej z licznika czasu, zatem usuwamy go za pomocą KillTimer.

Gdy okno zamknie się, biblioteka zwróci sterowanie do głównego programu.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.