Okna dialogowe
Mało która windowsowa aplikacja składa się z jednego jedynego okienka. Jeśli chcemy zbudować coś bardziej skomplikowanego niż Notatnik, powinniśmy do dyspozycji użytkownika dać jeszcze wiele innych okienek: do otwierania plików, do pokazywania pomocy i informacji o aktualnej wersji programu, do wyszukiwania słów w tekście, do zmiany ustawień itp.
Te wszelkie pomniejsze okienka zwane są na ogół '''oknami dialogowymi''', ponieważ służą właśnie do czegoś w rodzaju dialogu między userem a aplikacją. Niektórzy leniwi programiści (tak jakby istnieli pracowici... ;-)) idą sobie na łatwiznę i z dialogu robią często monolog, tzn. użytkownik wpisuje jakieś dane, a program ma to wszystko tylko zaakceptować. Praktyka pokazuje jednak, że beztroska lub niewiedza userów nie zna granic, dlatego też wszelkie wprowadzane przez te ofiary losu dane należy na bieżąco kontrolować, czy spełniają ustalone wymagania, czy też nie. Ale to tylko tak na marginesie.
Zasoby z dialogami
Okno dialogowe najłatwiej stworzyć za pośrednictwiem '''pliku zasobów''' (''resource file''). Jest to zwykły plik tekstowy z rozszerzeniem *.rc, zawierający skrypty zasobów. Wiele IDE tworzy domyślnie taki plik dla każdego projektu. I tak na przykład Dev-C++ w wersji oznaczonej czwórką miał nawet swój własny, mały edytorek do takich plików, niestety podczas tworzenia piątej wersji edytorek gdzieś się twórcom zapodział :-(. Bardziej profesjonalne narzędzia w rodzaju MS Visual C++ posiadają potężne wizualne edytory to tworzenia dialogów. Bez względu na to, czy twój IDE ma jakiś edytor plików *.rc czy też nie, na pewno przyda ci się wiedza, jak taki plik wygląda w środku:
#include <windows.h>
#include "dialog.h"
IDD_MOJDIALOG DIALOG DISCARDABLE 20, 20, 200, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Mój własny dialog"
FONT 8, "MS Sans Serif"
{
DEFPUSHBUTTON "&Fajnie!", IDOK, 124, 18, 50, 14
PUSHBUTTON "&Anuluj", IDCANCEL, 124, 35, 50, 14
LTEXT "To jest okno dialogowe", IDC_LABEL, 16, 18, 80, 33
}
Cały ten badziew powyżej odpowiedzialny jest za stworzenie prostego okna dialogowego, co określa pierwsza linijka. Zaczyna się ona od identyfikatora
IDD_MOJDIALOG. Jest to zwykła stała o ustalonej przez nas wartości (praktycznie dowolnej, byle unikalnej - w skali aplikacji), a konkretnie makro, które musimy sobie zdefiniować sami, za pomocą dyrektywy
#define oczywiście. Najlepiej będzie zrobić osobny plik nagłówkowy, na przykład
dialog.h (tak jak to zrobiliśmy w powyższym przykładzie) i wrzucić do niego tę oraz wszystkie następne deklaracje identyfikatorów dla elementów naszego dialogu:
#define IDD_MOJDIALOG 200
#define IDC_LABEL 201
Czasami jest konieczne dołączenie do pliku zasobów odpowiedniego nagłówka, zawierającego definicje takich stałych, jak
PUSHBUTTON czy
LTEXT, niezbędnych do przetwarzania plików *.rc (wszystkie te stałe opisane są tam gdzie zwykle, czyli w MSDN :-)). Nazwa takiego nagłówka jest zależna od używanego IDE, zazwyczaj jednak wystarczy dołączyć
windows.h, który zajmie się całą brudną robotą :-).
Słowo
DISCARDABLE oznacza, że dany zasób może być wrzucony do pamięci wirtualnej na dysku, jeśli nie jest akurat używany. Następujące po nim liczby, nietrudno się domyślić, to współrzędne i wymiary okna dialogowego. Niestety, nie w pikselach. Oficjalnie dla ułatwienia programistom życia, praktycznie w celu dokładnie odwrotnym wymyślono, że jednostki miary w plikach zasobów zależne są od wielkości czcionki używanej przez system. Poniekąd słusznie, gdyż jeśli trafi się jakiś odmieniec używający fonta wielkości 20 punktów, to nasz dialog będzie automatycznie do tego przystosowany...
Druga linijka zasobu z naszym dialogiem nie jest trudna do rozszyfrowania - tak, to kombinacja stylów dla okna, są one identyczne z tymi stosowanymi przez funkcję
CreateWindow. Styl
DS_MODALFRAME jest typowy dla modalnych okien dialogowych, o tym trochę później. Trzecia i czwarta linijka - tajne zastosowanie, tylko dla orłów ;-).
Dalej otwieramy "blok instrukcji" (niektóre kompilatory mogą wymagać słów
BEGIN i
END zamiast nawiasów klamrowych). Jest to miejsce na deklaracje elementów naszego dialogu, czyli kontrolek, które się na nim znajdą. U nas będą trzy: dwa przyciski, OK i Anuluj (pierwszy z nich będzie domyślnym przyciskiem), oraz jakiś napis. Niepotrzebne nam są tutaj dodatkowe identyfikatory dla przycisków, ponieważ dla niektórych najczęściej używanych elementów okna dialogowego mamy predefiniowane identyfikatory systemowe - w naszym przypadku
IDOK i
IDCANCEL.
Procedura dialogowa
Za wszystkie akcje użytkownika na głównym oknie odpowiedzialna jest procedura okna. Okno dialogowe nie jest tutaj żadnym wyjątkiem, też musi mieć podobną procedurę. Wygląda ona mniej więcej tak:
BOOL CALLBACK DlgProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
switch( Msg )
{
case WM_INITDIALOG:
{
}
break;
case WM_COMMAND:
{
}
break;
default: return FALSE;
}
return TRUE;
}
Nazwa procedury dialogowej zależy oczywiście wyłącznie od ciebie, nie musi to być
DlgProc.
Najważniejszą rzecz widać od razu po typie naszej procedurki: dla komunikatów, których nie obsłużymy, nie musimy (a nawet nie możemy) wywołać
DefWindowProc. Jeśli nie interesuje nas dany komunikat, po prostu zwracamy
FALSE, w przeciwnym wypadku obsługujemy komunikat, po czym
break przenosi nas na zewnątrz konstrukcji
switch, skąd procedura wraca z wartością
TRUE.
Wspomniałem wcześniej, że tworzymy '''dialog modalny'''. Oznacza to, że od momentu pokazania okna dialogowego aż do jego zamknięcia użyszkodnik nie może wykonywać żadnych czynności na oknie rodzicielskim (czyli w naszym przypadku na oknie głównym naszego programu). A więc procedura
DlgProc nie zwróci wartości, dopóki okno dialogowe nie zostanie zamknięte. Do tego momentu sterowanie należy do dialogowej pętli komunikatów (każdy dialog ma własną, niezależną od tej, którą umieściliśmy w funkcji
WinMain!).
No właśnie - jak zamknąć okno dialogowe? Oczywiście, użytkownik zawsze może skorzystać ze standardowych sposobów (Alt+F4, krzyżyk w prawym górnym rogu itp.), ale byłoby iście podłym czynem zmuszać go do szukania takich drastycznych rozwiązań. Użytkownik też człowiek, jak by nie patrzeć, a w dodatku posiada też rozum i wolną wolę i naprawdę nie lubi programów o nieprzyjaznym interfejsie, a jak nie lubi, to i nie będzie ich używać - czego, rzecz ciemna, nie chcemy. Ale dość gadania - zwykle na oknie dialogowym umieszcza się przyciski OK i Anuluj, lub coś podobnego. Zapominamy o
DestroyWindow, no a żeby przycisk zamykał dialog, należy mu przypisać następujący kod:
EndDialog( hwnd, ID_RETVAL );
Proste, nieprawdaż? Oczywiście
hwnd to uchwyt okna dialogowego, które niszczymy, a zamiast
ID_RETVAL wstawiamy wartość, którą ma zwrócić makro
DialogBox (zwykle jest to ID jakiegoś przycisku albo coś w tym rodzaju). Warto by jeszcze wspomnieć o jednym drobiazgu, mianowicie dla dialogów nie występuje komunikat
WM_CREATE, zamiast niego mamy
WM_INITDIALOG.
Wywołanie dialogu
No to brudną robotę mamy za sobą, czas poznać instrukcję, otwierającą dialog. Zowie się ona po prostu
DialogBox (uwaga: jest to makro, a nie funkcja!), a składnię ma taką oto:
Składnia:
DialogBox( HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent,
DLGPROC lpDialogFunc )
Jako uchwyt do wystąpienia programu możemy wykorzystać parametr
hInstance, przekazany przez system do funkcji
WinMain. Możemy również uzyskać ten uchwyt poza
WinMain, wywołując funkcję
GetModuleHandle z argumentem
NULL.
Identyfikator naszego dialogu to
IDD_MOJDIALOG (czyli
200). Aby z liczby tej zrobić argument typu
LPCSTR, używamy makra
MAKEINTRESOURCE.
No i argument
hWndParent oznacza okno, wywołujące dialog (będzie ono rodzicem dla okna dialogowego), natomiast
lpDialogFunc - adres utworzonej przez nas przed chwilą procedury dialogowej. To wszystko, dialog gotowy. Przedstawiam teraz kompletny kod przykładowej aplikacji z dialogiem (jednak bez tworzenia okna, bo pewne rzeczy na pewnym etapie zaczynają nudzić ;-)):
#include <windows.h>
#include "dialog.h"
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK DlgProc( HWND, UINT, WPARAM, LPARAM );
int WINAPI WinMain( HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument,
int nFunsterStil )
{
HWND hPrzyc = CreateWindowEx( 0, "BUTTON", "Dialog", WS_CHILD | WS_VISIBLE,
5, 5, 50, 25, hwnd, NULL, hThisInstance, NULL );
ShowWindow( hwnd, nFunsterStil );
while( GetMessage( & msg, NULL, 0, 0 ) )
{
TranslateMessage( & msg );
DispatchMessage( & msg );
}
return msg.wParam;
}
LRESULT CALLBACK WndProc( HWND hwnd, UINT mesg, WPARAM wParam, LPARAM lParam )
{
switch( mesg )
{
case WM_COMMAND:
{
int ret = DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( 200 ), hwnd, DlgProc );
if( ret == IDOK )
MessageBox( hwnd, "Wybrałeś \'Fajnie\' w oknie dialogowym!", "Test", MB_ICONINFORMATION );
else if( ret == IDCANCEL )
MessageBox( hwnd, "Wybrałeś \'Anuluj\' w oknie dialogowym!", "Test", MB_ICONINFORMATION );
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
default:
return DefWindowProc( hwnd, mesg, wParam, lParam );
}
return 0;
}
BOOL CALLBACK DlgProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
switch( Msg )
{
case WM_COMMAND:
{
switch( LOWORD( wParam ) )
{
case IDOK: EndDialog( hwnd, IDOK ); break;
case IDCANCEL: EndDialog( hwnd, IDCANCEL ); break;
}
}
break;
default: return FALSE;
}
return TRUE;
}
Na koniec miła niespodzianka: pętla komunikatów okna dialogowego będzie wykonywała za nas pewne niemiłe czynności! Mam na myśli przełączanie się między kontrolkami za pomocą TAB-a oraz dostęp do kontrolek za pomocą skrótów z lewym ALT-em. W dalszej części tego kursu pokażę prostą sztuczkę, dzięki której można trochę "oszukać" system i zaimplementować te przydatne drobiazgi również i w "zwykłym" oknie.