Ćwiczenie Obsługa klawiatury i myszy Tematy ćwiczenia klawiatura, mysz, wyprowadzenie tekstu. Sprawozdanie Na każdym zajęciu laboratoryjnym sporządza się za pomocą edytora Word sprawozdanie na bazie materiałów ćwiczenia. Bazowa zawartość sprawozdania musi być przygotowana przed ćwiczeniem. W czasie ćwiczenia do sprawozdania są dodawane wyniki testowania. Treść sprawozdania: o strona tytułowa, o o spis treści sporządzony za pomocą Word'a, dla każdego zadania rozdziały "Zadanie", "Opracowanie zadania" (rozdział z tekstem programu, obliczeniami i komentarzami), "Testowanie" (rozdział z opisem danych wejściowych i wynikami testowania, w tym zrzuty okien). Nazwa pliku ze sprawozdaniem musi zawierać skrót "P_", numer ćwiczenia i nazwisko studenta (bez polskich liter, żeby można było archiwizować). Pliki ze sprawozdaniem są przekazywane do archiwum grupy. Projekt Wywołać środowisko Visual Studio i stworzyć nowy projekt. Skopiować poprzedni program i zmodyfikować, aby realizować zadanie z tabeli wariantów. a) Obsługa klawiatury Zadanie Stworzyć program, w którym reagować na kolejne naciśniecie przycisków. Przyjmować znaki z klawiatury, wyświetlać, analizować i wychodzić z aplikacji (zamykać okno) w przypadku kolejnego naciśnięcia przycisków wskazanych w tabeli wariantów. Na dole okna wypisać komunikat o tym, jak można zakończyć aplikację. Opracowanie zadania <tekst programu> Testowanie <zrzut okna aplikacji> -----------------------------------------------------------------------------------------------------------------
Tabela wariantów dla punktu a) Np Klawisze Np Klawisze Np Klawisze Np Klawisze 1 A, B 9 Q, R 17 E, D 25 U, T 2 B, C 10 R, S 18 F, E 26 V, U 3 C, D 11 S, T 19 G, F 27 W, V 4 D, E 12 T, U 20 H, G 28 X, W 5 E, F 13 U, V 21 I, H 29 Y, X 6 F, G 14 V, W 22 J, I 30 Z, Y 7 G, H 15 W, X 23 K, J 31 1, Z 8 H, I 16 X, Y 24 L, K 32 2, Y b) Obsługa myszy Zadanie Dodać do programu reakcję na naciśnięcie okna myszą. Jednokrotne naciśniecie lewym przyciskiem myszy musi przemieszczać kursor. Od miejsca kursora muszą być wyświetlane wprowadzane znaki. Wychodzić z aplikacji (zamykać okno) po kombinacji naciśnięć przycisku i myszy według tabeli wariantów. Na dole okna wypisać komunikat o tym, jak można zakończyć aplikację. Opracowanie zadania <tekst programu> Testowanie <zrzut okna aplikacji> ----------------------------------------------------------------------------------------------------------------- Tabela wariantów dla punktu b) Np Mysz i klawisz Np Mysz i klawisz Np Mysz i klawisz Np Mysz 1 Lewy Alt + prawy myszy 9 Lewy Ctrl + prawy myszy 17 Lewy Alt + prawy myszy 25 Lewy Ctrl + prawy myszy 2 Lewy Alt + prawy myszy 10 Lewy Ctrl + prawy myszy 18 Lewy Alt + prawy myszy 26 Lewy Ctrl + prawy myszy 3 Lewy Alt + prawy myszy 11 Lewy Ctrl + prawy myszy 19 Lewy Alt + prawy myszy 27 Lewy Ctrl + prawy myszy 4 Lewy Alt + prawy myszy 12 Lewy Ctrl + prawy myszy 20 Lewy Alt + prawy myszy 28 Lewy Ctrl + prawy myszy 5 Lewy Alt + lewy myszy 13 Lewy Ctrl + lewy myszy 21 Lewy Alt + lewy myszy 29 Lewy Ctrl + lewy myszy 6 Lewy Alt + lewy myszy 14 Lewy Ctrl + lewy myszy 22 Lewy Alt + lewy myszy 30 Lewy Ctrl + lewy myszy 7 Lewy Alt + lewy myszy 15 Lewy Ctrl + lewy myszy 23 Lewy Alt + lewy myszy 31 Lewy Ctrl + lewy myszy 8 Lewy Alt + lewy myszy 16 Lewy Ctrl + lewy myszy 24 Lewy Alt + lewy myszy 32 Lewy Ctrl + lewy myszy
Wskazówki Programowanie z zastosowaniem funkcji API Win32 W programowaniu aplikacji Windows są używane funkcje API (Application Programming Interface). Wewnątrz systemu Windows funkcja API wykorzystuje funkcje przerwań MS-DOS, ale programista nie widzi tego i pracuje jakby o jeden poziom wyżej. Ponieważ ilość funkcji API przekracza 2000, programowanie aplikacji Windows nie jest możliwe bez dokumentacji Microsoft Win32 Programmer s Reference. Aplikacje Windows można rozdzielić na następujące grupy: o aplikacja bezokienkowa, o aplikacja klasyczna korzystająca z jednego głównego okna, o aplikacja dialogowa, w której główne okno jest oknem dialogowym, o aplikacja konsolowa, w której główne okno jest oknem konsoli, o usługa specjalizowana aplikacja lub biblioteka dynamiczna, o sterownik - aplikacja lub biblioteka dynamiczna sterująca urządzeniem peryferyjnym. Funkcje API Win32 Funkcje API Win32 przyjmują parametry tylko przez stos; nie stosuje się przekazywania parametrów przez rejestry. Nazwa bibliotecznej funkcji API może być trochę inna niż nazwa pokazana przez system na ekranie pomocy. Na przykład w bibliotekach import32.lib i user32.lib znajduje się funkcja WriteConsoleA, a na ekranie widzimy informacje o funkcji WriteConsole. Dodatkowa litera A w nazwie funkcji WriteConsoleA i podobnych funkcji odpowiada rodzaju tej funkcji dla wierszy w kodzie ANSI. Jeśli na końcu nazwy funkcji jest litera W, to funkcja operuje wierszami UNICODE. Na zakończenie swojej pracy funkcja API przez rejestr-akumulator EAX zwraca wartość, która świadczy o wyniku procedury. Często wartość 0 lub wartość dodatnia odpowiadają pomyślnemu zakończeniu działań, a wartość -1 wskazuje na niepowodzenie. W przypadku niepowodzenia funkcja API wywołuje funkcję SetLastError, do której przekazuje podwójne słowo z kodem niepowodzenia. Ten kod można przeczytać wywołując funkcje GetLastError bezpośrednio po wywołaniu funkcji API. Programowanie aplikacji konsolową Konsolową nazywamy aplikację, w której stosuje się tylko tekstowy tryb monitora. Aplikacja konsolowa przyjmuje polecenia użytkownika z klawiatury lub od myszy oraz wyświetla wyniki pracy w postaci tekstu. Aplikacja konsolowa może być otwarta w osobnym oknie lub aplikacja może korzystać z okna MS DOS, które Explorator Windows otwiera spod punktu menu Plik / Otwórz wiersz poleceń. Do zalet aplikacji konsolową należy szybkość jej napisania, co wynika z prostego faktu, że nie trzeba opracowywać graficzną część aplikacji. System Windows tworzy okno konsoli jako okno najprostszego rodzaju i kieruje do okna minimalną ilość komunikatów. Do komunikacji z oknem konsoli Windows tworzy trzy bufory: wejściowy, przez który konsola przyjmuje symboli z klawiatury, wyjściowy, zawartość którego jest wyświetlana przez konsolę, komunikatów o błędach. Każdy z buforów posiada deskryptor. Do pobrania deskryptora buforu służy funkcja API GetStdHandle. Na rodzaj deskryptora wskazuje stała STD_INPUT_HANDLE lub STD_OUTPUT_HANDLE. W aplikacjach konsolowych często jest stosowana funkcja CharToOem, która zamienia kody znaków tekstu na kody OEM (Original Equipment Manufacturer), aby konsol wyświetlała polskie znaki na polskojęzycznym komputerze. Przez stos funkcja otrzymuje dwa argumenty: wskaźnik na wiersz źródłowy i wskaźnik na bufor dla tekstu przekształconego.
Funkcje API Win32 związane z konsolą, klawiaturą i myszą BOOL AllocConsole(void); Alokacja konsoli. BOOL FreeConsole(void); Zamykanie wszystkich konsoli. BOOL SetConsoleTitleA(LPCTSTR lpconsoletitle); Wypisywanie nagłówka okna konsolowego. Argument: lpconsoletitle adres wiersza - nagłówka. BOOL SetConsoleTextAttribute(HANDLE hconsoleoutput, WORD wattributes); Ustawienie atrybutów tekstowych. wattributes kolor. COORD GetLargestConsoleWindowSize(HANDLE hconsoleoutput); Zwraca maksymalny rozmiar okna konsoli. Argument: hconsoleoutput deskryptor buforu wyjściowego konsoli BOOL FillConsoleOutputAttribute(HANDLE hconsoleoutput,word wattribute, DWORD nlength,coord dwwritecoord,lpdword lpnumberofattrswritten); Wpisuje do buforu wyjściowego (ekranu) atrybuty znakowe. wattribute kolor, nlength ilość pozycji, dwwritecoord współrzędne X, Y pierwszej pozycji w oknie konsoli, lpnumberofattrswritten adres zmiennej, do której będzie zapisana faktyczna ilość pozycji. BOOL SetConsoleCursorPosition(HANDLE hconsoleoutput,coord dwcursorposition); Ustawienie pozycji kursora. dwcursorposition współrzędne X, Y (typ COORD) pozycji w oknie konsoli. BOOL ReadConsoleInput(HANDLE hconsoleinput,pinput_record lpbuffer, DWORD nlength,lpdword lpnumberofeventsread); Czyta komunikaty okna konsoli. hconsoleinput deskryptor buforu wejściowego konsoli, lpbuffer adres obiektu typu INPUT_RECORD, nlength ilość komunikatów (zapisów), lpnumberofeventsread adres zmiennej, do której będzie zapisana faktyczna ilość przeczytanych komunikatów (zapisów). BOOL FlushConsoleInputBuffer(HANDLE hconsoleinput) Zerowanie kolejki komunikatów. Argument: hconsoleinput deskryptor buforu wejściowego konsoli. BOOL WriteConsoleOutputCharacter(HANDLE hconsoleoutput, LPCTSTR lpcharacter,dword nlength,coord dwwritecoord, LPDWORD lpnumberofcharswritten); Wyprowadzenie znaków od pozycji kursora. lpcharacter adres bufora z tekstem, nlength ilość znaków, dwwritecoord współrzędne X, Y pierwszej pozycji w oknie konsoli, lpnumberofcharswritten adres zmiennej, do której będzie zapisana faktyczna ilość wyprowadzonych znaków.
BOOL WriteConsole(HANDLE hconsoleoutput,const VOID* lpbuffer, DWORD nnumberofcharstowrite,lpdword lpnumberofcharswritten, LPVOID lpreserved); Wyprowadzenie znaków do buforu konsoli lpbuffer adres bufora z tekstem, nnumberofcharstowrite ilość znaków, lpnumberofcharswritten adres zmiennej, do której będzie zapisana faktyczna ilość wyprowadzonych znaków,. lpreserved rezerwa (musi być 0). Aplikacja korzystająca z otwartego okna konsoli Program najprostszej aplikacji konsolową korzystającej z otwartego okna konsoli musi zawierać trzy zasadniczy części: o pobranie deskryptora bufora konsoli za pomocą funkcji GetStdHandle i tym samym otwarcie odrębnego procesu na konsoli, o operacja z buforem, na przykład wyprowadzenie tekstu za pomocą funkcji WriteConsole, o zamykanie procesu przez wywołanie funkcji ExitProcess. Aplikacja z tworzeniem okna konsoli Program aplikacji konsolowej, w której jest tworzone okno konsoli, musi zawierać alokacje konsoli za pomocą funkcji AllocConsole i zamykanie konsoli przez wywołanie funkcji FreeConsole. Funkcja FreeConsole musi być wywołana też na początku programu, aby zamknąć inne konsoli tego procesu. Okno konsoli posiada nagłówek, który jest przekazywany za pomocą funkcji SetConsoleTitleA. W trybie konsolowym jest możliwe zastosowanie 16 kolorów tła i 16 kolorów znaku. Funkcja Fill- ConsoleOutputAttribute przypisuje do każdej komórki bufora zadany atrybut koloru. Informacje o zdarzeniach System operacyjny daje możliwość otrzymania informacji o stanie klawiatury, myszy, okna i menu. Dowolna zmiana stanu sprzętu jest interpretowana jako zdarzenie. Przy zmianie stanu informacja o zdarzeniu jest zapisywana do 20-bajtowej struktury INPUT_RECORD: typedef struct _INPUT_RECORD //struktura z informacją o zdarzeniu WORD EventType; //typ zdarzenia WORD two_byte_alignment;//pustę; wyrównanie do granicy 4-bajtowej union KEY_EVENT_RECORD KeyEvent; MOUSE_EVENT_RECORD MouseEvent; WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent; } Event; } INPUT_RECORD; Łańcuchy zapisów o zdarzeniach (ang. event records) można czytać za pomocą funkcji ReadConsoleInput. Struktura INPUT_RECORD posiada w pierwszym podwójnym słowie identyfikator źródła zdarzenia. Identyfikator może mieć następujące wartości: 1 = KEY_EVENT zapis dotyczy klawiatury, 2 = MOUSE_EVENT zapis dotyczy myszy, 4 = WINDOW_BUFFER_SIZE_EVENT zapis zawiera informację o zmianie rozmiaru okna, 8 = MENU_EVENT zapis dotyczy menu, 16 = FOCUS_EVENT zapis zawiera informację o zmianie fokusu.
W zależności od identyfikatora różne interpretowane są pozostałe 16 bajtów. Struktura typu KEY_EVENT: typedef struct _KEY_EVENT_RECORD BOOL bkeydown; //znacznik naciśnięcia któregokolwiek klawisza WORD wrepeatcount; //ilość powtórzeń kodu przy długim naciśnięciu WORD wvirtualkeycode; //wirtualny kod klawisza WORD wvirtualscancode; //scan-kod klawisza union WCHAR UnicodeChar; //UNICODE kod klawisza CHAR AsciiChar; //ASCII kod klawisza } uchar; DWORD dwcontrolkeystate; //znaczniki klawiszy sterujących } KEY_EVENT_RECORD; Struktura zapisu typu KEY_EVENT: 4 bajty znacznik naciśnięcia któregokolwiek klawisza (0 nie naciśnięty, nie 0 naciśnięty), 2 bajty ilość powtórzeń kodu w wyniku długiego naciśnięcia, 2 bajty wirtualny kod klawisza, 2 bajty scan-kod klawisza, 2 bajty ASCII lub UNICODE kod klawisza (funkcja ReadConsoleInputA podaje kod ASCII, a funkcja ReadConsoleInputW podaje kod UNICODE), 4 bajty podwójne słowo znaczników klawiszy sterujących: 1 = RIGHT_ALT_PRESSED Alt prawy, 2 = LEFT_ALT_PRESSED Alt lewy, 4 = RIGHT_CTRL_PRESSED Ctrl prawy, 8 = LEFT_CTRL_PRESSED Ctrl lewy, 0x10 = SHIFT_PRESSED Shift naciśnięty, 0x20 = NUMLOCK_ON Num Lock włączony, 0x40 = SCROLLLOCK_ON Scroll Lock włączony, 0x80 = CAPSLOCK_ON Caps Lock włączony, 0x100 = ENHANCED_KEY klawisz jest zwolniony, System Windows wysyła do konsoli nie wszystkie kombinacji klawisz. Nie jest wysyłana kombinacja Ctrl+C, która daje możliwość awaryjnego zakończenia aplikacji. Struktura typu MOUSE_EVENT: typedef struct _MOUSE_EVENT_RECORD COORD dwmouseposition; //współrzędne X, Y kursora myszy DWORD dwbuttonstate; //znaczniki naciśnięcia przycisków myszy DWORD dwcontrolkeystate; //znaczniki naciśnięcia klawiszy sterujących DWORD dweventflags; //znaczniki przesuwania i podwójnego kliknięcia myszą } MOUSE_EVENT_RECORD; Struktura zapisu typu MOUSE_EVENT: 2 bajty - współrzędna X kursora myszy, 2 bajty - współrzędna Y kursora myszy,
4 bajty podwójne słowo znaczników naciśnięcia przycisków myszy: 1 = FROM_LEFT_1ST_BUTTON_PRESSED przycisk lewy, 2 = RIGHTMOST_BUTTON_PRESSED przycisk prawy, 4 = FROM_LEFT_2ND_BUTTON_PRESSED przycisk środkowy (drugi z lewa), 8 = FROM_LEFT_3ND_BUTTON_PRESSED przycisk trzeci z lewa, 0x10 = FROM_LEFT_4ND_BUTTON_PRESSED przycisk czwarty z lewa, 4 bajty podwójne słowo znaczników naciśnięcia klawiszy sterujących (słowo jest podobne do znaczników struktury KEY_EVENT), 4 bajty podwójne słowo znaczników przesuwania i podwójnego klikniecie myszy: 1 = MOUSE_MOVED mysz przesuwa się, 2 = DOUBLE_CLICK - podwójne klikniecie, 4 = MOUSE_WHEELED poruszenie kółkiem myszy, Struktura typu COORD: typedef struct _COORD SHORT X; //współrzędna X SHORT Y; //współrzędna Y } COORD, *PCOORD; Struktura typu WINDOW_BUFFER_SIZE_EVENT: typedef struct _WINDOW_BUFFER_SIZE_RECORD COORD dwsize; } WINDOW_BUFFER_SIZE_RECORD; Struktura zapisu typu WINDOW_BUFFER_SIZE_EVENT: 2 bajty nowy rozmiar okna wzdłuż X, 2 bajty - nowy rozmiar okna wzdłuż Y. Struktura typu MENU_EVENT: typedef struct _MENU_EVENT_RECORD UINT dwcommandid; } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD; Struktura zapisu typu MENU_EVENT: 4 bajty identyfikator punktu menu. Komunikat o zdarzeniu MENU_EVENT jest wykorzystywany przez system operacyjny i w programie musi być zignorowany. Struktura typu FOCUS_EVENT: typedef struct _FOCUS_EVENT_RECORD BOOL bsetfocus; //zarezerwowane } FOCUS_EVENT_RECORD; Struktura zapisu typu FOCUS_EVENT: 4 bajty znacznik ustawienia fokusu. Komunikat o zdarzeniu FOCUS_EVENT jest przeznaczony dla systemu operacyjnego i w programie musi być zignorowany.
Fragmenty programu //Aplikacja z tworzeniem okna konsoli #include <stdio.h> #include <stdlib.h> #include <math.h> #include <conio.h> #include <windows.h> #pragma warning (disable:4996)... char wiersz[]="zakończyć można przez naciśnięcie klawiszy Esc lub A lub " "podwójnym naciśnięciem myszy\r\n";... char str[2]=' ','\0'}; HANDLE hinp,hout; COORD YX; INPUT_RECORD zapis; int nzdarz=0; //ilość zdarzeń int rbuf,rfakt,rout,lengbuf; //--- Kolory ---- unsigned short kolor1 = 0xFC; // Kolory: BG_I,R,G,B,FG I,R,G,B unsigned short kolor2 = 0xF9; // Kolory: BG_I,R,G,B,FG I,R,G,B int atryb=kolor2; int main(void) //--- tworzenie konsoli --- FreeConsole(); AllocConsole(); //--- CharToOemA(tytul,tytul); SetConsoleTitleA(tytul); //--- hout=getstdhandle(std_output_handle); // deskryptor wyjściowego bufora konsoli hinp=getstdhandle(std_input_handle); // deskryptor wejściowego bufora konsoli //--- obliczenie rozmiaru buforu ekranu YX=GetLargestConsoleWindowSize(hout); rbuf=yx.x * YX.Y; //--- napełnienie komórek jednakowym atrybutem --- YX.X=0; YX.Y=0; FillConsoleOutputAttribute(hout,kolor2,rbuf,YX,&rfakt); //--- //--- wyprowadzenie komunikatu informacyjnego --- SetConsoleTextAttribute(hout,kolor1); CharToOemA(tekst1,buf); //konwersja polskich znaków lengbuf=(int)strlen(buf); WriteConsoleA(hout,buf,lengbuf,&rfakt,NULL); //--- ustawienie kursora YX.X=0; YX.Y=20; SetConsoleCursorPosition(hout,YX); //ustawienie pozycji kursora //--- wyprowadzenie komunikatu informacyjnego --- SetConsoleTextAttribute(hout,kolor1); CharToOemA(wiersz,buf); //konwersja polskich znaków lengbuf=(int)strlen(buf); WriteConsoleA(hout,buf,lengbuf,&rfakt,NULL); //---
powt: //--- odczyt komunikatu zdarzenia FlushConsoleInputBuffer(hinp); //czyszczenie kolejki komunikatów ReadConsoleInputA(hinp,&zapis,1,&nzdarz); //odczyt komunikatu zdarzenia //--- sprawdzanie typu zdarzenia if (zapis.eventtype==mouse_event) goto mysz; if (zapis.eventtype==key_event) goto klaw; goto powt; //--- komunikat od myszy mysz: if (zapis.event.mouseevent.dweventflags & DOUBLE_CLICK) goto kon; //na koniec programu if ((zapis.event.mouseevent.dwbuttonstate & FROM_LEFT_1ST_BUTTON_PRESSED)==0) goto powt; //--- jest naciśnięty lewy klawisz myszy YX=zapis.Event.MouseEvent.dwMousePosition; //współrzędne X,Y SetConsoleCursorPosition(hout,YX); //ustawienie pozycji kursora goto powt; //--- komunikat od klawiatury --- klaw: if (zapis.event.keyevent.bkeydown==0) goto powt; //porównanie z zerem; jeśli nie ma naciśnięcia, to skok if (zapis.event.keyevent.wvirtualkeycode==vk_escape) goto kon;//porównanie z kodem Esc str[0]=zapis.event.keyevent.uchar.asciichar; if (str[0]=='a') goto kon; //porównanie z kodem litery A WriteConsoleOutputCharacterA(hout,str,1,YX,&rout); //wypisywanie znaku goto powt; //--- zamknięcie konsoli kon: FreeConsole(); return 0; }