Rozdział XVIII - Typowe kontrolki


Na tej lekcji nauczymy się, czym są typowe kontrolki (common controls) oraz jak się ich używa. Potraktuj tę lekcję jedynie jako szybkie i zgrubne wprowadzenie do tematu.

 

 

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

 

Teoria

System Windows 95 został wyposażony w kilka rozszerzeń interfejsu użytkownika w stosunku do systemu Windows 3.1x. Rozszerzenia te wzbogacają graficzny interfejs użytkownika (GUI). Kilka z nich było szeroko używane jeszcze przed nadejściem systemu Windows 95, np. pasek stanu, paski narzędziowe, itp. Programiści musieli je kodować sami. Teraz firma Microsoft dołączyła je do swojego systemu Windows 9x/NT/2000/XP. Poznamy je na tej lekcji.

Oto zestawienie nowych kontrolek:

Ponieważ kontrolek tych jest tak dużo, to ładowanie ich wszystkich do pamięci i rejestrowanie byłoby marnotrawstwem zasobów. Wszystkie opisane powyżej kontrolki z wyjątkiem Rich edit są przechowywane w bibliotece comctl32.dll, którą aplikacje mogą załadować, gdy chcą używać typowych kontrolek. Kontrolka tekstu formatowanego (Rich edit) posiada swoją własną bibliotekę o nazwie richedXX.dll, ponieważ jest bardzo skomplikowana (to w sumie kompletny edytor tekstu) i stąd dużo większa od jej współplemieńców.

Aby upewnić się, iż biblioteka comctl32.dll została poprawnie załadowana, powinieneś na początku swojego programu wywołać funkcję InitCommonControls. Obecnie funkcja ta nic nie robi. Zawiera tylko pojedynczy rozkaz ret. Jedynym jej celem jest umieszczenie odwołania do comctl32.dll w sekcji importu, aby program ładujący systemu Windows załadował tę bibliotekę z naszym programem. Funkcja ta nie musi być nawet uruchamiana - wystarczy, że występuje w twoim programie. Czarną robotę wykonuje funkcja wejściowa biblioteki DLL, która rejestruje wszystkie klasy typowych kontrolek przy załadowaniu biblioteki do pamięci. Typowe kontrolki tworzy się na podstawie tych klas w identyczny sposób jak kontrolki okien dialogowych, takie jak pola edycji, pola list itp.

Zupełnie osobną sprawą jest edytor tekstu formatowanego Rich edit. Jeśli chcesz go zastosować, musisz skorzystać z wywołania LoadLibrary, a później z FreeLibrary przy usuwaniu kontrolki z pamięci.

A teraz o tworzeniu typowych kontrolek. Możesz skorzystać z edytora zasobów do umieszczenia ich w oknach dialogowych lub możesz utworzyć je samodzielnie. Prawie wszystkie typowe kontrolki są tworzone przez wywołanie funkcji CreateWindowEx lub CreateWindow i przekazanie im nazwy klasy kontrolki jako jeden z parametrów. Niektóre typowe kontrolki posiadają specyficzne funkcje tworzące je, jednakże funkcje te nie są czymś nowym a jedynie zastępują wywołanie CreateWindowEx w celu ułatwienia tworzenia kontrolek. Poniżej przedstawiam istniejące, specyficzne funkcje tworzenia typowych kontrolek:

Aby utworzyć typowe kontrolki, musisz znać ich nazwy klas. Zebrałem je w poniższej tabelce:

 

Nazwa klasy Typowa kontrolka
ToolbarWindow32 Pasek narzędziowy Toolbar
tooltips_class32 Podpowiedź Tooltip
msctls_statusbar32 Pasek stanu Status bar
SysTreeView32 Widok drzewa Tree view
SysListView32 Widok listy List view
SysAnimate32 Animacja Animation
SysHeader32 Nagłówek Header
msctls_hotkey32 Klawisz skrótu Hot-key
msctls_progress32 Pasek postępu Progress bar
RICHEDIT Edytor tekstu formatowanego Rich edit
msctls_updown32 Przycisk góra/dół Up-down
SysTabControl32 Zakładka Tab

 

