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):
...i tworzymy (wewnątrz WinMain):
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:
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:
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:
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:
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:
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):
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:
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:
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:
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:
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:
Listy
Chodzi oczywiście o kontrolki typu '''List Box''', a nie żadną tam pocztę ;-). Tworzymy je tak samo, jak i pola tekstowe:
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:
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:
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:
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:
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:
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 ;-)):
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:
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ć:
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):
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:
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:
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:
#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 ;-) ):
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:
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:
CheckDlgButton( hwnd, ID_CHECKBOX1, BST_CHECKED ); CheckDlgButton( hwnd, ID_CHECKBOX1, BST_UNCHECKED );
Jeśli nie dysponujemy akurat identyfikatorem kontrolki, a mamy tylko do niej uchwyt, możemy uzyskać identyfikator stosując
GetDlgCtrlID:
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:
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:
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:
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:
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:
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ę:
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.