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

Kontrolki

[lekcja] Rozdział 2. Tworzenie pierwszych kontrolek za pomocą WinAPI. Omówione kontrolki: przyciski, pola tekstowe, listy elementów i listy rozwijalne oraz paski do scrollowania. Ponadto w rodziale znalazły się informacje jak wstawiać elementy statyczne do okna takie jak tekst, ikony czy też obrazki. Rozdział opisuje również w jaki sposób obsługuje się zdarzenia kontrolek i jak rozpoznawać z której kontrolki dotarł komunikat do procedury obsługującej komunikaty. W rozdziale zawarto ponadto wprowadzenie do obsługi czcionek oraz podstawowe informacje o checkboxach i radiobuttonach.

Podstawowe kontrolki

Nasze puste okienko jakoś blado wygląda przy aplikacjach typu Word, albo chociażby przy samym Dev-C++. Warto by dorzucić te różne fajne bajery, jak przyciski, menu, pola tekstowe... Te wszystkie rzeczy nazywa się po angielsku ''controls'', co po naszemu tłumaczy się (może niezbyt trafnie, ale całkiem funkcjonalnie) jako kontrolki.
 
Kontrolki to po prostu takie "małe" okienka, które wchodzą w skład "dużego" okna macierzystego. Tak samo jak "zwykłe" okna, kontrolki tworzymy więc funkcjami CreateWindow oraz CreateWindowEx.
Co wrzucić do tych funkcji, żeby zamiast normalnego okna wyprodukowały nam kontrolkę? Przede wszystkim musimy wpisać predefiniowaną nazwę klasy. Jak być może pamiętasz, podczas tworzenia głównego okna programu mogliśmy tam wpisać cokolwiek, np. "MyWindowClass". Tworząc przycisk wpisujemy zaś "BUTTON". Pozostałe klasy, których możemy użyć, to m.in.: COMBOBOX, EDIT, LISTBOX, SCROLLBAR, STATIC. Ich przeznaczenia zapewne się domyślasz, a jeśli nie, to i tak zaraz wszystko omówimy.
 
Druga rzecz, którą trzeba dorzucić do argumentów funkcji tworzącej kontrolkę to styl WS_CHILD, który oznacza, że tworzone przez nas okienko (kontrolka) jest oknem potomnym dla głównego okna programu. Jeśli już jesteśmy przy stylach okna, to musisz pamiętać, że każda kontrolka ma swój specyficzny zestaw stylów, więc nie wszystkie style odnoszące się do "zwykłego" okna działają z kontrolkami (i vice versa). Szczegóły omówimy w rozdziałach poświęconych konkretnym kontrolkom.

Przyciski

Jak się rzekło, nazwa klasy odpowiadającej za przyciski to BUTTON. Zestaw stylów specyficznych dla tej klasy zaczyna się przedrostkiem BS_, ale w sumie nie potrzebujemy na razie żadnego z nich do szczęścia. Stwórzmy sobie najprostszy z możliwych przycisków. Najpierw deklaracja globalnego uchwytu (poza funkcją WinMain):

C/C++
HWND g_hPrzycisk;

...i tworzymy (wewnątrz WinMain):

C/C++
g_hPrzycisk = CreateWindowEx( 0, "BUTTON", "Nasz przycisk", WS_CHILD | WS_VISIBLE,
100, 100, 150, 30, hwnd, NULL, hInstance, NULL );

Kod z tego przykładu należy wkleić tuż po instrukcji tworzącej okienko główne. Jeśli program, do którego ten przykładowy fragment wklejasz, używa innej zmiennej przechowującej uchwyt głównego okna niż hwnd, to oczywiście musisz to w tym fragmencie odpowiednio zmienić. To samo tyczy się uchwytu programu (w moim przykładzie hInstance).
 