Kontrolki arkuszy własności, stron własności i list obrazowych posiadają swoje własne specyficzne funkcje tworzące. Kontrolka listy przewijanej jest przyspieszoną wersją pola listy, zatem nie posiada własnej klasy. Powyższe nazwy klas zostały zweryfikowane przez analizę skryptu zasobów wygenerowanego przez edytor zasobów Visual C++. Różnią się one od nazw klas wymienionych przez pliki pomocy dla API Win32 firmy Borland oraz podręcznik Programming Windows 95 Charlesa Petzolda. Podana lista jest tę właściwą.

Typowe kontrolki mogą stosować ogólne style okien, takie jak WS_CHILD itp. Posiadają one również swoje własne, specyficzne style w rodzaju TVS_XXXXX dla kontrolki widoku drzewa, LVS_xxxx dla kontrolki widoku listy, itd. Najlepszym przyjacielem w tym względzie będzie dla ciebie plik pomocy dla biblioteki API Win32.

Teraz gdy już wiemy, jak tworzyć typowe kontrolki, możemy przejść do metod komunikowania się pomiędzy nimi a ich oknem nadrzędnym. W przeciwieństwie do kontrolek okna dialogowego typowe kontrolki nie komunikują się z procesem nadrzędnym poprzez wiadomości WM_COMMAND. Zamiast nich wysyłają wiadomości WM_NOTIFY do swojego okna nadrzędnego, gdy dzieje się z nimi coś interesującego. Okno nadrzędne może kontrolować swoje elementy potomne wysyłając do nich wiadomości. Dla tych nowych kontrolek zdefiniowano wiele nowych typów wiadomości. Więcej szczegółów na ten temat znajdziesz w pomocy dla biblioteki API Win32.

 

Przykład

W poniższym przykładzie przetestujmy kontrolkę paska postępu (progress bar) oraz kontrolkę paska stanu (status bar).

Plik COMCTL.ASM.

 

.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

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

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

.CONST

IDC_PROGRESS EQU 1
IDC_STATUS   EQU 2
IDC_TIMER    EQU 3

.DATA

ClassName     DB "CommonControlWinClass", 0
AppName       DB "Demonstracja typowych kontrolek", 0
ProgressClass DB "msctls_progress32", 0
Message       DB "Skończone!", 0
TimerID       DD 0

.DATA?

hInstance    HINSTANCE ?
hwndProgress DD        ?
hwndStatus   DD        ?
CurrentStep  DD        ?

.CODE

start:

    INVOKE GetModuleHandle,  NULL
    mov    hInstance, eax
    INVOKE WinMain,  hInstance, NULL, NULL,  SW_SHOWDEFAULT
    INVOKE ExitProcess, eax
    INVOKE InitCommonControls

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_APPWORKSPACE
    mov    wc.lpszMenuName, NULL
    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_OVERLAPPED + WS_CAPTION + \
                           WS_SYSMENU + WS_MINIMIZEBOX + \
                           WS_MAXIMIZEBOX + WS_VISIBLE,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           400, 200, NULL, NULL, hInst, NULL
    mov    hwnd, eax

    .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_CREATE
        INVOKE CreateWindowEx, NULL,\
                               ADDR ProgressClass, NULL, \
                               WS_CHILD + WS_VISIBLE,\
                               50, 70, 300, 20,\
                               hWnd, IDC_PROGRESS, hInstance, NULL
        mov    hwndProgress, eax
        mov    eax, 1000
        mov    CurrentStep, eax
        shl    eax, 16
        INVOKE SendMessage, hwndProgress, PBM_SETRANGE, 0, eax
        INVOKE SendMessage, hwndProgress, PBM_SETSTEP, 10, 0
        INVOKE CreateStatusWindow, WS_CHILD + WS_VISIBLE,\
                                   NULL, hWnd, IDC_STATUS
        mov    hwndStatus, eax
        INVOKE SetTimer, hWnd, IDC_TIMER, 100, NULL
        mov    TimerID, eax
    .ELSEIF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
        .IF TimerID!=0
            INVOKE KillTimer, hWnd, TimerID
        .ENDIF
    .ELSEIF uMsg==WM_TIMER
        INVOKE SendMessage, hwndProgress, PBM_STEPIT, 0, 0
        sub CurrentStep, 10
        .IF CurrentStep==0
            INVOKE KillTimer, hWnd, TimerID
            mov    TimerID, 0
            INVOKE SendMessage, hwndStatus,\
                                SB_SETTEXT, 0, ADDR Message
            INVOKE MessageBox, hWnd,\
                               ADDR Message, ADDR AppName,\
                               MB_OK + MB_ICONINFORMATION
            INVOKE SendMessage, hwndStatus, SB_SETTEXT, 0, 0
            INVOKE SendMessage, hwndProgress, PBM_SETPOS, 0, 0
        .ENDIF
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam		
        ret
    .ENDIF

    xor eax, eax
    ret

