Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Toolbar, cz. 1

[lekcja] Rozdział 30. Opisane zostało tworzenie toolbaru oraz umieszczanie na nim przycisków. Omówiono także obsługę komunikatów generowanych przez tą kontrolkę.

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:

C/C++
InitCommonControls();

Można użyć też bardziej rozbudowanej wersji funkcji:

C/C++
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof( INITCOMMONCONTROLSEX );
icc.dwICC = ICC_BAR_CLASSES; // toolbary, statusbary i tooltipy
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 ;-).

Pole Typ Znaczenie
iBitmap int Indeks bitmapy
idCommand int Indeks komendy
fsState BYTE Stan (lub kombinacja stanów) przycisku
fsStyle BYTE Styl (lub kombinacja stylów) przycisku
dwData DWORD Do wszelakich zastosowań ;-)
iString int Indeks etykiety przycisku lub wskaźnik do stringa

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:
  • TBSTYLE_BUTTON
  • TBSTYLE_CHECK
  • TBSTYLE_GROUP
  • TBSTYLE_CHECKGROUP
  • TBSTYLE_SEP

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:

Stała Znaczenie
TBSTATE_CHECKED Przycisk ma styl TBSTYLE_CHECKED i jest wciśnięty
TBSTATE_PRESSED Przycisk jest wciśnięty
TBSTATE_ENABLED Przycisk jest włączony (może reagować na działania użytkownika)
TBSTATE_HIDDEN Przycisk jest niewidoczny i wyłączony
TBSTATE_INDETERMINATE Przycisk jest wyłączony (nie reaguje na klikanie)
TBSTATE_WRAP Po przycisku następuje złamanie linii (czyli następny przycisk znajduje się w nowej linii). Ten stan musi być połączony z TBSTATE_ENABLED.

 
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:

C/C++
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
);

Argument Znaczenie
hwnd Uchwyt do okna rodzicielskiego tworzonego toolbaru
ws Style okna toolbaru. Musi zawierać styl WS_CHILD.
wID Identyfikator tworzonego toolbaru
nBitmaps Liczba fragmentów, na jakie ma być podzielona bitmapa, określona przez następne dwa parametry.
hBMInst Uchwyt programu, w którego zasobach znajduje się bitmapa.
wBMID Identyfikator zasobu z bitmapą dla toolbaru. Jeśli hBMInst równy jest NULL, to parametr ten powinien zawierać nie identyfikator, lecz uchwyt (HBITMAP) do bitmapy.
lpButtons Wskaźnik do tablicy struktur TBBUTTON, zawierających informację o poszczególnych przyciskach.
iNumButtons Liczba przycisków na toolbarze.
dxButton, dyButton Wymiary przycisku w pikselach.
dxBitmap, dyBitmap Wymiary bitmap przycisków (tyż w pikselach).
uStructSize Rozmiar (w bajtach) struktury TBBUTTON.

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:

Przykładowa bitmapa do toolbaru
Przykładowa bitmapa do toolbaru


Musimy ją wczytać z dysku:

C/C++
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ą:

C/C++
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:

C/C++
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:

Pierwsze koty za płoty (Windows 98)
Pierwsze koty za płoty (Windows 98)

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":

C/C++
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:

C/C++
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 ;-).

Było źle, jest jeszcze gorzej - stare, nudne, doskonale znane ikonki ;-) (Windows 98)
Było źle, jest jeszcze gorzej - stare, nudne, doskonale znane ikonki ;-) (Windows 98)

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:

C/C++
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:

C/C++
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:

C/C++
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:

C/C++
SendMessage( hToolbar, TB_ADDBUTTONS, 2,( LPARAM ) & tbb2 );

Otrzymujemy w rezultacie coś w tym stylu:

Przybyło to i owo... (Windows 98)
Przybyło to i owo... (Windows 98)

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:

C/C++
#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 ;-).

C/C++
case WM_COMMAND:
{
   
//sprawdzamy, czy komunikat pochodzi od toolbaru
   
if(( HWND ) lParam == hToolbar )
   
{
       
// selekcja identyfikatorów
       
switch( LOWORD( wParam ) )
       
{
       
case TOOL_NOWY:
           
// brak zdarzenia ;-)
           
break;
           
       
case TOOL_OTWORZ:
           
// brak zdarzenia ;-)
           
break;
           
       
case TOOL_ZAPISZ:
           
// brak zdarzenia ;-)
           
break;
           
       
case TOOL_KONIEC:
           
{
               
// Zakończ program
               
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 ;-).
Poprzedni dokument Następny dokument
Kontrolki Toolbar, cz. 2