Możliwości klasy BUTTON są jednak znacznie większe, niż mogłoby się na pierwszy rzut oka wydawać. Oprócz bowiem zwykłych przycisków, używając tej klasy możemy jeszcze tworzyć checkboxy, przyciski radiowe (czyli te okrągłe, z których tyko jeden w danym momencie może być wciśnięty), a nawet ramki grupujące różnego rodzaju kontrolki:

Różne rodzaje kontrolek klasy BUTTON (Windows 98)
Różne rodzaje kontrolek klasy BUTTON (Windows 98)


Jak to wszystko uzyskać? To bardzo proste. Wystarczy dołączyć odpowiedni styl - BS_CHECKBOX dla checkboxa, BS_RADIOBUTTON dla przycisku radiowego, BS_GROUPBOX dla ramki grupującej. Możesz też poeksperymentować z innymi stylami, których opisy znajdziesz w MSDN. A oto przykład tworzenia przycisku z innym stylem, tak na wszelki wypadek:

C/C++
g_hPrzycisk = CreateWindowEx( 0, "BUTTON", "Checkbox", WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
100, 100, 150, 30, hwnd, NULL, hInstance, NULL );

Naciśnięcie naszego przycisku generuje komunikat WM_COMMAND, dzięki czemu możemy przyciskowi przypisać jakąś akcję, np. wyświetlenie wiadomości. Wystarczy dodać obsługę komunikatu WM_COMMAND:

C/C++
case WM_COMMAND:
MessageBox( hwnd, "Nacisnąłeś przycisk!", "Ha!", MB_ICONINFORMATION );
break;

Tak się jednak składa, że komunikat WM_COMMAND może zostać wygenerowany przez bardzo wiele różnych kontrolek, np. przez menu albo toolbar, w dodatku jeśli mamy w naszej aplikacji kilka przycisków, to każdy z nich oczywiście będzie generował  WM_COMMAND. Jak więc sprawdzić, czy pochodzi on właśnie od przycisku, i to tego przycisku, który nas interesuje? Otóż musimy przyjrzeć się uważniej argumentom naszej procedury okna. Szczególnie dwa z nich są dla nas interesujące podczas pisania obsługi komunikatów - lParam i wParam. Są to dwie 32-bitowe liczby (przynajmniej w systemach od Windows 95 w górę), które zawierają różne rzeczy bardzo przydatne lub wręcz niezbędne podczas obsługiwania tych komunikatów. Dla każdego komunikatu inna jest rola wParam i lParam. I tak na przykład kiedy chcemy obsłużyć WM_COMMAND, parametr lParam zawiera uchwyt kontrolki, która wygenerowała ten komunikat. Tego właśnie szukaliśmy! Teraz możemy poprawić kod, obsługujący kliknięcie na przycisku:

C/C++
case WM_COMMAND:
if(( HWND ) lParam == g_hPrzycisk )
   
 MessageBox( hwnd, "Nacisnąłeś przycisk!", "Ha!", MB_ICONINFORMATION );

break;

Wyjaśnić należało by dwie rzeczy. Po pierwsze, jeśli mamy porównywać wartość  lParam z uchwytem okna (czyli kontrolki), to należy lParam  przekonwertować do  HWND, inaczej kompilator się zdenerwuje. Druga sprawa - nasz przycisk tworzyliśmy wewnątrz funkcji WinMain, gdybyśmy w tym samym miejscu zadeklarowali uchwyt do tego przycisku, to nie mielibyśmy dostępu do tego uchwytu w procedurze okna (u nas WndProc). Dlatego właśnie uchwyt ten jest u nas  zmienną globalną (na zewnątrz wszystkich funkcji programu), co w tym przykładzie zaznaczyliśmy przez dodanie przedrostka  g_ do nazwy zmiennej (przyjęcie takiej konwencji jest bardzo pożytecznym zwyczajem, polecam).
 
Tym oto sposobem mamy przycisk, i nawet przycisk ten działa jak przycisk ;-). Skoro osiągnęliśmy tak zadowalające rezultaty, pora na następną kontrolkę...

