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

Okna dialogowe, cz. 8

[lekcja]

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 » Kurs WinAPI, C++ » PodstawyOkna dialogowe, cz. 6 lekcja 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.

KomendaOpisSkładniaZastrzeżenia
AUTO3STATEAutomatyczny CheckBox 3-stanowyAUTO3STATE text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
AUTOCHECKBOXAutomatyczny CheckBox 2-stanowyAUTOCHECKBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]];
AUTORADIOBUTTONAutomatyczny przycisk radiowy (ten okrągły)AUTORADIOBUTTON text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]];
COMBOBOXComboBoxCOMBOBOX id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]ComboBox'a można wypełnić treścią dopiero w trakcie wykonania programu.
CTEXTText wyrównywany do środkaCTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
DEFPUSHBUTTONZwykły przycisk, domyślnie zaznaczonyCTEXT text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
EDITTEXTZwykły EDITEDITTEXT id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
GROUPBOXRamka grupującaGROUPBOX text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
ICONIkonkaICON text, id, x, y [, szerokosc, wysokosc, style [, style-rozszerzone]]Parametr text to identyfikator ikonki w zasobach, która ma się wyświetlić.
LISTBOXKontrolka ListBoxLISTBOX 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 lewejLTEXT  text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
PUSHBOXTo samo co PUSHBUTTONPUSHBOX  text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]PUSHBOX nie wyświetla przyciskowej ramki tylko sam text do wciskania.
PUSHBUTTON Zwykły przyciskPUSHBUTTON  text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]][/tt]
RTEXT Text wyrównywany do prawejRTEXT  text, id, x, y, szerokosc, wysokosc [, style [, style-rozszerzone]]
SCROLLBAR Zwykły scrollbarSCROLLBAR  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ę » Kurs WinAPI, C++ » KontrolkiDrzewo (TreeView) lekcja

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

  • Kontrolki dodawane w fazie wykonania mają rozmiary i pozycję określoną w pixelach.
  • Kontrolki dodawane w fazie wykonania mają standardową, brzydką czcionkę, czyli nie mają tej samej czcionki, co jest ustawiona w dialogu.

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:

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

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

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

C/C++
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 ;-).

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

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

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

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

Nasz własny dialog logowania (Windows Vista)
Nasz własny dialog logowania (Windows Vista)

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 :-).
Poprzedni dokument Następny dokument
Okna dialogowe, cz. 7 Menu