Wstęp
Aplikacje windowsowe, nawet te wyposażone w menu, są wredne i nieprzyjazne użytkownikowi, jeśli nie mają paska narzędzi, zwanego też z angielska toolbarem. Za chwilę dowiemy się o tym fajnym urządzeniu wszystkiego, albo raczej prawie wszystkiego ;-).
Biblioteka Common Controls
Na początek zaznaczam, że toolbar jest kontrolką należącą do grona tzw.
Common Controls. Jest to grupa kontrolek, z której korzysta każdy bardziej rozbudowany program windowsowy, a często nawet całkiem malutkie programiki. Mimo tej powszechności korzystanie z nich wymaga nieco zachodu - przede wszystkim musimy dolinkować do programu odpowiednią bibliotekę. W Devie jest to plik
libcomctl32.a, w innych środowiskach (np. VC++, LCC) nazywa się on
comctl32.lib. Następnie - dołączamy nagłówek
commctrl.h. Wreszcie - umieszczamy PRZED jakimikolwiek instrukcjami odwołującymi się do kontrolek typu
Common Controls wywołanie funkcji
InitCommonControls - najlepiej uczynić to gdzieś w okolicach miejsca, gdzie tworzymy główne okno:
Można użyć też bardziej rozbudowanej wersji funkcji:
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof( INITCOMMONCONTROLSEX );
icc.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx( & icc );
Tworzenie toolbaru
Po tych zabiegach możemy wreszcie utworzyć toolbar. Metody są dwie: albo robimy to znaną już doskonale funkcją
CreateWindowEx (wtedy korzystamy ze stałej
TOOLBARCLASSNAME, określającą nazwę klasy toolbaru), albo też (to wersja wygodniejsza) - specjalną funkcją
CreateToolbarEx.
Metoda numer dwa jest o tyle lepsza, że daje nam możliwość zrobienia od razu kompletnego toolbaru (z przyciskami), więc nie będziemy musieli ich później dodawać po kolei. Argumentów ma ta
CreateToolbarEx sporo, a najważniejszy z nich to tablica struktur typu
TBBUTTON - jak się pewnie domyślasz, każda z takich struktur opisuje kolejny przycisk. I jak się pewnie domyślasz, za moment sobie tę strukturę dokładnie omówimy ;-).
Jak widzisz, przyciskom nie przypisujemy pojedynczych uchwytów do bitmap, tylko indeksy - to dlatego, że toolbar posiada własną, wewnetrzną listę bitmap. Indeks, który przypisujemy przyciskowi z toolbaru, to właśnie numer bitmapy na wewnętrznej liście. Podobnie sprawa wygląda z etykietami przycisków - najpierw tworzymy listę stringów, potem przypisujemy ich indeksy przyciskom (chociaż niekoniecznie - o tym później).
Przyciski toolbaru nie mają sztywno przypisanych identyfikatorów - możemy je ustalić sami, wypełniając pole
idCommand. Wciśnięcie przycisku na toolbarze powoduje wysłanie "zwykłego" komunikatu
WM_COMMAND. Jak wiemy, parametr
lParam tego komunikatu zawiera zawsze uchwyt kontrolki, która wysyła ten komunikat (tym razem będzie to uchwyt toolbaru), natomiast parametr
wParam zawiera kod notyfikacji (górne słowo) oraz identyfikator kontrolki lub elementu, którego dotyczy komunikat (dolne słowo). To ostatnie to będzie w tym przypadku właśnie wartość, którą podamy jako
idCommand.
Style przycisku mogą zaś być następujące:
Zazwyczaj używamy tego pierwszego. Działanie stylu
TBSTYLE_CHECK chyba nietrudno odgadnąć.
TBSTYLE_GROUP, jak również sama nazwa wskazuje, służy do grupowania przycisków (będzie wciśnięty, dopóki użytkownik nie wciśnie innego przycisku z grupy). Podobnie działa
TBSTYLE_CHECKGROUP, tylko że dla przycisków typu
TBSTYLE_CHECK. Ostatni styl tworzy nam nie tyle normalny przycisk, co separator - czyli po prostu odstęp między poszczególnymi grupami przycisków.
Stylów jest trochę więcej, niektóre z nich są dostępne tylko dla określonych wersji biblioteki, inne w nowszych wersjach są dostępne, ale pod inną nazwą, jeszcze inne stosuje się nie do pojedynczych przycisków, lecz do całego toolbaru - krótko mówiąc, burdel na kółkach z tym jest. Tak więc omówimy je sobie przy konkretnych przypadkach.
Stany przycisku mogą być takie:
Tyle teorii, bierzemy się do roboty. Nasze zadanie to zrobienie toolbaru do programiku, który tworzyliśmy sobie w odcinku poświęconym menu. Będzie on miał na razie trzy przyciski: Nowy, Otwórz i Zapisz. W tym momencie nie wymigam się już od przedstawienia pełnej składni funkcji
CreateToolbarEx, oto i ona:
HWND CreateToolbarEx( HWND hwnd,
DWORD ws,
UINT wID,
int nBitmaps,
HINSTANCE hBMInst,
UINT wBMID,
LPCTBBUTTON lpButtons,
int iNumButtons,
int dxButton,
int dyButton,
int dxBitmap,
int dyBitmap,
UINT uStructSize
);
Funkcja zwraca uchwyt do nowego toolbaru (uchwyt typu
HWND, oczywiście), jeśli tworzenie się powiedzie. Jeśli funkcja potknie się po drodze, to zwróci
NULL.
Zaczynamy od stworzenia bitmapy z odpowiednimi ikonami. Domyślne wymiary pojedynczej ikony to
16x16 pikseli. Cała bitmapa będzie wyglała tak:
Musimy ją wczytać z dysku:
HBITMAP hbmTool =( HBITMAP ) LoadImage( hThisInstance, "tool.bmp", IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_LOADMAP3DCOLORS );
Teraz tworzymy sobie tablicę trzech elementów typu
TBBUTTON i wypełnieniamy ją:
TBBUTTON tbb[ 3 ];
ZeroMemory( tbb, sizeof( tbb ) );
for( int i = 0; i < 3; ++i ) {
tbb[ i ].idCommand = i;
tbb[ i ].iBitmap = tbb[ i ].iString = i;
tbb[ i ].fsState = TBSTATE_ENABLED;
tbb[ i ].fsStyle = TBSTYLE_BUTTON;
}
Jak widać, stworzyliśmy trzy przyciski. Ich identyfikatory to kolejne liczby:
0, 1, 2. Takie same są indeksy bitmap. Program wczyta bitmapę i "podzieli" ją na trzy fragmenty (według wymiarów, które za chwilę podamy w funkcji
CreateToolbarEx), które będą identyfikowane właśnie kolejnymi liczbami całkowitymi. Najwyższa pora stworzyć nasz toolbar:
HWND hToolbar = CreateToolbarEx( hwnd, WS_CHILD | WS_VISIBLE, 500, 3, NULL,( UINT ) hbmTool, tbb, 3,
16, 16, 16, 16, sizeof( TBBUTTON ) );
Mamy już toolbar, nadaliśmy mu identyfikator
500 oraz trzy przyciski na podstawie tablicy
tbb. Przyciski mają wymiary
16x16, tak jak sobie zapowiadaliśmy. Oto nasze cudo:
Biblioteka
comctl32 udostępnia nam szereg standardowych ikonek do toolbaru. Są wśród nich między innymi nasze trzy: Nowy, Otwórz i Zapisz. Po co więc mamy się męczyć tworzeniem własnych bitmap, skoro można skorzystać z gotowych? Robi się to bardzo prosto. Po pierwsze - zamiast
NULL, podajemy funkcji
CreateToolbarEx uchwyt do biblioteki
comctl32. Jest on zawarty w stałej
HINST_COMMCTRL. Następnie jako identyfikator bitmapy podajemy stałą
IDB_STD_SMALL_COLOR (nietrudno zgadnąć, że
IDB_STD_LARGE_COLOR oznacza duże wersje ikon). Wreszcie usuwamy z pętli linijki, w których przypisujemy numer bitmapy i wszystkie trzy ustawiamy "ręcznie":
tbb[ 0 ].iBitmap = STD_FILENEW;
tbb[ 1 ].iBitmap = STD_FILEOPEN;
tbb[ 2 ].iBitmap = STD_FILESAVE;
Pełną listę dostępnych stałych znajdziesz w pliku
commctrl.h. A tymczasem wprowadzamy wszystkie omówione zmiany do wywołania
CreateToolbarEx:
HWND hToolbar = CreateToolbarEx( hwnd, WS_CHILD | WS_VISIBLE, 500, 3, HINST_COMMCTRL,
IDB_STD_SMALL_COLOR, tbb, 3, 16, 16, 16, 16, sizeof( TBBUTTON ) );
Udało się, zamiast ikonek w stylu Dev-a mamy zwykłe, nudne ikonki windowsowe ;-).
Bardzo często się zdarza, że większość przycisków na toolbarze to przyciski ze standardowymi ikonkami, ale potrzebujemy też jednego lub kilka przycisków z własnymi obrazkami. Nie pozostaje nam nic innego, jak tylko dodawać własne przyciski i bitmapy do nich już po utworzeniu "głównej" części toolbaru. Załóżmy przykładowo, że chcemy sobie dorzucić na końcu przycisk Koniec ;-). Dorzucimy też separator - choćby po to, żebyś później nie pytał, jak to zrobić ;-).
Zacznijmy od wczytania bitmapy do przycisku Koniec - tego już nie będę pokazywał któryś tam raz z rzędu, popatrz sobie wyżej. Załóżmy, że mamy już bitmapę i uchwyt do niej -
hbmKoniec. Musimy teraz stworzyć oraz wypełnić strukturę
TBADDBITMAP, co też czynimy:
TBADDBITMAP tbab;
tbab.hInst = NULL;
tbab.nID =( UINT ) hbmKoniec;
Z powyższego możemy wywnioskować, że istnieje również możliwość dodawania bitmap z pliku zasobów programu, jeśli jako
hInst podamy uchwyt od tego programu, a jako
nID - identyfikator bitmapy w pliku zasobów. Możemy też, analogicznie do powyższego przykładu, dodawać przy wykorzystaniu struktury
TBADDBITMAP pojedyncze ikony z
comctl32.
Teraz musimy jedynie wysłać komunikat
TB_ADDBITMAP, przekazując w nim liczbę bitmap, które dodajemy (u nas tylko jedna), oraz adres struktury
TBADDBITMAP:
int nIndeks = SendMessage( hToolbar, TB_ADDBITMAP, 1,( LPARAM ) & tbab );
Następnie tworzymy drugą tablicę z przyciskami. Tym razem dodajemy dwa przyciski - jeden separator i jeden zwykły przycisk:
TBBUTTON tbb2[ 2 ];
ZeroMemory( tbb2, sizeof( tbb2 ) );
tbb2[ 0 ].idCommand = 3;
tbb2[ 0 ].iString = 3;
tbb2[ 0 ].iBitmap = 0;
tbb2[ 0 ].fsState = TBSTATE_ENABLED;
tbb2[ 0 ].fsStyle = TBSTYLE_SEP;
tbb2[ 1 ].idCommand = 4;
tbb2[ 1 ].iString = 4;
tbb2[ 1 ].iBitmap = nIndeks;
tbb2[ 1 ].fsState = TBSTATE_ENABLED;
tbb2[ 1 ].fsStyle = TBSTYLE_BUTTON;
Dodajemy przyciski, wykorzystując odpowiedni komunikat:
SendMessage( hToolbar, TB_ADDBUTTONS, 2,( LPARAM ) & tbb2 );
Otrzymujemy w rezultacie coś w tym stylu:
Obsługa zdarzeń
Taki toolbar może i fajnie wygląda, ale zda się psu na budę, jeśli nie będzie do tego działał jak należy, tzn. wywoływał odpowiednich komend po wciśnięciu odpowiednich przycisków. Zaimplementowanie tego jest na szczęście całkiem proste. Jak już wspomniałem, wciśnięcie przycisku wysyła komunikat
WM_COMMAND. Musimy najpierw sprawdzić, czy komunikat ten faktycznie pochodzi od toolbaru i w tym celu możemy posłużyć się parametrem
lParam komunikatu. Z kolei parametr
wParam, a konkretniej jego dolna połówka, zawiera identyfikator wciśniętego przycisku (o ile mu go nadaliśmy).
W naszym przykładzie mamy teraz 5 przycisków (w tym jeden separator) o identyfikatorach od
0 do
4. Warto by zdefiniować jakieś stałe dla tych identyfikatorów, coby w kodzie było od razu widać, że chodzi o przyciski:
#define TOOL_NOWY 0
#define TOOL_OTWORZ 1
#define TOOL_ZAPISZ 2
#define TOOL_KONIEC 4
Identyfikatora dla separatora oczywiście nie potrzebujemy, gdyż nie generuje on żadnych komunikatów :-). Teraz pora napisać sobie obsługę komunikatu
WM_COMMAND. Zrobimy to tylko dla przycisku Koniec, bo to będzie najprostsze ;-).
case WM_COMMAND:
{
if(( HWND ) lParam == hToolbar )
{
switch( LOWORD( wParam ) )
{
case TOOL_NOWY:
break;
case TOOL_OTWORZ:
break;
case TOOL_ZAPISZ:
break;
case TOOL_KONIEC:
{
DestroyWindow( hwnd );
}
break;
}
}
}
break;
Dzięki tym zabiegom kliknięcie ikonki drzwi będzie nas faktycznie wywalało za drzwi ;-). Jeśli do wypróbowania przykładów z tego odcinka wykorzystujesz ten sam kod, w którym ćwiczyłeś tworzenie menu (co jest zalecane), to prawdopodobnie już masz w nim obsługę innych komunikatów
WM_COMMAND (np. pochodzących od menu), więc musisz to uwzględnić przy wklejaniu fragmentu dotyczącego toolbaru, inaczej narobisz sobie niezłej kaszanki ;-).