Pola tekstowe

Te fajne białe prostokąty, w które wpisuje się tekst lub liczby, zwą się czasami polami tekstowymi, po angielsku ''text boxes''. Odpowiadająca im nazwa klasy to EDIT:

C/C++
HWND hText = CreateWindowEx( 0, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 50, 50, 150, 20,
hwnd, NULL, hInstance, NULL );

Zauważ, że zamiast nazwy okna podaliśmy NULL. Dla przycisku dobrze było, kiedy miał on wyświetloną jakąś nazwę, bo pusty przycisk wygląda raczej podejrzanie ;-). Pole tekstowe czegoś takiego nie potrzebuje, bo i tak nie ma gdzie wyświetlić takiej nazwy.
 
Zapewne nie będziesz specjalnie zachwycony efektem - otrzymaliśmy zwykły, płaski jak decha prostokąt, i tak dobrze, że z ramką. Dorzućmy więc rozszerzony styl WS_EX_CLIENTEDGE (pierwszy argument funkcji CreateWindowEx):

C/C++
HWND hText = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
50, 50, 150, 20, hwnd, NULL, hInstance, NULL );

Teraz wszystko jest jak Pan Bóg przykazał, aczkolwiek oprócz małych pól tekstowych stosuje się też czasem większe, w które biedny użytkownik musi wpisywać całe wypracowania. W takich wielkich polach tekstowych jest miejsce na wiele linijek tekstu, więc musimy sprawić, żeby w miarę wpisywania go zdania były automatycznie łamane i przenoszone do następnego wiersza. Co więcej, w razie gdyby użytkownika poniosła wena twórcza, musimy zapewnić możliwość przewijania naszego pola tekstowego. Tę pierwszą cechę zapewni nam styl ES_MULTILINE, zaś drugą - połączenie ES_AUTOVSCROLL (możliwość przewijania zawartości w pionie) i WS_VSCROLL (dodanie pionowego paska przewijania). Zauważ, że ten ostatni styl jest dostępny dla wszystkich rodzajów okien, nie tylko dla pól tekstowych. W praktyce wyjdzie to tak:

C/C++
HWND hText = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL, 5, 5, 150, 150, hwnd, NULL, hInstance, NULL );

Dzięki paru zaklęciom nasze pole się stosownie powiększyło, dodany został pasek pionowego przewijania (na razie będzie nieaktywny, dopóki nie wpiszemy do pola odpowiedniej ilości tekstu), a nie mieszczące się w danym wierszu wyrazy będą automatycznie przenoszone do następnego. Będzie to wyglądać mniej więcej tak:

Pole tekstowe w całej okazałości (Windows 98)
Pole tekstowe w całej okazałości (Windows 98)
 

Teraz miła niespodzianka: jeśli klikniesz na naszym polu tekstowym, pojawi się automatycznie menu kontekstowe, takie samo jak w większości "profesjonalnych" aplikacji, umożliwiające kopiowanie, wycinanie i wklejanie tekstu ze schowka! Działają też standardowe skróty klawiszowe, jak np. Ctrl+C, a nawet cofanie ostatniego polecenia. Wystarczy jeszcze tylko rozszerzyć nasze pole tekstowe na całe okno i mamy imitację Notatnika ;-).
 
Jeszcze tylko małe pytanie: jak wstawić lub pobrać tekst z pola w kodzie programu? Możemy użyć funkcji SetWindowText oraz GetWindowText. Na początek ta pierwsza - wklejamy ją tuż po kodzie tworzącym nasze pole tekstowe:

C/C++
SetWindowText( hText, "Wpisz tu coś" );

Funkcja GetWindowText wymaga oczywiście podania adresu bufora, do którego przekopiowany zostanie tekst z naszego pola. Bufor ten musi być odpowiednio duży, żeby pomieścił cały tekst zawarty w polu. Rozmiar bufora, czyli długość tekstu, należy podać jako ostatni argument dla funkcji GetWindowText. Długość tekstu z pola obliczymy przy pomocy funkcji GetWindowTextLength:

