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

Toolbar, cz. 3

[lekcja]
Niezaprzeczalnie toolbary to fajna sprawa. Ale w większych aplikacjach (jak np. Visual C++), gdzie do dyspozycji jest kilkadziesiąt czy nawet kilkaset poleceń menu, wstawienie ich wszystkich do toolbaru jest fizycznie niemożliwe - w końcu na ekranie musi zostać jeszcze trochę miejsca na pozostałe elementy aplikacji ;-). Z drugiej strony ograniczanie użytkownika do kilku najczęściej stosowanych poleceń kłóci się z polityką maksymalnej przyjazności interfejsu, zresztą programiści może i są inteligentni, ale na pewno żaden z nich nie jest jasnowidzem i nie potrafi na 100% stwierdzić, czy każdy użytkownik będzie korzystał z danego przycisku na toolbarze czy też nie.
 
Z powyższych powodów (a także kilku innych) każdy cywilizowany toolbar powinien dać się dowolnie modyfikować. Użytkownik musi mieć możliwość dodawania i usuwania przycisków, grupowania ich według własnego uznania, wstawiania separatorów, przenoszenia poszczególnych grup przycisków w inne miejsce (także poza obszar toolbaru, jako osobne okienko), a nawet definiowania własnych przycisków, których przeznaczenie nie zostało przewidziane przez programistów albo przewidziane tylko częściowo. Wszystkich tych cudów (może za wyjątkiem ostatniego) za moment się nauczymy. Brzmi to może dość kosmicznie, ale jest całkiem proste w implementacji - większość akcji wykonuje za nas automatycznie biblioteka Comctl32.dll.

Rebar


Pod względem wizualnym najwięcej nowości oferuje nam specjalny rodzaj kontrolki, zwany '''rebarem'''. Jest to rodzaj '''kontenera''' - można na nim umieszczać inne kontrolki (mniej więcej tak, jak to robiliśmy z ComboBoxem w poprzedniej części kursu). Kontrolki te mogą być samodzielnymi toolbarami lub też czymkolwiek innym, przy czym każda kontrolka musi występować na oddzielnej bandzie (należy ten termin kojarzyć raczej z obręczą, niż szajką zbirów ;-) ). Zresztą zobacz, jak to jest zrobione np. w Dev-C++ - kilka band zawiera toolbary, a w jednej są dwa ComboBoxy (co prawdopodobnie uzyskano w ten sposób, że banda zawiera toolbar, a ten z kolei ComboBoxy).

Rebar z programu ACDSee (Windows 98)
Rebar z programu ACDSee (Windows 98)

No to od czego zaczynamy? Pewnie od stworzenia rebaru. Mamy do dyspozycji klasę REBARCLASSNAME... Mamy? Nie mamy. Ale możemy mieć, jeśli ją zarejestrujemy. W tym celu zamieniamy wywołanie naszej funkcji InitCommonControls na jej rozszerzoną wersję - InitCommonControlsEx (znaną wśród programistów z poczuciem humoru jako InitCommonControlSex ;-)). Różni się ona od "zwykłej" wersji tym, że korzysta dodatkowo z pewnej struktury (już widzę, jak podskakujecie z radości ;-)). Struktura ta to INITCOMMONCONTROLSEX i omawiać jej nie będziemy, a przynajmniej nie tutaj. Aby ta struktura była dostępna musimy określić wersję IE taką dyrektywą:

C/C++
#define _WIN32_IE 0x0600

Jedyna interesujące nas w tym momencie zastosowanie jest następujące:

C/C++
INITCOMMONCONTROLSEX icex;

icex.dwSize = sizeof( INITCOMMONCONTROLSEX );
icex.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES;
InitCommonControlsEx( & icex );

W ten sposób zapewniliśmy sobie dostęp do "fajnych klas", w tym do rebaru. Teraz możemy spokojnie przystąpić sobie do jego tworzenia:

C/C++
hRebar = CreateWindowEx( WS_EX_TOOLWINDOW, REBARCLASSNAME, NULL, WS_CHILD |
WS_VISIBLE | WS_CLIPSIBLINGS | WS_BORDER |
WS_CLIPCHILDREN | RBS_VARHEIGHT | RBS_BANDBORDERS,
0, 0, 0, 0, hwnd, NULL, hInstance, NULL );

