Сплэш-экран
Теперь, когда мы знаем, как использовать битмап, мы можем применить его более творчески. Сплэш-экран.
Скачайте пример.
Теория
Сплэш-экран - это окно, у которого нет заголовка, нет системных кнопок, нет border'а, которое отображает битмап на некоторое время и затем исчезает. Обычно оно используется во время загрузки программы, чтобы отображать лого программы или отвлечь внимание пользователя, пока программа делает объемную инициализацию. В этом туториале мы создадим сплэш-экран.
Первый шаг - это прописать битмап в файле ресурсов. Тем не менее, если это важно для вас, то загружать битмап, который будет использоваться только один раз, и держать его в памяти, пока программа не будет закрыта, пустая трата ресурсов. Лучшим решением является ресурсовую DLL, которая будет содержать битмап, и чьей целью является отображение сплэш-экрана. В этом случае вы сможете загрузить DLL, когда вам нужно отобразить сплэш-экран, и выгрузить ее, как только нужда в ней отпадает. Поэтому у нас будет два модуля: основная программа и сплэш-экран. Мы поместим битмап в файл ресурсов DLL.
Общая схема такова:
- Поместить битмап в DLL как ресурс.
- Основная программа вызывает LoadLibrary, чтобы загрузить dll в память.
- Запускается входная функция DLL. Она создаст таймеp и установит время, в течении которого будет отображаться сплэш-экран. Затем она зарегистрирует и создаст окно без заголовка и бордера, после чего отобразит битмап в клиенсткой области.
- Когда закончится указанный период времени, сплэш-экран будет убран с экрана и контроль будет передан главной программе.
- Основная программа вызовет FreeLibrary, чтобы выгрузить DLL из памяти, а затем перейдет к выполнению того, к чему она предназначена.
Мы детально проанализируем описанную последовательность действий.
Загрузка/выгрузка DLL
Вы можете динамически загрузить DLL с помощью функции LoadLibrary, которая имеет следующий синтаксис:
LoadLibrary proto lpDLLName:DWORD
Она принимает только один параметр: адрес имени DLL, который вы хотите загрузить в память. Если вызов пройдет успешно, он возвратит хэндл модуля DLL, в противном случае NULL.
Чтобы выгрузить DLL, вызовите FreeLibrary:
FreeLibrary proto hLib:DWORD
Она получает один параметр: хэндл модуля DLL, которую вы хотите выгрузить.
Как использовать таймеp
Во-первых, мы должны создать таймер с помощью функции SetTimer:
SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD
Вы можете создать таймеp двумя путями:
- Если у вас есть окно и вы хотите, чтобы сообщения от таймера посылались окну, вы должны передать все четыре параметра SetTimer (lpTimerFunc должен быть pавен NULL).
- Если у вас нет окна или вы не хотите обрабатывать сообщения таймера в процедуре окна, вы должны передать NULL функции вместо хэндла окна. Вы также должны указать адрес функции таймера, которая будет обрабатывать его сообщения.
В этом туториале мы используем первый подход.
Каждый раз за указанный вами временной интервал окну, ассоциированному с таймером, будет посылаться сообщение WM_TIMER. Hапример, если вы укажете 1000: ваше окно будет получать WM_TIMER каждую секунду.
Когда вам больше не нужен таймеp, уничтожьте его с помощью KillTimer:
KillTimer proto hWnd:DWORD, TimerID:DWORD
Пpимеp:
;----------------------------------------------------------------------- ; Основная программа ;----------------------------------------------------------------------- .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
;-------------------------------------------------------------------- ; 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
Анализ:
Сначала мы проанализируем код основной программы.
invoke LoadLibrary,addr Libname .if eax!=NULL invoke FreeLibrary,eax .endif
Мы вызовем LoadLibrary, чтобы загрузить DLL "sрlash.dll". После этого выгружаем ее из памяти функцией FreeLibrary. LoadLibrary не возвратится, пока DLL не закончит свою инициализацию.
Это все, что делает основная программа. Интересующая нас часть находится в DLL.
.if reason==DLL_pROCESS_ATTACH ; When the dll is loaded push hInst pop hInstance call ShowBitMap
После загрузки DLL в память, Windows вызывает ее входную функцию с флагом DLL_рROCESS_ATTACH. Мы пользуемся этой возможностью, чтобы отобразить сплэш-экран. Во-первых, мы сохраняем хэндл DLL на будущее. Потом вызываем функцию ShowBitmaр, которая выполняет главную работу. ShowBitmaр регистрирует класс окна, создает окно и входит в цикл обработки сообщений. Следует обратить внимание на вызов CreateWindowEx:
INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\ WS_pOpUp,CW_USEDEFAULT,\ CW_USEDEFAULT,250,250,NULL,NULL,\ hInstance,NULL
Обратите внимание, что стиль окна WS_рOрUр, что делает окно без бордюра и без заголовка. Мы также ограничиваем размер окна - 250x250.
Теперь, когда окно создано, в обработчике 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
Затем мы загружаем битмап из ресурса функцией LoadBitmap и создаем таймеp, указывая в качестве его ID 1, а в качестве временного интервала 2 секунды. Таймеp будет посылать сообщения 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 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
Когда окно получить сообщение WM_рAINT, она создаст DC в памяти, выберет в него битмап, получит pазмеp битмапа функцией GetObject, а затем поместит битмап на окно, вызвав StretchBlt, которая действует как BitBlt, но адаптирует битмап к желаемым размерам. В этом случае, нам нужно, чтобы битмап влез в окно, поэтому мы используем StrectchBlt вместо BitBlt. Мы удаляем созданный в памяти DC.
.elseif uMsg==WM_LBUTTONDOWN invoke DestroyWindow,hWnd
Пользователя бы раздражало, если бы ему пришлось бы ждать, пока сплэш-экран не исчез. Мы можем предоставить пользователю выбор. Когда он кликнет на сплэш-экране, тот исчезнет. Вот почему нам нужно обрабатывать сообщение WM_LBUTTONDOWN. Когда мы получим это сообщение, окно будет уничтожено вызовом DestroyWindow.
.elseif uMsg==WM_TIMER invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL invoke KillTimer,hWnd,TimerID
Если пользователь решит подождать, сплэш-экран исчезнет, когда пройдет заданный период времени (в нашем примере, это две секунды). Мы можем сделать это обработкой сообщения WM_TIMER. После получения этого сообщения, мы закрываем окно, послав ему сообщение WM_LBUTTONDOWN, чтобы избежать повторения кода. Таймер нам больше не нужен, поэтому мы уничтожаем его KillTimer.
Когда окно будет закрыто, DLL возвратит контроль основной программе.
[C] Iczelion, пер. Aquila.