API и FreeBasic. (Сплэш-экран)

Сплэш-экран - это окно, у которого нет заголовка, нет системных кнопок, нет border'а, которое отображает битмап на некоторое время и затем исчезает. Обычно оно используется во время загрузки программы, чтобы отображать лого программы или отвлечь внимание пользователя, пока программа делает объемную инициализацию. В этом туториале мы создадим сплэш-экран.

Первый шаг - это прописать битмап в файле ресурсов. Тем не менее, если это важно для вас, то загружать битмап, который будет использоваться только один раз, и держать его в памяти, пока программа не будет закрыта, пустая трата ресурсов. Лучшим решением является ресурсовую DLL, которая будет содержать битмап, и чьей целью является отображение сплэш-экрана. В этом случае вы сможете загрузить DLL, когда вам нужно отобразить сплэш-экран, и выгрузить ее, как только нужда в ней отпадает. Поэтому у нас будет два модуля: основная программа и сплэш-экран. Мы поместим битмап в файл ресурсов DLL.

Общая схема такова:

  • Поместить битмап в DLL как ресурс.
  • Основная программа вызывает LoadLibrary, чтобы загрузить dll в память.
  • Запускается входная функция DLL. Она создаст таймеp и установит время, в течении которого будет отображаться сплэш-экран. Затем она зарегистрирует и создаст окно без заголовка и бордера, после чего отобразит битмап в клиенсткой области.
  • Когда закончится указанный период времени, сплэш-экран будет убран с экрана и контроль будет передан главной программе.
  • Основная программа вызовет FreeLibrary, чтобы выгрузить DLL из памяти, а затем перейдет к выполнению того, к чему она предназначена.

Мы детально проанализируем описанную последовательность действий.

Загрузка/выгрузка DLL

Вы можете динамически загрузить DLL с помощью функции LoadLibrary, которая имеет следующий синтаксис:

LoadLibrary( lpLibFileName As  LPCTSTR) As HINSTANCE

Она принимает только один параметр: адрес имени DLL, который вы хотите загрузить в память. Если вызов пройдет успешно, он возвратит хэндл модуля DLL, в противном случае NULL.

Чтобы выгрузить DLL, вызовите FreeLibrary:

FreeLibrary( lpLibFileName As  HMODULE) As BOOL

Она получает один параметр: хэндл модуля DLL, которую вы хотите выгрузить.

Как использовать таймеp

Во-первых, мы должны создать таймер с помощью функции SetTimer:

SetTimer( _
    hWnd As HWND,   _
    nIDEvent As Uinteger, _
    uElapse As Uinteger,  _
    TIMERPROC lpTimerFunc) As Uinteger

  • hWnd - хэндл окна, которое будет получать уведомительные сообщения от таймера. Этот парамет может быть равным NULL, если никакое окно не ассоциируется с таймером.
  • TimerID - заданное пользователем значение, которое будет использоваться в качестве ID таймера.
  • uElaрse - временной интервал в миллисекундах.
  • lрTimerFunc - адрес функции, которая будет обрабатывать уведомительные сообщения от таймера. Если вы передает NULL, сообщения от таймера будут посылаться окну, указанному в параметре hWnd.
  • SetTimer возвращает ID таймера, если вызов прошел успешно, иначе она возвратит NULL. Поэтому лучше не использовать ноль в качестве ID таймера.

Вы можете создать таймеp двумя путями:

  • Если у вас есть окно и вы хотите, чтобы сообщения от таймера посылались окну, вы должны передать все четыре параметра SetTimer (lpTimerFunc должен быть pавен NULL).
  • Если у вас нет окна или вы не хотите обрабатывать сообщения таймера в процедуре окна, вы должны передать NULL функции вместо хэндла окна. Вы также должны указать адрес функции таймера, которая будет обрабатывать его сообщения.

В этом туториале мы используем первый подход.

Каждый раз за указанный вами временной интервал окну, ассоциированному с таймером, будет посылаться сообщение WM_TIMER. Hапример, если вы укажете 1000: ваше окно будет получать WM_TIMER каждую секунду.