Coś tam sobie namodziliśmy w programie, ino efektów nie widać... I w sumie nic dziwnego - nie dodaliśmy przecież żadnych band. Dopiero kiedy to zrobimy, efekty naszych starań staną się widoczne. Do dodawania band, a także kilku innych zastosowań przydatna jest... o radości, kolejna nowa struktura. Tym razem nazywa się toto REBARBANDINFO.  
 
Ustalamy, że chcemy sobie wyczarować dwie bandy - jedną z toolbarem, jedną na przykład z ComboBoxem. Struktura REBARBANDINFO jest dość obszerna, więc warto na początek zastanowić się, które z pól tej struktury będą użyte przez obie bandy, a które trzeba będzie ustawiać oddzielnie. Na pewno obydwie wymagają ustawienia pola cbSize (analogicznie jak w przypadku większości struktur w WinAPI) i fMask (też analogicznie). Nie zaszkodzi też zaliczyć do tej grupy pola dwStyle, bowiem styl obu band powinien być taki sam. Deklarujemy więc zmienną i ustawiamy wspólne pola:

C/C++
REBARBANDINFO rbbi;
rbbi.cbSize = sizeof( REBARBANDINFO );
rbbi.fMask = RBBIM_TEXT | RBBIM_BACKGROUND | RBBIM_STYLE | RBBIM_CHILD |
RBBIM_CHILDSIZE | RBBIM_SIZE;
rbbi.fStyle = RBBS_CHILDEDGE | RBBS_FIXEDBMP | RBBS_GRIPPERALWAYS;
rbbi.hbmBack = LoadBitmap( hThisInstance, MAKEINTRESOURCE( IDB_BACKGRND ) );

Warto zwrócić uwagę, że wczytaliśmy tu sobie z pliku zasobów bitmapkę - możemy bowiem ustawić bitmapę tła dla kazdej bandy, co poprawia jej walory wizualne ;-) (jak na rysunku powyżej).
 
Teraz tworzymy kontrolki do poszczególnych band. Najpierw toolbar - procedurę jego tworzenia już znamy - wypełniamy strukturę z przyciskami i wywołujemy CreateToolbarEx:

C/C++
TBBUTTON tbb[ 3 ];

ZeroMemory( tbb, sizeof( tbb ) );
for( int i = 0; i < 3; ++i ) {
   
tbb[ i ].idCommand = i;
   
tbb[ i ].fsState = TBSTATE_ENABLED;
   
tbb[ i ].fsStyle = TBSTYLE_BUTTON;
}
tbb[ 0 ].iBitmap = STD_FILENEW;
tbb[ 1 ].iBitmap = STD_FILEOPEN;
tbb[ 2 ].iBitmap = STD_FILESAVE;

hToolbar = CreateToolbarEx( hRebar, WS_CHILD | WS_VISIBLE | CCS_NOPARENTALIGN |
CCS_NORESIZE | CCS_NODIVIDER | TBSTYLE_FLAT,
500, 3, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tbb, 3, 16, 16, 16, 16, sizeof( TBBUTTON ) );

Teraz wypełniamy te pola struktury REBARBANDINFO, które będą wykorzystane tylko do toolbaru, a później będą oddzielnie wypełnione dla ComboBoxa. Następnie dodajemy gotową bandę do rebaru, korzystając z odpowiedniego komunikatu:

C/C++
rbbi.lpText = "";
rbbi.hwndChild = hToolbar;
rbbi.cxMinChild = 100;
rbbi.cyMinChild = 22;
rbbi.cx = 100;

SendMessage( hRebar, RB_INSERTBAND,( WPARAM ) - 1,( LPARAM ) & rbbi );

Teraz kolej na ComboBox - używamy oczywiście funkcji CreateWindowEx:

C/C++
hCombo = CreateWindowEx( 0, "COMBOBOX", NULL, WS_CHILD | WS_BORDER | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CBS_DROPDOWN, 0, 0, 70, 200,
hRebar,( HMENU ) IDC_COMBOBOX, hThisInstance, 0 );

Będziemy potrzebowali zmiennej do zapamiętania wymiarów ComboBoxa - przydadzą się one za chwilę do określenia wymiarów całej bandy:
 
C/C++
RECT rc;
GetWindowRect( hCombo, & rc );

