Rozdział VII - Odczyt myszki


W tej lekcji nauczymy się odbierać i odpowiadać w naszej procedurze okna na zdarzenia związane z myszką. Przykładowy program będzie oczekiwał na kliknięcia lewym przyciskiem myszki i wyświetlał łańcuch tekstu dokładnie w miejscu kliknięcia na obszarze roboczym.

 

 

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

 

Teoria

Podobnie jak w przypadku danych z klawiatury system Windows wykrywa i wysyła powiadomienia o działaniach myszką, które są istotne dla każdego okna. Działania te obejmują kliknięcia lewym i prawym przyciskiem, ruch kursora ponad powierzchnią okna, podwójne kliknięcia. W przeciwieństwie do klawiatury, z której dane są kierowane do aktywnego w danej chwili okna, wiadomości myszki są przesyłane do dowolnego okna, nad którym w danej chwili znajduje się kursor myszki, bez względu na to, czy jest ono aktywne, czy nie. Dodatkowo są również wiadomości myszki dotyczące obszarów poza obszarem roboczym. Na szczęście zwykle możemy je spokojnie zignorować. Skupimy się tylko na wiadomościach dotyczących naszego obszaru roboczego.

Są dwie wiadomości dla każdego z przycisków myszki: WM_LBUTTONDOWN, WM_RBUTTONDOWN oraz WM_LBUTTONUP, WM_RBUTTONUP. Dla myszki z trzema klawiszami mamy również wiadomości WM_MBUTTONDOWN i WM_MBUTTONUP. Gdy kursor myszki przesuwa się ponad obszarem roboczym okna, system Windows wysyła wiadomości WM_MOUSEMOVE do okna pod kursorem.

Okno może otrzymywać wiadomości WM_LBUTTONDBCLK lub WM_RBUTTONDBCLK tylko wtedy, gdy jego klasa zawiera znacznik stylu CS_DBLCLKS, w przeciwnym razie okno to będzie jedynie otrzymywać ciąg wiadomości o wciśnięciu i zwolnieniu przycisków myszki.

We wszystkich wiadomościach myszki wartość IParam zawiera pozycję myszki. Młodsze słowo jest wartością współrzędnej x, a starsze słowo określa współrzędną y określone względem lewego górnego rogu obszaru roboczego okna. wParam określa stan przycisków myszki oraz klawiszy Shift i Ctrl.

 

.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
INCLUDE    \masm32\include\gdi32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\gdi32.lib

.DATA

ClassName  DB "SimpleWinClass",0
AppName    DB "Nasze pierwsze okno",0
MouseClick DB 0         ; 0=jeszcze nie było kliknięcia myszką

.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?
hitpoint    POINT     <>

.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_WINDOW+1
    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, NULL, ADDR ClassName, ADDR AppName,\
           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,\
           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 DispatchMessage, ADDR msg
    .ENDW
    mov    eax, msg.wParam
    ret

WinMain ENDP

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

LOCAL hdc: HDC
LOCAL ps:  PAINTSTRUCT

    .IF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSEIF uMsg==WM_LBUTTONDOWN
        mov    eax, lParam
        and    eax, 0ffffh
        mov    hitpoint.x, eax
        mov    eax, lParam
        shr    eax, 16
        mov    hitpoint.y, eax
        mov    MouseClick, TRUE
        INVOKE InvalidateRect, hWnd, NULL, TRUE
    .ELSEIF uMsg==WM_PAINT
        INVOKE BeginPaint, hWnd, ADDR ps
        mov    hdc, eax
        .IF MouseClick
            INVOKE lstrlen, ADDR AppName
            INVOKE TextOut, hdc, hitpoint.x, hitpoint.y,\
                            ADDR AppName, eax
        .ENDIF
        INVOKE EndPaint, hWnd, ADDR ps
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF

    xor eax, eax
    ret

WndProc ENDP

END start

Analiza

    .ELSEIF uMsg==WM_LBUTTONDOWN
        mov    eax, lParam
        and    eax, 0ffffh
        mov    hitpoint.x, eax
        mov    eax, lParam
        shr    eax, 16
        mov    hitpoint.y, eax
        mov    MouseClick, TRUE
        INVOKE InvalidateRect, hWnd, NULL, TRUE
    .ELSEIF uMsg==WM_PAINT


Procedura okna czeka na kliknięcie lewego przycisku myszki. Gdy otrzyma wiadomość WM_LBUTTONDOWN, lParam zawiera współrzędne kursora myszki w obszarze roboczym okna. Procedura zachowuje te współrzędne w zmiennej typu POINT, który jest zdefiniowany jako:


POINT STRUCT 
    x DD ? 
    y DD ? 
