Rozdział XXVII - Kontrolka podpowiedzi


Na tej lekcji poznamy kontrolkę podpowiedzi (tooltip control). Co to jest, jak to utworzyć i zastosować.

 

 

Załaduj {ten przykład}

 

Teoria

Podpowiedź jest małym, prostokątnym oknem wyświetlanym, gdy kursor myszki unosi się ponad określonym obszarem. Okno podpowiedzi zawiera pewien tekst, który programista życzy sobie wyświetlić dla użytkownika. W tym sensie podpowiedź odgrywa taka sama rolę, co wiersz stanu, lecz znika ona, gdy użytkownik kliknie lub przesunie wskaźnik myszki poza wyznaczony obszar. Prawdopodobnie znasz podpowiedzi powiązane z przyciskami paska narzędziowego. Te "podpowiedzi" są udogodnieniami udostępnianymi przez kontrolkę paska narzędziowego (tolbar control). Jeśli chcesz mieć podpowiedzi dla innych okien / kontrolek, musisz utworzyć własną kontrolkę podpowiedzi.

Teraz gdy już wiesz, czym jest podpowiedź, przejdźmy do sposobów jej tworzenia i używania. Poniżej wymieniam kolejne kroki:

  1. Utwórz kontrolkę podpowiedzi przy pomocy CreateWindowEx.
  2. Zdefiniuj obszar ruchu wskaźnika myszki, który będzie monitorowany przez kontrolkę podpowiedzi.
  3. Podporządkuj ten obszar kontrolce podpowiedzi.
  4. Przetransmituj wiadomości myszki z podporządkowanego obszaru do kontrolki podpowiedzi (ten krok może wystąpić wcześniej w zależności od zastosowanej metody przekazywania wiadomości).

Zbadamy teraz szczegółowo każdy z wymienionych powyżej kroków.

 

Tworzenie podpowiedzi

Kontrolka podpowiedzi jest kontrolką wspólną. Skoro tak, to musisz wywołać gdzieś w swoim kodzie źródłowym funkcję InitCommonControls, aby MASM niejawnie skonsolidował twój program z biblioteką comctl32.dll. Kontrolkę podpowiedzi tworzysz za pomocą CreateWindowEx. Typowy scenariusz jest następujący:

 

.DATA

TooltipClassName DB "Tooltips_class32", 0

.CODE
... 
    INVOKE InitCommonControls
    INVOKE CreateWindowEx, NULL, ADDR TooltipClassName,\
                           NULL, TIS_ALWAYSTIP,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           NULL, NULL, hInstance, NULL

 

Zwróć uwagę na styl okna TIS_ALWAYSTIP, który określa, iż podpowiedź będzie pokazywana, gdy wskaźnik myszki jest ponad wyznaczonym obszarem bez względu na stan okna zawierającego ten obszar. Mówiąc prosto, jeśli zastosujesz ten znacznik, to gdy wskaźnik myszki unosi się ponad obszarem zarejestrowanym dla kontrolki podpowiedzi, okno podpowiedzi pojawi się, nawet jeśli okno leżące pod wskaźnikiem myszki jest nieaktywne.

W wywołaniu CreateWindowEx nie musisz dołączać styli WS_POPUP i WS_EX_TOOLWINDOW, ponieważ procedura okna dla kontrolki podpowiedzi dodaje je automatycznie. Również nie musisz określać współrzędnych, wysokości i szerokości okna podpowiedzi: kontrolka podpowiedzi ustawi je automatycznie do rozmiaru wyświetlanego tekstu, zatem w czterech parametrach przekazujemy wartość CW_USEDEFAULT. Pozostałe parametry nie są godne uwagi.

Określanie narzędzia