Wypełniamy strukturę informacjami o naszej drugiej bandzie, po czym dodajemy ją do rebaru:

C/C++
rbbi.lpText = "ComboBox";
rbbi.hwndChild = hCombo;
rbbi.cxMinChild = rc.right - rc.left;
rbbi.cyMinChild = rc.bottom - rc.top;
rbbi.cx = 200;

SendMessage( hRebar, RB_INSERTBAND,( WPARAM ) - 1,( LPARAM ) & rbbi );

To wszystko, możemy się już delektować widokiem, który będzie przypominał coś z tego screena. Zmieniłem tutaj kolor tła na ciemnoszary, żeby rebar był lepiej widoczny:

A oto i rebar własnej roboty (Windows 98)
A oto i rebar własnej roboty (Windows 98)

Powyższy przykład pomija oczywiście całe mnóstwo "nikomu niepotrzebnych" informacji o rebarze, ale gdybym zaczął opisywać wszystkie możliwości tej kontrolki, to wyszłoby z dziesięć dodatkowych odcinków tego kursu, a przecież do wszystkiego możesz sobie w miarę potrzeb dojść sam, wspierając się tym, co już się dowiedziałeś w tej części kursu oraz MSDN.

Dostosowywanie toolbaru


Rebar, nawet tak prosty jak ten przedstawiony powyżej, daje nam już pewne możliwości dopasowania go do naszych wyśrubowanych potrzeb. Możemy sobie zmieniać szerokość poszczególnych band, zamieniać je miejscami, a nawe umieścić każdą bandę w osobnym wierszu. Ale to dziabkę za mało. Potrzebna jeszcze możliwość manipulowania rozmieszczeniem przycisków w obrębie każdej bandy.
 
Zadanie jest całkiem proste. Na poczatek wystarczy ustawić toolbarowi styl CCS_ADJUSTABLE. Wtedy zacznie od wysyłać do swego okna rodzicielskiego rozmaite ciekawe powiadomienia w momencie, gdy użytkownik kliknie na ten toolbar dwukrotnie. Te powiadomienia mają służyć do pobrania od programisty informacji, jak dokładnie użytkownik może zmodyfikować toolbar (tj. jakie przyciski może dodać, jakie usunąć, jakie przesunąć na inną pozycję itp.). Jeśli odpowiemy przynajmniej na obowiązkowe powiadomienia, wyświetlony będzie dialog dostosowywania toolbaru:

Tak wygląda dialog dostosowywania... (Windows 98)
Tak wygląda dialog dostosowywania... (Windows 98)

Nie będziemy się bawili w przydługie wstępy, od razu na sam początek zobaczymy, w jaki sposób uzyskać "na skróty" możliwość modyfikowania paska w fazie run-time:

C/C++
case WM_NOTIFY: {
   
LPNMHDR lpn =( LPNMHDR ) lParam;
   
   
switch( lpn->code ) {
   
case TBN_QUERYINSERT:
       
{ return TRUE; }
       
   
case TBN_QUERYDELETE:
       
{ return TRUE; }
       
   
case TBN_GETBUTTONINFO:
       
{ return FALSE; }
       
       
default: break;
   
}
}
break;

Jak widać, te "obowiązkowe" powiadomienia (jeśli na nie nie odpowiemy, to dialog dostosowywania się w ogóle nie ukaże) to TBN_QUERYINSERT, TBN_QUERYDELETE i TBN_GETBUTTONINFO, a odpowiadanie na nie polega po prostu na zwróceniu odpowiedniej wartości. Teraz pora na szczegóły. Po wspomnianym już dwukrotnym kliknięciu na toolbar wysyłane są następujące powiadomienia:

  • powiadomienie TBN_BEGINADJUST
  • powiadomienie TBN_INITCUSTOMIZE
  • seria powiadomień TBN_QUERYINSERT
  • seria powiadomień TBN_QUERYDELETE
  • seria powiadomień TBN_GETBUTTONINFO

TBN_BEGINADJUST po prostu zawiadamia aplikację, że użytkownik chce dostosowywać toolbar i tego powiadomienia obsługiwać nie musimy. Podobnie jest z TBN_INITCUSTOMIZE, które służy praktycznie tylko do ukrywania przycisku Pomoc, jeśli programista ma taką chęć (wówczas musi zwrócić TBNRF_HIDEHELP).
 