POINT ENDS


i ustawia znacznik MouseClick na TRUE, co oznacza, iż było kliknięcie lewego przycisku myszki nad obszarem roboczym okna.


    mov eax, lParam
    and eax, 0ffffh
    mov hitpoint.x, eax


Ponieważ współrzędna x znajduje się w młodszym słowie lParam, a pola struktury POINT mają rozmiar 32 bitów, musimy wyzerować starsze słowo rejestru eax przed umieszczeniem jego zawartości w hitpoint.x.


    mov eax, lParam
    shr eax, 16
    mov hitpoint.y, eax


Ponieważ współrzędna y znajduje się w starszym słowie lParam, musimy przemieścić ją do młodszego słowa rejestru eax przed umieszczeniem w hitpoint.y. Wykonujemy to przesuwając zawartość eax o 16 bitów w prawo.


Uwaga od tłumacza.
Opisane operacje można również przeprowadzić dużo prościej po prostu zerując rejestr eax, a następnie pobierając do jego części 16 bitowej słowo spod adresu lParam i słowo spod adresu lParam+2, przesyłając zawartość eax kolejno do pola x i y struktury hitpoint. Odpowiedni fragment kodu jest następujący:


    xor eax, eax                ;zerujemy rejestr 32 bitowy eax
    mov ax, WORD PTR lParam     ;pobieramy współrzędną x kursora myszki
    mov hitpoint.x, eax         ;przesyłamy wynik do struktury
    mov ax, WORD PTR lParam + 2 ;pobieramy współrzędną y
    mov hitpoint.y, eax         ;przesyłamy do struktury y


Po zapamiętaniu pozycji myszki ustawiamy znacznik MouseClick na TRUE, aby kod malujący w sekcji WM_PAINT wiedział, iż wystąpiło kliknięcie lewego klawisza myszki w obszarze roboczym okna i aby mógł wyrysować łańcuch znaków na pozycji myszki. Następnie wywołujemy funkcję InvalidateRect wymuszającą przerysowanie przez okno całego swojego obszaru roboczego.


    .IF MouseClick
        INVOKE lstrlen, ADDR AppName
        INVOKE TextOut, hdc, hitpoint.x, hitpoint.y,\
               ADDR AppName, eax
    .ENDIF


Kod malujący w sekcji WM_PAINT musi sprawdzić, czy MouseClick ma wartość TRUE, ponieważ gdy zostało utworzone okno, system wysłał wiadomość WM_PAINT, a w tym czasie nie było jeszcze kliknięć myszką, więc na obszarze roboczym nie powinien pojawiać się żaden napis. Inicjujemy MouseClick na FALSE i zmieniamy tę wartość na TRUE, gdy pojawi się rzeczywiste kliknięcie lewego przycisku myszki.

Jeśli zdarzyło się przynajmniej jedno kliknięcie myszką, kod rysuje napis na obszarze roboczym okna na pozycji kursora myszki. Zwróć uwagę, iż wywołuje on funkcję lstrlen do obliczenia długości tekstu do wyświetlenia i przesyła tę długość jako ostatni parametr (eax) funkcji TextOut.

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

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

program Mouse;

uses Windows;

const
  AppName = 'Nasze pierwsze okno';

var
  MouseClick : boolean; //false = nie było jeszcze kliknięcia myszką
  hitpoint   : POINT;

function WndProc(h:HWND;uMsg:UINT;wP:WPARAM;lP:LPARAM) : longint;
var
  HDevCnt: HDC;
  ps     : PAINTSTRUCT;
begin
  WndProc := 0;
  case uMsg of
    WM_DESTROY: PostQuitMessage(0);
    WM_LBUTTONDOWN : begin
                       hitpoint.x := lP and $ffff;
                       hitpoint.y := (lP shr 16);
                       MouseClick := true;
                       InvalidateRect(h,0,true);                    
                     end;
    WM_PAINT       : begin
                       HDevCnt := BeginPaint(h,ps);
                       if MouseClick then
                          TextOut(HDevCnt,hitpoint.x,hitpoint.y,
                                  AppName,lstrlen(AppName));
                       EndPaint(h,ps);
                     end;
    else  WndProc := DefWindowProc(h,uMsg,wP,lP);
  end;
end;

function WinMain(hI,hPI:HINST;CmdLine:LPSTR;cmdShow:longint) : longint;
const
  ClassName = 'SimpleWinClass';
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_WINDOW + 1;
  wc.lpszMenuName  := 0;
  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,
                      CW_USEDEFAULT,CW_USEDEFAULT,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
  MouseClick := false;
  ExitProcess(WinMain(GetModuleHandle(0),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.