C/C++
DWORD dlugosc = GetWindowTextLength( hText );
LPSTR Bufor =( LPSTR ) GlobalAlloc( GPTR, dlugosc + 1 );
GetWindowText( hText, Bufor, dlugosc + 1 );

Tekst powędruje tym samym do stringa Bufor, którego będziemy mogli sobie używać do woli w różnych niecnych celach :-). Przy okazji pokazaliśmy, jak alokować dynamicznie pamięć w Windows (funkcja GlobalAlloc). W szczegóły alokacji zagłębimy się przy innej okazji ;-). Na razie jednak musimy wiedzieć, w jaki sposób zwolnić pamięć zajmowaną przez bufor, gdy ten nie będzie już potrzebny:

C/C++
GlobalFree( Bufor );

Listy


Chodzi oczywiście o kontrolki typu '''List Box''', a nie żadną tam pocztę ;-). Tworzymy je tak samo, jak i pola tekstowe:

C/C++
HWND hListBox = CreateWindowEx( WS_EX_CLIENTEDGE, "LISTBOX", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
5, 5, 150, 200, hwnd, NULL, hInstance, NULL );

No i tak samo jak pola tekstowe wyglądają :-/. Przynajmniej dopóki nie dodamy do nich żadnych elementów. Żeby cokolwiek dodać do naszej listy, musimy wysłać odpowiedni komunikat do naszego okienka-kontrolki, na początek najlepszy będzie LB_ADDSTRING:

C/C++
SendMessage( hListBox, LB_ADDSTRING, 0,( LPARAM ) "Element 1" );
SendMessage( hListBox, LB_ADDSTRING, 0,( LPARAM ) "Element 2" );

Zwróć uwagę na jawną konwersję ostatniego argumentu (wskaźnika do stringa) do typu LPARAM
– jest ona konieczna, jeśli nie chcemy oglądać komunikatów o błędach kompilacji. Jeśli chodzi o funkcję SendMessage, najlepiej jest stosować takie konwersje w każdym przypadku. Tymczasem efekt naszych wysiłków będzie następujący:

Dwa elementy na naszej czarnej liście (Windows 98)
Dwa elementy na naszej czarnej liście (Windows 98)


O listach nie będę się zbytnio rozpisywał, bo używa się ich stosunkowo rzadko. Generalnie cała obsługa list odbywa się przy pomocy podobnych komunikatów, jak ten powyżej do dodawania elementów. Pełną listę tych komunikatów możesz znaleźć w MSDN.

Listy rozwijalne, czyli ComboBox


Znacznie częściej wykorzystywanym rodzajem listy jest tzw. '''Combo Box'''. Nazwa ta nie oznacza, że jej twórcy byli fanami Mortal Kombat, tylko że kontrolka ta jest kombajnem, łączącym w sobie funkcje listy i pola tekstowego. Intuicyjny (zwawałoby się) sposób jego utworzenia da dość dziwny rezultat:

C/C++
HWND hCombo = CreateWindowEx( WS_EX_CLIENTEDGE, "COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
50, 50, 150, 200, hwnd, NULL, hInstance, NULL );

Powstało nam małe pole tekstowe, a pod spodem - lista... Kto widział takie dziwadło w profesjonalnych aplikacjach!? Konia z rzędem takiemu. My tu nie będziemy wiochy robić i zmajstrujemy sobie prawdziwego, cywilizowanego ComboBoxa. A wystarczy tylko dodać odpowiedni styl, mianowicie CBS_DROPDOWN:
 
C/C++
HWND hCombo = CreateWindowEx( WS_EX_CLIENTEDGE, "COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
CBS_DROPDOWN, 50, 50, 150, 200, hwnd, NULL, hInstance, NULL );

