3 MYSZ I KLAWIATURA Wiele rzeczy wymyÅ›lono po to, aby nie trzeba byÅ‚o dużo myÅ›leć. Regedit TytuÅ‚owe dwa urzÄ…dzenia wejÅ›ciowe (ang. input devices) sÄ… najintensywniej wykorzystywanymi Å›rodkami do komunikacji użytkownika z komputerem. Historycznie starsza jest klawiatura, jednak obecnie oba te sprzÄ™ty wzajemnie siÄ™ uzupeÅ‚niajÄ…, a obsÅ‚uga wiÄ™kszoÅ›ci dobrych programów może być realizowana przy pomocy każdego z nich. PowstaÅ‚o naturalnie mnóstwo innych urzÄ…dzeÅ„ wejÅ›ciowych, do których należą choćby joysticki czy tablety graficzne. Coraz wiÄ™cej mówi siÄ™ też o sterowaniu aplikacjami za pomocÄ… komend gÅ‚osowych. Wydaje siÄ™ jednak, że nawet jeÅ›li ten nowe interfejsy komunikacyjne zostanÄ… w przyszÅ‚oÅ›ci udoskonalone, to tradycyjne klawiatury i myszki (albo ich zastÄ™pniki, np. trackballe) nigdy nie odejdÄ… caÅ‚kiem do lamusa. Praca z nimi jest po prostu szybka i wygodna, a nadto dyskretna - i chyba nie zmieniÄ… tego żadne nadchodzÄ…ce nowniki. Klawiatury zyskajÄ… oczywiÅ›cie wiÄ™cej klawiszy, myszki - wiÄ™cej przycisków i rolek, ale zasadnicze przeznaczenie i wykorzystanie obu tych urzÄ…dzeÅ„ bÄ™dzie przez caÅ‚y czas takie same. Skoro wiÄ™c sÄ… one dzisiaj podstawowÄ… metodÄ… porozumienia siÄ™ użytkownika z komputerem, nowoczesny system operacyjny w rodzaju Windows musi zapewniać wÅ‚aÅ›ciwÄ… obsÅ‚ugÄ™ klawiatury i myszy. I rzeczywiÅ›cie, Å›rodowisko aplikacji rodem z Microsoftu daje bodaj wszystko, co jest potrzebne, by programista mógÅ‚ zaoferować użytkownikom swych produktów peÅ‚nÄ… współpracÄ™ z możliwoÅ›ciami tych dwóch kluczowych urzÄ…dzeÅ„. Ta kooperacja jest realizowana w ramach Windows API, którego część za to odpowiedzialnÄ… poznamy bliżej w tym oto rozdziale. ObsÅ‚uga myszy Mysz jest urzÄ…dzeniem wskazujÄ…cym (ang. pointing device), którego przeznaczeniem jest współpraca z graficznym interfejsem użytkownika. Nie ma ono wiÄ™kszego zastosowania w konsoli tekstowej, gdzie prym caÅ‚y czas wiedzie (i musi wieść) klasyczna klawiatura. Każda osoba posÅ‚ugujÄ…ca siÄ™ komputerem wie oczywiÅ›cie, w jaki sposób dziaÅ‚a myszka. Nie wszyscy jednak wiedzÄ…, że nie jest ona jedynym możliwym urzÄ…dzeniem, za pomocÄ… którego można sterować kursorem na ekranie. Do innych należy chociażby trackball; jego obsÅ‚uga polega na umiejÄ™tnym poruszaniu kulkÄ…, której obroty powodujÄ… ruch kursora na ekranie. UrzÄ…dzenie to ma sporÄ… zaletÄ™ w postaci braku koniecznoÅ›ci posiadania specjalnej podkÅ‚adki i dlatego jest szczególnie czÄ™sto wykorzystywane w komputerach przenoÅ›nych. SÅ‚owo trackball weszÅ‚o już na dobre do sÅ‚ownika komputerowego i nikt już nawet nie próbuje go tÅ‚umaczyć. Ale jeszcze kilka lat można byÅ‚o okazjonalnie spotkać wyjÄ…tkowo idiotyczne okreÅ›lenie dla tego urzÄ…dzenia: otóz nazywano je kotem, chcÄ…c je rzekomo 424 odróżnić od standardowej myszki. WyjaÅ›nienie to jest raczej dziwne, bo chociaż komputerowa mysz może faktycznie budzić skojarzenia z pospolitym gryzoniem, to przecież trackball nie różni siÄ™ od niej prawie wcale. SÅ‚usznie wiÄ™c zdaje siÄ™, że obecność jednego komputerowego zwierzÄ™cia w zupeÅ‚noÅ›ci nam wystarczy. Fotografia 3 i 4. Komputerowe urzÄ…dzenia wskazujÄ…ce: myszka oraz trackball (fotografie pochodzÄ… z serwisu internetowego firmy Logitech) Jako przyjazny system operacyjny Windows zawiera naturalnie odpowiedniÄ… obsÅ‚ugÄ™ urzÄ…dzeÅ„ wskazujÄ…cych - niezależnie od tego, czym one sÄ…. W WinAPI przyjęło siÄ™ aczkolwiek nazywać je wszystkie myszami, ponieważ tak jest po prostu wygodniej. My również bÄ™dziemy tak wobec tego czynić. W tym podrozdziale zajmiemy siÄ™ wiÄ™c tÄ… częściÄ… Windows API, która umożliwia programom okienkowym wykorzystanie obecnoÅ›ci myszy. Poznamy wpierw wszystkie najważniejsze komunikaty o zdarzeniach myszy oraz reguÅ‚y ich otrzymywania przez okna. Pózniej nauczymy siÄ™ odczytywać stan myszy bezpoÅ›rednio, a nawet symulować jego zmianÄ™. Na sam koniec zostawimy sobie odczytywanie różnorakich parametrów myszy. Zdarzenia myszy System Windows posÅ‚uguje siÄ™ Å‚Ä…cznie kilkudziesiÄ™cioma (!) komunikatami o zdarzeniach pochodzÄ…cych od myszy. SpoÅ›ród tej mnogoÅ›ci najważniejszych jest dla nas kilkanaÅ›cie, informujÄ…cych przede wszystkim o wciÅ›niÄ™ciu lub puszczeniu któregoÅ› z przycisków myszy, ruchu kursora lub też zmianie pozycji rolki (jeżeli jest obecna). Tymi wÅ‚aÅ›nie komunikatami zajmiemy siÄ™ w tej sekcji. One wszystkie posiadajÄ… przynajmniej jednÄ… przyjemnÄ… cechÄ™, zwiÄ…zanÄ… ze swymi parametrami wParam i lParam. Otóż znaczenie tych parametrów jest dla wymienionych zdarzeÅ„ zawsze takie samo: zmienne te zawierajÄ… mianowicie aktualnÄ… pozycjÄ™ kursora myszy oraz informacjÄ™ o tym, czy pewne klawisze sÄ… w danej chwili wciÅ›niÄ™te. Pierwsza z tych danych zawarta jest w lParam. Pozioma i pionowa współrzÄ™dna kursora jest w niej zapisana w dolnym i górnym sÅ‚owie tej 32-bitowej wartoÅ›ci. Aby je uzyskać, możemy zatem posÅ‚użyć siÄ™ poznanymi makrami LOWORD() oraz HIWORD(). Windows API deklaruje też dwa bardziej wyspecjalizowane makra: nX = GET_X_LPARAM(lParam); nY = GET_Y_LPARAM(lParam); Jak wskazujÄ… ich nazwy, sÅ‚użą one wÅ‚aÅ›nie do pobrania pozycji kursora z parametru lParam. Aby z nich skorzystać, trzeba jeszcze doÅ‚aczyć nagłówek windowsx.h: #include 425 Istnieje również makro MAKEPOINTS(), które zmienia lParam w strukturÄ™ POINTS - bardzo podobnÄ… do poznanej wczeÅ›niej POINT, ale z polami typu SHORT (16-bitowymi). Z kolei wParam zawiera nieco innÄ… informacjÄ™120. Jest to bowiem kombinacja bitowa pewnych flag, które okreÅ›lajÄ… stan kilku ważnych klawiszy na klawiaturze oraz przycisków myszy. Można tam znalezć wartoÅ›ci staÅ‚ych wymienionych w tabeli: staÅ‚a klawisz MK_CONTROL Ctrl MK_SHIFT Shift MK_LBUTTON lewy przycisk myszy MK_MBUTTON Å›rodkowy przycisk myszy MK_RBUTTON prawy przycisk myszy Tabela 42. StaÅ‚e parametru wParam komunikatów myszy, okreÅ›lajÄ…ce wciÅ›niÄ™te przy ich okazji klawisze Jako że sÄ… to flagi bitowe, wParam może mieć ustawionÄ… wiÄ™cej niż jednÄ… takÄ… staÅ‚Ä… naraz. Sprawdzenia, czy jakaÅ› flaga jest tu zawarta, należy dokonywać za pomocÄ… odpowiedniej operacji bitowej: if ((wParam & staÅ‚a) /* != 0 */) { // staÅ‚a jest ustawiona } PrzykÅ‚adowo, aby dowiedzieć siÄ™, czy w momencie zajÅ›cia zdarzenia myszy wciÅ›niÄ™ty byÅ‚ klawisz Shift, trzeba posÅ‚użyć siÄ™ warunkiem: if (wParam & MK_SHIFT) WiÄ™cej informacji o flagach bitowych możesz znalezć w Dodatku B, Reprezentacja danych w pamiÄ™ci. To wszystko, jeżeli chodzi o parametry komunikatów myszy. Teraz wypadaÅ‚oby przyjrzeć siÄ™ bliżej każdemu z tych ważnych zdarzeÅ„. KlikniÄ™cia przycisków Ewolucja komputerowych myszek, jaka nastÄ™powaÅ‚a przez ostatnie dekady, polegaÅ‚a w dużej mierze na dodawania kolejnych przycisków. Pierwsze urzÄ…dzenia tego typu posiadaÅ‚y tylko jeden taki przycisk, pózniej standardem staÅ‚y siÄ™ dwa. Dzisiaj minimalna liczba przycisków, potrzebna dla wygodnej pracy z każdÄ… aplikacjÄ…, to trzy; jednak wiele myszek posiada teraz nawet szerszy ich asortyment, z których wszystkie sÄ… czÄ™sto konfigurowalne. LiczbÄ™ dostÄ™pnych przycisków myszy można pobrać za pomocÄ… wywoÅ‚ania GetSystemMetrics(SM_CMOUSEBUTTONS). Wszystkie wersje Windows szeroko używane w chwili obecnej zapewniajÄ… standardowÄ… obsÅ‚ugÄ™ dla trzech przycisków myszy: 120 W przypadku komunikatu WM_MOUSEWHEEL informacja ta zajmuje tylko mÅ‚odsze sÅ‚owo z wParam (LOWORD(wParam)), gdyż starsze jest przeznaczone na dane o pozycji rolki. Podobnie jest też z trzema komunikatami WM_XBUTTON* w Windows 2000/XP. 426 lewego, używanego zdecydowanie najczęściej. KlikniÄ™cia tym przyciskiem sÄ… standardowÄ… metodÄ… wyboru elementów interfejsu użytkownika, jak na przykÅ‚ad przycisków czy opcji menu prawego, sÅ‚użącego głównie do pokazywania menu podrÄ™cznego (ang. context menu) oraz specjalnych typów przeciÄ…gania (ang. dragging) obiektów Å›rodkowego, którego dziaÅ‚ania jest zwykle zależne od aplikacji. Dla przykÅ‚adu, w programie 3ds max sÅ‚uży on miÄ™dzy innymi do przewijania dÅ‚ugich pasków narzÄ™dzi; fani gry Saper zapewne znajÄ… zastosowanie tego przycisku w ich ulubionej grze Myszy dwuprzyciskowe symulujÄ… Å›rodkowy przycisk za pomocÄ… jednoczesnego wciÅ›niÄ™cia swego lewego i prawego przycisku. Każdy z tych trzech przycisków myszy może z kolei generować trójkÄ™ zwiÄ…zanych ze sobÄ… zdarzeÅ„: wciÅ›niÄ™cie przycisku (ang. button down) zwolnienie przycisku (ang. button up) dwukrotne klikniÄ™cie (ang. double click) Zauważmy, że Windows nie generuje oddzielnego komunikatu dla pojedynczego klikniÄ™cia danym przyciskiem myszy. Takie klikniÄ™cie jest bowiem interpretowane jako dwa zdarzenia: wciÅ›niÄ™cia i zwolnienia przycisku, nastÄ™pujÄ…ce po sobie. Nazwy komunikatów Trzy przyciski i trzy możliwe do wystÄ…pienia akcje& Nie trzeba być specem od matematyki, by wywnioskować, że Å‚Ä…cznie daje to nam 9 komunikatów o zdarzeniach myszy. Każdemu z nich odpowiada oczywiÅ›cie pewna staÅ‚a, której nazwÄ™ można Å‚atwo zbudować wedle nastÄ™pujÄ…cego schematu: WM_przyciskBUTTONakcja Etykiety przycisk i akcja powinny być w nim zastÄ…pione fragmentami nazw, odnoszÄ…cymi siÄ™ do jednego z przycisków oraz do rodzaju wystÄ™powanego zdarzenia. Możliwe warianty w obu tych kwestiach przedstawiajÄ… dwie poniższe tabelki: przycisk akcja przycisk myszy zdarzenie przycisku L DOWN lewy wciÅ›niÄ™cie przycisku M UP Å›rodkowy zwolnienie przycisku R DBLCLK prawy dwukrotne klikniÄ™cie Tabela 43 i 44. Fragmenty nazw komunikatów o zdarzeniach myszy BudujÄ…c z tych informacji wszystkie możliwe nazwy komunikatów, otrzymamy dziewięć odpowiadajÄ…cych im staÅ‚ych: zdarzenie wciÅ›niÄ™cie przycisku zwolnienie przycisku dwukrotne klikniÄ™cie przycisk WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK lewy WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK Å›rodkowy WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCK prawy Tabela 45. Nazwy komunikatów o zdarzeniach myszy Windows 2000 i XP posiada też wbudowanÄ… obsÅ‚ugÄ™ ewentualnych dwóch dodatkowych przycisków myszy, oznaczanych jako X1 i X2. ZwiÄ…zana jest z nimi aczkolwiek tylko trójka stosownych komunikatów (zamiast szeÅ›ciu); obydwa przyciski sÄ… bowiem 427 rozróżnianie przez wartość górnego sÅ‚owa wParam. Wszystkie informacje na temat możesz naturalnie znalezć w MSDN przy opisach komunikatów WM_XBUTTONDOWN, WM_XBUTTONUP i WM_XBUTTONDBLCLK. Poznamy obecnie nieco bliżej wszystkie wymienione tu komunikaty. Pojedyncze klikniÄ™cia Jak już nadmieniÅ‚em, Windows nie wyróżnia żadnego komunikatu do informowania o pojedynczych klikniÄ™ciach przycisku myszy. WysyÅ‚a za to powiadomienia o wciÅ›niÄ™ciu oraz puszczeniu każdego z przycisków. Szczególnie komunikaty o przyciÅ›niÄ™ciach sÄ… dla nas interesujÄ…ce. To wÅ‚aÅ›nie ich używa siÄ™, by reagować na klikniÄ™cia w obszarze klienta okna. WÅ›ród tych zdarzeÅ„ zdecydowanie najczęściej jest z kolei wykorzystywane zawiadomienie WM_LBUTTONDOWN. Jest to bowiem prosta droga reagowania na klikniÄ™cia myszy dotyczÄ…ce okna. Na ten komunikat odpowiadaliÅ›my chociażby w przykÅ‚adowym programie TaskbarHider z poprzedniego rozdziaÅ‚u. NaciÅ›niÄ™cie lewego przycisku myszy powodowaÅ‚o tam pokazywanie lub ukrywanie systemowego paska zadaÅ„. MówiÄ…c na temat komunikatów o wciÅ›niÄ™ciu lub zwolnieniu przycisków myszy trzeba jeszcze zwrócić uwagÄ™ na pewien trudno uchwytny fakt. Otóż wystÄ…pienie WM_?BUTTONDOWN wcale nie musi pociÄ…gać za sobÄ… pózniejszego pojawienia siÄ™ WM_?BUTTONUP. Jeżeli bowiem użytkownik, wcisnÄ…wszy przycisk, przeniesie kursor poza obszar klienta okna programu, wówczas komunikat o puszczeniu przycisku nie trafi do tego okna. Niekiedy bywa to zachowaniem niepożądanym, ale na szczęście Windows oferuje możliwość jego zmiany. Poznamy jÄ… w jednym z nastÄ™pnych paragrafów. Dwukrotne klikniÄ™cia Zdarzenie podwójnego klikniÄ™cia wystÄ™puje wtedy, gdy nastÄ…pi dwukrotne, szybkie wciÅ›niÄ™cie i zwolnienie jednego z przycisków myszy (nie tylko lewego). Musi to nastÄ…pić w odpowiednio krótkim czasie oraz przy stosunkowo niewielkiej lub żadnej zmianie pozycji kursora. Wiele modeli myszek umożliwia też przypisanie akcji dwukrotnego klikniÄ™cia lewym przyciskiem do jednego z dodatkowych przycisków myszy. Windows traktuje takie emulowane klikniÄ™cia identycznie jak normalne, jednak z wiadomych wzglÄ™dów nie stosujÄ… siÄ™ do nich wymienione wyżej ograniczenia. Restrykcyjność tych ograniczeÅ„ można oczywiÅ›cie regulować i dopasować do swoich potrzeb. Maksymalny interwaÅ‚ czasu jest ustawiany w Panelu Sterowania, zaÅ› tolerowane przesuniÄ™cie myszy przy pomocy narzÄ™dza Tweak UI. Oba te parametry systemowe można też zmienić programowo poprzez Windows API - tego również nauczymy siÄ™ w tym podrozdziale. Wróćmy jednak do samych komunikatów o dwukrotnych klikniÄ™ciach. Od razu trzeba powiedzieć na ich temat dwie ważne kwestie. Po pierwsze, żadnemu oknu nie jest bezwarunkowo dane odbieranie tych komunikatów. Być może (mam nadziejÄ™ :D) pamiÄ™tasz, że w grÄ™ wchodzÄ… tu style klasy okna. UÅ›ciÅ›lajÄ…c to stwierdzenie, trzeba powiedzieć, iż: Tylko okna, których klasy zawierajÄ… styl CS_DBLCKLS, odbierajÄ… komunikaty o dwukrotnych klikniÄ™ciach przyciskami myszy. 428 Tak wiÄ™c ażeby reagować na te zdarzenia, należy wpierw ustawić odpowiedni styl klasy okna - na przykÅ‚ad w ten sposób: KlasaOkna.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; Jeżeli bowiem nie zrobimy tego, nasze okno nie otrzyma żadnego z komunikatów WM_?BUTTONDBLCLK. Druga kwestia dotyczy rzeczywistej sekwencji komunikatów, jakie dostaje okno w przypadku wystÄ…pienia dwukrotnego klikniÄ™cia. Nie jest tak, że WM_?BUTTONDBLCLK zastÄ™puje informacje o pojedynczych klikniÄ™ciach, które skÅ‚adajÄ… siÄ™ w sumie na to podwójne. Prawdziwa kolejność komunikatów wyglÄ…da bowiem tak: // nieustawiony styl CS_DBLCLKS // ustawiony styl CS_DBLCLKS WM_?BUTTONDOWN WM_?BUTTONDOWN WM_?BUTTONUP WM_?BUTTONUP WM_?BUTTONDOWN WM_?BUTTONDBLCLK // to ten! :) WM_?BUTTONUP WM_?BUTTONUP Widać, że WM_?BUTTONDBLCLK zastÄ™puje drugi z komunikatów WM_?BUTTONDOWN. Pierwsza notyfikacja o wciÅ›niÄ™ciu przycisku myszy trafia jednak do okna i jest przetwarzana tak, jak zwykÅ‚e pojedyncze klikniÄ™cie. Dopiero potem do okna dociera również WM_?BUTTONDBLCLK, interpretowane jako podwójne naciÅ›niÄ™cie przycisku. Z tego powodu ważne jest, aby kod obsÅ‚ugi dwukrotnego klikniÄ™cia nie byÅ‚ caÅ‚kiem inny od reakcji na pojedyncze wciÅ›niÄ™cie przycisku myszy. Powinien raczej uzupeÅ‚niać jÄ…; dobrym przykÅ‚adem jest tu Eksplorator Windows. W programie tym pojedyncze klikniÄ™cie na ikonÄ™ pliku powoduje jego zaznaczenie, zaÅ› podwójne poleca otwarcie pliku w domyÅ›lnej aplikacji. Akcja otwarcia jest wiÄ™c uzupeÅ‚nieniem akcji zaznaczenia. Komunikaty spoza obszaru klienta DziewiÄ…tka opisanych tu komunikatów oraz WM_MOUSEMOVE, który zostanie omówione za chwilÄ™, powiadamia okno o zdarzeniach myszy, zachodzacych wewnÄ…trz jego obszaru klienta. Takie zdarzenia mogÄ… jednakże zachodzić także poza nim; Windows informuje o nich poprzez dziesięć odmiennych komunikatów121. OdpowiadajÄ… one dokÅ‚adnie każdemu ze zdarzeÅ„ klienckich i majÄ… nawet podobne nazwy. Dodany jest w nich jedynie przedrostek NC, przez co ich staÅ‚e to na przykÅ‚ad WM_NCLBUTTONDOWN czy WM_NCMOUSEMOVE. Ponieważ komunikaty te dotyczÄ… zdarzeÅ„ wystÄ™pujÄ…cych w pozaklienckim obszarze okna, zwykle nie potrzeby pisania kodu reakcji na nie. DomyÅ›lna procedura zdarzeniowa radzi sobie z nimi w standardowy dla Windows sposób, dbajÄ…c np. o to, aby klikniÄ™cie w przycisk powodowaÅ‚o zamkniÄ™cie okna, a przeciÄ…ganie za pasek tytuÅ‚u skutkowaÅ‚o jego przesuwaniem. WtrÄ…canie siÄ™ w ten naturalny ukÅ‚ad prowadzi najczęściej do dezorientacji użytkownika programu i dlatego nie jest szczególnie wskazane. Jeden z pozaklienckich komunikatów myszy nie ma swego odpowiednika w zdarzeniach obszaru klienta. Tym komunikatem jest WM_NCHITTEST. Zdarzenie to jest interesujÄ…ce również z innego powodu. Otóż można je uważać za przyczynek wszystkich pozostaÅ‚ych zdarzeÅ„ myszy. Windows poprzedza nim każdy komunikat o zmianie stanu komputerowego gryzonia, wysyÅ‚ajÄ…c do okna razem z nim także aktualnÄ… pozycjÄ™ kursora. Procedura zdarzeniowa okna analizuje te dane i na ich podstawie stwierdza, którego miejsca okna dotyczy dane zdarzenie myszy. W ten sposób 121 Lub raczej poprzez trzynaÅ›cie komunikatów, jeżeli uwzglÄ™dnić także powiadomienia o stanie dodatkowych prxycisków myszy (WM_[NC]XBUTTON*). 429 rozróżniana jest potrzeba wysÅ‚ania komunikatu klienckiego lub pozaklienckiego, zaÅ› system Windows wie, czy klikniÄ™to np. w pasek tytuÅ‚u czy też wciÅ›niÄ™to przycisk bÄ™dÄ…c już w obszarze klienta okna. Zadanie rozróżniania tych wszystkich możliwoÅ›ci przypada najczęściej domyÅ›lnej procedurze zdarzeniowej DefWindowProc(), jako że zazwyczaj nie zajmujemy siÄ™ komunikatem WM_NCHITTEST. ObsÅ‚użenie go może jednak pozwolić na swego rodzaju oszukanie systemu - tak, by myÅ›laÅ‚ on, że zainstniaÅ‚e zdarzenie (np. klikniÄ™cie) dotyczy innego fragmentu okna niż w rzeczywistoÅ›ci. Typowym zastosowaniem tej techniki jest umożliwienie przesuwania okna poprzez przeciÄ…ganie za jego obszar klienta (a nie tylko za pasek tytuÅ‚u). Prezentuje to przykÅ‚adowy program ClientMove. Jeżeli jednak chcesz napisać kod obsÅ‚ugi tych zdarzeÅ„ i jednoczeÅ›nie nie przeszkadzać systemowi w normalnej reakcji na nie, możesz samodzielnie wywoÅ‚ywać domyÅ›lnÄ… procedurÄ™ DefWindowProc(). PrzykÅ‚adowa reakcja na WM_NCLBUTTONDOWN może wiÄ™c wyglÄ…dać tak: case WM_NCLBUTTONDOWN: { // twój kod return DefWindowProc(hWnd, uMsg, wParam, lParam); } PowinieneÅ› też pamiÄ™tać, że w przypadku komunikatów spoza obszaru klienta współrzÄ™dne kursora podane w lParam sÄ… liczone wzglÄ™dem ekranu, a nie obszaru klienta okna. Ruch myszy NastÄ™pnym z komunikatów myszy, któremu poÅ›wiÄ™cimy swojÄ… uwagÄ™, jest WM_MOUSEMOVE. System Windows wysyÅ‚a go do okna, gdy kursor przelatuje nad jego obszarem klienta - także wtedy, kiedy samo okno jest nieaktywne. Otrzymanie tego zdarzenia wskazuje, że pozycja strzaÅ‚ki myszy ulegÅ‚a jakiejÅ› zmianie. Okno jest informowane o każdej takiej zmianie - nawet, jeÅ›li byÅ‚o to tylko przesuniÄ™cie kursora o jeden jedyny piksel. WM_MOUSEMOVE powiadamia bowiem o ruchu myszy; na to też wskazuje nazwa tego komunikatu. Przy jego przetwarzaniu, bardziej niż w pozostaÅ‚ych zdarzeniach myszy, przydajÄ… siÄ™ dostarczane wraz z nim dane dodatkowe. Szczególnie interesujÄ…ca jest zmienna lParam, zawierajÄ…ca nowÄ… pozycjÄ™ kursora, liczonÄ… wzglÄ™dem lewego górnego rogu obszaru klienta okna. Możemy wyÅ›wietlić te współrzÄ™dne chociażby na pasku tytuÅ‚u: Screen 60. WspółrzÄ™dne kursora na pasku tytuÅ‚u okna // CursorPos - pokazywanie pozycji kursora w oknie // (fragment procedury zdarzeniowej) case WM_MOUSEMOVE: { 430 // pobieramy współrzÄ™dne kursora i zapisujemy je jako napis std::stringstream Strumien; Strumien << "(" << GET_X_LPARAM(lParam) << "; " << GET_Y_LPARAM(lParam) << ")"; // ustawiamy tytuÅ‚ okna na ów napis SetWindowText (hWnd, Strumien.str().c_str()); return 0; } Oprócz współrzÄ™dnych w lParam, komunikat WM_MOUSEMOVE dostarcza też w wParam tych samych informacji o wciÅ›niÄ™tych klawiszach, które omawialiÅ›my na samym poczÄ…tku poznawania zdarzeÅ„ myszy. Po obsÅ‚użeniu zdarzenia WM_MOUSEMOVE zwracamy do systemu tradycyjnÄ… wartość zero. Pozaklienckim odpowiednikiem przedstawionego komunikatu jest oczywiÅ›cie WM_NCMOUSEMOVE. Okno otrzymuje go, kiedy kursor myszy porusza siÄ™ ponad paskiem tytuÅ‚u albo brzegiem okna. Do tego zdarzenia stosujÄ… siÄ™ wszystkie uwagi o pozaklienckich komunikatach myszy, wymienione w poprzednim paragrafie. Szczegółowe wiadomoÅ›ci można jak zwykle znalezć w MSDN. Rolka Od kilku lat wszystkie modele komputerowych myszek sÄ… wyposażane w pewien dodatkowy instrument, uzupeÅ‚niajÄ…cy dziaÅ‚anie przycisków. Jest to tak zwana rolka myszy (ang. mouse wheel), sÅ‚użąca głównie do przewijania dokumentów i stron internetowych. Znajduje siÄ™ ona zwykle w miejscu Å›rodkowego przycisku myszy, zachowujÄ…c jednak jego peÅ‚niÄ… funkcjonalność (można niÄ… klikać tak, jak przyciskiem). Ponadto możliwe jest też obracanie rolkÄ… w przód i w tyÅ‚ - powoduje to najczęściej przewiniÄ™cie oglÄ…danego tekstu w górÄ™ lub w dół. Przydatność i wygoda rolki jest bardzo duża, zwÅ‚aszcza podczas przeglÄ…dania serwisów WWW: nie trzeba wówczas kierować kursora, zajÄ™tego kilkaniem w hiperÅ‚Ä…cza, do pasków przewijania, aby przejść w inne miejsce na stronie. Podobnie w edytorach tekstu rolka uÅ‚atwia i usprawnia pracÄ™. Komunikat rolki i jego adresaci Windows zapewnia współpracÄ™ z rolkÄ… myszy poprzez komunikat WM_MOUSEWHEEL. Jak nietrudno siÄ™ domyÅ›lić, jest on wysyÅ‚any wtedy, gdy użytkownik zmieni pozycjÄ™ gryzoniowego pokrÄ™tÅ‚a. Kto jednak otrzyma ten komunikat?& Sprawa nie jest tak prosta jak w przypadku innych zdarzeÅ„ myszy. KrÄ™cenie rolkÄ… nie jest bowiem zdarzeniem podobnym choćby do wciÅ›niÄ™cia przycisku. W tamtym przypadku komunikat dostawaÅ‚o zawsze to okno, które znajdowaÅ‚o siÄ™ pod kursorem . JednoczeÅ›nie, na co nie zwróciliÅ›my dotÄ…d uwagi, stawaÅ‚o siÄ™ ono oknem aktywnym. Uaktywnienie okna objawia siÄ™ zmianÄ… koloru jego paska tytuÅ‚u, z szarego na (domyÅ›lnie) niebieski. Innym objawem, mniej dostrzegalnym dla normalnych okien (ale widocznym doskonale dla pól tekstowych), jest też przejÄ™cie wejÅ›cia od klawiatury - czyli uzyskanie fokusu (ang. focus). Obecnie nie interesujemy siÄ™ rzecz jasna obsÅ‚ugiwaniem klawiatury, jednak pojÄ™cie fokusu ma znaczenie także dla myszki i jej rolki, ponieważ: Komunikat rolki WM_MOUSEWHEEL otrzymuje tylko to okno, którego w danej chwili posiada fokus. WiedzÄ…c o tym, Å‚atwo wyjaÅ›nić, dlaczego możemy przewijać dokumenty i strony WWW za pomocÄ… rolki także wtedy, gdy wyjedziemy kursorem poza okna ich programów. JeÅ›li jednak klikniemy nastÄ™pnie którymÅ› z przycisków myszy, okno straci fokus, a my 431 możliwość przewijania jego zawartoÅ›ci za pomocÄ… rolki. Możemy jÄ… oczywiÅ›cie przywrócić poprzez ponowne uaktywnienie okna (np. klikniÄ™ciem). ObsÅ‚uga rolki Niektóre kontrolki potomne, jak listy zwykÅ‚e i rozwijalne oraz przewijane pola tekstowe, majÄ… standardowo zapewnionÄ… odpowiedniÄ… reakcjÄ™ na komunikat WM_MOUSEWHEEL. Warto jednak wiedzieć, jak możemy sami na niego reagować. Zacznijmy od parametrów tego komunikatu. W dużym stopniu sÄ… one zbieżne z parametrami zdarzeÅ„ przycisków oraz WM_MOUSEMOVE. IstniejÄ… aczkolwiek pewne drobne różnice. Atoli skoncetrujmy siÄ™ wpierw na podobnieÅ„stwach. Przede wszystkim lParam zawiera doskonale znany nam zestaw dwóch wartoÅ›ci, okreÅ›lajÄ…cych pozycjÄ™ kursora myszki. Możemy je uzyskać za pomocÄ… makr GET_X_LPARAM() i GET_Y_LPARAM() (doÅ‚Ä…czywszy wczeÅ›niej nagłówek windowsx.h). Odmiennie należy traktować wartość wParam - zawiera ona tutaj dwie dane: dolne sÅ‚owo to kombinacja bitowa flag, okreÅ›lajÄ…cych klawisze wciÅ›niÄ™te w chwili zajÅ›cia zdarzenia. ZostaÅ‚a ona przedstawiona na poczÄ…tku tej sekcji, wraz z wielce przydatnÄ… tabelkÄ… odpowiednich staÅ‚ych :) Parametr ten możemy uzyskać przy pomocy makra GET_KEYSTATE_WPARAM() górne sÅ‚owo specyfikuje dystans, o jaki obróciÅ‚a siÄ™ rolka. Pobieramy go poprzez makro GET_WHEEL_DELTA_WPARAM() Zauważmy, że nie ma czegoÅ› takiego jak aktualna pozycja rolki , podobna do bieżącej pozycji kursora myszki. Obrót rolki nie jest bowiem ograniczony żadnÄ… skalÄ… i może dokonywać siÄ™ w obu kierunkach bez żadnych ograniczeÅ„. GET_WHEEL_DELTA_WPARAM(wParam) jest wiÄ™c miarÄ… obrotu, jakiego dokonaÅ‚ palec użytkownika, poruszajÄ…cy rolkÄ…. Wyraża siÄ™ on liczbÄ… caÅ‚kowitÄ… ze znakiem: dodatnie wartoÅ›ci oznaczajÄ… obrót naprzód (w kierunku od użytkownika ), powodujÄ…cy zazwyczaj przewijanie ekranu do góry; wartoÅ›ci ujemne odpowiadajÄ… obrotowi w tyÅ‚ ( do użytkownika ) i przewijaniu tekstów w dół. Sama wartość jest natomiast wielokrotnoÅ›ciÄ… staÅ‚ej WHEEL_DELTA, ustawionej na 120. Liczba ta odpowiada jednej elementarnej akcji (krokowi), jakÄ… ma powodować obrót rolki - przykÅ‚adowo, może to być przewiniÄ™cie tekstu o okreÅ›lonÄ… liczbÄ™ linii (zwykle trzy122). WHEEL_DELTA nie jest równe jednoÅ›ci, aby stanowić furtkÄ™ dla możliwych przyszÅ‚ych urzÄ…dzeÅ„, wyposażonych w bardziej dokÅ‚adne rolki. Wtedy wartość zapisana w górnym sÅ‚owiem wParam nie bÄ™dzie musiaÅ‚a być koniecznie caÅ‚kowitÄ… wielokrotnoÅ›ciÄ… delty, lecz mogÅ‚a wynosić, powiedzmy, 40. Taka liczba powinna wiÄ™c spowodować wykonanie jednej trzeciej akcji przewidzianej na caÅ‚Ä… deltÄ™ - w opisywanym przypadku bÄ™dzie to przewiniÄ™cie tekstu o jednÄ… linijkÄ™. Już teraz pojawiajÄ… siÄ™ myszki, umożliwiajÄ…ce w miarÄ™ pÅ‚ynne przewijanie, zatem należy być przygotowanym na odbieranie zdarzeÅ„ obrotu rolki o mniej niż jednÄ… deltÄ™. W idealnym przypadku powinny one skutkować podjÄ™ciem wÅ‚aÅ›ciwego, uÅ‚amkowego dziaÅ‚ania. Jeżeli jednak nie jest to możliwe, wtedy najlepiej dodawać przychodzÄ…ce dane o obrocie i wykonywać akcjÄ™ dopiero wtedy, gdy tak powstaÅ‚a suma osiÄ…gnie wartość co najmniej WHEEL_DELTA: 122 Ilość przewijanych za jednym razem linii jest ustawieniem systemowym i należy je pobierać za pomocÄ… wywoÅ‚ania SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &nPrzewijaneLinie, 0);, gdzie nPrzewijaneLinie jest zmiennÄ… typu caÅ‚kowitego. Aby zaÅ› obliczyć liczbÄ™ wierszy przewijanym w reakcji na WM_MOUSEWHEEL, trzeba przemnożyć pobranÄ… wielkość przez liczbÄ™ wielokrotnoÅ›ci WHEEL_DELTA w parametrze zdarzenia, tj.: float fLinie = (float) GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA * nPrzewijanieLinie;. 432 // zmienna globalna przechowujÄ…ca obrót rolki int g_nCalkowityObrot = 0; // (procedura zdarzeniowa) case WM_MOUSEWHEEL: { // dodajemy otrzymanÄ… wartość obrotu g_nCalkowityObrot += GET_WHEEL_DELTA_WPARAM(wParam); // sprawdzamy, czy jest on bezwglÄ™dnie wiÄ™kszy niż WHEEL_DELTA if (abs(g_nCalkowityObrot) >= WHEEL_DELTA) { // dla pewnoÅ›ci obliczamy ilość kroków - // - wielokrotnoÅ›ci WHEEL_DELTA int nKroki = g_nCalkowityObrot / WHEEL_DELTA; // podejmujemy odpowiednie akcje... // odejmujemy wykorzystane obroty od licznika // (ustawiajÄ…c go na resztÄ™ z dzielenia przez WHEEL_DELTA) g_nCalkowityObrot %= WHEEL_DELTA; } // tradycyjnie zwracamy zero return 0; } Można siÄ™ spodziewać, że wraz z upowszechnieniem myszek z pÅ‚ynnie obracajÄ…cymi siÄ™ rolkami coraz wiÄ™cej programów bÄ™dzie oferowaÅ‚o ciÄ…gÅ‚e, a nie tylko skwantowane przewijanie dokumentów. Aapanie myszy Tyle okien, a tylko jedna myszka& - tak mógÅ‚by jÄ™knąć spersonifikowany system Windows, gdy umiaÅ‚ mówić. Programy komputerowe jako twory martwe nie wyrażajÄ… jednak swoich opinii i dlatego Windows musi potulnie i sprawnie radzić sobie z problemem współdzielenia jednego urzÄ…dzenia miÄ™dzy wiele aplikacji. WÅ‚adza nad myszkÄ… CaÅ‚y mechanizm odbierania zdarzeÅ„ od myszki opiera siÄ™ na prostej zasadzie. Mówi ona, że dany komunikat (np. o klikniÄ™ciu) zostanie wysÅ‚any zawsze do tego okna, nad którym aktualnie przebywa kursor myszki. W ten sposób różne okna w systemie dostajÄ… informacje tylko o tych zdarzeniach, które bezpoÅ›rednio ich dotyczÄ…. IstniejÄ… jednak sytuacje, w których jedno okno powinno otrzymywać wszystkie komunikaty o zdarzeniach myszki. W takim przypadku powinno ono przejąć od systemu wÅ‚adzÄ™ nad myszkÄ…. Okno posiadajÄ…ce wÅ‚adzÄ™ nad myszkÄ… (ang. mouse capture) otrzymuje informacje o wszystkich zdarzeniach, pochodzÄ…cych od urzÄ…dzenia wskazujÄ…cego. W normalnej sytuacji myszka jest wolna - żadne okno nie posiada nad niÄ… wÅ‚adzy. Gdy chcemy to zmienić, musimy posÅ‚użyć siÄ™ odpowiednimi funkcjami Windows API. PrzykÅ‚ad przechwycenia myszki Zobaczmy to na klasycznym już przykÅ‚adzie okienkowego szkicownika (ang. scribble). Jest to prosty program, pozwalajÄ…cy rysować szlaczki i inne zawijasy w swoim oknie: 433 Screen 61. Okno komputerowego szkicownika Linie kreÅ›limy w nim poprzez klikniÄ™cie lewym przyciskiem myszy, przytrzymanie go i poruszanie kursorem. Taki programik pomaga poczÄ…tkujÄ…cym użytkownikom komputera nabrać wprawy w przeciÄ…ganiu. My oczywiÅ›cie nie potrzebujemy żadnych ćwiczeÅ„ tego typu i dlatego spojrzymy raczej na kod tej przykÅ‚adowej aplikacji: // Scribble - okienkowy szkicownik #include #define WIN32_LEAN_AND_MEAN #include #include // nazwa klasy okna std::string g_strKlasaOkna = "od0dogk_Window"; // dane okna HDC g_hdcOkno; // uchwyt kontekstu urzÄ…dzenia okna // ------------------- procedura zdarzeniowa okna ------------------------ LRESULT CALLBACK WindowEventProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_LBUTTONDOWN: // przejmujemy myszkÄ™ SetCapture (hWnd); // przesuwamy pióro (sÅ‚użące do rysowania po oknie) // w punkt klikniÄ™cia MoveToEx (g_hdcOkno, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), NULL); // zwracamy zero return 0; case WM_MOUSEMOVE: // jeżeli nasze okno posiada myszkÄ™ if (GetCapture() == hWnd) // rysujemy linie od poprzedniego do aktualnego // miejsca kursora myszki LineTo (g_hdcOkno, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); // zwracamy zero return 0; case WM_LBUTTONUP: // oddajemy wÅ‚adzÄ™ nad myszkÄ… do systemu 434 ReleaseCapture(); return 0; //------------------------------------------------------------- case WM_DESTROY: // koÅ„czymy program PostQuitMessage (0); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } // ------------------------funkcja WinMain() ---------------------------- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { /* rejestrujemy klasÄ™ okna */ WNDCLASSEX KlasaOkna; // wypeÅ‚niamy strukturÄ™ WNDCLASSEX // (pomijamy z tego wiÄ™kszość pól) KlasaOkna.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); KlasaOkna.style = CS_OWNDC; // wÅ‚asny kontekst urzÄ…dzenia okna // rejestrujemy klasÄ™ okna RegisterClassEx (&KlasaOkna); /* tworzymy okno */ // tworzymy okno funkcjÄ… CreateWindowEx // (znana czynność, wiÄ™c pomijamy (uchwyt trafia do hOkno)) // pobieramy uchwyt do kontekstu urzÄ…dzenia obszaru klienta okna g_hdcOkno = GetDC(hOkno); // pokazujemy nasze okno ShowWindow (hOkno, nCmdShow); /* pÄ™tla komunikatów */ // (w zwyczajowej formie, darujemy jÄ… sobie) // zwracamy kod wyjÅ›cia return static_cast(msgKomunikat.wParam); } Ogólna zasada dziaÅ‚ania tej aplikacji jest prosta. W momencie wciÅ›niÄ™cia lewego przycisku myszy (WM_LBUTTONDOWN) przejmuje ona wÅ‚adzÄ™ nad myszkÄ…, ustawiajÄ…c jÄ… dla swego okna: SetCapture (hWnd): OdtÄ…d bÄ™dzie ono otrzymywaÅ‚o informacje o wszystkich zdarzeniach myszki. Zanim jednak zajmiemy siÄ™ nimi, musimy zapamiÄ™tać pozycjÄ™ kursora w chwili klikniÄ™cia - tak, aby móc potem rysować Å›lad jego ruchu. WyrÄ™cza nas w tym sam Windows: 435 MoveToEx (g_hdcOkno, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), NULL); Funkcja MoveToEx() przesuwa tzw. pióro, zwiÄ…zane z kontekstem urzÄ…dzenia naszego okna (g_hdcOkno) w miejsce o współrzednych klikniÄ™cia. Koordynaty te pobieramy naturalnie za pomocÄ… makr GET_X/Y_LPARAM(). Każda linia, jakÄ… teraz narysujemy w oknie, bÄ™dzie siÄ™ zaczynaÅ‚a we wskazanym przed chwilÄ… punkcie. A kiedyż to rysujemy linie w naszym oknie? Otóż robimy to w reakcji na zdarzenie WM_MOUSEMOVE: case WM_MOUSEMOVE: if (GetCapture() == hWnd) LineTo (g_hdcOkno, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return 0; WczeÅ›niej sprawdzamy jeszcze, czy główne (i notabene jedyne) okno programu posiada istotnie wÅ‚adzÄ™ nad myszkÄ…. Dokonujemy tego przy pomocy funkcji GetCapture(); jeżeli zwracany przezeÅ„ uchwyt jest zgodny z uchwytem docelowego okna zdarzenia WM_MOUSEMOVE, wtedy czujemy siÄ™ zobowiÄ…zani do narysowania linii znaczÄ…cej drogÄ™ kursora. Zamiast GetCapture() moglibyÅ›my wykorzystać też zmiennÄ… logicznÄ…, okreÅ›lajÄ…cÄ… czy okno programu przechwyciÅ‚o myszkÄ™. UstawialibyÅ›my jÄ… na true w reakcji na WM_LBUTTONDOWN i na false w WM_LBUTTONUP, a tutaj dokonywalibyÅ›my sprawdzenia jej wartoÅ›ci. WykorzystaÅ‚em jednak GetCapture(), aby pokazać wszystkie funkcje zwiÄ…zane z zagadnieniem wÅ‚adzy nad myszkÄ…. LiniÄ™ te rysujemy poprzez LineTo(), podajÄ…c tej funkcji docelowe współrzÄ™dne drugiego koÅ„ca odcinka. Oprócz kreÅ›lenia rzeczonej linii, funkcja ta dokonuje też przesuniÄ™cia pióra w owe miejsce, tak wiÄ™c nastÄ™pne rysowane odcinki bÄ™dÄ… siÄ™ Å‚Ä…czyÅ‚y z poprzednimi. Tym sposobem powstanie ciÄ…gÅ‚y Å›lad drogi kursora myszki, a o to nam przecież chodzi. O funkcjach MoveToEx() oraz LineTo() pomówimy sobie dokÅ‚adnie przy omawianiu geometrycznej części biblioteki Windows GDI w nastÄ™pnym rozdziale. Wreszcie dochodzimy do komunikatu WM_LBUTTONUP, oznaczajÄ…cego zwolnienie lewego przycisku myszki. W odpowiedzi na niego wykonujemy tylko jednÄ… czynność: oddajemy wÅ‚adzÄ™ nad myszkÄ… z powrotem do systemu, wywoÅ‚ujÄ…c funkcjÄ™ ReleaseCapture(). Od tej pory notyfikacje o zdarzeniach myszy bÄ™dÄ…, jak zwykle, trafiać do okna mieszczÄ…cego siÄ™ pod kursorem myszy, a nie do naszego programu. Nasuwa siÄ™ jeszcze pytanie: Co wÅ‚aÅ›ciwie czyni ta kombinacja funkcja SetCapture() i ReleaseCapture()? Czy nie można byÅ‚oby obejść siÄ™ bez niej?& Teoretycznie jest to możliwe123, jednak niesie pewnie nieprzyjemne konsekwencje praktyczne. Wyobrazmy sobie, że użytkownik wciska lewy przycisk myszki, a nastÄ™pnie przeciÄ…ga kursor poza obrÄ™b okna i zwalnia przycisk. Kiedy teraz powróci z powrotem w obszar okna programu, kursor bÄ™dzie kreÅ›liÅ‚ sobÄ… linie - mimo że przecież przycisk myszki nie jest wciÅ›niÄ™ty! Dzieje siÄ™ tak dlatego, że po przeciÄ…gniÄ™ciu kursora poza okno, komunikat WM_LBUTTONUP nie dociera już do naszego programu. Ten myÅ›li wiÄ™c, że lewy przycisk jest nadal 123 O ile dodamy jeszcze wspomnianÄ… kilka akapitów wyżej zmiennÄ… logicznÄ…, która bÄ™dzie okreÅ›laÅ‚a, czy należy rysować Å›lad kursora. 436 wciÅ›niÄ™ty, a zatem rysuje linie w Å›lad za strzaÅ‚kÄ…. DziÄ™ki przechwytywaniu wÅ‚adzy nad myszkÄ… zapobiegamy podobnej sytuacji. Zastosowania Przejmowanie wÅ‚adzy nad myszkÄ… ma sporo zastosowaÅ„ przede wszystkich w różnych programach graficznych, choćby tak prostych jak zaprezentowany przykÅ‚ad. Nie dotyczy to tylko swobodnego rysowania, ale też wyznacza linii prostych, krzywych Beziera; nawet aplikacje do trójwymiarowego modelowania korzystajÄ… z tej techniki. Innym zastosowaniem jest też implementowanie specyficznego rodzaju przeciÄ…gania jakichÅ› elementów. Kontrolowanie wejÅ›cia od myszy Przyjmowanie komunikatów o zdarzeniach to nie jedyna forma kooperacji z myszkÄ…, dostÄ™pna w Windows. W tej sekcji poznamy wiÄ™kszość pozostaÅ‚ych, które dajÄ… peÅ‚en obraz możliwoÅ›ci WinAPI w zakresie obsÅ‚ugi urzÄ…dzeÅ„ wskazujÄ…cych. Pozycja kursora MyszkÄ™ na ekranie monitora reprezentuje kursor, majÄ…cy zwykle postać strzaÅ‚ki. Znajduje siÄ™ on w okreÅ›lonej pozycji, wyrażonej we współrzÄ™dnych ekranowych. PozycjÄ™ tÄ™ otrzymujemy ze wszystkimi komunikatami o zdarzeniach myszy124. Możemy też na niÄ… wpÅ‚ywać w inny sposób niż tylko poprzez bezpoÅ›rednie poruszanie gryzoniem. Spójrzmy wiÄ™c, jak to siÄ™ odbywa. Pobieranie i ustawianie pozycji kursora Aktualne współrzedne kursora, oprócz tego że dostajemy w lParam każdego zdarzenia myszy, możemy pobrać za pomocÄ… funkcji GetCursorPos(): BOOL GetCursorPos(LPPOINT lpPoint); Podajemy jej wskaznik do prostej struktury typu POINT, posiadajÄ…cej dwa pola x i y. Z nich też odczytujemy żądanÄ… pozycjÄ™ strzaÅ‚ki. JedynÄ… różnicÄ… w stosunku do danych otrzymywanych przy okazji zdarzeÅ„ jest to, iż: GetCursorPos() zwraca ekranowe współrzÄ™dne kursora. Jak zaÅ› pamiÄ™tamy, lParam komunikatów myszy zawiera pozycjÄ™ kursora relatywnÄ… do lewego górnego rogu obszaru klienta okna. A co z ustawianiem pozycji kursora? SÅ‚uży do tego funkcja SetCursorPos(): BOOL SetCursorPos(int X, int Y); Aatwo można siÄ™ domyÅ›lić, że podajemy jej nowe współrzÄ™dne dla kursora myszy w obu parametrach. SÄ… to również koordynaty ekranowe, zatem wywoÅ‚anie w postaci: SetCursorPos (0, 0); Przesunie strzaÅ‚kÄ™ do lewego górnego skraju ekranu (pulpitu) - co byÅ‚o do okazania ;) 124 Przy czym jest to pozycja liczona wzglÄ™dem obszaru klienta okna-adresata komunikatu. 437 (Bez)wzglÄ™dne współrzÄ™dne Dwie metody liczenia współrzÄ™dnych kursora (i nie tylko kursora) mogÄ… być trochÄ™ kÅ‚opotliwe - szczególnie, jeżeli nie byÅ‚oby prostego sposobu konwersji miÄ™dzy nimi. Taki sposób jednak istnieje i stanowiÄ… go niniejsze dwie funkcje: BOOL ClientToScreen(HWND hWnd, LPPOINT lpPoint); BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint); Ich przeznaczenie dobrze obrazujÄ… nazwy. ClientToScreen() zamienia współrzÄ™dne liczone wzglÄ™dem obszaru klienta okna na koordynaty ekranowe. Należy podać jej uchwyt okna (hWnd) oraz rzeczone współrzÄ™dne w postaci adresu struktury POINT. StamtÄ…d też odczytamy nowe współrzedne (ekranowe) po wykonaniu funkcji. Odwrotnie dziaÅ‚a ScreenToClient(). Tutaj podajemy jej liczby odnoszÄ…ce siÄ™ do ekranu, a w zamian dostajemy koordynaty tyczÄ…ce siÄ™ obszaru klienta okna o uchwycie hWnd. Ogólnie wiÄ™c zapamiÄ™tajmy, że: ClientToScreen() dokonuje konwersji typu obszar klienta ekran. ScreenToClient() zamienia współrzÄ™dne wedle schematu ekran obszar klienta. Te dwie funkcje przydajÄ… siÄ™ w wielu typowych i nietypowych sytuacjach programistycznych. Ograniczanie swobody w poruszaniu kursorem Inicjalnie kursor posiada nieograniczonÄ… swobodÄ™ w poruszaniu siÄ™ po caÅ‚ym ekranie. Jeżeli z jakichÅ› wzglÄ™dów nie odpowiada to nam, możemy ograniczyć do wybranego prostokÄ…ta rejon ekranu, który bÄ™dzie dla myszy dostÄ™pny. Czynimy to za pomoca funkcji ClipCursor(): BOOL ClipCursor(const RECT* lpRect); Jako parametru żąda ona wskaznika do struktury RECT, opisujÄ…cej tenże prostokÄ…t, ograniczajÄ…cy kursor. SkÄ…d go wezmiemy - to już nasza sprawa: może być to np. prostokÄ…t okna naszego programu: RECT rcOkno; GetWindowRect (hWnd, &rcOkno); ClipCursor (&rcOkno); UruchamiajÄ…c powyższy kod sprawimy, iż użytkownik nie bÄ™dzie w stanie wyjechać kursorem poza obrÄ™b okna aplikacji. Takie zachowanie ogranicza wiÄ™c wygodÄ™ korzystania z aplikacji i systemu operacyjnego, zatem powinno być stosowane jedynie w uzasadnionych przypadkach. Zawsze też należy pamiÄ™tać o uwolnieniu kursora, gdy bÄ™dzie to już możliwe: ClipCursor (NULL); Przekazanie NULL do funkcji ClipCursor() spowoduje rozciÄ…gniÄ™cie rejonu dostÄ™pnego dla myszy na caÅ‚y ekran. BÄ™dzie to wiÄ™c powrót do stanu poczÄ…tkowego. Sprawdzanie przycisków myszy O wciÅ›niÄ™ciu i zwolnieniu przycisków myszy informujÄ… nas zdarzenia WM_?BUTTONDOWN/UP. O aktualnym stanie tychże przycisków możemy też dowiedzieć siÄ™ podczas przetwarzania 438 któregokolwiek z klienckim komunikatów myszy - wystarczy odczytać wartość wParam125 tego komunikatu i porównać jÄ… z odpowiedniÄ… flagÄ… bitowÄ… (jednÄ… ze staÅ‚ych MK_). Jako elastyczny system operacyjny Windows oferuje jednak także inne sposoby na pozyskanie bieżącej kondyncji przycisków myszy - czyli informacji o ich wciÅ›niÄ™ciu. SÅ‚użą do tego na przykÅ‚ad funkcje GetKeyState() i GetAsyncKeyState(). Kody wirtualne przycisków myszy Nazwy tych dwóch funkcji sugerujÄ…, że ich zasadniczym przeznaczeniem jest kontrola stanu klawiszy na klawiaturze. To faktycznie prawda, jednak w Windows API pod pojÄ™ciem klawisz (ang. key) kryjÄ… siÄ™ także przyciski wÅ‚aÅ›ciwe innym urzÄ…dzeniom wejÅ›ciowym - na przykÅ‚ad myszce. AÄ…cznie nazywa siÄ™ je klawiszami wirtualnymi (ang. virtual-keys). Takie podejÅ›cie może siÄ™ wydawać dziwne, ale w praktyce jest bardzo wygodne. Każdemu klawiszowi (cokolwiek to sÅ‚owo chwilowo znaczy& ) przyporzÄ…dkowany jest pewien kod (ang. virtual-key code), który go jednoznacznie identyfikuje za pomocÄ… staÅ‚ej o nazwie zaczynajÄ…cej siÄ™ przedrostkiem VK_. Nas oczywiÅ›cie interesujÄ… teraz tylko te kody, za którymi kryjÄ… siÄ™ przyciski myszy. Przedstawia je poniższa tabelka: staÅ‚a wartość przycisk VK_LBUTTON 0x0001 lewy VK_RBUTTON 0x0002 prawy VK_MBUTTON 0x0004 Å›rodkowy VK_XBUTTON1 0x0005 pierwszy dodatkowy (X1) VK_XBUTTON2 0x0006 drugi dodatkowy (X2) Tabela 46. Kody wirtualne przycisków myszy (dwa ostatnie przyciski sÄ… dostÄ™pne tylko w Windows 2000/XP lub nowszych) Zerknijmy teraz na funkcje Get[Async]KeyState() i zobaczmy, jak mogÄ… nam one pomóc w pozyskiwaniu stanu przycisków myszy. Kontrola stanu przycisków myszy Omawiane dwie funkcje sÄ… na tyle do siebie podobne, że możemy je rozpatrywać Å‚Ä…cznie - również pod wzglÄ™dem prototypów: SHORT Get[Async]KeyState(int nKey); Widzimy, że funkcje te żądajÄ… jednego parametru. Jest nim kod wirtualnego klawisza, który ma być sprawdzany; u nas bÄ™dzie to rzecz jasna jedna z piÄ™ciu staÅ‚ych wÅ‚aÅ›ciwych przyciskom myszy. Co zaÅ› otrzymujemy w zamian? Otóż dostajemy wartość 16-bitowÄ…, która Å‚Ä…cznie niesie w sobie aż dwie dane. Poznamy je obie przy omawianiu obsÅ‚ugi klawiatury, a teraz skoncetrujemy siÄ™ na ważniejszej z nich, zawartej w starszym bajcie sÅ‚owa zwracanego przez Get[Async]KeyState(). Jak nietrudno zgadnąć, mam tu na myÅ›li pożądanÄ… przez caÅ‚y czas informacjÄ™ o tym, czy dany przycisk myszy jest w aktualnej chwili wciÅ›niÄ™ty, czy też nie. Sprawdzić można to w prosty sposób: należy ustalić, czy starszy bajt wyniku jest liczbÄ… różnÄ… od zera. JeÅ›li tak, znaczy to, iż kontrolowany przycisk jest w danym momencie wciÅ›niÄ™ty. Aby wiÄ™c skontrolować stan lewego przycisku myszy, można użyć wywoÅ‚ania podobnego do poniższego: 125 W przypadku WM_MOUSEWHEEL jest to dolne sÅ‚owo wParam, uzyskiwane poprzez GET_KEYSTATE_WPARAM(). 439 if (HIBYTE(Get[Async]KeyState(VK_LBUTTON)) /* != 0 */) { // lewy przycisk myszy jest aktualnie wciÅ›niÄ™ty } Sprawa wyglÄ…da identycznie dla czterech pozostaÅ‚ych przycisków. Różnica maÅ‚a, lecz ważna WypadaÅ‚oby teraz rozróżnić wreszcie funkcje GetKeyState() i GetAsyncKeyState(). PeÅ‚nego rozgraniczenia tych dwóch procedur dokonamy wtedy, gdy poznamy je caÅ‚kowicie - stanie siÄ™ to przy okazji poznawania zagadnieÅ„ zwiÄ…zanych z klawiaturÄ… z WinAPI. Obecnie skupimy siÄ™ na jednym niuansie, dotyczÄ…cym przycisków myszy. Chodzi o to, iż Windows oferuje pewne przydatne udogodnienie dla osób leworÄ™cznych. Użytkownicy posÅ‚ugujÄ…cy siÄ™ odmiennÄ… koÅ„czynÄ… niż pozostali chcÄ… bowiem trzymać mysz raczej po lewej stronie biurka, w lewej dÅ‚oni. Wówczas pod palcem wskazujÄ…cym znajdzie siÄ™ nie lewy, lecz prawy przycisk myszy; analogicznie palec serdeczny spocznie na lewym przycisku myszy, który dla użytkownika-maÅ„kuta wydaje siÄ™ prawym. Nie jest to przy tym problemem, ponieważ Windows daje możliwość dostosowania siÄ™ do tej sytuacji. Polega ona na zamianie zwyczajowego znaczenia lewego i prawego przycisku na wzajemnie odwrotne. Opcja taka może być ustawiona na przykÅ‚ad w systemowym Panelu Sterowania. Jak to jednak czesto bywa, uÅ‚atwienia dla użytkownika sÄ… utrudnieniami dla programisty. Fakt, że lewy przycisk myszy może w pewnych sytuacjach odpowiadać prawemu i odwrotne, wprowadza trochÄ™ zamieszania. Ale przecież nie z takimi rzeczami radziliÅ›my sobie wczeÅ›nie, prawda? :) Na poczÄ…tek dodajmy do naszego sÅ‚ownika dwa przydatne okreÅ›lenia: fizycznych i logicznych przycisków myszy. Fizyczne przyciski myszy (ang. physical mouse buttons) to przyciski umieszczone na urzÄ…dzeniu wskazujÄ…cym (zwykle myszy). Logiczne przyciski myszy (ang. logical mouse buttons) to systemowa interpretacja fizycznych przycisków myszy. Przyciski fizyczne sÄ… dosÅ‚ownie namacalne - możemy ich dotknąć i je wciskać. Poza tym powinniÅ›my zwrócić uwagÄ™ na pozornie oczywisty fakt: fizyczne przyciski zawsze pozostajÄ… sobÄ… - lewy przycisk jest zawsze lewym, a prawy prawym. Inaczej jest w przypadku przycisków logicznych. W wiÄ™kszoÅ›ci przypadków bÄ™dÄ… one odpowiadaÅ‚y swym fizycznym braciom& z wyjÄ…tkiem jednego wyjÄ…tku :) DomyÅ›lasz siÄ™, że tÄ… nietypowÄ… sytuacjÄ… jest wÅ‚Ä…czona opcja zamiany przycisków. Wtedy też przyciski myszy sÄ… interpretowane na opak : Schemat 42. Mapowanie fizycznych przycisków myszy na logiczne 440 No dobrze, ale jak ta sytuacja ma siÄ™ do odbierania przez system Windows zdarzeÅ„ od myszy oraz do bezpoÅ›redniego pobierania jej stanu?& Otóż prawie zawsze liczÄ… siÄ™ tu wyÅ‚Ä…cznie logiczne przyciski myszy. Niemal wszystkie elementy Windows API przeznaczone do pracy z przyciskami myszy operujÄ… na logicznych przyciskach. Nieprzypadkowo zaznaczyÅ‚em to drobne słówko - niemal . Istnieje bowiem jedna funkcja, która odczytuje stan wyÅ‚Ä…cznie fizycznych przycisków myszy - jest niÄ… GetAsyncKeyState(). GetKeyState() pobiera stan logicznych przycisków myszy. GetAsyncKeyState() pobiera stan fizycznych przycisków myszy. I to jest wÅ‚aÅ›nie ta różnica, na którÄ… chciaÅ‚em zwrócić uwagÄ™. Wynika z niej, że dwa poniższe wywoÅ‚ania mogÄ… w istocie sprawdzać fizycznie odmienne przyciski: GetKeyState(VK_LBUTTON) GetAsyncKeyState(VK_LBUTTON) Zależy to od ustawienia systemowego, wprowadzanego w Panelu Sterowania. Programowo możemy je odczytać poprzez GetSystemMetrics(SM_SWAPBUTTON): // sprawdzenie stanu lewego przycisku i opcji zamiany przycisków... if (HIBYTE(GetAsyncKeyState(VK_LBUTTON)) && GetSystemMetrics(SM_SWAPBUTTON)) { // fizycznie wciÅ›niÄ™to lewy przycisk, ale ze wzglÄ™du na ustawionÄ… // należy go zinterpretować jako prawy } Powyższy kod odpowiada z grubsza (bo nie do koÅ„ca, o czym powiemy pózniej) prostszej instrukcji z użyciem GetKeyState(): if (HIBYTE(GetKeyState(VK_LBUTTON))) { // wciÅ›niÄ™to logicznie lewy przycisk } Jest ona także bardziej przejrzysta, lecz aby jÄ… wÅ‚aÅ›ciwie stosować, trzeba dowiedzieć siÄ™ nieco wiÄ™cej o kwestiach różniÄ…cych funkcje GetKeyState() i GetAsyncKeyState() w odniesieniu do wszystkich klawiszy wirtualnych. Uczynimy to w podrozdziale na temat klawiatury w Windows. O pobieraniu ustawieÅ„ myszy, takich jak przytoczona tu zamiana przycisków, powiemy sobie natomiast w jednym z najbliższych paragrafów. Symulowanie zdarzeÅ„ myszy Normalnie zadaniem programu okienkowego jest reakcja na czynnoÅ›ci wykonywane przez użytkownika. Wiążę siÄ™ to z odbieraniem i obsÅ‚ugÄ… komunikatów systemowych. Komunikaty te generuje poÅ›rednio osoba korzystajÄ…ca z aplikacji; czyni to za pomocÄ… urzÄ…dzeÅ„ wejÅ›ciowych. Także sam program może postawić siÄ™ w tej roli i symulować wystÄ™powanie odpowiednich zdarzeÅ„. Najprostszym sposobem zdawaÅ‚oby siÄ™ bezpoÅ›rednie wysyÅ‚anie komunikatów o zdarzeniach poprzez funkcjÄ™ SendMessage() lub PostMessage(). 441 Jednakże tÄ… drogÄ… bÄ™dziemy emulować jedynie skutek, a nie przyczynÄ™ wystÄ™powania pewnych zdarzeÅ„. Jest to tylko udawanie systemowej interpretacji danych od urzÄ…dzeÅ„, nie zaÅ› danych jako takich. Nie bez znaczenia jest też fakt, że z wysyÅ‚anym komunikatem Å‚Ä…czy siÄ™ wiele pobocznych aspektów, którymi zwykle siÄ™ nie zajmujemy, lecz które mogÄ… okazać siÄ™ ważne (np. kwestia wÄ…tków). Wreszcie, komunikaty muszÄ… być skierowane do konkretnego okna, majÄ…cego je otrzymać, a przecież wiemy, że Windows zwykÅ‚ sam o tym decydować (przykÅ‚adem jest klikniÄ™cie myszkÄ…: w zależnoÅ›ci od pozycji kursora komunikat o tym zdarzeniu mogÄ… dostać zupeÅ‚nie różne okna). Samodzielne produkowanie zdarzeÅ„ nie jest wiÄ™c dobrym rozwiÄ…zaniem. ByÅ‚oby lepiej, gdyby to system oferowaÅ‚ jakiÅ› wÅ‚asny sposób udawania sygnałów od myszki czy klawiatury. I tak siÄ™ przypadkowo skÅ‚ada, iż podobny mechanizm faktycznie istnieje :D Poznamy go teraz, zajmujÄ…c siÄ™ programowym symulowaniem myszki. Funkcja SendInput() W starszych wersjach Windows do generowania zdarzeÅ„ myszy sÅ‚użyÅ‚a funkcja mouse_event(). PoczÄ…wszy od Windows 98 zalecane jest jednak użycie innej funkcji126 - SendInput(): UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize); Nie wyglÄ…da ona na zbyt zÅ‚ożonÄ…, przyjrzyjmy siÄ™ wiÄ™c jej parametrom: typy parametry opis Te dwa argumenty okreÅ›lajÄ… tablicÄ™ struktur typu INPUT, która zostanie przekazana do funkcji. nInputs zawiera liczbÄ™ UINT nInputs elementów tej tablicy, zaÅ› pInputs - wskaznik do niej. Każdy LPINPUT pInputs element jest natomiast oddzielnÄ… strukturÄ…, opisujÄ…cÄ… jedno symulowane zdarzenie myszy lub klawiatury. Musimy podać tutaj rozmiar typu INPUT w bajtach, czyli po int cbSize prostu sizeof(INPUT). Tabela 47. Parametry funkcji SendInput() Widać, że funkcja ta potrafi wygenerować naraz wiÄ™cej niż jedno zdarzenie od urzÄ…dzenia wejÅ›ciowego, ponieważ pobiera ona tablicÄ™ struktur INPUT. NastÄ™pnie przetwarza jÄ… element po element, zwracajÄ…c w wyniku liczbÄ™ poprawnie zasymulowanych zdarzeÅ„. Struktura INPUT Punkt ciężkoÅ›ci zagadnienia przesuwa siÄ™ nam z funkcji SendInput() na strukturÄ™ INPUT. Spójrzmy zatem na definicjÄ™ tego typu: struct INPUT { DWORD type; union { MOUSEINPUT mi; KEYBDINPUT ki; HARDWAREINPUT hi; 126 Funkcja ta zastÄ™puje również keybd_event(), sÅ‚użącÄ… do symulowania klawiatury. Jak to robi - o tym napiszÄ™ w nastÄ™pnym podrozdziale. 442 }; }; Różni siÄ™ on zdecydowanie od wiÄ™kszoÅ›ci typów strukturalnych, z jakimi mieliÅ›my dotÄ…d do czynienia. WzglÄ™dnÄ… nowoÅ›ciÄ… jest bowiem anonimowa unia (ang. anonymous union), zamykajÄ…ca trzy pola struktury. Jeżeli pamiÄ™tamy, jak funkcjonujÄ… unie, to wiemy, iż taka deklaracja powoduje nastÄ™pujÄ…cy efekt: tylko jedno z pól - mi, ki lub hi - może być wykorzystane do zapisywania sensownych informacji. Jest to najlogiczniejsza realizacja zaÅ‚ożenia, aby jedna struktura INPUT opisywaÅ‚a tylko jedno zdarzenie - myszki, klawiatury czy też specjalnego rodzaju sprzÄ™towego . Niemniej jednak system operacyjny (zredukowany chwilowo do funkcji SendInput()) musi wiedzieć, jakiego typu symulowane zdarzenie chcemy wygenerować. Informujemy o tym w jedynym pozaunijnym polu struktury INPUT - type. W tym celu może ono przyjmować jednÄ… z nastÄ™pujÄ…cych wartoÅ›ci, odpowiadajÄ…cych poszczególnym polom unii: staÅ‚a znaczenie pole unii INPUT_MOUSE mi symulowane wejÅ›cie od myszy INPUT_KEYBOARD ki symulowane zdarzenie klawiatury INPUT_HARDWARE hi symulacja innego urzÄ…dzenia (tylko Windows 9x/Me) Tabela 48. StaÅ‚e pola type struktury INPUT Trzecia z nich jest już mocno przestarzaÅ‚a i dlatego nie należy jej używać. Druga nie interesuje nas w tej chwili, gdyż obecnie nie zajmujemy siÄ™ klawiaturÄ…. Wybieramy zatem bramkÄ™ numer 1 - INPUT_MOUSE :) Struktura MOUSEINPUT Jako że chcemy symulować akcje myszy, powinniÅ›my użyć pola mi (oraz wartoÅ›ci INPUT_MOUSE w polu type). Pole mi jest, jak możnaby przypuszczać, również strukturÄ…. Tym razem typem tej struktury jest MOUSEINPUT, a opisuje ona wszystkie szczegóły naszego wymuszonego zdarzenia myszy: struct MOUSEINPUT { LONG dx; LONG dy; DWORD mouseData; DWORD dwFlags; DWORD time; ULONG_PTR dwExtraInfo; }; CaÅ‚kiem ich sporo, wiÄ™c nie od rzeczy bÄ™dzie ujÄ™cie opisów powyższych pól w zgrabnej tabelce: typ pola opis Wpisujemy tutaj współrzÄ™dne opisujÄ…ce ruch kursora myszy (jeżeli mamy zamiar nim poruszać). WspółrzÄ™dne te mogÄ… być podane w liczbach bezwzglÄ™dnych - sÄ… wówczas liczone w odniesieniu do lewego górnego rogu ekranu; sÄ… to dx wiÄ™c koordynaty ekranowe. Alternatywnie możliwe jest LONG dy podanie współrzÄ™dnych wzglÄ™dnych, bÄ™dÄ…cych raczej okreÅ›leniem przesuniÄ™cia kursora; system operacyjny doda je wtedy do aktualnej pozycji strzaÅ‚ki, otrzymujÄ…c w ten sposób jej nowe poÅ‚ożenie. 443 typ pola opis O tym, jakiego rodzaju współrzÄ™dne podajemy w polach dx i dy informuje obecność lub brak flagi MOUSEEVENTF_ABSOLUTE w polu dwFlags. W tym polu podajemy dodatkowe dane na temat zdarzenia myszy. MogÄ… one przyjąć jednÄ… z dwóch postaci, zależnie od rodzaju zdarzenia: w przypadku symulowanej zmiany poÅ‚ożenia rolki myszy pole mouseData zawiera wartość jej obrotu, czyli deltÄ™. Jest to taka sama wartość, jakÄ… otrzymujemy w górnym sÅ‚owie parametru wParam przy DWORD mouseData przetwarzaniu komunikatu WM_MOUSEWHEEL gdy mamy na celu emulowanie wciÅ›niÄ™cia jednego z dwóch dodatkowych przypcisków myszy - oznaczonych X1 i X2, a nazywanych wspólnie przyciskami X - pole mouseData powinno zawierać wskazanie jednego z tych przycisków: staÅ‚a XBUTTON1 wskazuje na przycisk X1 staÅ‚a XBUTTON2 odpowiada przyciskowi X2 Tutaj dostarczamy kombinacjÄ™ flag bitowych, okreÅ›lajÄ…cych m.in. rodzaj zdarzenia myszy (przesuniÄ™cie, DWORD dwFlags klikniÄ™cie, itd.), jakie chcemy zasymulować. Dopuszczalnych flag jest caÅ‚kiem sporo, wiÄ™c ujmie je za chwile kolejna tabelka :D Pole time okreÅ›la moment zaistnienia zdarzenia. Ma on być wyrażony w spotkanej już przez nas formie liczby milisekund od startu systemu. Czas w takiej postaci można DWORD time uzyskać poprzez GetTickCount() i umieÅ›cić w tym polu; można też zostawić w nim zero, wtedy system sam zapisze tutaj chwilÄ™ generacji zdarzenia. To pole może przechowywać jakieÅ› pomocnicze dane dla odbiorcy zdarzenia. Zwykle nie ma potrzeby przekazywania żadnych takich danych, zatem wpisujemy tu najczęściej zero. ULONG_PTR dwExtraInfo Owe dodatkowe dane można uzyskać podczas przetwarzania komuniaktów o zdarzeniach - wystarczy wywoÅ‚ać funkcjÄ™ GetMessageExtraInfo(). Tabela 49. Pola struktury MOUSEINPUT O rodzaju symulowanego zdarzenia, oraz o kilku innych kwestiach, informujemy funkcjÄ™ SendInput() za poÅ›rednictwem pola dwFlags. Jest to kombinacja jednej lub kilku flag bitowych spoÅ›ród poniższych: flaga znaczenie MOUSEEVENTF_MOVE ruch myszÄ… MOUSEEVENTF_LEFTDOWN wciÅ›niÄ™cie lewego przycisku myszy MOUSEEVENTF_LEFTUP zwolnienie lewego przycisku myszy MOUSEEVENTF_MIDDLEDOWN wciÅ›niÄ™cie Å›rodkowego przycisku myszy MOUSEEVENTF_MIDDLEUP zwolnienie Å›rodkowego przycisku myszy MOUSEEVENTF_RIGHTDOWN wciÅ›niÄ™cie prawego przycisku myszy MOUSEEVENTF_RIGHTUP zwolnienie prawego przycisku myszy MOUSEEVENTF_XDOWN wciÅ›niÄ™cie jednego z dodatkowych przycisków myszy MOUSEEVENTF_XUP zwolnienie jednego z dodatkowych przycisków myszy MOUSEEVENTF_WHEEL obrót rolkÄ… myszy 444 flaga znaczenie Obecność tej flagi sprawia, że pola dx oraz dy bÄ™dÄ… traktowane jako docelowe, bezwzglÄ™dne współrzedne ekranowe kursora. Funkcja SendInput() ustawi wiÄ™c strzaÅ‚kÄ™ myszy w pozycji wyznaczonej przez te pola Jeżeli zaÅ› flaga nie bdzie obecna w polu dwFlags, wtedy dx i dy zostanÄ… potraktowane jako okreÅ›lenie przesuniÄ™cia kursora, czyli dystansu poziomowego i pionowego, który zostanie dodany do aktualnego poÅ‚ożenia kursora po to, aby MOUSEEVENTF_ABSOLUTE otrzymać nowe. Rzeczywiste przesuniÄ™cie kursora może siÄ™ nieco różnić od wartoÅ›ci podanych w dx i dy, gdyż system operacyjny bierze jeszcze pod uwagÄ™ kilka innych czynników, jak np. aktualnÄ… prÄ™dkość ruchu myszki. JeÅ›li interesujÄ… ciÄ™ szczegóły, zajrzyj do opisu struktury MOUSEINPUT w MSDN. Flaga ta dziaÅ‚a tylko w poÅ‚Ä…czeniu z MOUSEEVENTF_ABSOLUTE. Jej ustawienie powoduje, że absolutne koordynaty kursora podane w dx i dy sÄ… MOUSEEVENTF_VIRTUALDESK traktowane w odniesieniu do caÅ‚ego pulpitu, a nie do ekranu bieżącego monitora. Ma to znaczenie wyÅ‚Ä…cznie w systemach wielomonitorowych. Tabela 50. Flagi bitowe pola dwFlags struktury MOUSEINPUT Ze wzglÄ™du na fakt, iż dwFlags jest kombinacjÄ… bitowÄ…, możliwe jest ustawienie wiÄ™cej niż jednej flagi naraz. Tym samym można zasymulować kilka zdarzeÅ„ myszy za pomocÄ… jednej struktury [MOUSE]INPUT. Niedozwolone jest jedynie poÅ‚Ä…czenie MOUSEEVENTF_XDOWN/UP z MOUSEEVENTF_WHEEL; powody sÄ… czysto techniczne: oba zdarzenia korzystajÄ… bowiem z pola mouseData, ale każde na swój wÅ‚asny sposób i nie potrafiÄ… siÄ™ tym polem podzielić. Stosowalność praktyczna Uff, sporo tej teorii, w dodatku nie jest ona wcale taka prosta. Najlepiej wiÄ™c zająć siÄ™ konkretnymi przypadkami: wtedy wszystko stanie siÄ™ jasne, a przy okazji zdobÄ™dziesz praktyczne umiejÄ™tnoÅ›ci generowania zdarzeÅ„ myszy. A zatem spójrzmy na sposoby sztucznego wywoÅ‚ywania każdego z możliwych zdarzeÅ„ myszy. Stosunkowo najproÅ›ciej wytworzyć oszukane przyciÅ›niÄ™cia lub zwolnienia trzech przycisków myszy. Ignorujemy wówczas prawie wszystkie pola struktury MOUSEINPUT - wszystkie z wyjÄ…tkiem dwFlags, w którym ustawiamy tylko jednÄ… jedynÄ… flagÄ™: którÄ…Å› z MOUSEEVENTF_*UP/DOWN. Zobaczmy przykÅ‚adowy kod, generujÄ…cy programowo wciÅ›niÄ™cie lewego przycisku myszy: // struktura INPUT, przechowujÄ…ca nasze zdarzenie INPUT Klik; ZeroMemory (&Klik, sizeof(INPUT)); // zerujemy jÄ… // ustawiamy odpowiednie parametry Klik.type = INPUT_MOUSE; // informujemy o tym, że zajmujemy siÄ™ myszÄ… Klik.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; // lewy przycisk "w dół" 445 SendInput (1, &Klik, sizeof(INPUT)); // generujemy zdarzenie127 Nieco bardziej skomplikowane jest zasymulowanie klikniÄ™cia jednym z dwóch dodatkowych przycisków - wymaga to wykorzystania jeszcze pola mouseData: Klik.mi.mouseData = XBUTTON1; // przycisk X1 Klik.mi.dwFlags = MOUSEEVENTF_XDOWN; // dodatkowy przycisk "w dół" SendInput (1, &Klik, sizeof(INPUT)); // i jazda :D Analogicznie jak w dwóch powyższych kodach możemy również emulować zwolnienie wciÅ›niÄ™tych przycisków, zamieniajÄ…c flagi *DOWN na *UP. NastÄ™pnym interesujÄ…cym wydarzeniem jest ruch myszy. Jak można wnioskować z opisu struktury MOUSEINPUT, może on odbywać siÄ™ na dwa sposoby. Pierwszym jest natychmiastowa teleportacja kursora w okreÅ›lony rejon ekranu: INPUT Ruch; ZeroMemory (&Ruch, sizeof(INPUT)); // ustawiamy kursor w Å›rodku ekranu Ruch.type = INPUT_MOUSE; Ruch.mi.dx = GetSystemMetrics(SM_CXSCREEN) / 2; // współ. pozioma Ruch.mi.dy = GetSystemMetrics(SM_CYSCREEN) / 2; // współ. pionowa Ruch.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // flagi SendInput (1, &Ruch, sizeof(INPUT)); Można zapytać, czym różni siÄ™ powyższy kod od wywoÅ‚ania SetCursorPos() (pomijajÄ…c wiÄ™kszÄ… jego dÅ‚ugość)?& Odmienność tych dwóch dróg osiÄ…gniÄ™cia celu jest żadna - obie powodujÄ… dokÅ‚adnie to samo. Zdaje siÄ™, że możliwość bezwglÄ™dnej zmiany poÅ‚ożenia kursora za pomocÄ… SendInput() zostaÅ‚a raczej gwoli kompletnoÅ›ci w symulowaniu myszy - Å›wiadczy o tym choćby fakt, iż dziaÅ‚anie to wymaga podania dodatkowej flagi. Jedynie doÅ‚Ä…czenie MOUSEEVENTF_VIRTUALDESK sprawia wyraznÄ… różnicÄ™, która jednak jest widoczna tylko w systemach z kilkoma monitorami128. Inaczej jest w przypadku relatywnego przesuwania kursora, gdy SendInput() jest caÅ‚kowicie niezastÄ…piona (chyba że przez przestarzaÅ‚Ä… mouse_event()). PrzesuniÄ™cie kursora może też odbywać siÄ™ w odniesieniu do jego bieżącej pozycji. Jak już kilkakrotnie wspominaÅ‚em, wartoÅ›ci pól MOUSEINPUT::dx i MOUSEINPUT::dy zostanÄ… wtedy zwyczajnie dodane do aktualnych współrzÄ™dnych myszy. Takie dziaÅ‚anie jest w zasadzie domyÅ›lne, gdyż nie wymaga podania żadnej dodatkowej flagi (naturalnie poza niezbÄ™dnÄ… MOUSEEVENTF_MOVE, okreÅ›lajÄ…cÄ… rodzaj symulowanego zdarzenia myszy): Ruch.mi.dwFlags = MOUSEEVENTF_MOVE; // bez MOUSEEVENTF_ABSOLUTE Ponieważ użycie SendInput() jest jedynym sposobem na relatywne przesuniÄ™cie kursora, zaÅ› przemieszczenie bezwglÄ™dne ma swój odpowiednik w funkcji SetCursorPos(), flaga MOUSEEVENTF_ABSOLUTE jest używana raczej rzadko. SetCursorPos() jest zwyczajnie prostszÄ… drogÄ… osiÄ…gniÄ™cia tego samego celu, czyli ustawienia kursora w Å›ciÅ›le okreÅ›lonym miejscu ekranu. 127 Korzystamy tu z operatora pobrania adresu, ponieważ mamy pojedynczÄ… zmiennÄ… (strukturÄ™), a nie tablicÄ™. Klik możnaby aczkolwiek zadeklarować jako INPUT Klik[1];, lecz wtedy musielibyÅ›my odwoÅ‚ywać siÄ™ do jego pól poprzez poprzez Klik[0].. Poza tym tablica skÅ‚adajÄ…ca siÄ™ z jednego elementu to raczej dziwny twór, nieprawdaż? :) (podobne uwagi mogÄ… dotyczyć także każdego z nastÄ™pnych kodów w tym akapicie) 128 SetCursorPos() potrafi przesuwać kursor tylko w obrÄ™bie aktualnego monitora, zaÅ› SendInput() ze wspomnianÄ… flagÄ… może dziaÅ‚ać na caÅ‚ym pulpicie, rozciÄ…gniÄ™tym nawet na kilka monitorów. 446 OstatniÄ… akcjÄ… zwiÄ…zanÄ… z myszÄ… jest obrót jej rolki. Emulowanie tego zjawiska nie należy do trudnych zadaÅ„: wiemy, że wartość żądanego obrotu, wyrażonÄ… jako (pod)wielokrotność WHEEL_DELTA, należy wpisać w polu MOUSEINPUT::mouseData. Oprócz tego należy jeszcze podać odpowiedniÄ… flagÄ™; w caÅ‚oÅ›ci wyglÄ…da to mniej wiÄ™cej tak: INPUT Obrot; ZeroMemory (&Obrot, sizeof(INPUT)); // obrót rolki o jeden krok w przód ("od użytkownika") Obrot.type = INPUT_MOUSE; Obrot.mi.mouseData = WHEEL_DELTA; // wartość obrotu rolki Obrot.mi.dwFlags = MOUSEEVENTF_WHEEL; // akcja == obrót rolkÄ… SendInput (1, &Obrot, sizeof(INPUT)); // dziaÅ‚amy PamiÄ™tajmy, że symulowanie obrotu rolkÄ… myszy jest możliwe, tylko wtedy, gdy zainstalowana w komputerze mysz faktycznie takÄ… rolkÄ™ posiada. O sprawdzaniu tej i innych cech myszy powiemy sobie w nastÄ™pnej sekcji. MożliwoÅ›ci i ustawienia myszy Jeszcze nie tak dawno temu niezwykle popularne wÅ›ród użytkowników komputerów byÅ‚y myszy zaledwie dwuprzyciskowe. Szybko dorobiÅ‚y siÄ™ jednak kolejnego przycisku, a nawet wiÄ™kszej ich liczby; potem zyskaÅ‚y też obrotowe rolki, czasem nawet w liczbie wiÄ™kszej niż jedna. Dzisiaj na komputerowym rynku i podkÅ‚adkach użytkowników istnieje caÅ‚e mnóstwo modeli urzÄ…dzeÅ„ wskazujÄ…cych, różniÄ…cych siÄ™ swoimi możliwoÅ›ciami. Co wiÄ™cej, na potencjaÅ‚ tych urzÄ…dzeÅ„ można w dużym stopniu wpÅ‚ywać programowo, za poÅ›rednictwem różnorodnych opcji, jakie oferuje Windows. BÄ™dÄ…c caÅ‚kiem elastycznym systemem operacyjnym, pozwala on na dostrojenie bardzo wielu ustawieÅ„ z rejonu myszy i okolic. Opcje te sÄ… ustawiane przede wszystkim przez użytkownika w Panelu Sterowania. Nie znaczy to jednak, że aplikacje dziaÅ‚ajÄ…ce pdo kontrolÄ… systemu nie majÄ… do nich dostÄ™pu. Przeciwnie, mogÄ… one nie tylko odczytywać stan tychże opcji, ale też samodzielnie je zmieniać. W tym celu twórcy programów muszÄ… oczywiÅ›cie skorzystać z odpowiednich funkcji Windows API - tych, które teraz poznamy. SÄ… nimi głównie dwa wywoÅ‚ania: znane ci już skÄ…dinÄ…d GetSystemMetrics() oraz nowe SystemParametersInfo(). Przypomnijmy prototyp pierwszej z tych funkcji: int GetSystemMetrics(int nIndex); Być może pamiÄ™tasz, że w jej parametrze podajemy jednÄ… ze staÅ‚ych SM_* (oznaczajÄ…cych globalne ustawienia systemowe), a w zamian otrzymujemy wartość przyporzÄ…dkowanej jej opcji. JeÅ›li nie, to wÅ‚aÅ›nie sobie o tym przypomniaÅ‚eÅ› :D Druga z ważnych dla nas funkcji to SystemParametersInfo(): BOOL SystemParametersInfo(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni); Ma ona nieco wiÄ™cej parametrów, gdyż sÅ‚uży nie tylko do pobierania, ale też do zmiany opcji systemowych. Niniejsza tabelka opisuje te parametry: typy parametry opis UINT uiAction Tu podajemy staÅ‚Ä… identyfikujÄ…cÄ… opcjÄ™, której ustawienie chcemy 447 typy parametry opis pozyskać lub zmodyfikować. Każdej takiej opcji odpowiada staÅ‚a o nazwie z przedrostkiem SPI_, a ich liczba oscyluje wokół setki. Nie bÄ™dziemy oczywiÅ›cie omawiać ich wszystkich; w tym podrozdziale zajmiemy siÄ™ tylko tymi, które dotyczÄ… myszy. UINT uiParam SÄ… to dwa parametry specyficznego przeznaczenia, których PVOID pvParam użycie zależy od wartoÅ›ci uiAction. Ten parametr okreÅ›la sposób powiadomienia dziaÅ‚ajÄ…cych UINT fWinIni programów o zainstniaÅ‚ej zmianie ustawienia systemowego. Zwykle nie przejmujemy siÄ™ tym parametrem i wpisujemy doÅ„ zero. Tabela 51. Parametry funkcji SystemParametersInfo() Warto zajrzeć do opisu SystemParametersInfo() w MSDN. Jest w nim zawarta m.in. peÅ‚na lista wartoÅ›ci, jakie może przyjmować parametr uiAction. W dalszej części tej sekcji zajmiemy siÄ™ niektórymi z wyliczeniowych staÅ‚ych, jakie można przekazać do funkcji GetSystemMetrics() i SystemParametersInfo(), a także poznamy kilka innych, bardziej specyficznych funkcji. Rzecz jasna, wszystkie te elementy Windows API bÄ™dÄ… dotyczyÅ‚y wyÅ‚Ä…cznie ustawieÅ„ myszy. Rekonesans możliwoÅ›ci myszy Najsampierw chcielibyÅ›my wiedzieć, z jak potężnym urzÄ…dzeniem mamy do czynienia. Innymi sÅ‚owy, zrobimy teraz szybki wglÄ…d w arsenaÅ‚ funkcji, w które zostaÅ‚a wyposażona mysz. Czy jest na pokÅ‚adzie& ? MaÅ‚o kto zdaje sobie sprawÄ™, że myszka nie jest niezbÄ™dnym elementem zestawu komputerowego, pracujÄ…cego pod kontrolÄ… systemu Windows. Nasz okienkowy OS radzi sobie caÅ‚kiem dobrze, majÄ…c do dyspozycji wyÅ‚Ä…cznie klawiaturÄ™. Tego samego nie można zwykle powiedzieć o użytkowniku pozbawionym myszy, co jednak nie znaczy, że takich użytkowników już nie ma. Zobaczmy zatem, jak sprawdzić obecność myszy w komputerze. Na szczęście jest to bardzo proste i ogranicza siÄ™ do wywoÅ‚ania funkcji GetSystemMetrics() z parametrem SM_MOUSEPRESENT: BOOL bMyszkaObecna = GetSystemMetrics(SM_MOUSEPRESENT); W wyniku otrzymujemy wartość TRUE lub FALSE o oczywistym znaczeniu; możemy jÄ… wykorzystać chociażby tak: if (!bMyszkaObecna) { MessageBox (NULL, "Ten program nie może dziaÅ‚ać bez myszki!", "Brak myszy", MB_OK | MB_ICONSTOP) PostQuitMessage (0); } Niemniej pamiÄ™tajmy, że pomimo powszechnoÅ›ci wystÄ™powania myszek w komputerach użytkowników, dobry program powinien zapewniać również wygodne wsparcie dla klawiatury. 448 Liczba przycisków Poszczególne modele myszek różniÄ… siÄ™ miÄ™dzy innymi liczbÄ… dostÄ™pnych przycisków. Dzisiejsze minimum zakÅ‚ada przynajmniej trzy przyciski: lewy, Å›rodkowy i prawy, ale rzeczywista ich ilość może być wiÄ™ksza lub mniejsza. LiczbÄ™ przycisków myszy też pobieramy za pomocÄ… GetSystemMetrics(), lecz tym razem parametrem jest SM_CMOUSEBUTTONS: UINT uPrzyciskiMyszy = GetSystemMetrics(SM_CMOUSEBUTTONS); W wyniku otrzymujemy naturalnie liczbÄ™ caÅ‚kowitÄ…, okreÅ›lajÄ…cÄ… ilość dostÄ™pnych przycisków myszy - lub zero, jeÅ›li mysz nie jest obecna w systemie. Wykrywanie rolki Prawie niezbÄ™dnym elementem myszy staÅ‚a siÄ™ rolka, sÅ‚użąca do przewijania dÅ‚ugich dokumentów, a okazjonalnie peÅ‚niÄ…ca honory Å›rodkowego przycisku. Posiadanie przez myszkÄ™ rolki możemy ustalić za pomocÄ…& funkcji GetSystemMetrics() oczywiÅ›cie :) Tym razem jej parametrem musi być SM_MOUSEWHEELPRESENT: BOOL bRolkaObecna = GetSystemMetrics(SM_MOUSEWHEELPRESENT); Trzeba tu przypomnieć, że brak lub obecność kółka myszy determinuje nie tylko oczywiste tego nastÄ™pstwa, ale też możliwość programowej symulacji obrotu rolki poprzez funkcje SendInput() czy mouse_event(). Nie można bowiem udawać dziaÅ‚ania czegoÅ›, czego tak naprawdÄ™ nie ma. Ustawienia podwójnego klikniÄ™cia Mechanizm podwójnego klikniÄ™cia wprowadzono do Windows głównie po to, aby skrócić czas wykonywania najczÄ™stszych operacji. PrzykÅ‚adem niech bÄ™dzie zarzÄ…dzanie plikami w Eksploratorze Windows: pojedyncze klikniÄ™cie powoduje zaznaczenie pliku, co pozwala na wykonanie na jego rzecz pewnych poleceÅ„, dostepnych na pasku menu programu. Bardzo czÄ™sto takim poleceniem bÄ™dzie otwarcie pliku, wiÄ™c wymyÅ›lono dla niego Å‚atwiejszy sposób wywoÅ‚ywania - podwójne klikniÄ™cie. Nie wymaga ona dÅ‚ugiej wÄ™drówki kursorem do paska menu, a jedynie dwóch szybkich wciÅ›niÄ™c lewego przycisku. Chociaż podwójne klikniÄ™cie jest z pewnoÅ›ciÄ… wygodne, poczÄ…tkujÄ…cym użytkownikom, nieobytym z myszÄ…, może ono sprawiać problemy. Dlatego też system Windows umożliwia dostrojenie parametrów dwukrotnego klikniÄ™cia tak, aby odpowiadaÅ‚y ony indywidualnym preferencjom. W tym paragrafie zobaczymy, w jaki sposób nasze programy mogÄ… pobierać i ustawiać opcje podwójnego klikniÄ™cia. InterwaÅ‚ czasu pomiÄ™dzy klikniÄ™ciami Aby system mógÅ‚ zinterpretować wciÅ›niÄ™cia przycisku myszy jako dwukrotne klikniÄ™cie, muszÄ… one zaistnieć odpowiednio szybko. Jeżeli drugie klikniÄ™cie bÄ™dzie spóznione, wówczas Windows zarejestruje dwa pojedyncze przyciÅ›niÄ™cia, a nie jedno podwójne. Ważne jest wiÄ™c wÅ‚aÅ›ciwe dopasowanie ustawienia systemowego, regulujÄ…cego maksymalny interwaÅ‚ czasu pomiÄ™dzy dwoma klikniÄ™ciami, które bÄ™dÄ… rejestrowane jako jedno podwójne. Rzeczona opcja znajduje siÄ™ w aplecie WÅ‚aÅ›ciwoÅ›ci: Mysz Panelu Sterowania: 449 Screen 62. Ustawianie szybkoÅ›ci dwukrotnego klikniÄ™cia (podziÄ™kowania dla gemGrega za wykonanie tego screena) Naturalnie, możliwa jest także jej programowa kontrola za pomocÄ… funkcji Windows API. Zauważ, że choć użytkownik może mówić o szybkoÅ›ci dwukrotnego klikniÄ™cia, to my, programiÅ›ci, bÄ™dziemy zajmowali siÄ™ czasem pomiÄ™dzy oboma klikniÄ™ciami. Nietrudno domyÅ›lić siÄ™, że obie te wielkoÅ›ci sÄ… do siebie odwrotnie proporcjonalne, tj. wiÄ™ksza szybkość oznacza mniejszy interwaÅ‚ czasu. Zobaczmy teraz, jak można pobrać i ustawić tÄ™ wielkość systemowÄ…. A wiÄ™c: w celu pobrania aktualnego interwaÅ‚u czasu dwukrotnego klikniÄ™cia (ang. double- click time) należy posÅ‚użyć siÄ™ specjalnÄ… funkcjÄ… GetDoubleClickTime(): UINT uCzasDwukliku = GetDoubleClickTime(); WywoÅ‚anie jej jest banalnie proste, bo nie potrzebuje żadnych dodatkowych parametrów. W wyniku dostajemy żądany interwaÅ‚ czasu w milisekundach (tysiÄ™cznych częściach sekundy). Ustawienie czasu dwukrotnego klikniÄ™cia jest natomiast możliwe aż na dwa sposoby: poprzez wywoÅ‚anie specjalnej funkcji SetDoubleClickTime(). Podajemy jej nowy interwaÅ‚ czasu, oczywiÅ›cie w milisekundach: SetDoubleClickTime (250); za pomocÄ… funkcji SystemParametersInfo(); należy wtedy w jej pierwszym parametrze podać staÅ‚Ä… SPI_SETDOUBLECLICKTIME, zaÅ› w drugim nowÄ… wartość ustawienia (trzeci i czwarty parametr wypeÅ‚niamy zerami): SystemParametersInfo (SPI_SETDOUBLECLICKTIME, 250, NULL, 0); Z uwagi na fakt, że pierwsza metoda jest znacznie Å‚atwiejsza, nie obrażę siÄ™, jeżeli caÅ‚kiem zapomnisz o drugiej :) Powiedzmy jeszcze, że w obu przypadkach możemy jako interwaÅ‚ podać zero& Nie, nie spowoduje to wyÅ‚Ä…czenia dwukrotnego klikniÄ™cia. W takiej sytuacji Windows przyjmie po prostu wartość domyÅ›lnÄ… dla tego ustawienia, czyli 500 milisekund (pół sekundy). Dopuszczalne przesuniÄ™cie kursora Zmieszczenie siÄ™ w wÄ…skim przedziale czasu nie jest jedynym wymogiem, jakie stawia system wobec dwukrotnego klikniÄ™cia. Drugim (i na szczęście ostatnim) jest nieruchomość myszy podczas dokonywania operacji. Nie znaczy to aczkolwiek, że kursor miÄ™dzy jednym a drugim przyciÅ›niÄ™ciem nie może siÄ™ przesunąć nawet o piksel. Toleracja w tym wzglÄ™dzie jest nieco wiÄ™ksza, a co wiÄ™cej - można jÄ… również ustawić. Użytkownik może tego dokonać przy pomocy narzÄ™dzia Tweak UI, ustalajÄ…c wartość na zakÅ‚adce Mouse: 450 Screen 63. Dostrajanie tolerancji przesuniÄ™cia myszy podczas dwukrotnego klikniÄ™cia (opcja Double-click; druga z opcji, Drag, okreÅ›la najmniejsze przemieszczenie, które inicjuje operacjÄ™ przeciÄ…gania - o tej wartoÅ›ci nie bÄ™dziemy tutaj mówić) (program Tweak UI możesz Å›ciÄ…gnąć ze strony Microsoftu) Podobnie jak to byÅ‚o w przypadku szybkoÅ›ci dwukrotnego klikniÄ™cia, dla programistów obowiÄ…zuje nieco inna miara opisywanej opcji. Tweak UI stosuje odlegÅ‚ość pomiÄ™dzy miejscami kolejnych kliknięć, zaÅ› Windows API posÅ‚uguje siÄ™ okalajÄ…cym je prostokÄ…tem. System wymaga, aby oba klikniÄ™cia zawieraÅ‚y siÄ™ w tym prostokÄ…cie (o domyÅ›lnych wymiarach kilku pikseli), gdyż w przeciwnym razie nie zostanÄ… potraktowane jako dwukrotne: Schemat 43. ProstokÄ…t dwukrotnego klikniÄ™cia Na tym schemacie nieprzypadkowo opatrzyÅ‚em wymiary prostokÄ…ta (x i y) nazwami SM_CXDOUBLECLK i SM_CYDOUBLECLK. SÄ… to bowiem nazwy staÅ‚ych, jakie należy przekazać do funkcji GetSystemMetrics() celem pobrania wymiarów prostokÄ…ta dwukrotnego klikniÄ™cia: SIZE cProstokatDwukliku; cProstokatDwukliku.cx = GetSystemMetrics(SM_CXDOUBLECLK); cProstokatDwukliku.cy = GetSystemMetrics(SM_CYDOUBLECLK); SIZE to predefiniowany typ strukturalny z WinAPI, który zawiera dwa pola typu LONG - cx i cy, przeznaczone do przechowywania wymiarów (szerokoÅ›ci i wysokoÅ›ci) wszelkiego rodzaju obiektów. Możliwe jest też ustawienie tych wartoÅ›ci przy pomocy funkcji SystemParametersInfo() oraz staÅ‚ych SPI_SETDOUBLECLKWIDTH/HEIGHT: SystemParametersInfo (SPI_SETDOUBLECLKWIDTH, 1, NULL, 0); SystemParametersInfo (SPI_SETDOUBLECLKHEIGHT, 1, NULL, 0); 451 Wykonanie powyższego kodu ustawi prostokÄ…t dwukliku na rozmiar 1×1 piksela, zatem Windows nie bÄ™dzie teraz tolerowaÅ‚ żadnego przesuniÄ™cia myszy w czasie dwukrotnego klikniÄ™cia. UÅ‚atwienia dostÄ™pu Teraz poznamy takie opcje myszy, które uÅ‚atwiajÄ… korzystanie z komputera także osobom z upoÅ›ledzonÄ… sprawnoÅ›ciÄ… ruchowÄ… i niepeÅ‚nosprawnym. Co ciekawe, niektóre z tych opcji sÄ… na tyle przydatne, że bywajÄ… pomocne również dla w peÅ‚ni sprawnych użytkowników. Zamiana przycisków myszy LeworÄ™czni użytkownicy Windows chcieliby trzymać mysz w w lewej rÄ™ce, po lewej stronie monitora. Aby w takim ustawieniu swobodnie z niej korzystać, należy zamienić miejscami (ang. button swap) funkcjonalność lewego i prawego przycisku myszy. Można to uczynić w Panelu Sterowania. Program dziaÅ‚ajÄ…cy w Windows może pobrać stan ten opcji za pomocÄ… funkcji GetSystemMetrics() z parametrem SM_SWAPBUTTON: BOOL bZamienionePryciski = GetSystemMetrics(SM_SWAPBUTTON); PamiÄ™tamy, że ustawienie tej opcji ma znaczenie w momencie, gdy sprawdzamy wciÅ›niÄ™cie lewego (VK_LBUTTON) lub prawego (VK_RBUTTON) przycisku myszy za pomocÄ… funkcji GetAsyncKeyState(). Należy wtedy uwzglÄ™dnić tÅ‚umaczenie fizycznych przycisków myszy na logiczne, jak to byÅ‚o mówione w odpowiednim paragrafie poprzedniej sekcji. Zmiana opisywanej opcji jest natomiast możliwa dwiema drogami: poprzez dedykowanÄ… funkcjÄ™ SwapMouseButton(). Należy jej podać wartość logicznÄ…, okreÅ›lajÄ…cÄ… wÅ‚Ä…czenie (TRUE) lub wyÅ‚Ä…czenie (FALSE) opcji: SwapMouseButton (FALSE); // domyÅ›lne znaczenie przycisków Funkcja ta zwraca w wyniku poprzednie ustawienie przy pomocy wywoÅ‚ania SystemParametersInfo() ze staÅ‚Ä… SPI_SETMOUSEBUTTONSWAP oraz z nowym stanem opcji w parametrze uiParam: SystemParameters (SPI_SETMOUSEBUTTONSWAP, FALSE, NULL, 0); Podobnie jak w przypadku czasu dwukrotnego klikniÄ™cia, specjalnie wydelegowana funkcja jest znacznie protsza w użyciu niż uniwersalne SystemParametersInfo(). Uważajmy, gdyż tak poczyniona zmiana jest znaczÄ…ca i dotyczy natychmiast caÅ‚ego systemu, zatem należy jÄ… stosować tylko w uzasadnionych okolicznoÅ›ciach. Åšlad kursora Na niektórych monitorach (szczególnie starszych ciekÅ‚okrystalicznych, w laptopach) obraz jest na tyle niewyrazny, że dostrzeżenie ruchu jest czÄ™sto trudne. Również wady wzroku (spowodowane np. dÅ‚ugim siedzeniem przed komputerem :D) mogÄ… to utrudniać. W takiej sytuacji można wÅ‚Ä…czyć ciekawÄ… nomen omen opcjÄ™ Å›ladu kursora (ang. cursor trails), podążajÄ…cego za ruchomÄ… strzaÅ‚kÄ… myszy. 452 Screen 64. WÅ‚Ä…czenia Å›ladu kursora dokonujemy we wÅ‚aÅ›ciwoÅ›ciach myszy w Panelu Sterowania, tam też ustalamy jego dÅ‚ugość. Nawet jeÅ›li nie masz kÅ‚opotów ze Å›ledzeniem kursora, ta opcja może być interesujÄ…cym fajerwerkiem graficznym Pewnie ciÄ™ nieco zmartwiÄ™, ale do programowej kontroli tego ustawienia sÅ‚uży wyÅ‚Ä…cznie funkcja SystemParametersInfo(). By pobrać stan opcji należy wywoÅ‚ać tÄ™ funkcjÄ™ ze staÅ‚Ä… SPI_GETMOUSETRAILS: int nSladKursora; SystemParametersInfo (SPI_GETMOUSETRAILS, 0, &nSladKursora, 0); Wynik odczytujemy ze zmiennej liczbowej, do której wskaznik przekazujemy w trzecim parametrze. JeÅ›li ma ona wartość wiÄ™kszÄ… od 1, wtedy opcja jest wÅ‚Ä…czona; jednoczeÅ›nie otrzymana wartość wskazuje na dÅ‚ugość Å›ladu, jaki zostawia kursor (innymi sÅ‚owy, ile razy jest on powielany). Modyfikacja Å›ladu wymaga wywoÅ‚ania naszej wielce szacownej funkcji z parametrem SPI_SETMOUSETRAILS. NowÄ… dÅ‚ugość Å›ladu podajemy z kolei w drugim parametrze, np.: SystemParametersInfo (SPI_SETMOUSETRAILS, 4, NULL, 0); W powyższy sposób ustawiamy Å›lad kursora Å›redniej dÅ‚ugoÅ›ci (strzaÅ‚ka bÄ™dzie replikowana 4 razy). Sonar InnÄ… metodÄ… poprawy widocznoÅ›ci kursora jest sonar (ang. mouse sonar). Nie ma on niestety nic wspólnego z Å‚odziami podwodnymi i peÅ‚nymi skarbów okrÄ™tami na dnie morza. Mechanizm sonaru w systemie Windows polega na wizualnym wyróżnieniu kursora myszy koncentrycznymi krÄ™gami. SÄ… one pokazywane, gdy użytkownik wciÅ›nie klawisz Ctrl. Sonar uÅ‚atwia wiÄ™c lokalizowanie kursora na ekranie zaÅ›mieconym oknami, przy wysokiej rozdzielczoÅ›ci lub zepsutym wzroku. Sonar jest dostÄ™pny tylko Windows Me i XP. Sprawdzenia, czy sonar jest wÅ‚Ä…czony, dokonujemy poprzez SystemParametersInfo() ze staÅ‚Ä… SPI_GETMOUSESONAR: BOOL bSonar; SystemParametersInfo (SPI_GETMOUSESONAR, 0, &bSonar, 0); Wynik kontroli lÄ…duje oczywiÅ›cie w zmiennej BOOLowskiej, której adres podajemy w trzecim parametrze. W(y)Å‚Ä…czenia sonaru dokonujemy także za pomocÄ… doskonale już znanej funkcji o dÅ‚ugiej nazwie, a także jej parametru SPI_SETMOUSESONAR: SystemParametersInfo (SPI_SETMOUSESONAR, TRUE, NULL, 0); // wÅ‚Ä…cz. sonaru InformacjÄ™ o nowym stanie opcji sonaru wysyÅ‚amy z drugim argumentem funkcji. 453 KlawiszeMyszy Bardzo ciekawÄ… opcjÄ… z zakresu uÅ‚atwieÅ„ Windows sÄ… KlawiszeMyszy (ang. MouseKeys). Pozwala ona na przesuwanie kursora myszy za pomocÄ… klawiszy strzaÅ‚ek klawiatury numerycznej (8, 4, 6 i 2) oraz Shift i Ctrl. Te dwa ostatnie oferujÄ… możliwość zwolnienia lub przyspieszenia ruchu kursora. OsobiÅ›cie sÄ…dzÄ™, że ciężko zaliczyć tÄ™ możliwość do kategorii uÅ‚atwieÅ„, bo opanowanie mechaniki kursora tÄ… metodÄ… wydaje siÄ™ o wiele trudniejsze i mniej efektywnie niż poruszanie. KlawiszeMyszy majÄ… jednak pewnÄ… niezaprzeczalnÄ… zaletÄ™, która może być bardzo przydatna podczas tworzenia grafiki czy konstruowania interfejsu użytkownika. Mam tu na myÅ›li możliwość precyzyjnego poruszania myszÄ… - z dokÅ‚adnoÅ›ciÄ… do jednego piksela. Bardzo uÅ‚atwia do rozmieszczanie elementów grafiki czy UI. Programowa kontrola tego ustawienia, posiadajÄ…cego sporo podopcji, jest dość obszernym zagadnieniem, na który chyba nie ma tu już miejsca. Jeżeli ciÄ™ to interesuje, poczytaj w MSDN opis struktury MOUSEKEYS oraz informacje o parametrach SPI_GET/SETMOUSEKEYS funkcji SystemParametersInfo(). BlokadaKlikniÄ™cia Jeżeli masz kÅ‚opoty z przeciÄ…ganiem kursora myszy przy wciÅ›niÄ™tym przycisku, możesz wÅ‚Ä…czyć opcjÄ™ Windows znanÄ… jako ClickLock. TÅ‚umaczyć jÄ… można jako BlokadaKlikniÄ™cia . DziaÅ‚a ona w prosty sposób: jeÅ›li użytkownik wciÅ›nie logicznie lewy przycisk myszy i przytrzyma go przez okreÅ›lony czas (który można regulować), system uzna, że rozpoczyna siÄ™ operacja przeciÄ…gania. Można teraz już zwolnić przycisk myszy, a Windows nadal bÄ™dzie traktowaÅ‚ go jako wciÅ›niÄ™ty. PoruszajÄ…c myszÄ… w zwykÅ‚y sposób (przy fizycznie zwolnionym przycisku), można teraz dokonywać przeciÄ…gania bez koniecznoÅ›ci przytrzymywania przycisku palcem, jak to byÅ‚o dotychczas. Kiedy zaÅ› uznamy, że chcemy zakoÅ„czyć przeciÄ…ganie, kilkamy po prostu jeszcze raz, aby upuÅ›cić podniesiony obiekt (np. plik). BlokadaKlikniÄ™cia dziaÅ‚a tylko w Windows Me i XP. Niniejsza opcja jest także domenÄ… funkcji SystemParametersInfo(). By dowiedzieć siÄ™, czy jest aktualnie wÅ‚Ä…czona, należy zastosować parametr SPI_GETMOUSECLICKLOCK: BOOL bBlokadaKlikniecia; SystemParametersInfo (SPI_GETMOUSECLICKLOCK, 0, &bBlokadaKlikniecia, 0); Ze zmiennej typu BOOL, której adres należy podać w pokazany wyżej sposób, odczytamy stan opcji. Jak można siÄ™ domyÅ›lić, TRUE oznacza wÅ‚Ä…czone, a FALSE - wyÅ‚Ä…czone ustawienie. Jego zmiana to z kolei wywoÅ‚anie z użyciem SPI_SETMOUSECLICKLOCK: // wÅ‚Ä…czenie BlokadyKlikniÄ™cia SystemParametersInfo (SPI_SETMOUSECLICKLOCK, TRUE, NULL, 0); Nowy status opcji podajemy w drugim parametrze, uiParam. Kiedy już wÅ‚Ä…czymy BlokadÄ™, możemy też regulować czas (w milisekundach), po którym przycisk myszy zostanie zablokowany. Do jego pobrania sÅ‚uży SPI_GETMOUSECLICKLOCKTIME: unsigned uCzasBlokadyKlikniecia; SystemParametersInfo (SPI_GETMOUSECLICKLOCKTIME, 0, &uCzasBlokadyKlikniecia, 0); 454 Ponownie otrzymujemy pożądanÄ… wartość w zmiennej, do której wskaznik podajemy. To siÄ™ już robi caÅ‚kiem proste, prawda? ;) Ustawienie nowego interwaÅ‚u czas oznacza konieczność użycia staÅ‚ej SPI_SETMOUSECLICKLOCKTIME - o tak: SystemParametersInfo (SPI_SETMOUSECLICKLOCKTIME, 500, NULL, 0); Nowy czas podajemy w drugim parametrze. Powinien on być nie mniejszy niż kilkaset milisekund, gdyż w przeciwnym razie może być zbyt krótki na wykonanie zwykÅ‚ego klikniÄ™cia (bez przeciÄ…gania). *** Na tym koÅ„czy siÄ™ nasza opowieść o myszy. ByÅ‚y to dÅ‚uga historia, a mimo nie dotarliÅ›my do jej definitywnego epilogu. Oprócz kilku nieomówionych ustawieÅ„ systemowych (o których poczytasz sobie w MSDN), nie zajÄ™liÅ›my siÄ™ jeszcze zagadnieniem wÅ‚asnego ksztaÅ‚tu kursora myszy - stanie siÄ™ to w rozdziale o zasobach Windows. Wykorzystanie klawiatury Czy można sobie wyobrazić komputer bez klawiatury? OczywiÅ›cie odpowiesz pewnie Wezmy choćby HALa z Odysei kosmicznej 2001! Faktycznie, masz racjÄ™: komputery sterowane gÅ‚osem (może nie tak inteligentne jak HAL) istniejÄ… już dzisiaj i ta forma komunikacji z maszynÄ… na pewno bÄ™dzie siÄ™ rozwijać. Jednak klawiatury nie zniknÄ… nigdy, a powodem tego jest zwykÅ‚a potrzeba poufnoÅ›ci i prywatnoÅ›ci - nie chcielibyÅ›my przecież, aby wszyscy dookoÅ‚a sÅ‚yszeli, jakie informacje wprowadzamy do komputera. PoÅ›rednictwo klawiatury stwarza wiÄ™c pewnÄ… ochronÄ™ danych - o ile nikt nie zaglÄ…da nam przez ramiÄ™ :) Podobnie jak myszki i wszystkie urzÄ…dzenia peryferyjne, klawiatury przez lata byÅ‚y udoskonalane. Ich przodkami sÄ… zapewne maszyny do pisania, jednak od poczÄ…tku istnienia komputerów osobistych ich klawiatury nie ograniczaÅ‚y siÄ™ tylko do liter i cyfr. Szybko pojawiÅ‚y siÄ™ dodatkowe klawisze strzaÅ‚ek, klawisze funkcyjne, a ostatnio nawet specjalne klawisze uÅ‚atwiajÄ…ce serfowanie po Internecie. WyglÄ…d i ksztaÅ‚t klawiatur także ulegaÅ‚ przeobrażeniom. Ostatnimi czasy popularne sÄ… tzw. klawiatury ergonomiczne, których część alfanumeryczna jest rozdzielona na dwoje, a klawisz spacji odpowiednio wygiÄ™ty. W komputerach zadomowiÅ‚y siÄ™ też klawiatury bezprzewodowe, tworzÄ…c czÄ™sto zestawy z myszkami. Fotografia 5 i 6. PrzykÅ‚adowe modele dzisiejszych klawiatur (fotografie pochodzÄ… z serwisu internetowego firmy Logitech) Ten podrozdziaÅ‚ stanowi przeglÄ…d możliwoÅ›ci Windows API w zakresie współpracy z klawiaturÄ…. Poznamy w nim zdarzenia, jakie sÄ… generowane w reakcji na wciÅ›niÄ™cia klawiszy, sposoby na pobieranie stanu klawiatury oraz symulowania jego zmiany. Popatrzymy też na ustawienia systemowe, zwiÄ…zane z tym urzÄ…dzeniem. 455 Zdarzenia klawiatury Najważniejsze sÄ… oczywiÅ›cie komunikaty o zdarzeniach. Reakcja na nie bÄ™dzie lwiÄ… częściÄ… obsÅ‚ugi klawiatury w naszych programach okienkowych. Zajmijmy siÄ™ zatem ich powstawaniem i rodzajami. Potok klawiszy Zanim fakt fizycznego wciÅ›niÄ™cia lub zwolnienia klawisza dotrze do okna w postaci odpowiedniego komunikatu, przechodzi on dosyć dÅ‚ugÄ… drogÄ™. Nazywam jÄ… potokiem klawiszy (ang. keys pipeline) lub modelem wejÅ›cia od klawiatury (ang. keyboard input model). Obrazuje on, w jaki sposób system Windows zamienia sygnaÅ‚y pochodzÄ…ce od urzÄ…dzenia na komunikaty trafiajÄ…ce do okien. Potok klawiszy można przedstawić obrazowo na schemacie: Schemat 44. Potok klawiszy w Windows Opiszmy każdy z przedstawionych tu etapów, jakie pokonuje informacja o zdarzeniu klawiatury. TÅ‚umaczenie kodów Gdy użytkownik wciska lub zwalnia klawisz, wiadomość o tym przedostaje siÄ™ do systemu poprzez przerwanie klawiatury. Powiadomienie zawiera tzw. kod skanowania (ang. scan code) lub kod OEM (ang. OEM code) - po prostu pewnÄ… liczbÄ™. Jest ona specyficzna dla każdego klawisza, a dodatkowo różni siÄ™ w przypadku jego wciÅ›niÄ™cia i zwolnienia. Kody OEM sÄ… ponadto charakterystyczne dla urzÄ…dzenia i zależne od niego. Znaczy to, że różne modele klawiatur mogÄ… generować różne kody skanowania. ProgramujÄ…c w niskopoziomowym asemblerze, należaÅ‚oby uwzglÄ™dniać wszystkie możliwe kody interesujÄ…cych nas klawiszy. Ponieważ jednak pracujemy w bardziej przyjaznym Å›rodowisku Windows, ominie nas ta wÄ…tpliwa przyjemność. Kod skanowania zostaje bowiem przetÅ‚umaczony na inny, uniwersalny dla każdej możliwej klawiatury. Takiego tÅ‚umaczenia dokonuje sterownik urzÄ…dzenia (ang. device driver) - w tym przypadku chodzi oczywiÅ›cie o sterownik klawiatury, dostarczany wraz ze sprzÄ™tem lub systemem Windows. 456 Kody wirtualnych klawiszy Produktem tÅ‚umaczenia jest kod wirtualnego klawisza (ang. virtual-key code), zwany dla wygody kodem wirtualnym. Jest to 16-bitowy, jednoznaczny identyfikator każdego obsÅ‚ugiwanego przez Windows klawisza na dowolnej klawiaturze. Jak wiemy, swoje kody majÄ… także przyciski myszy. WiÄ™kszoÅ›ci wirtualnych kodów przyporzÄ…dkowane sÄ… odpowiednie staÅ‚e o nazwach zaczynajÄ…cych siÄ™ od przedrostka VK_. Poniższa tabelka przedstawia część najpopularniejszych klawiszy (z wyÅ‚Ä…czeniem przycisków myszy, których kody byÅ‚y podane wczeÅ›niej): staÅ‚a kod klawisz staÅ‚a kod klawisz VK_BACK 0x0008 VK_NEXT 0x0022 Backspace Page Down VK_TAB 0x0009 VK_END 0x0023 Tab End VK_RETURN 0x000D VK_HOME 0x0024 Enter Home VK_SHIFT 0x0010 VK_LEFT 0x0025 Shift StrzaÅ‚ka w lewo VK_CONTROL 0x0011 VK_UP 0x0026 Ctrl StrzaÅ‚ka w górÄ™ VK_MENU 0x0012 VK_RIGHT 0x0027 Alt StrzaÅ‚ka w prawo VK_ESCAPE 0x001B VK_DOWN 0x0028 Esc StrzaÅ‚ka w dół VK_SPACE 0x0020 VK_INSERT 0x002D Spacja Insert VK_PRIOR 0x0021 VK_DELETE 0x002E Page Up Delete Tabela 52. Niektóre kody wirtualnych klawiszy PeÅ‚nÄ… listÄ™ możesz znalezć w Tablicach C oraz w MSDN. Zapakowanie w komunikat Drugim produktem tÅ‚umaczenia, dokonywania przez sterownik klawiatury, jest wydzielona informacja o rodzaju zainstniaÅ‚ego zdarzenia (akcji). Na jej podstawie Windows wybiera komunikat, jaki wygeneruje. A możliwoÅ›ci sÄ… generalnie dwie: wciÅ›niÄ™cie klawisza (ang. key down) zwolnienie klawisza (ang. key up) System decyduje siÄ™ na jednÄ… z nich, tworzy odpowiedni komunikat i wysyÅ‚a go do kolejki komunikatów. Produkcja znaków StamtÄ…d prÄ™dzej czy pózniej wiadomość o zdarzeniu zostanie pobrana przez aplikacjÄ™ i trafi do jej pÄ™tli komunikatów. PÄ™tli, która w najprostszej wersji wyglÄ…da tak: MSG msgKomunikat; while (GetMessage(&msgKomunikat, NULL, 0, 0)) { TranslateMessage (&msgKomunikat); DispatchMessage (&msgKomunikat); } W każdym z jej możliwych wariantów niezmienne sÄ… przywoÅ‚ania dwóch funkcji - TranslateMessage() i DispatchMessage(). W tym momencie jesteÅ›my niezwykle zainteresowani pierwszÄ… z nich. Gdy przedstawiaÅ‚em pÄ™tlÄ™ komunikatów na poczÄ…tku kursu WinAPI, wspomniaÅ‚em, że niewielu programistów wie, co naprawdÄ™ robi funkcja TranslateMessage(). Zapewne nie jest to do koÅ„ca prawdÄ…, niemniej faktem jest, że nazwa tej funkcji niezupeÅ‚nie oddaje wykonywanÄ… przezeÅ„ czynność. 457 Przede wszystkim TranslateMessage() nie dokonuje żadnego tÅ‚umaczenia komunikatu - cokolwiek miaÅ‚oby to znaczyć. Jej rolÄ… jest bowiem produkcja dodatkowych zdarzeÅ„, tzw. komunikatów o znakach (ang. character messages) Generuje je na podstawie nastÄ™pujÄ…cych po sobie wciÅ›nięć i zwolnieÅ„ tego samego klawisza na klawiaturze. O zdarzeniach znaków i odpowiadajÄ…cych im komunikatach powiemy sobie wiÄ™cej w którymÅ› z nadchodzÄ…cych paragrafów. Finisz Na koniec wszystkie wytworzone komunikaty trafiajÄ… do docelowego okna. Dotyczy to zarówno stworzonych w jÄ…drze systemu powiadomieÅ„ o zdarzeniach klawiszy, jak i komunikatów o znakach, posÅ‚anych do kolejki przez TranslateMessage(). Wszystkie te zdarzenia sÄ… normalnie obsÅ‚ugiwane przez procedurÄ™ zdarzeniowÄ… okna, które je ostatecznie otrzyma. WÅ‚aÅ›nie - którego okna? SkÄ…d Windows wie, komu przekazywać informacje o zdarzeniach klawiatury? Czas odpowiedzieć i na to pytanie. Fokus Komputer posiada tylko jednÄ… klawiaturÄ™. To niewiele, jeżeli wezmie siÄ™ pod uwagÄ™ praktycznie nieograniczonÄ… liczbÄ™ programów, jakie mogÄ… jednoczeÅ›nie dziaÅ‚ać w Windows. DostÄ™p do tego cennego sprzÄ™tu musi być zatem wydzielany, co spoczywa na barkach systemu operacyjnego. Radzi on sobie z tym zadaniem poprzez kontrolÄ™ tzw. wejÅ›ciowego fokusu klawiatury (ang. keyboard input focus) - w skrócie fokusu (ang. focus). Jest to specjalna wÅ‚aÅ›ciwość, którÄ… w danej chwili może posiadać tylko jedno okno. Okno posiadajÄ…ce fokus otrzymuje wszystkie komunikaty o zdarzeniach klawiatury129. DziÄ™ki temu, że któreÅ› z okien w systemie posiada fokus, Windows wie, do kogo kierować pojawiajÄ…ce siÄ™ informacje od klawiatury. Takie okno jest wiÄ™c ostatecznym celem potoku klawiszy. PrzeÅ‚Ä…czanie fokusu Fokus jest dla klawiatury mniej wiÄ™cej tym samym, co dla myszki jest wÅ‚adza nad niÄ… (ang. mouse capture). WystÄ™pujÄ… tu jednak pewne różnice. Przede wszystkim, fokus nie jest tak ulotnÄ… wÅ‚asnoÅ›ciÄ… jak capture - zmienia siÄ™ on rzadziej i zazwyczaj tylko wtedy, gdy użytkownik sam sobie tego zażyczy. Osoba obsÅ‚ugujÄ…ca aplikacje jest też znacznie bardziej Å›wiadoma istnienia tego zjawiska, gdyż odzwierciedla siÄ™ ono w wyglÄ…dzie okien. Te posiadajÄ…ce fokus odznaczajÄ… siÄ™ np. innym kolorem paska tytuÅ‚u, migajÄ…cym kursorem (w polach tekstowych) czy kolorowÄ… belkÄ… zaznaczenia (w listach). Jak odbywa siÄ™ przekazywanie fokusu miÄ™dzy oknami?& Jeżeli programy nie ingerujÄ… w ten proces, to dzieje siÄ™ to zwykle poprzez: klikniÄ™cie myszÄ… w obszarze innego okna posÅ‚użenie siÄ™ odpowiednim klawiszem (kombinacjÄ…) przeÅ‚Ä…czania: Tab dla kontrolek potomnych, Alt+Tab dla nadrzÄ™dnych okien programów Wynika stÄ…d, że potoczne rozumienie aktywnoÅ›ci okna to nic innego, jak wÅ‚aÅ›nie posiadanie przezeÅ„ fokusu. Dla twórcy aplikacji fakt, że okno jest aktywne (ang. enabled), oznacza jednak co innego: wcale nie to, że bÄ™dzie ono przyjmowaÅ‚o dane 129 Dostaje też komunikat WM_MOUSEWHEEL, o czym powiedzieliÅ›my sobie wczeÅ›niej. 458 wejÅ›ciowe od urzÄ…dzeÅ„ zewnÄ™trznych (myszy, klawatury), lecz jedynie to, iż może ono takie dane przyjmować. Zatem okno nieaktywne nie może posiadać fokusu, a spoÅ›ród aktywnych okien fokus ma tylko jedno. KontrolujÄ…c fokus, Windows dba także, aby okna byÅ‚y odpowiednio informowane o jego zmianach. W tym celu wysyÅ‚ane sÄ… dwa komunikaty: WM_KILLFOCUS otrzymuje okno, które straciÅ‚o fokus na rzecz innego. Uchwyt tego nowego posiadacza jest zapisany w parametrze wParam niniejszego komunikatu (może to być NULL) WM_SETFOCUS dostaje z kolei to okno, które fokus otrzymaÅ‚o. W parametrze wParam zapisany jest wtedy uchwyt poprzedniego posiadacza (albo NULL) ObsÅ‚uga tych komunikatów jest konieczna, jeżeli posÅ‚ugujemy siÄ™ tzw. karetkÄ… (ang. caret), sÅ‚użącÄ… do oznaczania miejsca wpisywania tekstu w oknie. Nie bÄ™dziemy siÄ™ aczkolwiek zajmować bliżej tym zagadnieniem. Dla programisty gier i wszelkich innych aplikacji dziaÅ‚ajÄ…cych na peÅ‚nym ekranie dwa powyższe komunikaty sÄ… również bardzo ważne. PozwalajÄ… one wykryć czas, gdy program nie jest w krÄ™gu zainteresowania użytkownika i oszczÄ™dzić niepotrzebnego wysiÅ‚ku przeznaczanego np. na rendering kolejnych klatek. Funkcje pomocnicze Windows API zawiera dwe funkcje przeznaczone do kontroli fokusu klawiatury. PierwszÄ… z nich jest SetFocus(): HWND SetFocus(HWND hWnd); Po nazwie nietrudno domyÅ›lić siÄ™, że sÅ‚uży ona do przekazania fokusu innemu oknu; jego uchwyt podajemy w paramatrze funkcji. W wyniku dostajemy uchwyt poprzedniego posiadacza fokusu. DrugÄ… funkcjÄ… jest GetFocus(): HWND GetFocus(); Tutaj sprawa jest jeszcze prostsza: wywoÅ‚anie to zwraca po prostu uchwyt aktualnego wÅ‚aÅ›ciciela fokusu klawiatury. Parametry komunikatów klawiatury Teraz moglibyÅ›my w zasadzie przejść już do omawiania każdego z komunikatów zdarzeniowych klawiatury. BÄ™dzie jednak lepiej i wygodniej, jeżeli najpierw skoncetrujemy siÄ™ na ich parametrach wParam i lParam, jako że sÄ… one wspólne dla wszystkich komunikatów. Podobnego rozpatrzenia parametrów dokonaliÅ›my zresztÄ… przy okazji zdarzeÅ„ myszy, wiÄ™c tutaj tylko kontynuujemy tÄ™ chlubnÄ… tradycjÄ™ ;) Popatrzmy zatem na dane dodatkowe komunikatów klawiatury. Parametr wParam ZawartoÅ›ciÄ… tej danej jest specyficzny kod wciÅ›niÄ™tego (lub puszczonego) klawisza. Rodzaj owego kodu zależy od typu komunikatu: komunikaty o klawiszach zawierajÄ… tutaj kod wirtualnego klawisza, czyli wartość, której odpowiada jedna ze staÅ‚ych VK_ komunikaty o znakach majÄ… tu natomiast wpisany kod znaku, który zostaÅ‚ wprowadzony (najczęściej jest on w systemie Unicode) 459 DokÅ‚adniejsze wiadomoÅ›ci o obu rodzajach kodów otrzymasz przy omawianiu powiÄ…zanych z nimi komunikatów. Parametr lParam Drugi parametr komunikatu od klawiatury jest chyba najlepszym przykÅ‚adem gÄ™stego upakowania danych w zmiennej, która wydaje siÄ™ tylko liczbÄ…. Otóż lParam wcale nie jest tutaj liczbÄ… - zawiera w sobie bowiem aż sześć (!) różnych informacji. SÄ… one uÅ‚ożone w odpowiednich bitach tej 32-bitowej wartoÅ›ci, jak to jest ukazane na schemacie: Schemat 45. Informacje zawarte w parametrze lParam komunikatów klawiatury Poszczególne części oznaczyÅ‚em kolorami - tak, aby uÅ‚atwić ci ich dopasowanie do poniższych opisów. Część z szarymi konturami nie jest używana przez aktualne wersje Windows i zostaÅ‚a zarezerwowana dla ewentualnych przyszÅ‚ych celów. WytÅ‚umaczmy wiÄ™c po kolei znaczenie każdej z tych danych - poczynajÄ…c od najmÅ‚odszego bitu: licznik powtórzeÅ„ (ang. repeat count) okreÅ›la nam liczbÄ™ przyciÅ›nięć klawiszy, jakie reprezentuje otrzymany komunikat. W przypadku zdarzeÅ„ zwolnienia klawisza jest to zawsze 1; dla pozostaÅ‚ych wiadomoÅ›ci najczęściej też tak jest. Czasem jednak Windows nie nadąża z produkcjÄ… oddzielnych komunikatów dla każdego powtórzenia klawisza - dzieje siÄ™ tak wtedy, gdy ustawimy dużą czÄ™stotliwość powtarzania w Panelu Sterowania i przytrzymamy wciÅ›niÄ™ty klawisz kod skanowania (OEM) (ang. OEM scan code) jest niczym innym, jak tylko sprzetowym kodem, jaki klawiatura wysyÅ‚a do swojego sterownika. Jak pamiÄ™tasz, odbywa siÄ™ to na samym poczÄ…tku potoku klawiszy. DziaÅ‚anie sterownika klawiatury sprawia, że nie musimy siÄ™ martwić o interpretacjÄ™ kodu skanowania, zależnego od sprzÄ™tu, ponieważ mamy uniwersalne kody wirtualnych klawiszy. Niemniej jednak Windows doÅ‚Ä…cza oryginalny kod do komunikatu, na wypadek gdyby byÅ‚ on komuÅ› potrzebny flaga klawisza rozszerzonego (ang. extended key flag) mówi nam, czy wciÅ›niÄ™ty/zwolniony klawisz należy do tzw. zestawu rozszerzonego klawiatury 101- lub 102-klawiszowej. Obecnie takie modele nie sÄ… niczym nadzwyczajnym, wiÄ™c informacja ta nie sÅ‚uży zbyt wielkim celom i jest najczęściej ignorowana kod kontekstowy (ang. context code) tak naprawdÄ™ nie jest żadnym kodem ( I caÅ‚e szczęście powiesz zapewne ;D). Jest to tylko przeÅ‚Ä…cznik wskazujÄ…cy na to, czy zdarzenie klawiatury zaszÅ‚o w czasie, gdy wciÅ›niÄ™ty byÅ‚ klawisz Alt. Ten klawisz ma specjalnÄ… rolÄ™ w Windows (zwiÄ…zanÄ… np. z paskami menu) i dlatego jego wciÅ›niÄ™cie jest tak ważne. Faktycznie, oprócz innego kodu kontekstwego, zdarzenia tych samych klawiszy z wciÅ›niÄ™ciem i bez wciÅ›niÄ™cia Alta skutujÄ… zupeÅ‚nie innymi komunikatami. Wspomnimy sobie także i o tym zjawisku przy omawianiu komunikatów o klawiszach 460 poprzedni stan klawisza (ang. previous key state) jest bardzo prostÄ… informacjÄ…. OkreÅ›la ona stan klawisza, którego dotyczy zdarzenie, przed wystÄ…pieniem tegoż zdarzenia. Bit ten może być ustawiony na 1 - co odpowiada klawiszowi poprzednio wciÅ›niÄ™temu, lub na 0 - w przeciwnym wypadku stan przejÅ›cia (ang. transition state) precyzuje rodzaj zdarzenia, czyli co takiego staÅ‚o siÄ™ z klawiszem. Wartość 0 w tym bicie oznacza wciÅ›niÄ™cie klawisza, a 1 - zwolnienie. Pozostaje jeszcze pytanie - jak odczytywać te wartoÅ›ci? Wymaga to skonstruowania odpowiedniej maski bitowej i wykonania jej koniunkcji z wartoÅ›ciÄ… lParam. Pózniej należy jeszcze dosunąć wyizolowane bity do prawej strony za pomocÄ… operacji przesuniÄ™cia bitowego w prawo. Jeżeli nie możesz sobie poradzić z tym zadaniem, zajrzyj do Dodatku C, Manipulacje bitami. NajproÅ›ciej jest uzyskać licznik powtórzeÅ„. Zauważmy, że stanowi on 16 pierwszych bitów dwusÅ‚owa lParam, czyli jego dolnÄ… połówkÄ™. TÄ™ zaÅ› możemy wyekstrahować poprzez makro LOWORD(). LOWORD(lParam) jest wiÄ™c wartoÅ›ciÄ… licznika powtórzeÅ„ w komunikatach klawiatury. Komunikaty o klawiszach Najbardziej naturalnymi zdarzeniami klawiatury sÄ… komunikaty o klawiszach (ang. keystroke messages). Windows wysyÅ‚a je, gdy wykryje wciÅ›niÄ™cie lub puszczenie jednego z wirtualnych klawiszy, przyporzÄ…dkowanych oczywiÅ›cie tym realnie istniejÄ…cym na klawiaturze. W tym paragrafie przyjrzymy siÄ™ tego rodzaju komunikatom. Przedstawiamy je Mamy cztery komunikaty o zdarzeniach klawiszy w Windows. Przedstawia je wszystkie poniższa tabelka: zdarzenie wciÅ›niÄ™cie klawisza zwolnienie klawisza rodzaj WM_KEYDOWN WM_KEYUP pozasystemowe WM_SYSKEYDOWN WM_SYSKEYUP systemowe Tabela 53. Komunikaty o zdarzeniach klawiszy Nie jest żadnÄ… niespodziankÄ…, że wystÄ™pujÄ… komunikaty o wciÅ›niÄ™ciu i zwolnieniu klawisza. Oba te zdarzenia zachodzÄ… zwykle w parach, podobnie jak to siÄ™ dzieje w przypadku przycisków myszy. Możliwe jest jednak, że Windows wyÅ›le wiÄ™cej komunikatów WM_[SYS]KEYDOWN - bÄ™dzie tak, jeżeli raz wciÅ›niÄ™ty klawisz przytrzymamy na dÅ‚użej. Wówczas może zostać wysÅ‚ana wiÄ™ksza liczba komunikatów lub też nadchodzÄ…ce wiadomoÅ›ci WM_[SYS]KEYDOWN bÄ™dÄ… miaÅ‚y odpowiednio ustawiony licznik powtórzeÅ„ w dolnym sÅ‚owie parametru lParam. Zależy to od ustawionej przez użytkownika czÄ™stotliwoÅ›ci powtórzeÅ„ we WÅ‚aÅ›ciwoÅ›ciach Klawiatury Panelu Sterowania. Atoli najczęściej interesuje nas tylko sam fakt, iż dany przycisk jest aktualnie wciÅ›niÄ™ty i dlatego na komunikat WM_[SYS]KEYDOWN reagujemy podobnie jak na inne, proste zdarzenia notyfikujÄ…ce. Oto przykÅ‚ad reakcji na wciÅ›niÄ™cie klawisza Esc: case WM_KEYDOWN: { if (wParam == VK_ESCAPE) if (MessageBox("Czy na pewno chcesz wyjść?", "WyjÅ›cie", MB_YESNO | MB_ICONQUESTION) == IDYES)) 461 PostQuitMessage (0); return 0; } Sprawdzenia, czy wciskanym klawiszem jest rzeczywiÅ›cie klawisz ucieczki, dokonujemy, konfrontujÄ…c wartość wParam ze staÅ‚Ä… VK_ESCAPE. Wspomnimy jeszcze o takim porównaniu w dalszej części aktualnego paragrafu. Systemowe i inne ZauważyÅ‚eÅ› pewnie tajemnicze SYS w nazwach komunikatów WM_SYSKEYDOWN oraz WM_SYSKEYUP. Jego obecność znaczy te zdarzenia jako systemowe (ang. system keystrokes). Należy to rozumieć w ten sposób, iż sÄ… one ważniejsze dla samego systemu Windows niż dla pracujÄ…cych w nim aplikacji. WM_SYSKEYDOWN i WM_SYSKEYUP generowane sÄ… zwykle dla klawiszy wciÅ›niÄ™tych w poÅ‚Ä…czeniu z przytrzymanym klawiszem Alt. Takie kombinacje sÄ… ważne dla funkcjonowania caÅ‚ego systemu, gdyż nierzadko sÄ… im przypisane pewne standardowe lub niestandardowe akcje. Wezmy chociażby Alt+Tab, który sÅ‚uży do przeÅ‚Ä…czania siÄ™ miÄ™dzy oknami uruchomionych aplikacji, czy też Alt+Esc. IstniejÄ… też tzw. akceleratory, stanowiÄ…ce skróty do czÄ™sto używanych poleceÅ„ menu aplikacji. Kombinacje klawiszy dla akceleratorów ustala programista tworzÄ…cy menu, zaÅ› zapewnienie ich prawidÅ‚owej obsÅ‚ugi spoczywa potem wyÅ‚Ä…cznie na barkach Windows. Dlatego też wciÅ›niÄ™cia klawiszy akceleratorów sÄ… traktowane jako systemowe. O akceleratorach bÄ™dziemy mówić dokÅ‚adniej przy opisywaniu pasków menu dla okien. Aatwo zauważyć, że systemowe zdarzenia klawiatury sÄ… podobne w swej istocie i przeznaczeniu do pozaklienckich komunikatów myszy. Analogicznie też wyglÄ…da ich ewentualna obsÅ‚uga - jeżeli jest potrzebna. Ze wzglÄ™du na ważny charakter tych zdarzeÅ„ powinniÅ›my zawsze przekazywać je do domyÅ›lnej procedury zdarzeniowej - czyli obsÅ‚ugiwać w ten oto sposób: case WM_SYSKEYDOWN: // albo WM_SYSKEYUP, a także WM_SYS[DEAD]CHAR kod_obsÅ‚ugi_komunikatu return DefWindowProc(hWnd, uMsg, lParam, wParam); W przeciwnym wypadku okno może stać siÄ™ caÅ‚kowicie odporne na systemowe kombinacje klawiszy, co z kolei skutkować bÄ™dzie np. niemożnoÅ›ciÄ… użycia Alt+Tab do zmiany aktywnego programu. PozostaÅ‚e dwa komunikaty - WM_KEYDOWN i WM_KEYUP - sÄ… ignorowane przez domyÅ›lnÄ… procedurÄ… zdarzeniowÄ…. Ich obsÅ‚uga w programie nie napotyka wiÄ™c na żadne ograniczenia i może być realizowana dowolnie (albo nijak, jak to siÄ™ dziaÅ‚o dotÄ…d). Parametry Omawiane komunikaty, tak samo jak wszystkie powiadomienia od klawiatury, wykorzystujÄ… oba parametry wParam i lParam. wParam zawiera kod identyfikujÄ…cy klawisz. Jest to tzw. kod wirtualnego klawisza (ang. virtual-key code). To 16-bitowa, niezależna od sprzÄ™tu wartość przyporzÄ…dkowana każdemu wirtualnemu klawiszowi. Ponieważ klawisze sÄ… wÅ‚aÅ›nie wirtualne, czyli nieistniejÄ…ce w rzeczywistoÅ›ci (choć majÄ…ce rzeczywiste odpowiedniki), ich kody sÄ… 462 uniwersalne. W każdej wersji systemu Windows, obsÅ‚ugujÄ…cej w miarÄ™ normalnÄ… klawiaturÄ™130, kod każdego wirtualnego klawisza jest zawsze taki sam. Jest to oczywiÅ›cie bardzo wygodne, gdyż nie musimy przez to martwić siÄ™ o sprzÄ™towe kody (czyli kody skanowania, ang. scan code), jakie wysyÅ‚ajÄ… poszczególne modele klawiatur. Poza tym wiemy, że do zestawu klawiszy wirtualnych wÅ‚Ä…czono też przyciski myszy. Nie oznacza to aczkolwiek, że zdarzenia WM_KEYDOWN i WM_KEYUP odnoszÄ… siÄ™ także do kliknięć myszkÄ…. Klawiszowość przycisków myszy objawia siÄ™ tylko tym, że ich stan możemy sprawdzać za pomocÄ… funkcji w rodzaju GetKeyState(). MówiliÅ›my już o tym w podrozdziale o myszce, a niedÅ‚ugo rozciÄ…gniemy temat także na klawiaturÄ™. Natomiast o parametrze lParam i zawartym w nim koglomeracie szeÅ›ciu informacji zdoÅ‚aÅ‚em już napisać caÅ‚kiem sporo wyjaÅ›nieÅ„; możesz teraz do nich powrócić. PamiÄ™taj przy tym, że kombinacja bitowa o opisanym znaczeniu jest treÅ›ciÄ… lParam w przypadku każdego z oÅ›miu komunikatów klawiatury. Nie wiem jak ty, ale ja sÄ…dzÄ™, że to dobra wiadomość dla każdego programisty :) Komunikaty o znakach Drugim rodzajem zdarzeÅ„ klawiatury, z jakim spotykamy siÄ™ w Windows, sÄ… komunikaty o znakach (ang. character messages). Zamiast informować tylko o przyciÅ›niÄ™ciach klawiszy, komunikaty te mówiÄ… raczej o rzeczywistych znakach, wprowadzonych do programu. Obecność tych notyfikacji w Windows API jest niczym innym jak tylko uÅ‚atwieniem dla programisty. Komunikaty te sÄ… bowiem automatycznie wyprowadzane ze zdarzeÅ„ klawiszy. W zasadzie każda aplikacja mogÅ‚aby to robić sama, jednak system wyrÄ™cza je w tej czynnoÅ›ci. Za generowanie komunikatów o znakach odpowiada funkcja TranslateMessage(). Jej dziaÅ‚anie sprowadza siÄ™ w skrócie do: wyÅ‚owienia z kolejki komunikatu WM_[SYS]KEYDOWN sprawdzenia kodu wirtualnego klawisza, jaki jest przyporzadkowany takiemu komunikatowi przetÅ‚umaczenia go na kod odpowiedniego znaku, jeżeli jest to możliwe. W takim tÅ‚umaczeniu sÄ… uwzglÄ™dnianie również takie okolicznoÅ›ci jak wciÅ›niÄ™ty klawisz Shift czy aktywny Caps Lock posÅ‚ania do kolejki komunikatu WM_[SYS][DEAD]CHAR, zawierajÄ…cego przetÅ‚umaczony kod Komunikaty o znakach sÄ… wiÄ™c bardziej wysokopoziomowÄ… formÄ… obsÅ‚ugi klawiatury. Nie sÄ… one tylko prostymi informacjami, mówiÄ…cymi o wciÅ›niÄ™ciu klawisza, lecz przynoszÄ… ze sobÄ… także dodatkowe dane. UwzglÄ™dniajÄ…c stan kluczowych klawiszy oraz ustawienia jÄ™zykowe klawiatury, komunikaty te powiadamiajÄ… o wprowadzonych przez użytkownika znakach - nie zaÅ› o klawiszach, które wciska. PotrafiÄ… one rozróżnić znak wielkiej litery A od jej maÅ‚ej wersji ( a ), podczas gdy zdarzenia klawiszy mogÄ… jedynie poinformować o wciÅ›niÄ™ciu klawisza A. Komunikaty o znakach biorÄ… zatem pod uwagÄ™ szerszy kontekst przychodzÄ…cych do systemu informacji od klawiatury. Prosimy na scenÄ™ Jest raczej czystym przypadkiem to, że komunikaty o znakach również wystÄ™pujÄ… w liczbie czterech rodzajów. Nie sÄ… one jednak żadnymi odpowiednikami zdarzeÅ„ klawiszy, lecz zupeÅ‚nie inaczej zorganizowanymi powiadomieniami. Wszystkie komunikaty o znakach ujmuje nam poniższa tabelka (jest w niej także odpowiedni podziaÅ‚ tychże komunikatów): 130 Tzn. zawierajÄ…cÄ… litery z jÄ™zyków europejskich, a nie np. zestawy znaków jÄ™zyka chiÅ„skiego czy japoÅ„skiego. 463 rodzaj znaku zwykÅ‚y znak martwy znak rodzaj zdarzenia WM_CHAR WM_DEADCHAR pozasystemowe WM_SYSCHAR WM_SYSDEADCHAR systemowe Tabela 54. Komunikaty o zdarzeniach znaków Jak widać, także i tu można wyróżnić dwie nakÅ‚adajÄ…ce siÄ™ na siebie grupy komunikatów. PierwszÄ… z nich znamy już dość dobrze, podczas gdy druga wydaje siÄ™ dość tajemnicza - również z nazwy (martwy znak& ?). Dlatego też, aby mieć peÅ‚nÄ… jasność, wytÅ‚umaczymy sobie obie :) Aby być zupeÅ‚nie Å›cisÅ‚ym muszÄ™ wspomnieć, że istnieje jeszcze jeden komunikat o znaku - WM_UNICHAR. Różni siÄ™ on od WM_CHAR tym, iż kod znaku, jaki przynosi w parametrze wParam, jest zapisany w 32-bitowej wersji standardu Unicode (UTF-32). WM_CHAR używa tylko 16 bitów (UTF-16), jednak wiemy dobrze, że wystarcza to w zupeÅ‚noÅ›ci na reprezentacjÄ™ wszystkich znaków niemal każdego cywlizowanego i żywego jÄ™zyka. Zawarte w Windows wsparcie dla 4-bajtowych kodów jest wiÄ™c dalekim wybiegniÄ™ciem przed orkiestrÄ™ - zwÅ‚aszcza, że nawet dwubajtowy unikod nie jest jeszcze powszechnie wykorzystywany. Niemniej jednak należy siÄ™ spodziewać, że w bliższej lub (raczej) dalszej przyszÅ‚oÅ›ci pozostaÅ‚e komunikaty o znakach zostanÄ… przestawione na UTF-32. Wówczas WM_UNICHAR zaniknie. Ale zanim to siÄ™ stanie, możesz swobodnie przeczytać opis tego komunikatu w MSDN :D Systemowe - raz jeszcze WÅ›ród komunikatów o znaków również wystÄ™puje podziaÅ‚ na te systemowe i pozasystemowe. Kryteria owego podziaÅ‚u sÄ… też identyczne. Przypominam, że systemowe zdarzenie klawiatury jest wysyÅ‚ane w sytuacji, gdy towarzyszy mu wciÅ›niÄ™ty klawisz Alt. Kombinacje zawierajÄ…ce ten klawisz sÄ… bardzo ważne dla systemu jako caÅ‚oÅ›ci oraz ogólnego sposobu jego funkcjonowania. Systemowe komunikaty o znakach - WM_SYSCHAR i WM_SYSDEADCHAR - powinny być przetwarzane bez naruszania ich normalnej, domyÅ›lnej reakcji, za którÄ… odpowiada DefWindowProc(). Komunikaty te muszÄ… wiÄ™c ostatecznie trafić do tej standardowej procedury - również wtedy, kiedy po drodze przeszÅ‚y przez tÄ… naszÄ…. PrzykÅ‚ad obsÅ‚ugi systemowego komunikatu klawiatury podaÅ‚em w poprzednich paragrafie o zdarzeniach klawiszy. Zajrzyj tam, jeżeli tego potrzebujesz. UmarÅ‚ znak, niech żyje znak Zdarzenia WM_DEADCHAR i WM_SYSDEADCHAR noszÄ… intrygujÄ…cÄ… nazwÄ™ komunikatów o martwych znakach (ang. dead characters messages). Chociaż ich obsÅ‚użenie nie jest w wiÄ™kszoÅ›ci przypadków konieczne, omówienie tych zdarzeÅ„ może być interesujÄ…ce. NajproÅ›ciej mówiÄ…c, martwe znaki nie reprezentujÄ… samodzielnie żadnego symbolu, żadnej litery. Ich pojawianie siÄ™ wiąże siÄ™ wyÅ‚Ä…cznie z pewnymi ukÅ‚adami klawiatury, dostosowanymi do niektórych jÄ™zyków. Dobrym przykÅ‚adem jest jÄ™zyk niemiecki i wystÄ™pujÄ…ce w nim litery ä , ü czy ö . MajÄ… one tak zwany przegÅ‚os (niem. umlaut), który zmienia wymowÄ™ tych gÅ‚osek w stosunku do zwykÅ‚ych a , u i o . Faktycznie sÄ… one odrÄ™bnymi literami niemieckiego alfabetu. Wprowadzenie tych znaków do programu może siÄ™ odbywać na wiele sposobów, gdyż jak wiemy nie wystÄ™pujÄ… one na standardowej klawiaturze. JednÄ… z dróg może być zaprzÄ™gniÄ™cie do pracy okreÅ›lonych kombinacji klawiszy: najczęściej jest to Prawy Alt plus odpowiednia zwykÅ‚a litera. Wpisanie znaku ö odbywaÅ‚oby siÄ™ wówczas w ten sposób, iż użytkownik najpierw wciska i przytrzymuje Prawy Alt, a nastÄ™pnie uderza w 464 klawisz O. W wyniku tej czynnoÅ›ci na ekranie pojawia siÄ™ znak ö lub Ö (zależnie od stanu klawisza Shift i Caps Lock). Istnieje jeszcze inna metoda i to interesuje nas teraz bardziej. Otóż wprowadzenie znaku diakrytycznego może siÄ™ dokonywać poprzez oddzielne wciÅ›niÄ™cia dwóch klawiszy. Pierwszy wysyÅ‚a do systemu jedynie sam przegÅ‚os, natomiast drugi jest dopiero wÅ‚aÅ›ciwÄ… literÄ…, która ów umlaut otrzymuje. Ostatecznie uzyskujemy pożądany symbol. Gdzie jest wiÄ™c ten martwy znak?& Otóż jest nim sam przegÅ‚os - po wciÅ›niÄ™ciu odpowiadajÄ…cego mu klawisza, do okna z fokusem wysyÅ‚any jest komunikat WM_[SYS]DEADCHAR (przedtem oczywiÅ›cie WM_[SYS]KEYDOWN) zawierajÄ…cy kod znaku przegÅ‚osu. W tej chwili nie wiadomo jeszcze, jaka litera zostanie zaraz wpisana, ale rzeczony komunikat mówi nam, iż bÄ™dzie posiadaÅ‚a dany ozdobnik (w tym przypadku przegÅ‚os). Nie jest to wielce porywajÄ…ca informacja i nie ma koniecznoÅ›ci jej odczytywania. W nastÄ™pujÄ…cym dalej komunikacie WM_[SYS]CHAR dostajemy jÄ… bowiem niejako ponownie, lecz w bardziej użytecznej formie. Komunikat o żywym znaku bÄ™dzie mianowicie zawieraÅ‚ kod litery z już zaaplikowanym przegÅ‚osem (a wiÄ™c np. ö lub Ö ), nie zaÅ› odpowiadajÄ…cej mu litery bez niego (czyli o lub O ). PomieniÄ™ciem WM_[SYS]DEADCHAR nie czynimy wiÄ™c żadnej szkody ani sobie, ani systemowi. Dlatego też prawie zawsze możemy sobie na to pozwolić. W polskim ukÅ‚adzie klawiatury martwe znaki generuje klawisz tyldy (~). W poÅ‚Ä…czeniu z klawiszami A, L, O, Z, X itd. wprowadza on znaki diakrytyczne: Ä… , Å‚ , ó , ż , z itd. Parametry Komunikaty o znakach zachowujÄ… podanÄ… na poczÄ…tku sekcji konwencjÄ™ co do znaczenia parametrów wParam i lParam. Spójrzmy, co to oznacza w tym przypadku. Tradycyjnie wParam zawiera ważny dla zdarzenia kod. Tutaj jest to kod wprowadzonego znaku - nie klawisza, lecz wÅ‚aÅ›nie znaku. Jest to 16-bitowa liczba, która identyfikuje jeden z kilkudziesiÄ™ciu tysiÄ™cy znaków standardu Unicode (UTF-16). Standard ten w zupeÅ‚noÅ›ci wystarcza na kodyfikacjÄ™ zbioru liter wszystkich jÄ™zyków indoeuropejskich oraz używanych symboli matematycznych, fizycznych i innych. Nie bedÄ™ tu szczegółowo omawiaÅ‚ Unicode, bo jest to materiaÅ‚ na caÅ‚kiem sporÄ… książkÄ™; jeżeli interesuje ciÄ™ ten temat, możesz na poczÄ…tek zajrzeć na oficjalnÄ… stronÄ™ internetowÄ… standardu. MuszÄ™ jednak wspomnieć, co staÅ‚o siÄ™ z tablicÄ… znaków ASCII i ANSI, znanÄ… zapewne wiÄ™kszoÅ›ci czytelników. A zatem - nie staÅ‚o siÄ™ nic. Pierwsze 128 liczb (0x00 do 0x7F) jest nadal kodami znaków w systemie ASCII. Wraz z kolejnymi 128 wartoÅ›ciami (0x80 do 0xFF) tworzÄ… one tabelÄ™ ANSI. Ten drugi zestaw kodów jest specyficzny dla Windows i oznaczany nazwÄ… strony kodowej - w Polsce jest to Windows-1250. Różni siÄ™ ona chociażby od DOSowej strony 852 czy też unormowanego i popularnego w polskim Internecie systemu ISO-8859-2. Jest to nieunikniona konsekwencja stosowania tylko 256 znaków ANSI - Unicode ze swymi 65536 miejscami na znaki rozwiÄ…zuje wiÄ™kszość tego rodzaju kwestii. Znaki z ważniejszych stron kodowanych możesz znalezć w Tablicach C. A co lParam? Nic nadzwyczajnego. Parametr ten zawiera znany już agregat szeÅ›ciu danych. Raczej jednak nie majÄ… one praktycznego znaczenia, gdyż sÄ… dokÅ‚adnÄ… kopiÄ… lParam z komunikatu WM_[SYS]KEYDOWN, poprzedzajÄ…cego zdarzenie znaku. Zwykle wiÄ™c informacje odczytuje siÄ™ z wÅ‚aÅ›ciwego im komunikatu klawisza, a nie znaku. 465 Kontrola wejÅ›cia od klawiatury Zabawa z klawiaturÄ… w Windows API nie ogranicza siÄ™ li tylko do odbierania zdarzeÅ„ i reakcji na nie. Tak samo jak dla myszy możliwe jest przejÄ™cie wiÄ™kszej kontroli nad współpracÄ… sprzetu z systemem operacyjnym. I tym wÅ‚aÅ›nie zajmiemy siÄ™ tej sekcji. Pobieranie stanu klawiszy Otrzymywanie komunikatów o klawiszach jest biernym sposobem kooperacji z klawiaturÄ…. IstniejÄ… też metody, w których to aplikacja ma wiÄ™kszÄ… kontrolÄ™ nad tym procesem i sama sprawdza stany poszczególnych klawiszy. O takim sprawdzaniu opowiemy sobie w tym paragrafie. Obejmie to miÄ™dzy innymi dokÅ‚adne omówienie funkcji GetKeyState() i GetAsyncKeyState(), z którymi zapoznaliÅ›my siÄ™ już w podobnej sytuacji dotyczÄ…cej myszy. Stan pojedynczego klawisza Jak pamiÄ™tamy, sprawdzaniu stanu pojedynczego klawisza sÅ‚uży funkcja GetKeyState(): SHORT GetKeyState(int nVirtKey); Należy jej podać kod kontrolowanego, wirtualnego klawisza. Jest to jedna ze staÅ‚ych VK_, ewentualnie (w przypadku klawiszy liter i liczb) kod ASCII odpowiedniego znaku. W zamian dostajemy& wynik :) Jest nim wartość typu SHORT (2 bajty) i skÅ‚ada siÄ™ z dwóch części: górny bajt (czytany przez HIBYTE()) po zrzutowaniu na typ logiczny131 informuje o wciÅ›niÄ™ciu klawisza. true, TRUE lub ogólnie wartość różna od zera wskazuje na to, że klawisz jest wciÅ›niÄ™ty. Nietrudno siÄ™ domyÅ›lić, że zero znaczy coÅ› przeciwnego :D dolny bajt (LOBYTE()) daje wiedzÄ™ o tym, czy klawisz jest wÅ‚Ä…czony. WiÄ™kszoÅ›ci klawiszy nie dotyczy ta wÅ‚asność, jest ona ważna tylko dla locków : Num Lock, Caps Lock i Scroll Lock132. Podobnie jak wyżej, logiczna prawda wskazuje na wÅ‚Ä…czenie danego klawisza, faÅ‚sz - przeciwnie. Trzeba jeszcze powiedzieć jednÄ… bardzo ważnÄ… rzecz na temat tej funkcji - skÄ…d ona bierze stan klawiszy?& Wcale nie pyta o niego samej klawiatury (tak robi GetAsyncKeyState()), lecz uzyskuje go na podstawie kolejki komunikatów. Decyduje tu ostatnio otrzymany komunikat klawiatury, dotyczÄ…cy sprawdzanego klawisza. Uzyskiwane informacje sÄ… wiÄ™c zależne od komunikatów, jakie otrzymuje wÄ…tek (tzn. caÅ‚a aplikacja - najczęściej) i nie dotyczÄ… globalnego stanu klawiatury. Nie pochodzÄ… z poziomu przerwaÅ„ sprzetowych, lecz zdarzeÅ„ systemowych. Czy jest to wada? Nieszczególnie. GetKeyState() używamy głównie do sprawdzania stanu takich klawiszy jak Shift i Ctrl podczas przetwarzania zdarzeÅ„ innych klawiszy, np.: case WM_KEYDOWN: { switch (wParam) { case VK_F1: if (HIBYTE(GetKeyState(VK_SHIFT)) // kombinacja Shift+F1 131 Jak wiesz, takie rzutowanie można zastÄ…pić porównaniem z zerem - także tym niejawnym, stosowanym w warunkach if czy pÄ™tli. 132 Klawiszom tym odpowiadajÄ… staÅ‚e VK_NUMLOCK, VK_CAPITAL i VK_SCROLL. 466 else // samo F1 break: // itd. } } Stan tych innych klawiszy otrzymujemy w komunikatach i to wÅ‚aÅ›nie ich powinniÅ›my używać do reakcji na wciÅ›niÄ™cia i zwolnienia klawiszy w normalnych aplikacjach. Stan caÅ‚ej klawiatury W Windows API znajdziemy też funkcjÄ™ pobierajÄ…ca stan wszystkich klawiszy - GetKeyboardState(): BOOL GetKeyboardState(PBYTE lpKeyState); DziaÅ‚a ona mniej wiÄ™cej tak, jak zastosowanie GetKeyState() dla parametrów z przedziaÅ‚u od zera do 256. GetKeyboardState() przyjmuje mianowicie tablicÄ™ 256 bajtów, której indeksami sÄ… kody kolejnych wirtualnych klawiszy. Wynikiem funkcji jest TRUE dla operacji zakoÅ„czonej powodzeniem i zero (FALSE) w innym przypadku. Jest jeszcze funkcja SetKeyboardState(), pozwalajÄ…ca ustawić chwilowy stan klawiatury dla danego wÄ…tku. Możesz o niej poczytać w MSDN. LepszÄ… formÄ… zmiany stanu klawiatury jest aczkolwiek użycie symulowane wejÅ›cia, czyli funkcji SendInput(). Asynchroniczne pobieranie stanu klawisza DrugÄ… z funkcji stworzonych do uzyskiwania stanu pojedynczego klawisza jest GetAsyncKeyState(): SHORT GetAsyncKeyState(int vKey); Tak samo przynosimy jej kod wirtualnego klawisza, który chcemy sprawdzać. A co z wynikiem? Również jest podzielony na dwie części po jednym bajcie każda: starszy bajt (HIBYTE()) znaczy to samo, co w GetKeyState(): po konwersji na wartość logicznÄ… informuje o wciÅ›niÄ™tym klawiszu (true) lub zostawionym w spokoju (false) mÅ‚odszy bajt (LOBYTE()) wskazuje, czy klawisz byÅ‚ wciskany (logiczna prawda) od czasu ostatniego wywoÅ‚ania GetAsyncKeyState(). Trzeba jednak wiedzieć, że to ostatnie wywoÅ‚anie wcale nie musi pochodzić z naszej aplikacji i dlatego omawiana tu wartość nie ma praktycznego sensu Co różni tÄ™ funkcjÄ™ od GetKeyState()? Pewne wskazówki co do tego mogÅ‚eÅ› wyczytać miÄ™dzy wierszami powyższego opisu i w akapicie o tamtej pokrewnej funkcji. Powiedzmy jednak wprost, o co chodzi. Otóż GetAsyncKeyState(), czyniÄ…c zadość swej nazwie, pobiera tzw. asynchroniczny stan klawisza. Asynchroniczy to znaczy niezależny od wÄ…tku, a mówiÄ…c po ludzku - globalny dla caÅ‚ego systemu oraz niezależny od kolejki komunikatów. Funkcja ta pobiera dane bezpoÅ›rednio od sprzÄ™tu, niejako z pominiÄ™ciem mechanizmu zdarzeÅ„ Windows. Przejawia siÄ™ to chociażby w tym, że kontroluje stan fizycznych, a nie logicznych przycisków myszki, o czym wspomniaÅ‚em przy pierwszym spotkaniu z tÄ… funkcjÄ…. 467 Ze wzglÄ™du na ten sposób dziaÅ‚ania GetAsyncKeyState() jest przydatna w programach czasu rzeczywistego. BÄ™dziemy wiÄ™c używać tej funkcji do pobierania stanu klawiszy w naszych grach - przynajmniej na poczÄ…tku. Przygotuj siÄ™ zatem na wiele dÅ‚ugich i owocnych spotkaÅ„ z funkcjÄ… GetAsyncKeyState() ;) Symulowanie klawiatury W poprzednim podrozdziale nauczyliÅ›my siÄ™ udawać myszkÄ™. Nie ma wiÄ™c powodu, abyÅ›my tego samego nie mogli czynić z klawiaturÄ…. Jest to o tyle proste, iż odbywa siÄ™ za pomocÄ… niemal tych samych narzÄ™dzi. SÄ… nimi: funkcja SendInput() i struktura INPUT, które sobie przypomnimy, oraz struktura KEYBDINPUT, którÄ… teraz poznamy. A zatem do dzieÅ‚a! Funkcja SendInput() i struktura INPUT - powtórzenie Jak pamiÄ™tamy, do generowania sztucznych zdarzeÅ„ od urzÄ…dzeÅ„ wejÅ›ciowych sÅ‚uży funkcja SendInput(): UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize); Przypomnijmy, że w pierwszym parametrze nInputs należy jej przekazać tablicÄ™ struktur INPUT o liczbie elementów okreÅ›lonej drugim parametrem, pInputs. Trzeci argument trzeba natomiast ustawić na rozmiar struktury INPUT, czyli po prostu sizeof(INPUT). Pojedynczy element przekazywanej do funkcji tablicy opisuje jedno symulowane zdarzenie od urzÄ…dzenia wejÅ›ciowego. Czyni to za pomocÄ… struktury INPUT: struct INPUT { DWORD type; union { MOUSEINPUT mi; KEYBDINPUT ki; HARDWAREINPUT hi; }; }; W niej też pole type okreÅ›la nam zródÅ‚o zdarzenia, czyli rodzaj urzÄ…dzenia. Niedawno, zajmujÄ…c siÄ™ myszkÄ…, ustawialiÅ›my je na INPUT_MOUSE. Obecnie, gdy chcemy emulować klawiaturÄ™, posÅ‚użymy siÄ™ raczej staÅ‚Ä… INPUT_KEYBOARD. Wiąże siÄ™ to także z porzuceniem pola mi, używanego dotÄ…d. Zdarzenie klawiatury musimy bowiem zapisać w polu ki, należącym do innego typu - KEYBDINPUT. Struktura KEYBDINPUT Struktura opisujÄ…ca zdarzenie klawiatury przedstawia siÄ™ w ten oto sposób: struct KEYBDINPUT { WORD wVk; WORD wScan; DWORD dwFlags; DWORD dwTime; ULONG_PTR dwExtraInfo; }; 468 Co z szczęście - tylko pięć pól ;D Ich znaczenie opisuje niniejsza tabelka: typ parametry opis W którymÅ› z tych parametrów należy podać kod klawisza, którego ma dotyczyć zdarzenie. Może to być kod wirtualnego klawisza - wtedy wprowadzamy go w wVk - lub kod skanowania (OEM) - wówczas wykorzystujemy pole wScan. WORD wVk Nieużywane pole wypeÅ‚niamy zwykle zerem lub ignorujemy. WORD wScan Znaczenie obu tych pól zmienia aczkolwiek flaga KEYEVENTF_UNICODE, jeżeli jest ustawiona w polu dwFlags. Za chwilÄ™ powiemy nieco wiÄ™cej na ten temat. SÄ… to flagi kontrolujÄ…ce produkowane zdarzenie. Ich lista DWORD dwFlags jest podana poniżej. Te dwa pola majÄ… identyczne przeznaczenie, jak time i dwExtraInfo w strukturze MOUSEINPUT. Przypomnijmy tylko, że pierwsze z nich okreÅ›la moment wystÄ…pienia DWORD dwTime symulowanego zdarzenia w formie liczby milisekund od ULONG_PTR dwExtraInfo startu systemu (czyli rezultatu GetTickCount()). Drugie pole to natomiast jakieÅ› dodatkowe informacje zwiÄ…zane ze zdarzeniem, zwykle niewykorzystywane. Tabela 55. Pola struktury KEYBDINPUT Z tabelki dowiedzieliÅ›my siÄ™, że możliwe jest podanie kodu klawisza, który bierze udziaÅ‚ w generowanej akcji klawiatury. To jednak niewystarczajÄ…ca informacja i dlatego jest jeszcze pole dwFlags, bÄ™dÄ…ce kombinacjÄ… bitowÄ… odpowiednich flag. Flagi te podsumowuje nastÄ™pna tabela: flaga znaczenie Obecność tej flagi informuje funkcjÄ™ SendInput(), że ma brać pod uwagÄ™ pole wScan, a wiÄ™c sprzÄ™towy kod skanowania KEYEVENTF_SCANCODE klawisza. Analogicznie, jej brak sprawia, że ważne staje siÄ™ pole wVk, czyli że klawisz jest rozpoznawany na podstawie swego uniwersalnego kodu wirtualnego. TÄ™ flagÄ™ ustawiamy, gdy za pomocÄ… kodu skanowania (wScan) KEYEVENTF_EXTENDEDKEY generujemy zdarzenie klawisza rozszerzonego. Naturalnie, musi ona wystÄ…pić razem z KEYEVENTF_SCANCODE. Kiedy flaga ta jest ustawiona, symulowanym zdarzeniem KEYEVENTF_KEYUP bÄ™dzie zwolnienie klawisza. W przeciwnym wypadku klawisz zostanie programowo wciÅ›niÄ™ty. Pozwala na zasymulowanie wprowadzania znaku Unicode - jego kod powinien być w polu wScan. Z oczywistych wzglÄ™dów wszystkie znaki Unicode nie sÄ… dostÄ™pne na klawiaturze, wiÄ™c system radzi sobie tutaj w inny sposób: jako wciÅ›niÄ™ty wirtualny klawisz przyjmuje specjalnÄ… staÅ‚Ä… VK_PACKET - można jÄ… potem znalezć w parametrze wParam komunikatów WM_[SYS]KEYDOWN/UP. Natomiast kod znaku w WM_[SYS]CHAR KEYEVENTF_UNICODE jest już podanym w wScan 16-bitowym kodem Unicode. W sumie wiÄ™c aplikacjom wydaje siÄ™ , że użytkownik nabraÅ‚ magicznej mocy wprowadzania kilkudziesiÄ™ciu tysiÄ™cy znaków bezpoÅ›rednio ze swojej skromnej, nieco ponadstuklawiszowej klawiatury. Flaga KEYEVENTF_UNICODE musi wystapić z KEYEVENTF_KEYUP, 469 flaga znaczenie lecz bez KEYEVENTF_SCANCODE. Tabela 56. Flagi bitowe pola dwFlags struktury KEYBDINPUT JakÄ… wiedzÄ™ nabyliÅ›my stÄ…d? Przede wszystkim takÄ…, że domyÅ›lnie generowanym zdarzeniem jest zawsze wciÅ›niÄ™cie klawisza; jeżeli chcemy symulować jego zwolnienie, musimy posÅ‚użyć siÄ™ flagÄ… KEYEVENTF_KEYUP. Poza tym wiemy też, że standardowo SendInput() bierze pod uwagÄ™ kod wirtualnego klawisza, czyli wartość pola wVk; jeÅ›li pragniemy oprzeć siÄ™ na kodzie skanowania (polu wScan), powinniÅ›my podać flagÄ™ KEYEVENTF_SCANCODE. Wreszcie poznaliÅ›my ciekawÄ… możliwość symulowania zdarzeÅ„ fizycznie niemożliwych, czyli bezpoÅ›redniego wprowadzania znaków z caÅ‚ego zestawu Unicode - dzieje siÄ™ to dziÄ™ki fladze KEYEVENTF_UNICODE. PrzykÅ‚ady Gdy mamy już za sobÄ… formalny opis narzÄ™dzia, czas przyjrzeć siÄ™ przykÅ‚adom jego wykorzystania. Najpierw wiÄ™c programowo przyciÅ›niemy klawisz Enter. Do wykonania tego zadania można posÅ‚użyć siÄ™ takim kodem: // deklaracja i wyzerowanie struktury INPUT INPUT Klawisz; ZeroMemory (&Klawisz, sizeof(INPUT)); // ustawienie pól struktury i wygenerowanie zdarzenia Klawisz.type = INPUT_KEYBOARD; // generujemy zdarzenie klawiatury... Klawisz.ki.wVk = VK_RETURN; // a dokÅ‚adniej klawisza Enter SendInput (1, &Klawisz, sizeof(INPUT)); // i voilÄ… :) Zauważmy, że nie musieliÅ›my w nim w ogóle zajmować siÄ™ polem dwFlags. Jest tak, gdyż domyÅ›lne jego opcje (odczytanie kodu wirtualnego klawisza i jego wciÅ›niÄ™cie) caÅ‚kowicie nam odpowiadajÄ…. Po wykonaniu powyższych wierszy przycisk Enter pozostaje wciÅ›niÄ™ty - pamiÄ™tajmy o tym. KonsekwencjÄ… tego jest ciÄ…gÅ‚e wysyÅ‚anie komunikatów WM_[SYS]KEYDOWN, zgodnie z ustawionÄ… czÄ™stotliwoÅ›ciÄ… powtarzania. Aby przerwać tÄ™ seriÄ™, musimy zwolnić wciÅ›niÄ™ty klawisz: INPUT Klawisz; ZeroMemory (&Klawisz, sizeof(INPUT)); // zwolnienie klawisza Klawisz.type = INPUT_KEYBOARD; // wskazujemy na klawiaturÄ™ Klawisz.ki.wVk = VK_RETURN; // kod klawisza Enter Klawisz.ki.dwFlags = KEYEVENTF_KEYUP; // flaga zwolnienia klawisza SendInput (1, &Klawisz, sizeof(INPUT)); // it s showtime! ;) W ten sposób klawisz Enter wróci do stanu wyjÅ›ciowego, ale jego wciÅ›niÄ™cie i puszczenie zostanie zarejestrowane. Pora na ostatni przykÅ‚ad, znacznie bardziej skomplikowany. Napiszemy ciekawÄ… funkcjÄ™, która zasymuluje wprowadzenie caÅ‚ego tekstu, podanego jej w parametrze - klawisz po klawiszu. Funkcja ta mogÅ‚aby wyglÄ…dać tak133: 133 Intensywnie używam tu Biblioteki Standardowej, wiÄ™c jeÅ›li nie znasz jej choć trochÄ™, możesz mieć problemy ze zrozumieniem kodu. Komentarze powinny jednak sporo wyjaÅ›niać. 470 #include #include #include // ---------------------------------------------------------------------- bool SymulujTekst(const std::string& strTekst) { // sprawdzamy, czy napis nie jest pusty if (strTekst.empty()) return false; // zapisujemy dÅ‚ugość napisu w pomocniczej zmiennej UINT uDlugosc = (UINT) strTekst.length(); /* generujemy tablicÄ™ zdarzeÅ„ */ // deklarujemy zmienne std::vector aZdarzenia; // rzeczona tablica INPUT Zdarzenie; // jedno zdarzenie // w tablicy potrzebne sÄ… dwa elementy dla każdego // znaku napisu (wciÅ›niÄ™cie i zwolnienie odpowiedniego klawisza) // i tyleż rezerwujemy aZdarzenie.reserve (uDlugosc * 2); // iterujemy po napisie i dla każdego znaku tworzymy dwa zdarzenia for (std::string::const_iterator i = strTekst.begin(); i != strTekst.end(); ++i) { // kontrolujemy, czy znak nalezy do zestawu ASCII if ((*i) > 0x7F) return false; // ustawiamy strukturÄ™ na parametry wspólne obu zdarzeniom ZeroMemory (&Zdarzenie, sizeof(INPUT)); Zdarzenie.type = INPUT_KEYBOARD; Zdarzenie.ki.wVk = (*i); // kod ASCII znaku == kod wirt. klaw. // dodajemy pierwsze zdarzenie - wciÅ›niÄ™cie klawisza aZdarzenia.push_back (Zdarzenie); // dodajemy drugie zdarzenie - zwolnienie klawisza Zdarzenie.ki.dwFlags = KEYEVENTF_KEYUP; aZdarzenia.push_back (Zdarzenie); } /* symulujemy zdarzenia */ // wywoÅ‚ujemy SendInput(), sprawdzajÄ…c liczbÄ™ poprawnych zdarzeÅ„ // rzutowanie const_cast w drugim parametrze jest konieczne ze // wzglÄ™du na ewidentny burak w deklaracji SendInput(), gdzie // ten parametr jest zwkÅ‚ym wskaznikiem, zmiast staÅ‚ym do staÅ‚ej if (SendInput(aTablica.size(), const_cast(aTablica.data()), sizeof(INPUT)) < (UINT) aTablica.size()) // gdy wygenerowane mniej zdarzeÅ„ niż trzeba, zwracamy false return false; // w koÅ„cu, zwracamy true return true; 471 } WadÄ… tej funkcji jest nieumiejÄ™tność generowania zdarzeÅ„ znaków spoza zestawu ASCII. Ten problem można jednak obejść, jeżeli zastosuje siÄ™ flagÄ™ KEYEVENTF_UNICODE. Spróbuj samodzielnie napisać poprawionÄ… wersjÄ™ funkcji - teraz lub pózniej, bo bÄ™dzie to częściÄ… pracy domowej na koniec rozdziaÅ‚u :D Ustawienia klawiatury Klawiatura to pospolite urzÄ…dzenie, które jest w dużym stopniu konfigurowalne. Windows posiada kilka opcji, umożliwiajÄ…cych zmianÄ™ jego parametrów - bÄ™dÄ… one treÅ›ciÄ… tej sekcji. Użyjemy tutaj kilka razy funkcji SystemParametersInfo(), zatem dobrze byÅ‚oby, gdybyÅ› przypomniaÅ‚ jÄ… sobie - z poprzedniego podrozdziaÅ‚u o myszce lub bezpoÅ›rednio z MSDN. Powtarzanie znaku Chyba najważniejszymi ustawieniami personalizacyjnymi klawiatury (albo jednymi z najważniejszych) sÄ… opcje powtarzania znaku. Mam tu na myÅ›li regulacjÄ™ czasu przytrzymywania klawisza, po którym nastÄ™puje powtarzanie, oraz szybkoÅ›ci duplikacji. Ustawienie nieodpowiednich dla ciebie parametrów może prowadzić albo do powstawania tttaakkkiiiicchhh bbłęędóóww w pisaniu, albo do frustracji spowodowanej dÅ‚ugim czekaniem na wyprodukowanie np. sekwencji myÅ›lników (-) imitujÄ…cych poziomÄ… liniÄ™. Z punktu widzenia użytkownika opcje powtarzania można ustawić w aplecie Panelu Sterowania WÅ‚aÅ›ciwoÅ›ci: Klawiatura. Jego interesujÄ…cy fragment wyglÄ…da tak: Screen 65. Opcje powtarzania znaku Widzimy tu dwa ustawienia, dostrajane za pomocÄ… suwaków: Opóznienie powtarzania ma wpÅ‚yw na czas przytrzywania klawisza, po upÅ‚yniÄ™ciu którego znak jest powtarzany CzÄ™stotliwość powtarzania reguluje szybkość produkcji kolejnych znaków przy wciÅ›niÄ™tym i przytrzymanym klawiszu 472 Nas, jako programistów, bÄ™dzie naturalnie interesować sposób manipulowania tymi opcjami za poÅ›rednictwem funkcji Windows API. Tym wiÄ™c zajmiemy siÄ™ w aktualnym paragrafie - przyjrzymy siÄ™ obu ustawieniom powtarzania znaku. Opóznienie powtarzania InterwaÅ‚ czasu, po jakim rozpocznie siÄ™ powtarzanie, możemy kontrolować za pomocÄ… funkcji SystemParametersInfo(). Pobranie wartoÅ›ci tego ustawienia wiąże siÄ™ z wykorzystaniem staÅ‚ej SPI_GETKEYBOARDDELAY i wyglÄ…da tak: UINT uOpoznienie; SystemParametersInfo (SPI_GETKEYBOARDDELAY, 0, &uOpoznienie, 0); W zmiennej, której adres należy podać w trzecim parametrze (pvParam) odnajdziemy teraz liczbÄ™ z przedziaÅ‚u od 0 do 3, mówiÄ…cÄ… jak dÅ‚ugi jest omawiany okres czasu. Faktyczna jego rozciÄ…gÅ‚ość zależy od sprzÄ™tu i wynosi mniej wiÄ™cej 250 milisekund dla ustawienia 0, a nastÄ™pnie o tyleż przyrasta z każdym krokiem (osiÄ…ga wiÄ™c ok. 1 sekundÄ™ dla ustawienia 3). TakÄ… samÄ… jednostkÄ™ dla opóznienia musimy przyjąć, gdy chcemy je zmodyfikować. SÅ‚uży do tego staÅ‚a SPI_SETKEYBOARDDELAY użyta na przykÅ‚ad tak: SystemParametersInfo (SPI_SETKEYBOARDDELAY, 3, NULL, 0); W drugim parametrze SystemParametersInfo() należy podać nowÄ… wartość opcji. W powyższym kodzie bÄ™dzie wiÄ™c ona ustawiona na maksimum, a powtarzanie znaku rozpocznie siÄ™ dopiero po okoÅ‚o sekundzie przytrzymywania klawisza. CzÄ™stotliwość powtarzania Gdy repetycja już siÄ™ rozpocznie, za szybkość jej wykonywania odpowiada druga z opcji powtarzania, czyli czÄ™stotliwość. Jej programistyczna obsÅ‚uga także wymaga użycia funkcji SystemParametersInfo(). OczywiÅ›cie zaczniemy od pobierania. Aby uzyskać czÄ™stotliwość powtarzania znaku posÅ‚ugujemy siÄ™ identyfikatorem SPI_GETKEYBOARDSPEED: UINT uCzestotliwosc; SystemParametresInfo (SPI_GETKEYBOARSPEED, 0, &uCzestotliwosc, 0); Ponownie otrzymana wielkość nie jest bezwglÄ™dna i oscyluje w granicach od 0 (co odpowiada ok. 2-3 powtórzeniom znaku na sekundÄ™) do 31 (to znaczy przeciÄ™tnie 30 powtórzeÅ„ na sekundÄ™). DokÅ‚adna czÄ™stotliwość jest, podobnie jak opóznienie, zależna od posiadanego modelu klawiatury. Teraz zajmijmy siÄ™ ustawianiem tego ustawienia ;) By je zmodyfikować, należaÅ‚oby podeprzeć siÄ™ staÅ‚Ä… SPI_SETKEYBOARDSPEED w niniejszy sposób: SystemParametersInfo (SPI_SETKEYBOARDSPEED, 31, NULL, 0); Tak też ustawiamy najwiÄ™kszÄ… możliwÄ… prÄ™dkość powtarzania znaków (31). 473 UÅ‚atwienia dostÄ™pu Na koniec zapoznamy siÄ™ opcjami klawiatury, które uÅ‚atwiajÄ… pracÄ™ z komputerem osobom niepeÅ‚nosprawnym. Wiele z tych ustawieÅ„ może być aczkolwiek wygodna także dla zupeÅ‚nie zdrowych użytkowników. UÅ‚atwienia klawiatury sÄ… dość zÅ‚ożonymi zagadnieniami; każde z nich posiada na swój użytek pewnÄ… strukturÄ™, której pola należaÅ‚oby omówić. Nie ma na to już miejsca ani czasu, dlatego w tym paragrafie opiszÄ™ jedynie poszczególne uÅ‚atwienia i wskażę zródÅ‚a, z których możesz siÄ™ dowiedzić wiÄ™cej na ich temat. KlawiszeFiltru KlawiszeFiltru (ang. FilterKeys) sÄ… opcjÄ…, której zadaniem jest przeciwdziaÅ‚anie skutkom nieumyÅ›lnych wciÅ›nięć klawiszy. Odbywa siÄ™ to poprzez ignorowanie takich przyciÅ›nięć, które nie sÄ… przytrzymane przez odpowiednio dÅ‚ugi czas (dÅ‚ugi znaczy tu raczej uÅ‚amek sekundy). Możliwe jest także drastyczne zmniejszenie szybkoÅ›ci powtórzeÅ„ znaków. Programowa kontrola KlawiszyFiltru może być przeprowadzana funkcjÄ… SystemParametersInfo() oraz staÅ‚ymi SPI_GETFILTERKEYS i SPI_SETFILTERKEYS. Z opcjÄ… jest też zwiÄ…zana struktura FILTERKEYS. KlawiszeTrwaÅ‚e KlawiszeTrwaÅ‚e (ang. StickyKeys) zmieniajÄ… sposób dziaÅ‚ania klawiszy Ctrl, Shift i Alt, uÅ‚atwiajÄ…c wykonywanie zawierajÄ…cych je kombinacji. Zamiast jednoczesnego wciskania wszystkich klawiszy lub przytrzymywania wspomnianych trzech, wystarczy ich jednokrotne dociÅ›niÄ™cie i zwolnienie. Przy wÅ‚Ä…czonych KlawiszachTrwaÅ‚ych wykonanie kombinacje Alt+Tab sprowadza siÄ™ do wciÅ›niÄ™cia i puszczenia klawisza Alt, a nastÄ™pnie wciÅ›niÄ™cia Tab - nie trzeba przytrzymywać pierwszego z klawiszy. Za KlawiszeTrwaÅ‚e odpowiadajÄ… staÅ‚e SPI_GETSTICKYKEYS i SPI_SETSTICKYKEYS funkcji SystemParametersInfo() oraz struktura STICKYKEYS. KlawiszePrzeÅ‚Ä…czajÄ…ce Po uaktywnieniu KlawiszyPrzeÅ‚Ä…czajÄ…cych (ang. ToggleKeys) komputer bÄ™dzie generowaÅ‚ dzwiÄ™k w momencie wciÅ›niÄ™cia jednego z klawiszy Lock: Num Lock, Caps Lock i Scroll Lock. Powinno to na przykÅ‚ad zapobiec bÅ‚Ä™dom polegajÄ…cym na wpisywaniu tEKSTU pODOBNEGO dO tEGO :) Modyfikacja ustawieÅ„ KlawiszyPrzeÅ‚Ä…czajÄ…cych odbywa siÄ™ staÅ‚ymi SPI_GETTOGGLEKEYS i SPI_SETTOGGLEKEYS oraz strukturÄ… TOGGLEKEYS. *** Zaprezentowaniem powyższej trójcy uÅ‚atwieÅ„ dostÄ™pu koÅ„czymy nasze spotkanie z klawiaturÄ…. PoznaliÅ›my tutaj wiÄ™kszość aspektów jej wykorzystania przy pomocy Windows API, co powinno nam pomóc przy tworzeniu aplikacji okienkowych. Z ważniejszych, a nieomówionych kwestii należy wymienić ukÅ‚ady klawiatury oraz karetkÄ™. Jeżeli chcesz, możesz poczytać na ich temat w MSDN. Podsumowanie DobrnÄ™liÅ›my wreszcie do koÅ„ca tego rozdziaÅ‚u. Teraz wiesz już wszystko, co niezbÄ™dne do poprawnego wykorzystania klawiatury i myszy w twoich programach dla Å›rodowiska 474 Windows. Znasz już odpowiednie komunkaty oraz pomocnicze funkcje WinAPI, które bÄ™dÄ… ci w tym pomocne. W nastÄ™pnym rozdziale zajmiemy siÄ™ wreszcie rysowaniem i grafikÄ…. Wprawdzie nie bÄ™dzie to jeszcze DirectX, ale i tak powinieneÅ› być zadowolony. Zapoznamy siÄ™ bowiem dokÅ‚adnie z bogatÄ… bibliotekÄ… graficznÄ… Windows GDI. Pytania i zadania Oto niezbÄ™dny zestaw pytaÅ„ kontrolnych i zadaÅ„ do wykonania. MiÅ‚ej pracy ;) Pytania 1. Czym jest urzÄ…dzenie wejÅ›ciowe? Jakie znasz rodzaje takich urzÄ…dzeÅ„? 2. Co w Windows API rozumiemy pod pojÄ™ciem myszy? 3. Jakie rodzaje komunikatów myszy może otrzymać okno w Windows? 4. Jakie informacje sÄ… dostarczane w parametrach wParam i lParam każdego komunikatu myszy? 5. Który komunikat przycisku myszki należy obsÅ‚ugiwać, aby zapewnić reakcjÄ™ na pojedyncze klikniÄ™cie? 6. Jaki wymóg musi speÅ‚nić okno, aby otrzymywać informacje o dwukrotnych klikniÄ™ciach? 7. Które okno otrzymuje komunikat WM_MOUSEWHEEL o obrocie rolki myszy? 8. Co to znaczy, że okno ma wÅ‚adzÄ™ nad myszkÄ…? Jak można takÄ… wÅ‚adzÄ™ uzyskać? 9. Jak można pobrać pozycjÄ™ kursora w dowolnym momencie? 10. W jaki sposób sprawdzamy stan wciÅ›niÄ™cia przycisków myszy? O czym należy pamiÄ™tać, jeżeli używamy do tego funkcji GetAsyncKeyState()? 11. Jak można programowo symulować ruch myszy, wciÅ›niÄ™cia przycisków oraz obrót rolkÄ…? 12. Jak sprawdzamy obecność w komputerze i możliwoÅ›ci myszki? 13. Jakie uÅ‚atwienia dostÄ™pu sÄ… zwiÄ…zane z myszkÄ…? 14. Czym jest potok klawiszy i jakie sÄ… jego kolejne etapy? 15. Czym różni siÄ™ kod skanowania od kodu wirtualnego klawisza? 16. Które okno otrzymuje komunikatu o zdarzeniach klawiatury? 17. Jakie informacje można odczytać z parametru lParam komunikatów klawiatury? 18. Jakie komunikaty o klawiszach generuje system Windows? 19. Czym siÄ™ różni komunikat systemowy od pozasystemowego? 20. SkÄ…d pochodzÄ… komunikaty o znakach i jaka jest ich rola? 21. Co zawiera parametr wParam komunikatów o znakach? 22. Jakimi dwoma funkcjami pobieramy stan pojedynczego klawisza wirtualnego i czym różniÄ… siÄ™ one miÄ™dzy sobÄ…? 23. Jak wyglÄ…da programowe symulowanie klawiatury? 24. Jakie dwa ustawienia kontrolujÄ… powtarzanie znaku przy wciÅ›niÄ™tym klawiszu? 25. Podaj trzy uÅ‚atwienia dostÄ™pu zwiÄ…zane z klawiaturÄ…. Ćwiczenia 1. Napisz program, który wyÅ›wietli komunikat po klikniÄ™ciu lewym przyciskiem myszy w obszarze klienta swojego okna. 2. (Trudne) Stwórz aplikacjÄ™, która bÄ™dzie reagowaÅ‚a pokazaniem menu sterujÄ…cego okna w odpowiedzi na klikniÄ™cie jego wnÄ™trza. Wskazówka: wykorzystaj komunikat WM_NCHITTEST. 3. Zmodyfikuj przykÅ‚ad CursorPos tak, ażeby wyÅ›wietlaÅ‚ on współrzÄ™dne ekranowe kursora. Najlepiej, jeżeli nie wykorzystasz do tego funkcji GetCursorPos(). 4. ZmieÅ„ nasz przykÅ‚adowy szkicownik Scribble - niech okno nie traci swej zawartoÅ›ci po odrysowywaniu. 475 Wskazówka: przypomnij sobie omówienie procesu tworzenia okna z poprzedniego rozdziaÅ‚u. 5. Utwórz program pokazujÄ…cy w swym oknie kod wirtualnego klawisza, który wciska użytkownik. (Trudne) Dodaj do tego jeszcze nazwÄ™ klawisza w postaci tekstu, np. "Enter" czy "StrzaÅ‚ka w dół". 6. (Ekstremalne) Stwórz aplikacjÄ™ zliczajÄ…cÄ… wciÅ›niÄ™te przez użytkownika klawisze w caÅ‚ym systemie i pokazujÄ…cÄ… jÄ… w maÅ‚ym okienku w trybie zawsze na wierzchu Wskazówka: zainteresuj siÄ™ filtrami (ang. hooks), a szczególnie jednym rodzajem - WH_JOURNALRECORD. Potrzebne infomacje znajdziesz w opisie funkcji SetWindowsHookEx(). 7. Napisz program, który pozwalaÅ‚by na zmianÄ™ tytuÅ‚u swego okna. Niech bÄ™dzie on poczÄ…tkowo pusty, a wciÅ›niÄ™cia klawiszy alfanumerycznych niech powoduje dodanie do niego odpowiednich znaków. (Trudniejsze) Spraw jeszcze, aby klawisz Backspace usuwaÅ‚ już wprowadzone znaki. 8. (Trudniejsze) Napisz lepszÄ… wersjÄ™ funkcji SymulujTekst(). Powinna ona przyjmować dowolny tekst, najlepiej w formacie Unicode.