Rozdział IX - Kontrolki okna dialogowego


W tej lekcji zbadamy kontrolki okna dialogowego, które są bardzo ważnymi elementami do wprowadzania i wyprowadzania informacji w naszych programach.

 

 

Pobierz plik z przykładem {z tego archiwum}.

 

Teoria

System Windows udostępnia kilka predefiniowanych klas okien, które możemy bezpośrednio użyć we własnych programach. Przez większość czasu używamy ich jako komponenty okna dialogowego, dlatego noszą nazwę kontrolek okna dialogowego (child window controls). Kontrolki te samodzielnie przetwarzają wiadomości od myszki i klawiatury powiadamiając okno nadrzędne o zmianie swojego stanu. Ogromnie ułatwiają życie programistom, więc powinieneś z nich korzystać tak często, jak to możliwe. W tej lekcji umieściłem je w zwykłym oknie, aby zademonstrować sposób ich tworzenia i użycia, lecz w rzeczywistości powinieneś umieszczać je w oknie dialogowym.

Przykładami predefiniowanych klas okien są przycisk (button), lista (listbox), pole wyboru (checkbox), pole wyboru wyłącznego (radio button), pole edycji (edit box) itp.

Aby używać kontrolkę okna dialogowego, musisz utworzyć ją przy pomocy wywołania CreateWindow lub CreateWindowEx. Zwróć uwagę, iż nie musisz już rejestrować klasy okna, ponieważ została ona już zarejestrowana dla ciebie przez system Windows. Nazwa klasy MUSI być predefiniowaną nazwą klasy. Powiedzmy, iż chcesz utworzyć przycisk, zatem musisz określić nazwę klasy dla CreateWindowEx jako "button". Pozostałymi parametrami, które należy wpisać, są uchwyt okna nadrzędnego oraz numer kontrolki ID. Numer ten nie może się powtarzać wśród kontrolek okna. Służy on do identyfikacji i rozróżnienia kontrolek.

Po utworzeniu kontrolki będzie ona wysyłała wiadomości do swojego okna nadrzędnego, gdy zmieni się jej stan. Zwykle okna potomne tworzymy podczas przetwarzania wiadomości WM_CREATE okna nadrzędnego. Okno potomne wysyła wiadomości WM_COMMAND do okna nadrzędnego ze swoim numerem ID w młodszym słowie wParam i kodem powiadomienia w starszym słowie wParam, a swoim uchwytem okna w lParam. Każda kontrolka okna dialogowego posiada swój własny zbiór kodów powiadamiania. Więcej informacji na ten temat musisz poszukać w pliku pomocy dla funkcji API Win32.

Okno nadrzędne może również wysyłać wiadomości do swoich okien potomnych przez wywołanie funkcji SendMessage, która przesyła wiadomości wraz z towarzyszącymi im wartościami w wParam i lParam do okna określonego przez uchwyt okna (window handle). Jest to niesamowicie użyteczna funkcja, ponieważ potrafi ona wysłać wiadomość do dowolnego okna, o ile znasz jego uchwyt.

Zatem po utworzeniu okien potomnych okno nadrzędne musi przetwarzać wiadomości WM_COMMAND, aby było w stanie otrzymywać kody powiadomień z okien potomnych.

 

Przykład

Utworzymy okno zawierające kontrolkę edycyjną oraz przycisk. Gdy klikniesz w przycisk, pojawi się okno wiadomości pokazujące wprowadzony do pola edycyjnego tekst. Występuje również menu z 4 elementami:

  1. Powiedz Cześć - umieszcza tekst w polu edycyjnym
  2. Wyczyść pole edycyjne - usuwa zawartość pola edycyjnego.
  3. Pobierz tekst - wyświetla okienko wiadomości z tekstem pola edycyjnego
  4. Koniec - zamyka program
.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD

INCLUDE    \masm32\include\windows.inc
INCLUDE    \masm32\include\user32.inc
INCLUDE    \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib

.DATA

ClassName       DB "SimpleWinClass",0
AppName         DB "Nasze pierwsze okno",0
MenuName        DB "FirstMenu",0
ButtonClassName DB "button",0
ButtonText      DB "Nasz pierwszy przycisk",0
EditClassName   DB "edit",0
TestString      DB "Och! Jestem w edytorze",0

.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?
hwndButton  HWND      ?
hwndEdit    HWND      ?
buffer      DB 512 DUP(?)

.CONST

ButtonID    EQU 1
EditID      EQU 2
IDM_HELLO   EQU 1
IDM_CLEAR   EQU 2
IDM_GETTEXT EQU 3
IDM_EXIT    EQU 4

.CODE

start:
    INVOKE GetModuleHandle, NULL
    mov    hInstance, eax
    INVOKE GetCommandLine
    mov    CommandLine, eax
    INVOKE WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
    INVOKE ExitProcess, eax
    
