Ucząc się programowania, zaczynamy tworzyć coraz to bardziej złożone programy, które posiadają coraz to więcej przycisków i innych dobrodziejstw interfejsu użytkownika. Jednak prędzej czy później okaże się, że nasze okno programu jest zbyt małe by pomieścić to wszystko. Oczywiście możemy powiększyć okno, ale nasz monitor też prędzej czy później się cały zapełni, i co wtedy? Na szczęście z pomocą przychodzą nam paski przesuwu, czyli ScrollBary:
ScrollBar jest kontrolką umożliwiającą przesuwanie w poziomie i w pionie. Oto jak jest zbudowany ScrollBar:
Tworzenie ScrollBara
Samo tworzenie wygląda tak samo, jak w przypadku innych kontrolek. Nazwa klasy to
SCROLLBAR. Domyślnie tworzony jest pasek poziomy, natomiast do stworzenia paska pionowego trzeba użyć stylu
SBS_VERT. Zaczniemy od stworzenia globalnego uchwytu:
HWND g_hScrollBar = NULL;
I teraz możemy już stworzyć ScrollBar:
g_hScrollBar = CreateWindowEx( 0, "SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE, 50, 20, 220, 21, hwnd, NULL, hInstance, NULL );
Powyższy kod utworzy nam samodzielny pasek przesuwu. Możemy też dodać wbudowany ScrollBar do głównego okna naszego programu. Wystarczy przy tworzeniu okna dodać style
WS_HSCROLL lub
WS_VSCROLL w zależności od tego jaki pasek chcemy otrzymać.
Oczywiście jak to w WinAPI nie obędzie się bez problemów. Mianowicie jeśli skorzystamy z pierwszego sposobu, będziemy musieli potem ręcznie ustawić pasek przesuwu we właściwe miejsce, i z każdą zmianą rozmiaru okna na nowo go ustawiać. Oto jak to zrobić:
RECT okno;
GetClientRect( hwnd, & okno );
int iVThumb = GetSystemMetrics( SM_CYVTHUMB );
g_hScrollBar = CreateWindowEx( 0, "SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_VERT,
okno.right - 16, okno.top, 16, okno.bottom - iVThumb,
hwnd, NULL, hInstance, NULL );
Jeśli w naszym programie zezwalamy na zmianę rozmiaru okna, to musimy wrzucić powyższy kod do obsługi komunikatu
WM_SIZE.
Inicjalizacja ScrollBara
Zanim zaczniemy korzystać ze ScrollBara, musimy w nim ustawić parę rzeczy, między innymi pozycję maxymalną i minimalną, itd. W tym celu posłużymy się funkcją
SetScrollInfo, która jako jeden z argumentów przyjmuje strukturę
SCROLLINFO z wieloma mniej lub bardziej przydatnymi polami. Oto jak mniej więcej jest ona zadeklarowana:
typedef struct tagSCROLLINFO {
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, * LPCSCROLLINFO;
Oczywiście
cbSize to rozmiar struktury, który jak zawsze w WinAPI trzeba podawać. Pole
fMask oznacza które pola struktury zawierają poprawne informacje, czyli te które chcemy odczytać/zapisać. Teraz
nMin,
nMax i
nPage - cóż to takiego?
nMin to odległość początku przewijanego obszaru od góry (w przypadku całego okna
0),
nMax to wysokość całego okna (włącznie z niewidoczną częścią, do której trzeba będzie przewijać), natomiast
nPage to wysokość obszaru, który jest widoczny (w przypadku całego okna to wysokość obszaru klienta). Oczywiście to są informacje dotyczące pionowego paska, dla poziomego będzie to długość, nie wysokość. Pole
nPos to pozycja ScrollBara, natomiast pole
nTrackPos jest tylko do odczytu, więc go nie ruszamy.
Skoro już wiemy (prawie) wszystko o tej strukturze, to możemy ją wypełnić:
RECT okno;
GetClientRect( hwnd, & okno );
SCROLLINFO si;
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
si.nMin = 0;
si.nMax = 1000;
si.nPage = okno.bottom;
si.nPos = 0;
Na początku oczywiście zerujemy strukturę i wypełniamy pole
cbSize rozmiarem struktury. Potem ustawiamy pola
nMin,
nMax i
nPos. W naszym przykładzie zakładamy, że ScrollBar będzie przewijał cały obszar klienta w pionie. Pozycję początkową ustawiamy na
0 czyli na górę ;-). Teraz przejdźmy do funkcji
SetScrollInfo:
Składnia: SetScrollInfo (hwnd, fnBar, lpsi, fRedraw)
Tak więc dla ScrollBara jako samodzielnej kontrolki wywołamy funckję
SetScrollInfo następująco:
SetScrollInfo( g_hScrollBar, SB_CTL, & si, TRUE );
A dla ScrollBara będącego częścią okna (styl
WS_VSCROLL):
SetScrollInfo( hwnd, SB_VERT, & si, TRUE );
Przesuwanie
No właśnie, tyle się natrudziliśmy, a nasz ScrollBar wciąż nie działa! Nie dość, że nie przesuwa kontrolek w oknie, to w dodatku gdy go przesuniemy, sam wraca na stare miejsce! Dlaczego tak się dzieje? Otoż nie obsłużyliśmy odpowiednio komunikatów odpowiedzialnych za przesuwanie. Są tą
WM_VSCROLL i
WM_HSCROLL. W niższym słowie parametru
wParam znajdziemy stałą oznaczającą to, co użytkownik zrobił ze ScrollBarem. Stałe te zostały zebrane w tabelce (Uwaga: dla
ScrollBara pionowego):
Ponieważ nie wszyscy mogą kojarzyć o które części ScrollBara dokładnie chodzi, zamieszczam rysunek:
Na powyższym rysunku zaznaczone są też nazwy stałych dla ScrollBara poziomego, dlatego oddzielnej tabelki nie będzie. Dodam tylko że
SB_LEFT i
SB_RIGHT których nie ma na rysunku to odpowiednie końce poziomego paska przesuwu. Teraz twardszy orzech do zgryzienia - jak odczytać aktualną pozycję ScrollBara? Można ją znaleźć w wyższym słowie
wParam, ale:
Łatwo można dojść do wniosku, że ten sposób się nie nadaje i to prawda ;-) Instnieje funkcja
GetScrollPos do odczytywania pozycji, ale jako rzecze dokumentacja, nie wolno jej używać bo jest przestarzała. W zamian trzeba używać
GetScrollInfo! Funkcja ta odczytuje dane do struktury
SCROLLINFO, tej samej, którą używaliśmy przy inicjalizacji ScrollBara. Jest zbudowana tak samo jak
SetScrollInfo, tylko że nie ma 4 argumentu. A oto jak odczytać pozycję suwaka:
SCROLLINFO si;
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS;
SetScrollInfo( hwnd, SB_VERT, & si );
int pozycja = si.nPos;
Dobra, mamy już pozycję, teraz jak sprawić, by okno i suwak w rzeczywistości się przesunęły? Zaczniemy od suwaka, bo to prostsze ;-) Możemy się w tym celu posłużyć funkcją
SetScrollPos która ustawi nam nową pozycję suwaka. Ale znowu MSDN surowo zabrania jej używania, a w zamian daje
SetScrollInfo. Tą funkcję już znamy, korzystaliśmy już z niej wcześniej. Tym razem zmienimy tylko pozycję. No właśnie, na co zmienimy? To dobre pytanie. Wszystko zależy od tego co użytkownik zrobił ze ScrollBarem. Oto przykładowa obsługa przesunięcia ScrollBara:
case WM_VSCROLL: {
SCROLLINFO si;
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS | SIF_PAGE | SIF_TRACKPOS;
GetScrollInfo( hwnd, SB_VERT, & si );
int pozycja = si.nPos;
switch( LOWORD( wParam ) ) {
case SB_TOP:
pozycja = 0;
break;
case SB_BOTTOM:
pozycja = 1000;
break;
case SB_LINEUP:
if( pozycja > 0 ) {
pozycja--;
}
break;
case SB_LINEDOWN:
if( pozycja < 1000 ) {
pozycja++;
}
break;
case SB_PAGEUP:
pozycja -= si.nPage;
if( pozycja < 0 ) {
pozycja = 0;
}
break;
case SB_PAGEDOWN:
pozycja += si.nPage;
if( pozycja > 1000 ) {
pozycja = 1000;
}
break;
case SB_THUMBPOSITION:
pozycja = si.nTrackPos;
break;
case SB_THUMBTRACK:
pozycja = si.nTrackPos;
break;
}
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS;
si.nPos = pozycja;
SetScrollInfo( hwnd, SB_VERT, & si, TRUE );
}
break;
Jak widać poza pozycją odczytaliśmy także wielkość strony i parametr
nTrackPos którego na początku mieliśmy nie ruszać. Teraz jest on bardzo przydatny, gdyż właśnie w nim znajdziemy nowe wartości ScrollBara dla komunikatów
SB_THUMBPOSITION i
SB_THUMBTRACK. Obsługa pozostałych komunikatów jest chyba jasna.
Teraz zabierzmy się za przesuwania zawartości okna. Domyślasz się pewnie, że jest do tego jakaś funkcja np.
ScrollWindow. Owszem, taka funkcja istnieje, lecz jako rzecze MSDN ta funkcja jest zła i trzeba używać
ScrollWindowEx, która ma o wiele więcej parametrów ;-(. No cóż, takie jest życie. Wystarczy narzekania, przyjrzyjmy się bliżej tej funkcji:
Składnia: ScrollWindowEx(hwnd, dx, dy, prcScroll, prcClip, hrgnUpdate, prcUpdate, flags)
Teraz nadszedł czas zastanowić się co podać jako argumenty
dx i
dy by się przewijało tak jak Pan Bóg przykazał? Można to zrobić np. w ten sposób:
int dy = -( pozycja - si.nPos );
Jak pamiętamy ze wcześniejszych fragmentów kodu
pozycja to nowa pozycja, natomiast
si.nPos wciąż przechowuje starą pozycję. Dodatkowo musimy zamienić znaki w wyniku (operator
-), inaczej ScrollBar będzie przewijał na odwrót ;-) Ten kod musimy umieścić przez kodem ustawiającym nową pozycję ScrollBara (a konkretniej, przed
ZeroMemory) bo inaczej wartość
si.nPos zostanie skasowana.
Teraz przejdźmy do
ScrollWindowEx. Jako argument
dx dajemy
0, gdyż w naszym przykładzie mamy tylko pionowy pasek przesuwu. A oto kod:
ScrollWindowEx( hwnd, 0, dy,( CONST RECT * ) NULL,( CONST RECT * ) NULL,( HRGN ) NULL,( LPRECT ) NULL, SW_SCROLLCHILDREN );
UpdateWindow( hwnd );
Hura! Nasze kontrolki w oknie się przesuwają i zostawiają za sobą piękny ślad... No właśnie, coś tu jest nie tak, nie? W profesjonalnych aplikacjach nie ma takiego śladu! Na szczęście jego usunięcie nie jest zbyt kłopotliwe, wystarczy dorzucić flagi
SW_INVALIDATE i
SW_ERASE do ostatniego argumentu funkcji przewijającej. A oto kompletny kod obsługi komunikatu
WM_VSCROLL:
case WM_VSCROLL: {
SCROLLINFO si;
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS | SIF_PAGE | SIF_TRACKPOS;
GetScrollInfo( hwnd, SB_VERT, & si );
int pozycja = si.nPos;
switch( LOWORD( wParam ) ) {
case SB_TOP:
pozycja = 0;
break;
case SB_BOTTOM:
pozycja = 1000;
break;
case SB_LINEUP:
if( pozycja > 0 ) {
pozycja--;
}
break;
case SB_LINEDOWN:
if( pozycja < 1000 ) {
pozycja++;
}
break;
case SB_PAGEUP:
pozycja -= si.nPage;
if( pozycja < 0 ) {
pozycja = 0;
}
break;
case SB_PAGEDOWN:
pozycja += si.nPage;
if( pozycja > 1000 ) {
pozycja = 1000;
}
break;
case SB_THUMBPOSITION:
pozycja = si.nTrackPos;
break;
case SB_THUMBTRACK:
pozycja = si.nTrackPos;
break;
}
int dy = -( pozycja - si.nPos );
ScrollWindowEx( hwnd, 0, dy,( CONST RECT * ) NULL,( CONST RECT * ) NULL,( HRGN ) NULL,( LPRECT ) NULL, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE );
UpdateWindow( hwnd );
ZeroMemory( & si, sizeof( si ) );
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS;
si.nPos = pozycja;
SetScrollInfo( hwnd, SB_VERT, & si, TRUE );
}
break;
W powyższym przykładzie został pokazany ScrollBar pionowy. Obsługa poziomego odbywa się analogicznie, z tym że w funkcji
ScrollWindowEx uzupełniamy drugi argument, a nie trzeci jak w przykładzie powyżej. Komunikat do obsłużenia to
WM_HSCROLL.
Podsumowanie
Na koniec tego artykułu podsumujemy nasze osiągnięcia ;-) Nauczyliśmy się jak utworzyć ScrollBar i ustawić jego parametry. Dowiedzieliśmy się także co zrobić, żeby naprawdę przewijał. Jak to w WinAPI, nie było to zbyt proste zadanie, ale daliśmy sobie z nim radę bez problemu. Tak więc jakieś podstawy ScrollBarów już znamy, choć jest jeszcze wiele rzeczy, które można by omówić, np. stworzenie poziomego ScrollBara, jednak informacje zawarte w tym artykule powinny ci wystarczyć, by stworzyć taki pasek samemu.