WndProc ENDP

END start

Analiza

INVOKE WinMain,  hInstance, NULL, NULL,  SW_SHOWDEFAULT
    INVOKE ExitProcess, eax
    INVOKE InitCommonControls

 

Rozmyślnie umieściłem wywołanie funkcji InitCommonControls za ExitProcess, aby zademonstrować, iż jest ona tutaj jedynie dla wstawienia odwołania do biblioteki comctl32.dll w sekcji importu. Jak widać typowe kontrolki działają nawet, gdy InitCommonControls nie zostanie wykonana.

 

    .IF uMsg==WM_CREATE
        INVOKE CreateWindowEx, NULL,\
                               ADDR ProgressClass, NULL, \
                               WS_CHILD + WS_VISIBLE,\
                               50, 70, 300, 20,\
                               hWnd, IDC_PROGRESS, hInstance, NULL
        mov    hwndProgress, eax

 

Właśnie tutaj tworzymy typowe kontrolki. Zauważ, iż wywołanie CreateWindowEx zawiera hWnd jako uchwyt okna nadrzędnego. Określa również numer identyfikacyjny ID dla kontrolki. Jednakże z uwagi na otrzymanie uchwytu okna kontrolki, numer ten nie będzie używany. Wszystkie kontrolki okna potomnego muszą mieć styl WS_CHILD.

 

        mov    eax, 1000
        mov    CurrentStep, eax
        shl    eax, 16
        INVOKE SendMessage, hwndProgress, PBM_SETRANGE, 0, eax
        INVOKE SendMessage, hwndProgress, PBM_SETSTEP, 10, 0

 

Po utworzeniu paska postępu możemy ustawić jego zakres. Standardowo zakres ten wynosi od 0 do 100. Jeśli cię to nie zadowala, możesz ustalić swój własny przy pomocy wiadomości PBM_SETRANGE. lParam tej wiadomości zawiera zakres, górna granica znajduje się w starszym słowie, a dolna w młodszym. Przy pomocy wiadomości PBM_SETSTEP możemy określić wielkość kroku. Nasz przykład ustala krok na 10, co oznacza, iż przy każdym wysłaniu wiadomości PBM_STEPIT do paska postępu jego wskaźnik przesunie się o 10. Możesz również określać własne położenie za pomocą wiadomości PBM_SETPOS, które dają ci ściślejszą kontrolę nad paskiem postępu.

 

        INVOKE CreateStatusWindow, WS_CHILD + WS_VISIBLE,\
                                   NULL, hWnd, IDC_STATUS
        mov    hwndStatus, eax
        INVOKE SetTimer, hWnd, IDC_TIMER, 100, NULL
        mov    TimerID, eax

 

W następnej kolejności tworzymy pasek stanu wywołując CreateStatusWindow. Wywołanie to można z łatwością zrozumieć, zatem nie skomentuje go. Po utworzeniu paska stanu tworzymy licznik czasu. W naszym przykładzie będziemy uaktualniali pasek postępu w regularnych odstępach czasu co 100 milisekund, zatem musimy utworzyć kontrolkę licznika czasu (timer control). Poniżej przedstawiam prototyp funkcji SetTimer.

 

SetTimer PROTO hWnd:         DWORD,\
               TimerID:      DWORD,\
               TimeInterval: DWORD,\
               lpTimerProc:  DWORD

Jeśli wywołanie się powiedzie, to dostaniemy wartość TimerID. Jeśli nie, to zostanie zwrócona wartość 0. Dlatego identyfikator licznika czasu nie może mieć wartości zero.

 

        .ELSEIF uMsg==WM_TIMER
        INVOKE SendMessage, hwndProgress, PBM_STEPIT, 0, 0
        sub CurrentStep, 10
        .IF CurrentStep==0
            INVOKE KillTimer, hWnd, TimerID
            mov    TimerID, 0
            INVOKE SendMessage, hwndStatus,SB_SETTEXT, 0, ADDR Message
            INVOKE MessageBox, hWnd,ADDR Message, ADDR AppName,MB_OK + MB_ICONINFORMATION
            INVOKE SendMessage, hwndStatus, SB_SETTEXT, 0, 0
            INVOKE SendMessage, hwndProgress, PBM_SETPOS, 0, 0
        .ENDIF

 