Kontrolka podpowiedzi jest utworzona, lecz nie pojawia się natychmiast. Chcemy pokazać okno podpowiedzi, gdy wskaźnik myszki unosi się ponad jakimś obszarem. Teraz nadszedł czas określenia tego obszaru. W naszym żargonie obszar ten nosi nazwę "narzędzia" (tool). Narzędzie jest prostokątem na obszarze roboczym okna, dla którego kontrolka podpowiedzi będzie monitorowała pozycję wskaźnika myszki. Jeśli wskaźnik myszki unosi się ponad narzędziem, to pojawia się okno podpowiedzi dla tego narzędzia (tooltip window). Prostokąt może pokrywać cały obszar roboczy lub tylko jego część. Zatem możemy rozdzielić narzędzie na dwa rodzaje: narzędzie implementowane jako okno i narzędzie implementowane jako prostokąt w obszarze roboczym okna. Oba rodzaje posiadają swoje własne zastosowania. Narzędzie pokrywające cały obszar roboczy okna jest najczęściej używane z kontrolkami takimi jak przyciski, kontrolki edycyjne itp. Nie musisz określać współrzędnych ani rozmiarów tego narzędzia: zakłada się, iż pokrywa cały obszar roboczy okna. Narzędzie implementowane jako prostokąt w obszarze roboczym jest użyteczne, gdy chcesz podzielić ten obszar roboczy okna na kilka regionów bez stosowania okien potomnych. Przy tym rodzaju narzędzia musisz określić współrzędne jego lewego górnego narożnika oraz szerokość i wysokość. Dane te umieszczasz w strukturze TOOLINFO o następującej definicji:

 

TOOLINFO STRUCT
    cbSize   DWORD  ?
    uFlags   DWORD  ?
    hWnd     DWORD  ?
    uId      DWORD  ?
    rect     RECT   <>
    hInst    DWORD  ?
    lpszText DWORD  ?
    lParam   LPARAM ?
TOOLINFO ENDS

Podsumowując, musisz wypełnić strukturę TOOLINFO przed wysłaniem jej do kontrolki podpowiedzi. Struktura ta opisuje charakterystykę pożądanego narzędzia.

Rejestrowanie narzędzia w kontrolce podpowiedzi.

Po wypełnieniu struktury TOOLINFO musisz wysłać ją do kontrolki podpowiedzi. Kontrolka ta może obsługiwać wiele narzędzi, zatem zwykle nie potrzeba tworzyć więcej niż jednej kontrolki dla jednego okna. Rejestrowanie narzędzia w kontrolce podpowiedzi polega na wysłaniu do niej wiadomości TTM_ADDTOOL. Parametr wParam nie jest używany, a lParam musi zawierać adres struktury TOOLINFO do zarejestrowania.

 

.DATA?

...
ti TOOLINFO <>
...

.CODE
 
    ...
<wypełnij strukturę TOOLINFO> 
    ... 
    INVOKE SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, ADDR ti
    ...

 

Funkcja SendMessage dla tej wiadomości zwróci TRUE, jeśli narzędzie zostało poprawnie zarejestrowane w kontrolce podpowiedzi, a FALSE w przypadku przeciwnym.

Narzędzie możesz wyrejestrować wysyłając wiadomość TTM_DELTOOL do kontrolki podpowiedzi.

Przekazywanie wiadomości myszki do kontrolki podpowiedzi

Gdy powyższy etap zostanie zakończony, kontrolka podpowiedzi zna już obszar monitorowania wiadomości myszki oraz tekst do wyświetlenia w oknie podpowiedzi. Brakuje jedynie elementu wyzwalającego te działania. Przemyśl to: obszar określony przez narzędzie znajduje się w obszarze roboczym innego okna. Jak kontrolka podpowiedzi może przechwycić wiadomości myszki przeznaczone dla tego okna? Musi to zrobić, aby zmierzyć czas przebywania wskaźnika myszki ponad jakimś punktem narzędzia, a gdy zadany czas upłynie, kontrolka podpowiedzi ma wyświetlić okno z tekstem podpowiedzi. Istnieją dwie metody osiągnięcia tego celu, jedna wymagająca współpracy z oknem zawierającym narzędzie, a druga nie korzystająca z tej współpracy ze strony okna.

Wszystkie inne wiadomości są ignorowane. Stąd w procedurze okna zawierającego narzędzie musi istnieć mechanizm przełączający w stylu:

 

WndProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD 
    ... 
    .IF uMsg==WM_CREATE 
        ... 
    .ELSEIF uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE   ||\
            uMsg==WM_LBUTTONUP   || uMsg==WM_RBUTTONDOWN ||\
            uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP   ||\
            uMsg==WM_MBUTTONUP 
        INVOKE SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, ADDR msg
        ...

To jest to. Na tym etapie twoja kontrolka podpowiedzi jest w pełni funkcjonalna. Istnieje kilka użytecznych wiadomości związanych z podpowiedziami, które powinieneś znać.

Przykład

Prezentowany przykład programu wyświetla proste okno dialogowe z dwoma przyciskami. Obszar roboczy okna dialogowego podzielony został na cztery obszary: lewy górny, lewy dolny, prawy górny i prawy dolny. Każdy obszar określony jest jako narzędzie z własnym tekstem podpowiedzi. Dwa przyciski również posiadają swoje własne teksty podpowiedzi.

 

.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

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

DlgProc        PROTO :DWORD, :DWORD, :DWORD, :DWORD
EnumChild      PROTO :DWORD, :DWORD
SetDlgToolArea PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD

.CONST

IDD_MAINDIALOG EQU     101
IDC_EXIT       EQU 1001

.DATA

ToolTipsClassName DB "Tooltips_class32", 0
MainDialogText1   DB "Lewy górny obszar okna dialogowego", 0
MainDialogText2   DB "Prawy górny obszar okna dialogowego", 0
MainDialogText3   DB "Lewy dolny obszar okna dialogowego", 0
MainDialogText4   DB "Prawy dolny obszar okna dialogowego", 0

.DATA?

hwndTool  DD ?
hInstance DD ?

.CODE

start:
    INVOKE GetModuleHandle, NULL
    mov    hInstance, eax
    INVOKE DialogBoxParam, hInstance, IDD_MAINDIALOG, NULL, ADDR DlgProc, NULL
    INVOKE ExitProcess, eax

DlgProc PROC hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

LOCAL ti   : TOOLINFO
LOCAL id   : DWORD
LOCAL rect : RECT

    .IF uMsg==WM_INITDIALOG
        INVOKE InitCommonControls
        INVOKE CreateWindowEx, NULL, ADDR ToolTipsClassName, NULL,\
                               TTS_ALWAYSTIP, CW_USEDEFAULT,\
                               CW_USEDEFAULT, CW_USEDEFAULT,\
                               CW_USEDEFAULT, NULL, NULL,\
                               hInstance, NULL
        mov    hwndTool, eax
        mov    id, 0
        mov    ti.cbSize, SIZEOF TOOLINFO
        mov    ti.uFlags, TTF_SUBCLASS
        push   hDlg
        pop    ti.hWnd
        INVOKE GetWindowRect, hDlg, ADDR rect
        INVOKE SetDlgToolArea, hDlg, ADDR ti, ADDR MainDialogText1, id, ADDR rect
        inc    d
        INVOKE SetDlgToolArea, hDlg, ADDR ti, ADDR MainDialogText2, id, ADDR rect
        inc    id
        INVOKE SetDlgToolArea, hDlg, ADDR ti, ADDR MainDialogText3, id, ADDR rect
        inc    id
        INVOKE SetDlgToolArea, hDlg, ADDR ti, ADDR MainDialogText4, id, ADDR rect
        INVOKE EnumChildWindows, hDlg, ADDR EnumChild, ADDR ti
    .ELSEIF uMsg==WM_CLOSE
        INVOKE EndDialog, hDlg, NULL
    .ELSEIF uMsg==WM_COMMAND
        .IF (lParam!=0) && \
            (WORD PTR wParam + 2 == BN_CLICKED) && \
            (WORD PTR wParam==IDC_EXIT)
            INVOKE EndDialog, hDlg, NULL
        .ENDIF
    .ELSE
        mov eax, FALSE
        ret
    .ENDIF
    mov eax, TRUE
    ret