Nooo, teraz to rozumiemy! Nowe Combo wygląda perfekcyjnie:

Cywilizowane Combo ;-)
Cywilizowane Combo ;-)


Warto zwrócić uwagę na małą pułapkę, w którą wpada niejeden początkujący amator ComboBoxów. Otóż jeśli zrobimy taką kontrolkę ze stylem CBS_DROPDOWN, to parametr określający wysokość okna-kotrolki (w naszym przypadku równy 200) nie określa rozmiaru pola tekstowego ze strzałką, tylko CAŁEGO ComboBoxa, razem z listą po rozwinięciu. Natomiast wysokość pola tekstowego jest domyślna i zależy od ustawień systemowych (można ją zmienić w Panelu Sterowania, tylko po co). Oczywiście jeśli dodamy do listy ComboBoxa więcej elementów, niż mogłoby się zmieścić w podanej przez nas wysokości, to pojawi się pasek przesuwania.
 
Właśnie, dodawanie. Wygląda ono analogicznie, jak w przypadku list - wysyłamy komunikat CB_ADDSTRING (różnica to ta jedna literka w nazwie ;-)):

C/C++
SendMessage( hCombo, CB_ADDSTRING, 0,( LPARAM ) "Element 1" );
SendMessage( hCombo, CB_ADDSTRING, 0,( LPARAM ) "Element 2" );

Na koniec powiem jeszcze, jak zrobić prostszą wersję ComboBoxa. Jeśli dodamy styl CBS_DROPDOWNLIST zamiast CBS_DROPDOWN, to otrzymamy ComboBox, w którego polu tekstowym nie można już nic wpisać, za to pojawia się tam aktualnie wybrany element. Postać taka jest moim zdaniem bardziej przyjazna zarówno dla użytkownika, jak i programisty, więc polecam ;-).

Paski przesuwu


...Czyli scrollbary. Wiecie o co biega, już je zresztą stosowaliśmy. Wtedy jednak były one integralnymi częściami innych kontrolek, a możemy stworzyć oddzielny pasek i wykorzystać go w sobie tylko wiadomych celach ;-). Samo tworzenie wygląda tak samo, jak w przypadku wcześniej opisanych kontrolek (nazwa klasy: SCROLLBAR), z tym, że domyślnie tworzony jest pasek poziomy, natomiast stworzenie paska pionowego trzeba wymusić, dodając styl SBS_VERT. Wygląda toto w ten sposób:

Paski przesuwu jako samodzielne kontrolki (Windows 98)
Paski przesuwu jako samodzielne kontrolki (Windows 98)


Obsługa pasków, tak samo jak i innych kontrolek, odbywa się przy pomocy odpowiednich komunikatów, więc nie będę się nad tym rozwodził - wszystko co trzeba znajdziesz w MSDN, a kiedyś może spiszę wszelkie niezbędne komunikaty i wrzucę na stronkę.

Statyczne elementy

Oprócz kontrolek interaktywnych, które użytkownik może wciskać, przesuwać, klikać i na inne sposoby molestować, istnieją też kontrolki statyczne - tych to żadną siłą nie ruszysz ;-). Wykorzystuje się je, po uaktywnieniu odpowiednich stylów, jako etykiety np. dla pól tekstowych (żeby user wiedział, co w nie wpisywać), jako nieruchome obrazki, linie rozdzielające poszczególne kontrolki, ramki itp. Tworzymy je używając klasy STATIC. Jako przykład posłuży nam najpowszechniejsza chyba ich rola, czyli wyświetlanie tekstu. Oto jak to zrobić:

C/C++
HWND hStatic = CreateWindowEx( 0, "STATIC", NULL, WS_CHILD | WS_VISIBLE |
SS_LEFT, 50, 50, 150, 200, hwnd, NULL, hInstance, NULL );