Когда вам больше не нужен таймеp, уничтожьте его с помощью KillTimer:

KillTimer( _
    hWnd As HWND, _
    uIDEvent As Uinteger _
   )As BOOL


Пpимеp:

'''''''''''''''''''''''''''''''
'Основная программа
'''''''''''''''''''''''''''''''
#INCLUDE "windows.bi"

Declare Function WinMain(hInst As HINSTANCE , hPrevInst As HINSTANCE,CmdLine As LPSTR,CmdShow As DWORD) As Integer
Declare Function WndProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer

#DEFINE IDB_MAIN 1

Dim Shared As String ClassName,AppName,Libname
Dim As HINSTANCE hinst
Dim ShowBitMap As FARPROC

ClassName =  "SplashDemoWinClass"
AppName = "Splash Screen Example"
Libname = "dll/Splash.dll"

Dim Shared hInstance As HINSTANCE
Dim Shared CommandLine As LPSTR

hinst = LoadLibrary(Strptr(Libname))
If hinst <>0 Then
    ShowBitMap = GetProcAddress(hinst, "SHOWBITMAP@0")
    If ShowBitMap<>0 Then ShowBitMap()
    FreeLibrary(hinst)
Endif

hInstance = GetModuleHandle(NULL)
CommandLine = GetCommandLine()

ExitProcess(WinMain(hInstance,NULL,CommandLine, SW_SHOWDEFAULT))

Function WinMain(hInst As HINSTANCE , hPrevInst As HINSTANCE,CmdLine As LPSTR,CmdShow As DWORD) As Integer

    Dim wc As WNDCLASSEX
    Dim msg As MSG
    Dim hwnd As HWND

    wc.cbSize = Sizeof(WNDCLASSEX)
    wc.style = CS_HREDRAW Or CS_VREDRAW
    wc.lpfnWndProc = @WndProc
    wc.hInstance = hInstance
    wc.hbrBackground = Cast(HBRUSH, COLOR_WINDOW+1)
    wc.lpszClassName = Strptr(ClassName)
    wc.hIcon = LoadIcon(NULL,IDI_APPLICATION)
    wc.hIconSm  = wc.hIcon
    wc.hCursor = LoadCursor(NULL,IDC_ARROW)
    RegisterClassEx(@wc)
    hwnd = CreateWindowEx(NULL,ClassName,AppName, _
    WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, _
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL, _
    hInst,NULL)
    ShowWindow(hwnd,SW_SHOWNORMAL)
    UpdateWindow(hwnd)
    While GetMessage(@msg,NULL,0,0)
        TranslateMessage(@msg)
        DispatchMessage(@msg)
    Wend
    Return msg.wParam
End Function

Function WndProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer
    If uMsg=WM_DESTROY Then
        PostQuitMessage(NULL)
    Else
        Return DefWindowProc(hWnd,uMsg,wParam,lParam)
    Endif
    Return 0
End Function

'''''''''''''''''''''''''''
'Splash.dll
'''''''''''''''''''''''''''

#INCLUDE "windows.bi"

Declare Function WndProc(hWnd As HWND,uMsg As UINT,wParam As WPARAM,lParam As LPARAM) As Integer

Dim Shared As String BitmapName,ClassName
Dim Shared As Uinteger TimerID
Dim Shared As HBITMAP hBitMap
Dim Shared As HINSTANCE hInstance
BitmapName = "MySplashBMP"
ClassName = "SplashWndClass"