DlgProc ENDP

EnumChild PROC USES edi hwndChild:DWORD, lParam:DWORD

LOCAL buffer[256]:BYTE

    mov edi, lParam

ASSUME edi: PTR TOOLINFO

    push   hwndChild
    pop    [edi].uId
    or     [edi].uFlags, TTF_IDISHWND
    INVOKE GetWindowText, hwndChild, ADDR buffer, 255
    lea    eax, buffer
    mov    [edi].lpszText, eax
    INVOKE SendMessage, hwndTool, TTM_ADDTOOL, NULL, edi

ASSUME edi: NOTHING

    ret

EnumChild ENDP

SetDlgToolArea PROC USES edi esi hDlg:   DWORD,\
                                 lpti:   DWORD,\
                                 lpText: DWORD,\
                                 id:     DWORD,\
                                 lprect: DWORD

    mov edi, lpti
    mov esi, lprect

ASSUME esi: PTR RECT
ASSUME edi: PTR TOOLINFO

    .IF id==0
        mov [edi].rect.left, 0
        mov [edi].rect.top, 0
        mov eax, [esi].right
        sub eax, [esi].left
        shr eax, 1
        mov [edi].rect.right, eax
        mov eax, [esi].bottom
        sub eax, [esi].top
        shr eax, 1
        mov [edi].rect.bottom, eax
    .ELSEIF id==1
        mov eax, [esi].right
        sub eax, [esi].left
        shr eax, 1
        inc eax
        mov [edi].rect.left, eax
        mov [edi].rect.top, 0
        mov eax, [esi].right
        sub eax, [esi].left
        mov [edi].rect.right, eax
        mov eax, [esi].bottom
        sub eax, [esi].top
        mov [edi].rect.bottom, eax
    .ELSEIF id==2
        mov [edi].rect.left, 0
        mov eax, [esi].bottom
        sub eax, [esi].top
        shr eax, 1
        inc eax
        mov [edi].rect.top, eax
        mov eax, [esi].right
        sub eax, [esi].left
        shr eax, 1
        mov [edi].rect.right, eax
        mov eax, [esi].bottom
        sub eax, [esi].top
        mov [edi].rect.bottom, eax
    .ELSE
        mov eax, [esi].right
        sub eax, [esi].left
        shr eax, 1
        inc eax
        mov [edi].rect.left, eax
        mov eax, [esi].bottom
        sub eax, [esi].top
        shr eax, 1
        inc eax
        mov [edi].rect.top, eax
        mov eax, [esi].right
        sub eax, [esi].left
        mov [edi].rect.right, eax
        mov eax, [esi].bottom
        sub eax, [esi].top
        mov [edi].rect.bottom, eax
    .ENDIF
    push   lpText
    pop    [edi].lpszText
    INVOKE SendMessage, hwndTool, TTM_ADDTOOL, NULL, lpti

ASSUME edi: NOTHING
ASSUME esi: NOTHING

    ret

SetDlgToolArea ENDP

END start

Analiza

Po utworzeniu głównego okna dialogowego tworzymy kontrolkę podpowiedzi za pomocą CreateWindowEx.

 

    .IF uMsg==WM_INITDIALOG
        INVOKE InitCommonControls
        INVOKE CreateWindowEx, NULL, ADDR ToolTipsClassName, NULL,\
                               TTS_ALWAYSTIP, CW_USEDEFAULT,\
                               CW_USEDEFAULT, CW_USEDEFAULT,\
                               CW_USEDEFAULT, NULL, NULL,\
                               hInstance, NULL
        mov    hwndTool, eax

 

Po wykonaniu tego zadania przechodzimy do definiowania czterech narzędzi dla każdego z narożników okna dialogowego.

 

        mov    id, 0                    ;używane jako numer ID narzędzia
        mov    ti.cbSize, SIZEOF TOOLINFO
        mov    ti.uFlags, TTF_SUBCLASS  ;nakazujemy kontrolce przejąć
                                        ;procedurę okna dialogowego
        push   hDlg
        pop    ti.hWnd                  ;uchwyt okna z narzędziem
        INVOKE GetWindowRect, hDlg, ADDR rect ;wymiary obszaru roboczego
        INVOKE SetDlgToolArea, hDlg, ADDR ti, ADDR MainDialogText1, id, ADDR rect

 