Styl SS_LEFT sprawia, że tekst jest wyrównywany do lewej i włącza łamanie linii. Można też użyć SS_RIGHT lub SS_CENTER (zgadnij co oznaczają ;-)). Póki co nie ma z naszej kontrolki wielkiego pożytku (w ogóle jej nie widać ;-)). Żeby wypełnić naszą kontrolkę tekstem używamy funkcji SetWindowText (tak jak w przypadku pól tekstowych):

C/C++
SetWindowText( hStatic, "Napis" );

Można też zamiast tekstu wyświetlić ikonkę. Żeby było to możliwe trzeba dodać flagę SS_ICON do naszej kontrolki:

C/C++
HWND hStaticIcon = CreateWindowEx( 0, "STATIC", NULL, WS_CHILD | WS_VISIBLE |
SS_ICON, 50, 50, 48, 48, hwnd, NULL, hInstance, NULL );


Żeby ustawić ikonkę, trzeba wysłać do kontrolki komunikat STM_SETICON. W parametrze wParam podajemy uchwyt do ikony:

C/C++
SendMessage( hStaticIcon, STM_SETICON,( WPARAM ) LoadIcon( NULL, IDI_APPLICATION ), 0 );

Analogicznie można wstawiać bitmapy (komunikat STM_SETIMAGE).

Identyfikowanie kontrolek w komunikatach

Wróćmy na moment do naszego przycisku. Kiedy go nacisnęliśmy, wysyłał on komunikat WM_COMMAND, dzięki czemu mogliśmy przypisać przyciskowi jakieś działanie, i nawet to zrobiliśmy - przyciśnięcie wywoływało pojawienie się krótkiej wiadomości. Nauczyliśmy się też rozróżniać komunikaty pochodzące od innych kontrolek oraz komunikaty od "zwykłych" przycisków. Wszystko fajnie aż do momentu (o którym też już zresztą napomknęliśmy), kiedy dorzucimy drugi przycisk - wtedy którykolwiek przycisk zawsze będzie powodował wyświetlenie wiadomości. Jak sprawdzić, który przycisk z tych dwóch został naciśnięty?

Na szczęście komunikat WM_COMMAND nie przybywa do nas z gołymi rękami, a przynosi ze sobą argumencik wParam, który zawiera '''identyfikator''' kontrolki, czyli unikalną, globalną liczbę, przypisaną danej kontrolce. Dzięki temu możemy wstawić instrukcję switch i dokładnie obsłużyć komunikat WM_COMMAND bez względu na to, ile kontrolek tej samej klasy mogło go wysłać.
 
Jak nadać kontrolce identyfikator? Robimy to na etapie tworzenia kontrolki funkcją CreateWindowEx. Jak wspomnieliśmy w części kursu poświęconej tworzeniu głównego okna, funkcja ta przyjmuje jako argument m.in. uchwyt do menu. W naszym oknie menu jeszcze nie tworzyliśmy, więc daliśmy tam NULL. Jeśli jednak tworzymy nie takie zwykłe okienko, lecz kontrolkę, uchwyt do menu pełni rolę '''identyfikatora'''. Mówiąc obrazowo: definiujemy jakieś stałe, najlepiej przy pomocy dyrektywy #define:
 
C/C++
#define ID_PRZYCISK1 501
#define ID_PRZYCISK2 502

A następnie tworzymy kontrolki, przypisując im identyfikatory tam, gdzie funkcja CreateWindowEx spodziewa się podania uchwytów do menu (dla niepoznaki konwertujemy go do HMENU ;-) ):
 
C/C++
HWND hButton1 = CreateWindowEx( WS_EX_CLIENTEDGE, "BUTTON", "Pierwszy", WS_CHILD | WS_VISIBLE |
WS_BORDER, 50, 50, 150, 30, hwnd,( HMENU ) ID_PRZYCISK1, hInstance, NULL ),
hButton2 = CreateWindowEx( WS_EX_CLIENTEDGE, "BUTTON", "Drugi", WS_CHILD | WS_VISIBLE |
WS_BORDER, 50, 100, 150, 30, hwnd,( HMENU ) ID_PRZYCISK2, hInstance, NULL );