Gdy upłynie zadany okres czasu, licznik wysyła wiadomość WM_TIMER. W tym miejscu umieszczasz kod, który w takim przypadku ma zostać wykonany. W tym przykładzie uaktualniamy pasek postępu, a następnie sprawdzamy, czy osiągnięta została jego górna granica. Jeśli tak, usuwamy licznik czasu, a następnie wyświetlamy tekst na pasku stanu za pomocą wiadomości SB_SETTEXT. Zostaje wyświetlone okno wiadomości, a gdy użytkownik kliknie przycisk OK, usuwamy tekst z paska stanu i czyścimy pasek przesuwu.

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

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

program ComCtl;

uses Windows;

const
  IDC_PROGRESS  = 1;
  IDC_STATUS    = 2;
  IDC_TIMER     = 3;
  ClassName     = 'CommonControlWinClass';
  AppName       = 'Demonstracja typowych kontrolek';
  ProgressClass = 'msctls_progress32';
  Message       = 'Skończone!';

var
  TimerID      : longint;
  hInstance    : HINST;
  hwndProgress : longword;
  hwndStatus   : longword;
  CurrentStep  : longword;

function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
begin
  Result := 0;
  case uMsg of
    WM_CREATE:
    begin
      hwndProgress := CreateWindowEx(0,ProgressClass,0,WS_CHILD + WS_VISIBLE,
                                     50,70,300,20,hWnd,IDC_PROGRESS,hInstance,0);
      CurrentStep := 1000;
      SendMessage(hwndProgress,PBM_SETRANGE,0,1000 shl 16);
      SendMessage(hwndProgress,PBM_SETSTEP,10,0);
      hwndStatus := CreateStatusWindow(WS_CHILD+WS_VISIBLE,0,hWnd,IDC_STATUS);
      TimerID := SetTimer(hWnd,IDC_TIMER,100,nil);
    end;
    WM_DESTROY:
    begin
      PostQuitMessage(0);
      if TimerID <> 0 then KillTimer(hWnd,TimerID);
    end;
    WM_TIMER:
    begin
      SendMessage(hwndProgress,PBM_STEPIT,0,0);
      CurrentStep := CurrentStep - 10;
      if CurrentStep = 0 then
      begin
        KillTimer(hWnd,TimerID);
        TimerID := 0;
        SendMessage(hwndStatus,SB_SETTEXT,0,longint(@Message[1]));
        MessageBox(hWnd,Message,AppName,MB_OK + MB_ICONINFORMATION);
        SendMessage(hwndStatus,SB_SETTEXT,0,0);
        SendMessage(hwndProgress,PBM_SETPOS,0,0);
      end;
    end;
    else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
  end;
end;

function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD) : longint;
var
  wc   : WNDCLASSEX;
  msg  : MSG;
  hwnd : HANDLE;

begin
  with wc do
  begin
    cbSize        := sizeof(WNDCLASSEX);
    style         := CS_HREDRAW or CS_VREDRAW;
    lpfnWndProc   := @WndProc;
    cbClsExtra    := 0;
    cbWndExtra    := 0;
    hInstance     := hInst;
    hbrBackground := COLOR_APPWORKSPACE;
    lpszMenuName  := 0;
    lpszClassName := ClassName;
    hIcon         := LoadIcon(0,IDI_APPLICATION);
    hIconSm       := hIcon;
    hCursor       := LoadCursor(0,IDC_ARROW);
  end;
  RegisterClassEx(wc);
  hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
                         WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_MINIMIZEBOX +
                         WS_MAXIMIZEBOX + WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
                         400,200,0,0,hInst,0);
  while GetMessage(msg,0,0,0) do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  Result := msg.wParam;
end;

begin
  InitCommonControls;
  hInstance := GetModuleHandle(0);
  ExitProcess(WinMain(hInstance,0,0,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.