WinMain PROC hInst:     HINSTANCE,\
             hPrevInst: HINSTANCE,\
             CmdLine:   LPSTR,\
             CmdShow:   DWORD

LOCAL wc:   WNDCLASSEX
LOCAL msg:  MSG
LOCAL hwnd: HWND

    mov    wc.cbSize, SIZEOF WNDCLASSEX
    mov    wc.style, CS_HREDRAW OR CS_VREDRAW
    mov    wc.lpfnWndProc, OFFSET WndProc
    mov    wc.cbClsExtra, NULL
    mov    wc.cbWndExtra, NULL
    push   hInst
    pop    wc.hInstance
    mov    wc.hbrBackground, COLOR_BTNFACE+1
    mov    wc.lpszMenuName, OFFSET MenuName
    mov    wc.lpszClassName, OFFSET ClassName
    INVOKE LoadIcon, NULL, IDI_APPLICATION
    mov    wc.hIcon, eax
    mov    wc.hIconSm, eax
    INVOKE LoadCursor, NULL, IDC_ARROW
    mov    wc.hCursor, eax
    INVOKE RegisterClassEx, ADDR wc
    INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
                           ADDR ClassName, ADDR AppName,\
                           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
                           CW_USEDEFAULT, 300, 200, NULL, NULL,\
                           hInst, NULL
    mov    hwnd, eax
    INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
    INVOKE UpdateWindow, hwnd

    .WHILE TRUE
        INVOKE GetMessage, ADDR msg, NULL, 0, 0
        .BREAK .IF (!eax)
        INVOKE TranslateMessage, ADDR msg
        INVOKE DispatchMessage, ADDR msg
    .ENDW

    mov eax, msg.wParam
    ret
    
WinMain ENDP

WndProc PROC hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM

    .IF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSEIF uMsg==WM_CREATE
        INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
                               ADDR EditClassName, NULL,\
                               WS_CHILD OR WS_VISIBLE OR\
                               WS_BORDER OR ES_LEFT OR\
                               ES_AUTOHSCROLL,\
                               50, 35, 200, 25, hWnd,\
                               EditID, hInstance, NULL
        mov    hwndEdit, eax
        INVOKE SetFocus, hwndEdit
        INVOKE CreateWindowEx, NULL,\
                               ADDR ButtonClassName, ADDR ButtonText,\
                               WS_CHILD OR WS_VISIBLE OR BS_DEFPUSHBUTTON,\
                               50, 70, 200, 25, hWnd, ButtonID,\
                               hInstance, NULL
        mov    hwndButton, eax
    .ELSEIF uMsg==WM_COMMAND
        mov eax, wParam
        .IF lParam==0
            .IF ax==IDM_HELLO
                INVOKE SetWindowText, hwndEdit, ADDR TestString
                INVOKE SendMessage, hwndEdit, WM_KEYDOWN, VK_END, NULL
            .ELSEIF ax==IDM_CLEAR
                INVOKE SetWindowText, hwndEdit, NULL
            .ELSEIF  ax==IDM_GETTEXT
                INVOKE GetWindowText, hwndEdit, ADDR buffer, 512
                INVOKE MessageBox, NULL, ADDR buffer, ADDR AppName, MB_OK
            .ELSE
                INVOKE DestroyWindow, hWnd
            .ENDIF
        .ELSE
            .IF ax==ButtonID
                shr eax, 16
                .IF ax==BN_CLICKED
                    INVOKE SendMessage, hWnd, WM_COMMAND, IDM_GETTEXT, 0
                .ENDIF
            .ENDIF
        .ENDIF
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF

    xor eax, eax
    ret
    
WndProc ENDP

END start


Plik zasobów:


#define IDM_HELLO   1
#define IDM_CLEAR   2
#define IDM_GETTEXT 3
#define IDM_EXIT    4

FirstMenu MENU
{
    POPUP "&Testowe Kontrolki"
    {
        MENUITEM "Powiedz &Cześć", IDM_HELLO
        MENUITEM "&Wyczyść pole edycyjne", IDM_CLEAR
        MENUITEM "&Pobierz tekst", IDM_GETTEXT
        MENUITEM SEPARATOR
        MENUITEM "&Koniec", IDM_EXIT
    }
}

 

Analiza

Przeanalizujmy ten program.


.ELSEIF uMsg==WM_CREATE
        INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\
                               ADDR EditClassName, NULL,\
                               WS_CHILD OR WS_VISIBLE OR\
                               WS_BORDER OR ES_LEFT OR\
                               ES_AUTOHSCROLL,\
                               50, 35, 200, 25, hWnd,\
                               EditID, hInstance, NULL
        mov    hwndEdit, eax
        INVOKE SetFocus, hwndEdit
        INVOKE CreateWindowEx, NULL,\
                               ADDR ButtonClassName, ADDR ButtonText,\
                               WS_CHILD OR WS_VISIBLE OR BS_DEFPUSHBUTTON,\
                               50, 70, 200, 25, hWnd, ButtonID,\
                               hInstance, NULL
        mov    hwndButton, eax