Po czym komplikujemy troszkę obsługę komunikatu WM_COMMAND tak, żeby uwzględniał istnienie kilku kontrolek jednocześnie:

C/C++
case WM_COMMAND:
switch( wParam )
{
case ID_PRZYCISK1:
   
MessageBox( hwnd, "Wcisnąłeś przycisk 1", "Test", MB_ICONINFORMATION );
   
break;
   
case ID_PRZYCISK2:
   
MessageBox( hwnd, "Wcisnąłeś przycisk 2", "Test", MB_ICONINFORMATION );
   
break;
   
   
default:
   
MessageBox( hwnd, "Zrobiłeś coś innego ;-)", "Test", MB_ICONINFORMATION );
}
break;

Zauważ, że "zapomnieliśmy" sprawdzić, czy komunikat faktycznie pochodzi od przycisku, czy może od zupełnie innego rodzaju kontrolki - była już o tym mowa wyżej. Tutaj jednak takie rozróżnienie  nie jest nam potrzebne, za to zaciemniłoby nieco sytuację ;-).
 
Jeśli wkleiłeś cały ten przykład i skompilowałeś, to być może zauważyłeś, że dodatkowo zmienił się wygląd przycisków. Fajne, nie? ;-) To oczywiście zasługa stylu WS_EX_CLIENTEDGE, którego wcześniej nie stosowaliśmy do przycisków (ale oczywiście nie jest to zabronione ;-)).

Stan przycisków

Skoro już jesteśmy przy identyfikatorach, to nauczmy się od razu, w jaki sposób ustawić bądź usunąć "fajeczkę" z checkboxów (do tej pory nie mogliśmy tego zrobić). Służy do tego funkcja CheckDlgButton, jej użycie wygląda tak:

C/C++
CheckDlgButton( hwnd, ID_CHECKBOX1, BST_CHECKED ); //ustaw "fajeczkę"
CheckDlgButton( hwnd, ID_CHECKBOX1, BST_UNCHECKED ); //usuń "fajeczkę"

Jeśli nie dysponujemy akurat identyfikatorem kontrolki, a mamy tylko do niej uchwyt, możemy uzyskać identyfikator stosując GetDlgCtrlID:

C/C++
int ID_CHECKBOX1 = GetDlgCtrlID( hCheckBox1 );

Musimy tylko pamiętać o tym, by w ogóle ten identyfikator został kontrolce nadany podczas jej tworzenia. A teraz sprawdźmy, czy checkbox jest zafajkowany, czy nie:

C/C++
BOOL bChecked =( IsDlgButtonChecked( hwnd, ID_CHECKBOX1 ) == BST_CHECKED );

Funkcję tę możemy stosować do wszystkich rodzajów przycisków, nie tylko do checkboxów. Warto wiedzieć, że oprócz BST_CHECKED i BST_UNCHECKED może ona jeszcze zwrócić BST_INDETERMINATE (czyli stan nieokreślony) lub 0 (co oznacza, że przycisk nie posiada żadnego ze stylów, które umożliwiałyby mu bycie w jednym z trzech wymienionych stanów).
 
Przyciski radiowe zwykle występują w grupie, więc jeśli jeden ma być zaznaczony, to pozostałe należy odznaczyć. Dlatego w tym przypadku użycie CheckDlgButton nie wystarcza i należy zastosować inną funkcję - CheckRadioButton. Jeśli na przykład mamy grupę siedmiu przycisków o identyfikatorach od ID_RADIO1 do ID_RADIO7, to w celu zaznaczenia przycisku z tej grupy o identyfikatorze ID_RADIO3 piszemy:

C/C++
CheckRadioButton( hwnd, ID_RADIO1, ID_RADIO7, ID_RADIO3 );

