Rozdział VI - Odczyt klawiatury


W tym rozdziale dowiemy się jak program dla Windows odczytuje klawiaturę.

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


Teoria

Skoro zwykle w każdym komputerze PC jest tylko jedna klawiatura, to wszystkie uruchomione w systemie Windows programy muszą się nią dzielić. Windows odpowiada za przesyłanie informacji o naciśniętych klawiszach do aktywnego okna.

Chociaż na ekranie może znajdować się kilka okien, tylko jedno z nich jest aktywne i tylko ono może otrzymywać informację o naciśniętych klawiszach. Aktywne okno można rozpoznać od innych patrząc na jego pasek tytułowy, który jest podświetlony.

W rzeczywistości istnieją dwa główne rodzaje wiadomości klawiatury, zależnie od sposobu jej potraktowania. Klawiaturę możesz potraktować jak zbiór klawiszy. W tym przypadku, jeśli zostanie naciśnięty klawisz, system Windows wysyła wiadomość WM_KEYDOWN do aktywnego okna informując je o tym fakcie. Gdy klawisz zostanie zwolniony, Windows wysyła wiadomość WM_KEYUP. Klawisz traktujesz jak przycisk. Innym sposobem potraktowania klawiatury jest przyjęcie, iż stanowi ona urządzenie wprowadzania znaków. Gdy naciśniesz klawisz "A", system Windows wysyła wiadomość WM_CHAR do aktywnego okna informując je, iż użytkownik przesyła do niego znak "A". W rzeczywistości Windows wysyła do okna wiadomości WM_KEYDOWN i WM_KEYUP i wiadomości te zostaną przetłumaczone na wiadomości WM_CHAR przez wywołanie funkcji TranslateMessage. Procedura okna może zdecydować się przetwarzać wszystkie trzy wiadomości klawiatury lub tylko te, którymi jest zainteresowana. W większości przypadków możesz zignorować wiadomości WM_KEYDOWN oraz WM_KEYUP, ponieważ wywołanie funkcji TranslateMessage w pętli wiadomości przetłumaczy je na wiadomości WM_CHAR. W tym rozdziale skupimy się na WM_CHAR.

 

Przykład

.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 "Tekst z klawiatury",0
char WPARAM 20h

.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?

.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 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

LOCAL hdc: HDC
LOCAL ps: PAINTSTRUCT

    .IF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSEIF uMsg==WM_CHAR
        push   wParam
        pop    char
        INVOKE InvalidateRect, hWnd, NULL, TRUE
    .ELSEIF uMsg==WM_PAINT
        INVOKE BeginPaint, hWnd, ADDR ps
        mov    hdc, eax
        INVOKE TextOut, hdc, 0, 0, ADDR char, 1
        INVOKE EndPaint, hWnd, ADDR ps
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF

    xor eax, eax
    ret

WndProc ENDP

END start

Analiza

char WPARAM 20h    ;znak odczytany z klawiatury


To jest zmienna, która będzie przechowywała znak odebrany z klawiatury. Ponieważ znak przesyłany jest w WPARAM procedury okna, to dla prostoty definiujemy tę zmienną jako typ WPARAM. Początkowa wartość wynosi 20h, co odpowiada spacji, ponieważ gdy nasze okno odświeża obszar roboczy po raz pierwszy, nie jest jeszcze odczytany żaden znak. Więc zamiast tego wyświetlimy spację.


.ELSEIF uMsg==WM_CHAR
        push   wParam
        pop    char
        INVOKE InvalidateRect, hWnd, NULL, TRUE


To zostało dodane w procedurze okna do obsługi wiadomości WM_CHAR. Kod ten po prostu umieszcza otrzymany znak w zmiennej o nazwie "char", a następnie wywołuje funkcję InvalidateRect, która powoduje, iż obszar roboczy okna staje się nieaktualny, co z kolei zmusza system Windows do wysłania wiadomości WM_PAINT do procedury okna. Jej składnia jest następująca:


InvalidateRect PROTO hWnd:   HWND,\ 
                     lpRect: DWORD,\ 
                     bErase: DWORD 

Zatem użyta przez nas strategia jest następująca: zapamiętujemy wszystkie niezbędne informacje związane z pomalowaniem obszaru roboczego i generujemy wiadomość WM_PAINT, aby ten obszar pomalować. Oczywiście kody w sekcji WM_PAINT muszą z góry wiedzieć, co się od nich oczekuje. Wydaje się to okrężną drogą, lecz w ten sposób działa Windows.

Właściwie moglibyśmy pomalować obszar roboczy podczas przetwarzania wiadomości WM_CHAR wywołując parę GetDC i ReleaseDC. To nie tutaj jest problem. Kłopoty rozpoczną się w momencie, gdy nasze okno będzie wymagało przemalowania swojego obszaru roboczego. Ponieważ kod malowania znaku znajduje się w sekcji WM_CHAR, to procedura okna nie będzie w stanie przemalować naszego znaku na obszarze roboczym. Morał z tego wynika taki: umieszczaj wszystkie niezbędne dane i kody malujące wewnątrz sekcji WM_PAINT. W każdej chwili i z każdego miejsca swojego kodu możesz wysłać wiadomość WM_PAINT, gdy chcesz przemalować obszar roboczy.


    INVOKE TextOut, hdc, 0, 0, ADDR char, 1
 

Gdy zostaje wywołana funkcja InvalidateRect. przesyła ona wiadomość WM_PAINT z powrotem do procedury okna. Zostaje zatem wywołany kod w sekcji WM_PAINT. Wywołuje on jak zwykle BeginPaint, aby pobrać uchwyt do kontekstu urządzenia a następnie wywołuje funkcję TextOut, która rysuje nasz znak w obszarze roboczym na pozycji x=0 i y=0. Gdy uruchomisz ten program i naciśniesz dowolny klawisz, zauważysz obraz literki w lewym górnym rogu obszaru okna. A gdy zminimalizujesz okno i maksymalizujesz je ponownie, to znak ten wciąż tam będzie, ponieważ cały kod wraz z danymi istotnymi do przemalowania go jest umieszczony w sekcji WM_PAINT.


Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

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

program Keyboard;

uses Windows;

var
  ch : WPARAM;       //Tutaj przechowujemy kod znaku odczytanego z klawiatury

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_CHAR     : begin
                    ch := wP;
                    InvalidateRect(h,0,true);
                  end;
    WM_PAINT    : begin
                    HDevCnt := BeginPaint(h,ps);
                    TextOut(HDevCnt,0,0,@ch,1);
                    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,'Tekst z klawiatury',
                      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
  ch := 32;
  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.