Sub ShowBitMap() Export
    hInstance = GetModuleHandle("Splash.dll")
    Dim  wc As WNDCLASSEX
    Dim  msg As MSG
    Dim  hwnd As HWND
    wc.cbSize = Sizeof(WNDCLASSEX)
    wc.style = CS_HREDRAW Or CS_VREDRAW
    wc.lpfnWndProc = @WndProc
    wc.hInstance = hInstance
    wc.hbrBackground = Cast(HBRUSH, COLOR_WINDOW+1)
    wc.lpszClassName = Strptr(ClassName)
    wc.hIcon = LoadIcon(NULL,IDI_APPLICATION)
    wc.hCursor = LoadCursor(NULL,IDC_ARROW)
    RegisterClassEx(@wc)
    hwnd = CreateWindowEx(NULL,Strptr(ClassName),NULL, _
    WS_POPUP,CW_USEDEFAULT, _
    CW_USEDEFAULT,250,250,NULL,NULL, _
    hInstance,NULL)
    ShowWindow(hwnd,SW_SHOWNORMAL)
    While GetMessage(@msg,NULL,0,0)
        TranslateMessage(@msg)
        DispatchMessage(@msg)
    Wend
End Sub


Function WndProc(hWnd As HWND,uMsg As UINT,wParam As WPARAM,lParam As LPARAM) As Integer
    Dim  ps As PAINTSTRUCT
    Dim  hdc As HDC
    Dim  hMemoryDC As HDC
    Dim  hOldBmp As HBITMAP
    Dim  bitmap As BITMAP
    Dim  DlgHeight As DWORD
    Dim  DlgWidth As DWORD
    Dim  DlgRect As RECT
    Dim  DesktopRect As RECT

    If uMsg=WM_DESTROY Then
        If hBitMap<>0 Then DeleteObject(hBitMap)
        PostQuitMessage(NULL)
    Elseif uMsg=WM_CREATE Then
        GetWindowRect(hWnd,@DlgRect)
        GetWindowRect(GetDesktopWindow,@DesktopRect)
        DlgHeight = DlgRect.bottom - DlgRect.top
        DlgWidth = DlgRect.right - DlgRect.left
        MoveWindow(hWnd ,(DesktopRect.right - DlgWidth)\2,(DesktopRect.bottom - DlgHeight)\2 ,DlgWidth,DlgHeight,0)
        hBitMap = LoadBitmap(hInstance,BitmapName)
        TimerID = SetTimer(hWnd,1,2000,NULL)
    Elseif uMsg=WM_TIMER Then
        SendMessage(hWnd,WM_LBUTTONDOWN,NULL,NULL)
        KillTimer(hWnd,TimerID)
    Elseif uMsg=WM_PAINT Then
        hdc = BeginPaint(hWnd,@ps)
        hMemoryDC =  CreateCompatibleDC(hdc)
        hOldBmp = SelectObject(hMemoryDC,hBitMap)
        GetObject(hBitMap,Sizeof(BITMAP),@bitmap)
        StretchBlt(hdc,0,0,250,250, _
        hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY)
        SelectObject(hMemoryDC,hOldBmp)
        DeleteDC(hMemoryDC)
        EndPaint(hWnd,@ps)
    Elseif uMsg=WM_LBUTTONDOWN Then
        DestroyWindow(hWnd)
    Else
        Return DefWindowProc(hWnd,uMsg,wParam,lParam)
    Endif
End Function


Файл ресурсов Splash.rc

MySplashBMP  BITMAP "JourneyStart.bmp"


Рисунок для проекта:

JourneyStart.bmp

Анализ:

Примечание: В связи с особенностью языка, техника вызова DLL немного изменена.

Сначала мы проанализируем код основной программы.

hinst = LoadLibrary(Strptr(Libname))
If hinst <>0 Then
    ShowBitMap = GetProcAddress(hinst, "SHOWBITMAP@0")
    If ShowBitMap<>0 Then ShowBitMap()
    FreeLibrary(hinst)
Endif

Мы вызовем LoadLibrary, чтобы загрузить DLL "Sрlash.dll". Далее получаем адрес на процедуру ShowBitMap и если вызов не закончился неудачей, то вызываем эту функцию. После этого выгружаем ее из памяти функцией FreeLibrary. LoadLibrary не возвратится, пока DLL не закончит свою инициализацию.

Это все, что делает основная программа. Интересующая нас часть находится в DLL.

