Nie tylko przyciski
Nasze dotychczasowe rozważania na temat okien dialogowych, sprowadzały się do prostych kontrolek jakimi są przyciski oraz predefiniowanych dialogów. W artykule
Okna dialogowe, cz. 6 pojawiły się jakieś tajemnicze zaklęcia np.
AUTOCHECKBOX, jednak wciąż nie potrafimy wstawić różnych kontrolek znanych z "nie-dialogowych" okienek. Dlatego też warto przyjrzeć się bliżej kontrolkom , które możemy umieścić w naszym dialogu.
Kontrolki w plikach zasobów
Istnieje wiele poleceń w plikach zasobów, które wprowadzają nam różne kontrolki do dialogu. Dotychczas znaliśmy
PUSHBUTTON,
DEFPUSHBUTTON i
LTEXT. Teraz przyjrzymy się pozostałym. Zostały one zebrane w tabeli. W kolumnie
składnia fragmenty kodu objęte nawiasami kwadratowymi są opcjonalne.
Komenda | Opis | Składnia | Zastrzeżenia |
AUTO3STATE | Automatyczny CheckBox 3-stanowy | AUTO3STATE text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
AUTOCHECKBOX | Automatyczny CheckBox 2-stanowy | AUTOCHECKBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]; |
AUTORADIOBUTTON | Automatyczny przycisk radiowy (ten okrągły) | AUTORADIOBUTTON text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]; |
COMBOBOX | ComboBox | COMBOBOX id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] | ComboBox'a można wypełnić treścią dopiero w trakcie wykonania programu. |
CTEXT | Text wyrównywany do środka | CTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
DEFPUSHBUTTON | Zwykły przycisk, domyślnie zaznaczony | CTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
EDITTEXT | Zwykły EDIT | EDITTEXT id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
GROUPBOX | Ramka grupująca | GROUPBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
ICON | Ikonka | ICON text, id, x, y [, szerokosc, wysokosc, style [, style-rozszerzone]] | Parametr text to identyfikator ikonki w zasobach, która ma się wyświetlić. |
LISTBOX | Kontrolka ListBox | LISTBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] | ListBox'a można wypełnić treścią dopiero w trakcie wykonania programu. |
LTEXT | Text wyrównywany do lewej | LTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
PUSHBOX | To samo co PUSHBUTTON | PUSHBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] | PUSHBOX nie wyświetla przyciskowej ramki tylko sam text do wciskania. |
PUSHBUTTON | Zwykły przycisk | PUSHBUTTON text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]][/tt] |
RTEXT | Text wyrównywany do prawej | RTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
SCROLLBAR | Zwykły scrollbar | SCROLLBAR text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]] |
Powyższa lista nie zawiera wszystkich możliwych kontrolek. Kontrolki z przedrostkiem
AUTO mają swoją "nieautomatyczną" wersję, ale ponieważ są rzadko używane, zostały tu pominięte. Parametr
style oznacza dowolny styl charakterystyczny dla danej kontrolki. Natomiast parametr
style-rozszerzone oznacza style podawane jako pierwszy argument funkcji
CreateWindowEx.
Jeżeli ustawiamy parametr
style, powinniśmy dodać tam styl
WS_TABSTOP, który spowoduje obsługę tab'a na kontrolce. Z kolei jeśli chcemy móc się dobrać do kontrolki w czasie wykonania, musimy jej dać unikatowy identyfikator. Jeśli nie, możemy wpisać w polu
id wartość
-1.
Polecenie CONTROL
Lista przedstawiona w poprzednim podrozdziale jest dość obszerna i ma wiele gotowych "specjalizacji" danych kontrolek, jednak zawiera ona tylko te podstawowe. Nie ma poleceń dla kontrolek z grupy ''Common Controls''. Ale za to mamy uniwersalne polecenie '''CONTROL''', które pozwala na wprowadzenie do dialogu dowolnej kontrolki o zadanej klasie, także takiej, którą może kiedyś sami sobie stworzymy ;-).
A więc polecenie
CONTROL to bardzo użyteczny twór. Teraz dowiemy się jak go używać. Oto jak schematycznie wygląda to polecenie:
CONTROL text, id, klasa, style, x, y, szerokosc, wysokosc [, style-rozszerzone]
Nowością jest parametr
klasa, który powinien zawierać nazwę klasy (np.
EDIT,
SysTreeView itp.) kontrolki, którą chcemy utworzyć. Zauważ, że parametr
style jest teraz w innym miejscu i jest wymagany. Oto przykład, jak za pomocą tego polecenia stworzyć kontrolkę
Drzewo (TreeView)CONTROL NULL, 1000, SysTreeView, WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 5, 100, 100
Zauważ że nazwy klasy nie ujmujemy w cudzysłów. Użyliśmy trzech stylów, które zawsze powinniśmy dawać, czyli
WS_CHILD,
WS_VISIBLE i
WS_TABSTOP. Ten ostatni sprawia, że działa tab na kontrolce, ewentualnie można go nie użyć. Poza tymi stylami możemy wprowadzić inne, specyficzne dla danej kontrolki.
Ktoś mógłby powiedzieć, że wystarczy tylko stosować polecenie
CONTROL, a inne są niepotrzebne, skoro już jest uniwersalne polecenie. W sumie miał by trochę racji ten ktoś, ale zauważ, że np. łatwiej jest użyć
LTEXT niż stworzyć
STATICa za pomocą
CONTROL. Po prostu jest miej pisaniny ;-)
Kontrolki w fazie wykonania
W trakcie wykonania programu, możemy dodawać kontrolki, do już istniejących dialogów, zdefiniowanych w plikach zasobów. Najczęściej robi się to w procedurze zdarzeniowej dialogu, obsługując polecenie
WM_INITDIALOG. Kontrolki dodawane w fazie wykonania różnią się od tych z plików zasobów. Oto kilka różnic:
By utworzyć kontrolkę w ten sposób, używamy znanej nam z "normalnych" okien funkcji
CreateWindowEx. Uchwyt do okna-rodzica (czyli dialogu) dostajemy w procedurze zdarzeniowej. Natomiast uchwyty do kontrolek można zapamiętać w zmiennych globalnych. Można też użyć lokalnych uchwytów, a w razie potrzeby pobierać je na podstawie ID. Oto przykład:
case WM_INITDIALOG: {
HWND hListView = CreateWindowEx( WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL, WS_CHILD |
WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, 0, 0, 200, 300,
hwnd,( HMENU ) 400, hInstance, NULL );
}
Modyfikacja Kontrolek w fazie wykonania
Mamy możliwość edycji kontrolek w fazie wykonania. Także tych, które zostały zdefiniowane w plikach zasobów. W takim przypadku posiadamy tylko ich identyfikator, a jest nam potrzebny uchwyt. Możemy go uzyskać za pomocą funkcji
GetDlgItem, która jako pierwszy argument przyjmuje uchwyt do okna-rodzica czyli do dialogu, a jako drugi argument przyjmuje ID kontrolki.
Kontrolki z dialogów można modyfikować przy użyciu tych samych komunikatów i funkcji, których używamy w stosunku do "normalnych" kontrolek. W przypadku niektórych kontrolek konieczna jest dobranie się do nich w fazie wykonania, by np. pobrać text z pola textowego lub wypełnić ComboBox'a.
Podczas obsługi komunikatów przychodzących od kontrolek może pojawić się problem, wynikający z tego, że procedura zdarzeniowa dialogu zwraca tylko
TRUE lub
FALSE, a kontrolki mogą wymagać zwrócenia "normalnej" wartości. Można do tego wykorzystać funkcję
SetWindowLongPtr.
SetWindowLongPtr( hwndDialog, DWLP_MSGRESULT,( LONG ) rezultat );
Po jej wywołaniu możemy zwrócić
TRUE za pomocą zwykłego
return.
Myślę że to wszystkie informacje, które powinny ci wystarczyć do tworzenia bardziej skomplikowanych dialogów.
Bardziej skomplikowane dialogi
Po tej dużej dawce teorii pora przejść do praktyki. Zmajstrujemy sobie dialog logowania, który może nam się przydać, gdy będziemy tworzyć program wykorzystujący internet. Przy okazji dowiemy się, jak zwrócić dane niebędące liczbą w dialogu. W przykładzie wykorzystamy kilka różnych kontrolek. Tak więc do dzieła!
Na początek utworzymy sobie kilka stałych, które będą reprezentować nasze kontrolki:
#define IDD_LOGINDIALOG 666
#define IDC_LOGIN 600
#define IDC_PASSWORD 601
#define IDC_REMEMBERME 602
#define IDC_COMBOBOX 603
Użyjemy dwóch pól tekstowych na login i hasło, CheckBox'a i ComboBox'a. Będziemy także używać etykiet, ale dla nich nie tworzymy identyfikatorów, bo nie ma takiej potrzeby. Nie będziemy się do nich dobierać w czasie wykonania programu. Teraz tworzymy sobie plik zasobów
*.rc, a w nim umieścimy definicję dialogu:
IDD_LOGINDIALOG DIALOG DISCARDABLE 20, 20, 190, 116
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Logowanie"
FONT 8, "MS Sans Serif" {
LTEXT "Login", - 1, 5, 5, 80, 10
EDITTEXT IDC_LOGIN, 5, 15, 180, 14
LTEXT "Hasło", - 1, 5, 35, 80, 10
EDITTEXT IDC_PASSWORD, 5, 45, 180, 14, WS_TABSTOP | ES_PASSWORD
AUTOCHECKBOX "Zapamiętaj mnie", IDC_REMEMBERME, 5, 65, 180, 14
LTEXT "Szyfrowanie", - 1, 5, 85, 100, 10
COMBOBOX IDC_COMBOBOX, 5, 95, 70, 48, WS_TABSTOP | CBS_DROPDOWNLIST
DEFPUSHBUTTON "&Zaloguj", IDOK, 80, 95, 50, 14
PUSHBUTTON "&Anuluj", IDCANCEL, 135, 95, 50, 14
}
Teraz możemy zacząć pisać procedurę zdarzeniową dla dialogu. Nazwiemy ją
LoginDialogProcedure. Na początek obsłużymy przycisk
Anuluj bo to najprostsze ;-).
BOOL CALLBACK LoginDialogProcedure( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
switch( msg ) {
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case IDCANCEL:
EndDialog( hwnd, 0 );
break;
}
}
break;
default:
return FALSE;
}
return TRUE;
}
Nasz ComboBox jakoś tak dziwnie wygląda, kiedy jest pusty, więc dodajmy do niego elementy. W tym celu można obsłużyć komunikat
WM_INITDIALOG. Użyjemy funkcji
GetDlgItem by uzyskać do niego uchwyt.
case WM_INITDIALOG: {
HWND hCombo = GetDlgItem( hwnd, IDC_COMBOBOX );
SendMessage( hCombo, CB_ADDSTRING, 0,( LPARAM ) "Nie" );
SendMessage( hCombo, CB_ADDSTRING, 0,( LPARAM ) "Tak" );
ComboBox_SetCurSel( hCombo, 1 );
SetFocus( GetDlgItem( hwnd, IDC_LOGIN ) );
}
break;
Poza dodaniem elementów do ComboBox'a zaznaczyliśmy pole textowe na login, żeby user mógł od razu wpisywać bez klikania.
Zwracanie wartości nieliczbowych
Załóżmy, że user wypełnił już nasz dialog. Teraz przydałoby się uzyskać wpisane przez niego informacje. Jednak dialogi mogą zwracać jedynie liczby typu
int. Można by obsłużyć samo logowanie wewnątrz procedury dialogowej, jednak nie byłoby to najlepsze rozwiązanie, bo gdybyśmy nasz dialog umieścili w bibliotece to nie nadawał by się do użytku. Znacznie lepszym rozwiązaniem jest zwrócenie wskaźnika na strukturę, która będzie zawierać dane z dialogu. Na większości komputerów liczba
int ma 4 bajty, czyli akurat tyle, ile trzeba na wskaźnik.
Zaczniemy od przygotowania odpowiedniej struktury dla naszego dialogu. Nic nie stoi na przeszkodzie żeby to była klasa, ale żeby nie utrudniać zostańmy przy strukturze:
struct LOGINDIALOGRESULT {
char * lpszLogin;
char * lpszPassword;
bool bRememberMe;
bool bEncryption;
};
Skoro mamy już strukturę, możemy wypełnić ją danymi. W tym celu musimy dodać obsługę przycisku
Zaloguj w naszym dialogu w taki sposób:
case WM_COMMAND: {
switch( LOWORD( wParam ) ) {
case IDOK: {
LOGINDIALOGRESULT * ldr = new LOGINDIALOGRESULT;
ZeroMemory( & ldr, sizeof( LOGINDIALOGRESULT ) );
HWND hLogin = GetDlgItem( hwnd, IDC_LOGIN );
HWND hPassword = GetDlgItem( hwnd, IDC_PASSWORD );
HWND hRememberMe = GetDlgItem( hwnd, IDC_REMEMBERME );
HWND hEncryption = GetDlgItem( hwnd, IDC_COMBOBOX );
DWORD dlugosc = GetWindowTextLength( hLogin );
ldr->lpszLogin = new char[ dlugosc + 1 ];
GetWindowText( hLogin, ldr->lpszLogin, dlugosc + 1 );
dlugosc = GetWindowTextLength( hPassword );
ldr->lpszPassword = new char[ dlugosc + 1 ];
GetWindowText( hPassword, ldr->lpszPassword, dlugosc + 1 );
ldr->bRememberMe =( IsDlgButtonChecked( hwnd, IDC_REMEMBERME ) == BST_CHECKED );
ldr->bEncryption =( bool )( ComboBox_GetCurSel( hEncryption ) );
EndDialog( hwnd,( int ) ldr );
}
break;
case IDCANCEL:
EndDialog( hwnd, 0 );
break;
}
}
break;
Powyższy kod jest dość przydługi, głównie przez instrukcje odczytujące wartośći z kontrolek. Zostały one szczegółowo omówione w artykule [[Kontrolki]]. Na początku tworzymy naszą strukturę za pomocą operatora
new. Dlaczego? Ano dlatego, że gdybyśmy użyli zwykłej zmiennej, to została by ona skasowana po wyjściu z bloku
case, przez co wskaźnik zwrócony przez dialog wskazywałby na nie nasz obszar pamięci. Należy pamiętać o zwolnieniu wartości zwróconej przez dialog za pomocą
delete, gdy już nie będziemy jej potrzebować.
Jeszcze przyjrzyjmy się dialogowi, który w tym odcinku kursu udało nam się stworzyć:
Podsumowanie
To już ostatnia część kursu okien dialogowych, więc dokonajmy małego podsumowania. W pierwszych dwóch częściach nauczyliśmy się podstaw, tworząc okna modalne i niemodalne. Poznaliśmy też sztuczkę pozwalającą na automatyczną obsługę klawisza Tab oraz kombinacji Alt+litera w okienkach. Następnie skorzystaliśmy z dobrodziejstw predefiniowanych dialogów systemu Windows na przykładzie dialogów wyboru pliku, czcionki, koloru oraz wyszukiwania/zamiany tekstu. Potem dokonaliśmy modyfikacji predefiniowanego dialogu wyboru pliku, rozszerzając nieco jego możliwości. Następnie dowiedzieliśmy się jak tworzyć dialogi od podstaw w kodzie programu. Na koniec poznaliśmy większość kontrolek dostępnych w plikach zasobów oraz dowiedzieliśmy się jak w dialogu zwrać wartości inne niż liczby. Teraz o okienkach dialogowych WinAPI wiemy już prawie wszystko :-).