Kontrolki tworzymy podczas przetwarzania wiadomości WM_CREATE. Funkcję CreateWindowEx wywołujemy z dodatkowym stylem okna - WS_EX_CLIENTEDGE, który sprawia, iż obszar roboczy będzie wyglądał na zagłębiony. Nazwa każdej kontrolki jest predefiniowana, "edit" dla pola edycji, "button" dla kontrolki przycisku. Następnie określamy style okna potomnego. Każda kontrolka posiada dodatkowe style oprócz zwykłych styli okna. Na przykład style przycisku mają przedrostek "BS_" od angielskiej nazwy "button style", style pola edycyjnego mają przedrostek "ES_" od "edit style". Opisu tych styli musisz poszukać w pliku pomocy dla API Win32. Zwróć uwagę, iż w miejscu uchwytu menu umieszczasz numer ID kontrolki. Nie powoduje to żadnych zakłóceń, ponieważ okna potomne nie mogą posiadać menu.

Po utworzeniu każdej kontrolki zachowujemy jej uchwyt w zmiennej dla przyszłego użytku.

Funkcja SetFocus jest wywoływana, aby uaktywnić pole edycji, co umożliwi użytkownikowi natychmiastowe rozpoczęcie wprowadzania tekstu.

Teraz nadchodzi naprawdę interesujący moment. Każde okno potomne wysyła powiadomienia do swojego okna nadrzędnego za pomocą wiadomości WM_COMMAND.


.ELSEIF uMsg==WM_COMMAND
        mov eax, wParam
        .IF lParam==0


Przypomnij sobie, iż menu również wysyła wiadomości WM_COMMAND, aby poinformować okno o swoim stanie. Jak rozróżnić wiadomości WM_COMMAND pochodzące od menu i od kontrolki? Poniżej jest odpowiedź.


Młodsze słowo wParam Starsze słowo wParam lParam
Menu Numer ID elementu menu 0 0
Kontrolka Numer ID kontrolki Kod powiadomienia Uchwyt Okna Potomnego

 

Wynika stąd, iż musisz sprawdzić wartość lParam. Jeśli wynosi zero, to bieżąca wiadomość WM_COMMAND pochodzi od elementu menu. Nie można zastosować wParam do rozróżnienia pomiędzy menu a kontrolką, ponieważ numery ID elementu menu i kontrolki mogą być identyczne, a kod powiadomienia może mieć wartość zero.


.IF ax==IDM_HELLO
        INVOKE SetWindowText, hwndEdit, ADDR TestString
        INVOKE SendMessage, hwndEdit, WM_KEYDOWN, VK_END, NULL
    .ELSEIF ax==IDM_CLEAR
        INVOKE SetWindowText, hwndEdit, NULL
    .ELSEIF  ax==IDM_GETTEXT
        INVOKE GetWindowText, hwndEdit, ADDR buffer, 512
        INVOKE MessageBox, NULL, ADDR buffer, ADDR AppName, MB_OK


W polu edycyjnym można umieścić łańcuch tekstowy wywołując funkcję SetWindowText. Zawartość pola edycyjnego czyścimy wywołując SetWindowText z parametrem NULL. Funkcja ta jest ogólną funkcją API. Możesz jej używać do zmiany tytułu okna lub napisu na przycisku.

Odczyt tekstu z pola edycyjnego uzyskujemy za pomocą funkcji GetWindowText.


    .IF ax==ButtonID
        shr eax, 16
        .IF ax==BN_CLICKED
            INVOKE SendMessage, hWnd, WM_COMMAND, IDM_GETTEXT, 0
        .ENDIF
    .ENDIF


Powyższy fragment kodu obsługuje przypadek, gdy użytkownik naciska nasz przycisk. Najpierw sprawdza on, czy młodsze słowo wParam odpowiada numerowi ID przycisku. Jeśli tak, to sprawdza starsze słowo, czy jest ono równe kodowi powiadomienia o kliknięciu przycisku - BN_CLICKED. Teraz nastąpi interesujący moment. Chcemy pobrać tekst z pola edycyjnego i wyświetlić go w okienku wiadomości. Możemy zdublować kod w powyższej sekcji IDM_GETTEXT, lecz to nie ma sensu. Jeśli moglibyśmy w jakiś sposób przesłać wiadomość WM_COMMAND z młodszym słowem wParam zawierającym wartość IDM_GETTEXT do naszej własnej procedury okna, moglibyśmy wtedy uniknąć dublowania kodu i uprościć nasz program. Rozwiązaniem jest funkcja SendMessage, która wysyła dowolną wiadomość do dowolnego okna z dowolnymi wartościami wParam i lParam. Zatem zamiast dublowania kodu wywołujemy SendMessage z uchwytem okna nadrzędnego, WM_COMMAND, IDM_GETTEXT i 0. Ma to identyczny efekt jak wybranie z menu opcji "Pobierz tekst". Procedura okna nie wykryje między nimi żadnej różnicy.