Dzięki temu mamy pewność, że jeden właściwy przycisk będzie zaznaczony, a pozostałe z tej grupy - nie :-).

Stan Combo Boxów

Wróćmy na chwilę do naszych ComboBoxów. Póki co nie mieliśmy z nich zbyt wielkiego pożytku, gdyż nie wiedzieliśmy którą pozycję wybrał user. Aby się tego dowiedzieć, można skorzystać z makra ComboBox_GetCurSel, które zwróci nam index (czyli numer ;-)) zaznaczonej pozycji w ComboBoxie (liczymy od 0). Jako parametr podajemy uchwyt do ComboBoxa:

C/C++
int numer = ComboBox_GetCurSel( hComboBox );

Jeżeli user nie wybrał żadnej pozycji funkcja zwróci wartość CB_ERR. Powyższy sposób sprawdzi się, jeżeli mamy ComboBoxa bez możliwości edycji tekstu, a dane trzymamy np. w tablicy. Jeśli dajemy użytkownikowi możliwość edycji tekstu, to interesuje nas właśnie ten tekst, a nie index pozycji. By pobrać tekst możemy wykorzystać makro ComboBox_GetText, które w argumentach przyjmuje kolejno: uchwyt do ComboBoxa, wskaźnik do stringa, maksymalną ilość znaków do przekopiowania. Wspomniany string musi być odpowiednio duży, by pomieścić cały tekst. Długość tekstu możemy poznać przy użyciu makra ComboBox_GetTextLength. Oto jak pobrać tekst z ComboBoxa:

C/C++
int dlugosc = ComboBox_GetTextLength( hComboBox );
LPSTR NapisZcomboBoxa =( LPSTR ) GlobalAlloc( GPTR, dlugosc + 1 );
ComboBox_GetText( hComboBox, NapisZcomboBoxa, dlugosc + 1 );

Najpierw zapisujemy długość tekstu do zmiennej. Potem alokujemy stringa o długości poznanej wcześniej. Następnie zapisujemy treść ComboBoxa. '''Pamiętaj o zwolnieniu pamięci''' funkcją GlobalFree.

Ładne czcionki w kontrolkach

Nie trudno zauważyć, że nasze kontrolki różnią się od tych spotykanych w "profesjonalnych" aplikacjach. W większości programów używana jest standardowa czcionka Windowsowa, a w naszych jest jakaś "niestandardowa", a w dodatku brzydka czcionka przez co użytkownicy łatwo zniechęcą się do naszych programów. Do takiej sytuacji nie możemy dopuścić, dlatego czas nauczyć się ustawiania standardowych czcionek Windowsa.

Aby użyć takiej czcionki, musimy ją najpierw wczytać. Służy do tego funkcja GetStockObject, która przyjmuje jako argument stałą, reprezentującą to, co chcemy wczytać. W naszym przypadku jest to zwykła czcionka czyli DEFAULT_GUI_FONT. Funkcja zwróci nam uchwyt do czcionki, który dobrze będzie zapisać w jakiejś zmiennej:

C/C++
HFONT hNormalFont =( HFONT ) GetStockObject( DEFAULT_GUI_FONT );

Skoro mamy już czcionkę, to możemy ją przypisać do kontrolki. Można to zrobić wysyłając komunikat WM_SETFONT do kontrolki. Jako parametr wParam należy podać uchwyt do czcionki, który uzyskaliśmy wcześniej. Tym sposobem można też przypisać dowolną inną czcionkę. Poniższy kod najlepiej umieścić zaraz po instrukcji tworzącej kontrolkę:

C/C++
SendMessage( hKontrolka, WM_SETFONT,( WPARAM ) hNormalFont, 0 );

Powyższy sposób zmienia tylko jedną kontrolkę, a nie wszystkie naraz. Dlatego czcionkę trzeba ustawiać osobno dla każdej kontrolki.
Poprzedni dokument Następny dokument
Podstawy WinAPI Obsługa myszy i klawiatury