Inicjujemy pola struktury TOOLINFO. Zwróć uwagę, iż chcemy podzielić obszar roboczy na cztery narzędzie, zatem musimy znać jego rozmiary. Dlatego właśnie wywołujemy GetWindowRect. Nie chcemy samodzielnie przekazywać wiadomości myszki do kontrolki podpowiedzi, zatem określamy znacznik TTF_SUBCLASS.

SetDlgToolArea jest funkcją obliczającą granice prostokąta dla każdego narzędzia i rejestrującą to narzędzie w kontrolce podpowiedzi. Nie będę zagłębiał się w szczegóły tych obliczeń, wystarczy, gdy powiem, iż dzieli ona obszar roboczy na cztery równe ćwiartki. Następnie wysyła wiadomość TTM_ADDTOOL do kontrolki podpowiedzi przekazując adres struktury TOOLINFO w parametrze lParam.

 

        INVOKE SendMessage, hwndTool, TTM_ADDTOOL, NULL, lpti

 

Po zarejestrowaniu wszystkich czterech narzędzi przechodzimy do przycisków w oknie dialogowym. Możemy obsłużyć każdy przycisk za pomocą jego numeru ID, lecz nie jest to zbyt wygodne. Zamiast tego użyjemy wywołania API EnumChildWindows do przeglądnięcia wszystkich kontrolek w oknie dialogowym i zarejestrowania ich w kontrolce podpowiedzi. Funkcja EnumChildWindows ma następującą składnię:

 

EnumChildWindows PROTO hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

Funkcja EnumChildProc ma następującą definicję:

 

EnumChildProc PROTO hwndChild:DWORD, lParam:DWORD

W naszym przykładzie wywołujemy funkcję EnumChildWindows następująco:

 

        INVOKE EnumChildWindows, hDlg, ADDR EnumChild, ADDR ti

 

W parametrze lParam przekazujemy adres struktury TOOLINFO, ponieważ w funkcji EnumChild będziemy rejestrować każdą kontrolkę potomną w kontrolce podpowiedzi. Jeśli nie skorzystamy z takiej metody, to strukturę ti musimy zadeklarować jako globalną, co może wprowadzić błędy.

Gdy wywołujemy EnumChildWindows, system Windows będzie przeglądał kolejno kontrolki potomne na naszym oknie dialogowym i dla każdej z nich wywoływał funkcję EnumChild. Zatem skoro nasze okno dialogowe wyposażone jest w dwa przyciski, funkcja EnumChild zostanie wywołana dwukrotnie.

Funkcja EnumChild wypełnia istotne pola struktury TOOLINFO, a następnie rejestruje to narzędzie w kontrolce podpowiedzi.

 

EnumChild PROC USES edi hwndChild:DWORD, lParam:DWORD

LOCAL buffer[256]:BYTE

    mov edi, lParam

ASSUME edi: PTR TOOLINFO

    push   hwndChild
    pop    [edi].uId    ;jako narzędzie stosujemy cały obszar kontrolki
    or     [edi].uFlags, TTF_IDISHWND
    INVOKE GetWindowText, hwndChild, ADDR buffer, 255
    lea    eax, buffer  ;jako podpowiedzi używamy tytułu okna
    mov    [edi].lpszText, eax
    INVOKE SendMessage, hwndTool, TTM_ADDTOOL, NULL, edi

ASSUME edi: NOTHING

    ret

EnumChild ENDP

 

Zwróć uwagę, iż w tym przypadku stosujemy inny rodzaj narzędzia, które pokrywa cały obszar roboczy okna. Dlatego musimy wypełnić pole uId uchwytem okna zawierającego to narzędzie. Również musimy określić znacznik TTF_IDISHWND w polu uFlags.

 

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.