Na następne wymienione powiadomienia odpowiedzieć już trzeba. TBN_QUERYINSERT wysyłane jest dla każdego przycisku na toolbarze osobno i jeśli odpowiemy na nie FALSE, to zablokujemy możliwość wstawienia innego przycisku po lewej stronie od tego, dla którego aktualnie wysyłane jest powiadomienie (a jak to z kolei sprawdzić, już wiemy - struktura NMHDR będzie bardzo pomocna ;-)). Przynajmniej na jedno powiadomienie TBN_QUERYINSERT musimy odpowiedzieć TRUE, inaczej dialog się nie wyświetli. W powyższym przykładzie odpowiadamy TRUE na wszystkie powiadomienia tego rodzaju (nie sprawdzamy nawet, od którego przycisku pochodzą), dając tym samym możliwość wstawiania dodatkowych przycisków gdzie popadnie ;-).
 
Powiadomienia TBN_QUERYDELETE również wysyłane są dla każdego przycisku z osobna i dzięki nim możemy poinformować system, które przyciski użytkownik może sobie usunąć z toolbaru, a które pozostają na nim dożywotnio. W przykładzie powyżej użytkownik może usunąć wszystkie przyciski (a niech się cieszy, bestyja ;-)).
 
Wreszcie - TBN_GETBUTTONINFO służy do tego, byśmy mogli wypełnić listę dostępnych przycisków do dodania na toolbar (lista ta jest wyświetlana po lewej stronie dialogu dostosowywania). Wypełnianie polega na wypełnieniu struktury NMTOOLBAR (do której wskaźnik otrzymujemy wraz z powiadomieniem TBN_GETBUTTONINFO) oraz zwróceniu TRUE. Operację taką należy powtórzyć dla każdego przycisku, który chcemy dodać na tę listę. Na koniec powinniśmy zaś zwrócić FALSE, co przerwie wysyłanie powiadomień TBN_GETBUTTONINFO. Powyżej zrobiliśmy to na samym początku, toteż na liście dostępnych przycisków do dodania mamy tylko domyśly separator (za to możemy ich sobie na toolbar wstawić od groma ;-)).
 
Nie będę tu się rozpisywał na temat wypełniania NMTOOLBAR, gdyż nie zawiera ona w zasadzie nic, o czym do tej pory byśmy nie mówili, więc jest to praktycznie tylko kwestia wypróbowania tego na własną rękę. Poza tym nie mam pomysłu, jakie by tu jeszcze przyciski dodać do toolbaru w naszym przykładzie ;-).
 
Podczas gdy użytkownik wyczynia z toolbarem cuda za pośrednictwem wspomnianego dialogu, jego niecne czyny oczywiście nie przechodzą niezauważone. Za każdym razem, gdy drań coś miesza, wysyłane są odpowiednie donosy, tak że w razie czego możemy zapobiec dalszym szkodom, a nawet ukarać łobuza ;-). Gdy wstawiany jest przycisk, system wysyła TBN_QUERYINSERT i możemy zwrócić FALSE - wtedy przycisk nie zostanie wstawiony i mroczne plany użytkownika spełzną na niczym. Gdy użytkownik usuwa przycisk, wysyłane jest powiadomienie TBN_DELETINGBUTTON. Jeśli użytkownik zmienia cokolwiek, przychodzi powiadomienie TBN_TOLBARCHANGE (takie uogólnienie dwóch poprzednich, obejmujące dodatkowo przeniesienie przycisku). Wreszcie TBN_CUSTHELP oznacza wciśnięcie przez usera przycisku Pomoc, a TBN_RESET - wciśnięcie przycisku Resetuj.
 
Na samym końcu, gdy użytkownikowi zabawa się już znudzi albo sumienie go ruszy, że za dużo napsuł, zamyka on dialog dostosowywania i wówczas dostajemy powiadomienie TBN_ENDADJUST (ufff...).
 
To, o czym tutaj napisałem, nie wyczerpuje oczywiście tematu nawet w połowie, ale jakieś tam wytyczne już mamy, reszta to już tylko praktyka...
Poprzedni dokument Następny dokument
Toolbar, cz. 2 ScrollBar