Powinieneś intensywnie wykorzystywać tę technikę, aby twój kod był bardziej zwarty.

I ostatnia, ale nie mniej ważna uwaga, nie zapomnij umieścić w pętli wiadomości wywołania funkcji TranslateMessage. Ponieważ musisz wpisywać jakiś tekst do pola edycyjnego, twój program musi przekształcać kody matrycowe klawiatury na czytelny tekst. Jeśli pominiesz tę funkcję, nie będziesz mógł nic wpisać w swoje pole edycyjne.

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program DialogControls;

uses Windows;

const

  ClassName       = 'SimpleWinClass';
  AppName         = 'Nasze pierwsze okno';
  MenuName        = 'FirstMenu';
  ButtonClassName = 'button';
  ButtonText      = 'Nasz pierwszy przycisk';
  EditClassName   = 'edit';
  TestString      = 'Och! Jestem w edytorze';
  ButtonID        = 1;
  EditID          = 2;
  IDM_HELLO       = 1;
  IDM_CLEAR       = 2;
  IDM_GETTEXT     = 3;
  IDM_EXIT        = 4;

var
  hInstance   : HINST;
  CommandLine : LPSTR;
  hwndButton  : HWND;
  hwndEdit    : HWND;
  buffer      : array[0..511] of byte;

function WndProc(h:HWND;uMsg:UINT;wP:WPARAM;lP:LPARAM) : longint;
begin
  WndProc := 0;
  case uMsg of
    WM_DESTROY: PostQuitMessage(0);
    WM_CREATE :
      begin
        hwndEdit := CreateWindowEx( WS_EX_CLIENTEDGE,EditClassName,0,
                                    WS_CHILD  or WS_VISIBLE or
                                    WS_BORDER or ES_LEFT or
                                    ES_AUTOHSCROLL,50,35,200,25,h,
                                    EditID,hInstance,0);
        SetFocus(hwndEdit);
        hwndButton := CreateWindowEx(0,ButtonClassName,ButtonText,
                                     WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,
                                     50,70,200,25,h, ButtonID,hInstance,Nil);
     end;
   WM_COMMAND :
     begin
       if lP = 0 then
         case (wP and $ffff) of
           IDM_HELLO: begin
                        SetWindowText(hwndEdit,TestString);
                        SendMessage(hwndEdit,WM_KEYDOWN,VK_END,0);
                      end;
           IDM_CLEAR: SetWindowText(hwndEdit,0);
           IDM_GETTEXT : begin
                           GetWindowText(hwndEdit,@buffer,512);
                           MessageBox(0,@buffer,AppName,MB_OK);
                         end;
           else DestroyWindow(h);
         end
       else if ((wp and $ffff) = ButtonID) and
               ((wP shr 16) = BN_CLICKED) then SendMessage(h,WM_COMMAND,IDM_GETTEXT,0);
    end;
    else WndProc := DefWindowProc(h,uMsg,wP,lP);
  end;
end;

function WinMain(hI,hPI:HINST;CmdLine:LPSTR;cmdShow:longint) : longint;
var
  wc : WndClassEx;
  ms : msg;
  h  : HWnd;
begin
  wc.cbSize        := SizeOf(WndClassEx);
  wc.style         := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc   := @WndProc;
  wc.cbClsExtra    := 0;
  wc.cbWndExtra    := 0;
  wc.hInstance     := hI;
  wc.hbrBackground := COLOR_BTNFACE + 1;
  wc.lpszMenuName  := MenuName;
  wc.lpszClassName := ClassName;
  wc.hIcon         := LoadIcon(0,IDI_APPLICATION);
  wc.hIconSm       := wc.hIcon;
  wc.hCursor       := LoadCursor(0,IDC_ARROW);
  RegisterClassEx(wc);
  h := CreateWindowEx(0,ClassName,AppName,
                      WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
                      300,200,0,0,hI,0);
  ShowWindow(h,CmdShow);
  UpdateWindow(h);
  while GetMessage(ms,0,0,0) do
  begin
    TranslateMessage(ms);
    DispatchMessage(ms);
  end;
  WinMain := ms.wParam;
end;

begin
  hInstance   := GetModuleHandle(Nil);
  CommandLine := GetCommandLine;
  ExitProcess(WinMain(hInstance,0,CommandLine,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.