При вызове ShowBitmaр, мы получаем хендл (своего )модуля DLL. Далее регистрируется класс окна, создает окно и входит в цикл обработки сообщений. Следует обратить внимание на вызов CreateWindowEx:

hwnd = CreateWindowEx(NULL,Strptr(ClassName),NULL, _
WS_POPUP,CW_USEDEFAULT, _
CW_USEDEFAULT,250,250,NULL,NULL, _
hInstance,NULL)

Обратите внимание, что стиль окна WS_РOРUР, что делает окно без бордюра и без заголовка. Мы также ограничиваем размер окна - 250x250.

Теперь, когда окно создано, в обработчике WM_CREATE мы передвигаем окно в центр экрана следующим кодом:

GetWindowRect(hWnd,@DlgRect)
GetWindowRect(GetDesktopWindow,@DesktopRect)
DlgHeight = DlgRect.bottom - DlgRect.top
DlgWidth = DlgRect.right - DlgRect.left
MoveWindow(hWnd , _
(DesktopRect.right - DlgWidth)\2, _
(DesktopRect.bottom - DlgHeight)\2 , _
DlgWidth,DlgHeight,0)


Мы получаем размеры десктопа и окан, а затем вычисляем координаты левого верхнего угла окна, чтобы оно было в центре.

hBitMap = LoadBitmap(hInstance,BitmapName)
TimerID = SetTimer(hWnd,1,2000,NULL)


Затем мы загружаем битмап из ресурса функцией LoadBitmap и создаем таймеp, указывая в качестве его ID 1, а в качестве временного интервала 2 секунды. Таймеp будет посылать сообщения WM_TIMER окну каждый две секунды.

Elseif uMsg=WM_PAINT Then
    hdc = BeginPaint(hWnd,@ps)
    hMemoryDC =  CreateCompatibleDC(hdc)
    hOldBmp = SelectObject(hMemoryDC,hBitMap)
    GetObject(hBitMap,Sizeof(BITMAP),@bitmap)
    StretchBlt(hdc,0,0,250,250, _
    hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY)
    SelectObject(hMemoryDC,hOldBmp)
    DeleteDC(hMemoryDC)
    EndPaint(hWnd,@ps)

Когда окно получит сообщение WM_РAINT, она создаст DC в памяти, выберет в него битмап, получит pазмеp битмапа функцией GetObject, а затем поместит битмап на окно, вызвав StretchBlt, которая действует как BitBlt, но адаптирует битмап к желаемым размерам. В этом случае, нам нужно, чтобы битмап влез в окно, поэтому мы используем StrectchBlt вместо BitBlt. Мы удаляем созданный в памяти DC.

Elseif uMsg=WM_LBUTTONDOWN Then
    DestroyWindow(hWnd)

Пользователя бы раздражало, если бы ему пришлось бы ждать, пока сплэш-экран не исчезнет. Мы можем предоставить пользователю выбор. Когда он кликнет на сплэш-экране, тот исчезнет. Вот почему нам нужно обрабатывать сообщение WM_LBUTTONDOWN. Когда мы получим это сообщение, окно будет уничтожено вызовом DestroyWindow.

Elseif uMsg=WM_TIMER Then
    SendMessage(hWnd,WM_LBUTTONDOWN,NULL,NULL)
    KillTimer(hWnd,TimerID)

Если пользователь решит подождать, сплэш-экран исчезнет, когда пройдет заданный период времени (в нашем примере, это две секунды). Мы можем сделать это обработкой сообщения WM_TIMER. После получения этого сообщения, мы закрываем окно, послав ему сообщение WM_LBUTTONDOWN, чтобы избежать повторения кода. Таймер нам больше не нужен, поэтому мы уничтожаем его KillTimer.

Когда окно будет закрыто, DLL возвратит контроль основной программе.

По окончанию работы удаляем битмэп посредством DeleteObject.

Перевод на русский с оригинала: Aquila, адаптация материалов под FreeBasic: Станислав Будинов

содержание | назад | вперед