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

Okna dialogowe, cz. 1

[lekcja] Rozdział 8. Tworzenie i wyświetlanie okien dialogowych utworzonych w plikach .rc. Omówione zostało także reagowanie na działania użytkownika poprzez ukazanie przykładowej procedury komunikatów.

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.

Przykładowy dialog (Windows 98)
Przykładowy dialog (Windows 98)

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:

C/C++
#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:

C/C++
#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:

C/C++
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:

C/C++
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:
C/C++
DialogBox( HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent,
DLGPROC lpDialogFunc )

ArgumentZnaczenie
hInstanceUchwyt do naszej aplikacji
lpTemplateIdentyfikator wzorca dialogu
hWndParentUchwyt okna wywołującego dialog
lpDialogFuncWskaźnik do procedury dialogowej

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ć ;-)):

C/C++
#include <windows.h>
#include "dialog.h"

// Prototypy funkcji obsługujących komunikaty.
// Sztuk dwie, jedna dla okna głównego, jedna dla dialogu ;-)

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 )
{
   
    //
    // Tworzenie okna - saaaam to sobie zrób ;-)
    //
   
    // Tworzymy jakiś przycisk do wywołania dialogu
   
    HWND hPrzyc = CreateWindowEx( 0, "BUTTON", "Dialog", WS_CHILD | WS_VISIBLE,
    5, 5, 50, 25, hwnd, NULL, hThisInstance, NULL );
   
    // Pokazujemy główne okno
    ShowWindow( hwnd, nFunsterStil );
   
   
    // No i standardowo - pętelka
    while( GetMessage( & msg, NULL, 0, 0 ) )
    {
        TranslateMessage( & msg );
        DispatchMessage( & msg );
    }
   
    return msg.wParam;
}

// Procedura okna
LRESULT CALLBACK WndProc( HWND hwnd, UINT mesg, WPARAM wParam, LPARAM lParam )
{
    switch( mesg )
    {
    case WM_COMMAND:
        {
            // Tutaj wywołujemy nasz dialog
            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;
}

// Procedura dialogowa
BOOL CALLBACK DlgProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
    switch( Msg )
    {
    case WM_COMMAND:
        {
            // reakcja na przyciski
            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.
Poprzedni dokument Następny dokument
Animacja Okna dialogowe, cz. 2