2 08 Mysz i klawiatura (2)


Rozdział 8.
Mysz i klawiatura


W tym rozdziale:

Komunikaty wejściowe i stan systemu
Komunikaty wejściowe pochodzące od myszy
Tworzenie funkcji obsługi komunikatów myszy
Układy współrzędnych okien i ekranu
Komunikaty myszy nie związane z obszarem roboczym
Zmiana wskaźnika myszy
Wprowadzanie danych z klawiatury


W tym rozdziale nauczymy Cię odbierania poleceń wydawanych za pomocą myszy i klawiatury. W pierwszej sekcji omówimy cały system używany przez Windows do zarządzania stanem mechanizmu wprowadzania. Przyjrzymy się także koncepcji zwanej lokalnym stanem wprowadzania, która pierwszy raz pojawiła się w Windows NT, po czym została zastosowana także w Windows 95 oraz Windows 98.
Następnie zajmiemy się bardziej szczegółowo obsługą myszki. Dzięki bibliotece MFC (Microsoft Foundation Classes) czasy, kiedy trzeba było pisać niskopoziomowe procedury obsługi myszy, odeszły w zapomnienie (chyba że masz mnóstwo wolnego czasu). W tej sekcji poznamy metody oferowane przez MFC do zarządzania i manipulowania myszką. Zajmiemy się także zagadnieniami takimi jak zmiana kształtu wskaźnika myszy (kursora), przechwytywanie wskaźnika myszy oraz ograniczanie ruchu wskaźnika do zadanego obszaru.
Na koniec przyjrzymy się różnym sposobom, w jakie program może wykorzystać informacje wprowadzane z klawiatury. Używając elementów interfejsu użytkownika takich jak kontrolka pola edycji, możemy zezwolić Windows i MFC na to, aby zamiast nas zarządzały klawiaturą. Jednak często zdarzy się, że będziemy chcieli mieć pełniejszą kontrolę nad klawiaturą. Jako część tej dyskusji omówimy ognisko wprowadzania, punkt wstawiania (kursor tekstowy), stan wyboru oraz inne właściwości interfejsu użytkownika związane z odczytem danych z klawiatury.
Komunikaty wejściowe i stan systemu
Wiele programistów nie mających dotąd do czynienia z programowaniem Windows może mieć na początku kłopoty wynikające z faktu, że system Windows jest sterowany zdarzeniami. W kontekście przetwarzania informacji wprowadzanych przez użytkownika sterowanie zdarzeniami oznacza, że zamiast odpytywać system czy zdarzyło się wciśnięcie klawisza lub ruch myszy, Windows samo przekazuje aplikacji komunikaty wejściowe za każdym razem, gdy użytkownik wykona jakąkolwiek czynność.
MFC odwzorowuje te komunikaty (należące do grupy komunikatów okienkowych) do funkcji C++ nazywanych funkcjami obsługi komunikatów. (Pełniejszy opis MFC znajdziesz w rozdziale 6.). Właśnie w tych funkcjach znajdą się wszystkie operacje logiczne związane z odczytem wprowadzanych informacji. Funkcjami obsługi komunikatów zajmiemy się jednak w dalszej części rozdziału. Na razie powinieneś zapamiętać, że funkcje obsługi komunikatów to funkcje C++ zajmujące się obsługą komunikatów okienkowych.
Pewnym wyzwaniem przy obsłudze danych wprowadzanych za pomocą myszy i klawiatury jest zrozumienie i zarządzania kontekstem, w którym wprowadzenie danych nastąpiło. Samo wprowadzanie raczej nie jest skomplikowanie; zarówno dla myszy jak i klawiatury występuje jedynie kilka rodzajów komunikatów. Gdy piszesz kod obsługi klawiatury, zwykle dokonujesz wyboru pomiędzy dwoma komunikatami klawiatury. Przy obsłudze myszy, w ogromnej większości przypadków, mamy do czynienia jedynie z dwoma lub trzema komunikatami.
Tworzenie funkcji obsługi komunikatów wejściowych jest całkiem łatwe. W rzeczywistości. Visual C++ udostępnia nawet specjalny kreator tworzący za ciebie szkielet takiej funkcji. Jednak samo posiadanie szkieletu funkcji nie wystarczy. Trzeba wypełnić ją kodem rozumiejącym kontekst, w którym powstał komunikat wejściowy, tak aby aplikacja odpowiednio się zachowywała. Dla użytkownika nie ma nic bardziej irytującego niż próby używania aplikacji zawierającej błędy w obsłudze wejścia. Aby tego uniknąć, musimy zrozumieć kontekst wprowadzania informacji - choć nie zawsze jest to oczywiste, lecz jest absolutnie konieczne, by dane wejściowe zostały zinterpretowane zgodnie ze stanem aplikacji, i to zarówno z punktu widzenia użytkownika, jak i samych Windows. Przejdźmy więc do obsługi komunikatów wejściowych, najpierw w przypadku myszy, a potem w przypadku klawiatury.
Obsługa myszy
Jak już wspominaliśmy, Windows przekazuje oknom programu dane wejściowe w postaci komunikatów pobieranych z kolejki wejściowej. W tabeli 8.1 zebraliśmy główne komunikaty Windows związane ze zdarzeniami pochodzącymi od myszki, na które możesz reagować obsługując myszkę.

Tabela 8.1. Komunikaty wejściowe pochodzące od myszy
Komunikat
Generowany gdy
WM_ LBUTTONDOWN-Został wciśnięty lewy przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ LBUTTONUP-Został wciśnięty lew6y przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ RBUTTONDOWN-Został wciśnięty prawy przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ RBUTTONUP-Został wciśnięty prawy przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ MBUTTONDOWN-Został wciśnięty środkowy przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ MBUTTONUP-Został wciśnięty środkowy przycisk myszy w momencie, gdy wskaźnik znajdował się w obszarze roboczym okna.

WM_ MOUSEMOYE-Wskaźnik myszy zmienił położenie w obszarze roboczym okna.



Tworzenie funkcji obsługi komunikatów myszy
W przypadku komunikatów z tabeli 8.1 tworzenie funkcji obsługi jest bardzo proste. Zacznij od przywołania ClassWizarda. Najszybszym sposobem jest kliknięcie nazwy klasy w oknie widoku projektu, a następnie wybranie w menu kontekstowym polecenia Add Windows Message Handler (dodaj funkcję obsługi komunikatu okienkowego), tak jak pokazano na rysunku 8.1.

Aby stworzyć funkcję obsługi komunikatu myszki, dwukrotnie kliknij komunikat, który chcesz obsłużyć. Funkcja zostanie utworzona i automatycznie dodana do listy funkcji składowych. Komunikaty, których nazwy zostały wypisane pogrubioną czcionką, wskazują, że w danej klasie istnieją już funkcje ich obsługi.
Dodawanie funkcji obsługi komunikatu myszy
Oto jak możesz dodać do swojego programu funkcję obsługi komunikatu myszy:
1. Kliknij prawym przyciskiem nazwę klasy.
2. Na liście komunikatów odszukaj komunikat, dla którego chcesz dodać funkcję obsługi, po czym dwukrotnie go kliknij.
Funkcja obsługi zostanie dodana do listy funkcji składowych.
Tworzenie funkcji obsługi
Gdy wiesz już, jak stworzyć funkcje obsługi dla komunikatów myszy, powinieneś poświęcić minutę i spróbować samodzielnie stworzyć taką funkcję. Dzięki temu będziesz miał pewność, że dobrze zrozumiałeś to, o czym mówiliśmy do tej pory.
1. Stwórz jednodokumentową aplikację (SDI) o nazwie MouseHandler.
2. Kliknij prawym przyciskiem myszy klasą CMouseHnadlerView, po czym w menu kontekstowym wybierz polecenie Add Windows Message Handler.
3. Odszukaj na liście komunikat WM_MOUSEMOVE i dwukrotnie go kliknij.
Rozdział 8. Mysz i klawiatura
155
4. Na liście funkcji składowych kliknij funkcję onMouseMove, po czym kliknij przycisk Edit Code (edytuj kod).
5. Do funkcji OnMouseMove () dopisz poniższy kod:
CClientDC ClientDC( this );
CString strlnfo;
strInfo.Format("X:%d Y:%d ",
point.x, point.y); CClientDC.TextOut(10, 10, strInfo);
6. Skompiluj i uruchom program. W momencie przesuwania myszy w obszarze roboczym ujrzysz zmieniające się współrzędne wskaźnika myszy.
Konwersja pomiędzy współrzędnymi ekranowymi a współrzędnymi okna
Do właśnie stworzonej przez Ciebie funkcji obsługi OnMouseMove () MFC przekazuje współrzędne wskaźnika myszy; znajdują się one w obiekcie klasy MFC o nazwie CPoint. Obiekt typu CPoint jest zwykle używany do reprezentacji współrzędnych punktu na ekranie. Zmienne składowe x i y obiektu tego typu, dostępne jako CPoint: :x oraz CPoint:: y, zawierają poziomą i pionową współrzędną reprezentowanego punktu.
Współrzędne przekazywane funkcji OnMouseMove () są współrzędnymi obszaru roboczego okna. Najniższą wartością obu współrzędnych może być zero. Z kolei największymi wartościami mogą być szerokość i wysokość obszaru roboczego (pomniejszone o jeden). Bez względu na tryb mapowania okna (o którym powiemy w dalszej części książki), przekazywane w tym komunikacie współrzędne są zawsze współrzędnymi fizycznymi, tj. wyrażonymi w pikselach ekranu.
Czasem zdarza się, że chcemy znać współrzędne myszy względem całego ekranu. Wygodną funkcją konwersji służącą do tego celu jest ciientToScreen(). Wymaga ona argumentu w postaci wskaźnika do obiektu klasy CPoint i zamienia zawarte w nim współrzędne obszaru roboczego okna na odpowiednie współrzędne ekranu.
Na przykład, okno może być umieszczone na ekranie w miejscu o współrzędnych 50, 75. Współrzędne wskaźnika myszy przekazane do funkcji OnMouseMove () są podawane względem punktu 50, 75. Jeśli użytkownik wskaże punkt wewnątrz okna znajdujący się 15 pikseli na prawo od lewej krawędzi i o 25 pikseli w dół od górnej krawędzi okna, współrzędnymi przekazanymi do funkcji OnMouseMove () będą 15 i 25. Używając funkcji clientToScreenf) te wartości zostaną zamienione na rzeczywiste współrzędne ekranu (poprzez dodanie położenia okna do współrzędnych w oknie). W omawianym przypadku współrzędne ekranu to 65, 100. Użycie funkcji ciientToScreen () ilustruje poniższa funkcja:
void CMyClassView::OnMouseMove(UINT nFlags, CPoint point) {
CPoint pt;
pt = point;
CiientToScreen( &pt);
CString strInfo;
strInfo("Ekran X:%d Ekran Y: %d
pt.x, pt.y);
CClientDC ClientDC(this); ClientDC.TextOut(10, 10, strlnfo) CView::OnMouseMove(nFlags, point)
}
Kolejną opcją jest zamiana współrzędnych ekranowych na współrzędne obszaru roboczego okna. Jest to przydatne, gdy otrzymujemy współrzędne myszy od funkcji Windows API GetCursorPos (). Ta funkcja zwraca współrzędne wskaźnika myszy względem całego ekranu. Oto przykład, w którym pobieramy pozycję wskaźnika myszy i zamieniamy ją na współrzędne obszaru roboczego okna:
void CMyClassView::TestFunction() {
POINT point;
::GetCursorPos( &point);
CPoint pt(point);
ScreenToClient(&pt);
CString strInfo;
strInfo("Ekran X:%d Ekran Y:%d Klient X:%d Klient Y:%d ", point.x, point.y, pt.x, pt.y);
AfxMessageBox(strlnfo);
CView::OnMouseMove(nFlags, point);
}
Tworzenie programu MFC obsługującego zdarzenia myszy
Stworzyliśmy prosty program demonstracyjny pokazujący, w jaki sposób można reagować na najważniejsze zdarzenia myszy. Program obsługuje ruchy myszki, a także wciskanie i zwalnianie lewego i prawego przycisku. Nie obsługuje zdarzeń związanych ze środkowym przyciskiem myszki.
MouseDemo1
Położenie na płytce CD-ROM: Rozdz08\MouseDemol
Nazwa programu: MouseDemol.exe
Moduł kodu źródłowego w tekście: MouseDemol View.cpp
Listing 8.1. Najważniejsze fragmenty kodu źródłowego z pliku MouseDemol Yiew.cpp ______
// MouseDemol View. cpp : implementation of // the CMouseDemo1View class
// CMouseDemolView construction/destruction CMouseDemolView : : CMouseDemolView ( )
(
// Zaczynamy w trybie wyświetlania informacji o położeniu myszy, // a nie w trybie wyświetlania siatki prostokątów m_nInfoMode = MOUSE_SHOWINFO;
// Czyszczenie dwuwymiarowej tablicy siatki for( int y=0; y<10; y++ )
fort int x=0; x<10; x++ ) m nGrid[x][y] = 0;
}
CMouseDemolView::-CMouseDemolView()
{
}
///1/////11/////////////////II11////II///////////////1/II/11//////// // CMouseDemolView drawing
void CMouseDemolView::OnDraw(CDC* pDC) {
CMouseDemo1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// Musimy odświeżać okno tylko wtedy, gdy // program jest w trybie MOUSE_SHOWGRID if( m_nInfoMode == MOUSE_SHOWGRID ){
// Aby narysować prostokąty siatki
// w odpowiednich proporcjach, wyznaczymy
// prostokąt obszaru roboczego.
RECT Rect;
GetClientRect( &Rect );
// Tworzymy czerwony, biały i niebieski pędzel. CBrush RedBrush( RGB{ 255, O, O ) }; CBrush BlueBrush( RGB( O, O, 255 ) ); CBrush WhiteBrusht RGB( 255, 255, 255 ) ); CBrush *pUseBrush;
// Siatka składa się z dziesięciu elementów // w poziomie i w pionie. for( int y=0; y<10; y++ ){ for( int x=0; x<10;
// Wyznaczamy rozmiar pojedynczego prostokąta // dzieląc wymiary obszaru roboczego przez 10. RECT DrawRect; DrawRect.left =
( x * Rect.right ) / 10; DrawRect.top =
( y * Rect.bottom ) / 10; DrawRect.right =
DrawRect.left + (Rect.right / 10) +1;
DrawRect.bottom =
DrawRect.top + (Rect.bottom / 10)


// Wybieramy pędzel na podstawie tego,
// czy rysowany prostokąt ma być czerwony, niebieski
// czy pusty.
pUseBrush = SWhiteBrush;
if( m_nGrid[x][y] == l )
pUseBrush = SBlueBrush; else if( m_nGrid[x][y] == 2 )
pUseBrush = SRedBrush;
// Rysujemy wypełniony prostokąt. pDC->FillRect( &DrawRect, pUseBrush );
}
}
}
)
////1////////1////////////II/t///////l////11IIII///////////1111//1/1 // CMouseDemolView message handlers
void CMouseDemolView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo( "LButtonDown", point, l };
// Wywołujemy domyślną funkcję OnLButtonDown(). CView::OnLButtonDown(nFlags, point);
}
void CMouseDemolView::OnLButtonUp(UINT nFlags, CPoint point)
{
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo{ "LButtonUp", point );
// Wywołujemy domyślną funkcję OnLButtonUp(). CView::OnLButtonUp(nFlags, point);
}
void CMouseDemolView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo( "LButtonDblClk", point );
// Wywołujemy domyślną funkcję OnLButtonDblClk(). CView::OnLButtonDblClk(nFlags, point);
}
void CMouseDemolView::OnRButtonDown(UINT nFlags, CPoint point
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo( "RButtonDown", point, 2 );
// Wywołujemy domyślną funkcję OnRButtonDown(). CView::OnRButtonDown(nFlags, point);
}
void CMouseDemolView: :OnRButtonUp (UINT nFlags, CPoint point)
{
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo ( "RButtonUp", point );
// Wywołujemy domyślną funkcję OnRButtonUp ( ) . CView: : OnRButtonUp (nFlags, point) ;
}
void CMouseDemolView::OnRButtonDblClk(UINT nFlags, CPoint point)
(
// Wywołujemy funkcję wyświetlającą informacje
// o położeniu myszy.
ShowMouselnfo( "RButtonDblClk", point );
// Wywołujemy domyślną funkcję OnRButtonDblClk(). CView::OnRButtonDblClk(nFlags, point);
}
void CMouseDemolView: : OnMouseMove (UINT nFlags, CPoint point)
{
// Pozycję wyświetlamy tylko wtedy, gdy program // znajduje się w trybie MOUSE_SHOWINFO if( m_n!nfoMode == MOUSE_SHOWINFO ){ CClientDC ClientDC( this );
CString strlnfo;
// Kopiujemy obiekt CPoint tak, aby // mieć także współrzędne ekranu. CPoint pt = point;
// Zamiana na współrzędne ekranu. ClientToScreen( &pt ) ;
// Formtowanie napisu. strlnfo. Format (
"X:%d Y:%d ScnX:%d ScnY:%d
point. x, point. y,
pt . x , pt . y ) ;
// Wyświetlenie napisu w oknie. ClientDC.TextOut ( O, O, strlnfo);
}
// Wywołanie domyślnej funkcji OnMouseMove CView::OnMouseMove(nFlags, point);
}
void CMouseDemolView::ShowMouselnfo(
const char *1pszText, CPoint point, int nFlag )
{
// Wykonujemy ten kod, gdy program znajduje się // w trybie MOUSE_SHOWGRID. if( m_n!nfoMode == MOUSE_SHOWGRID ){ if( nFlag != -l ){
// Pobieramy prostokąt obszaru roboczego // aby móc obliczyć indeksy x i y punktu, // w którym nastąpiło kliknięcie. RECT Rect; GetClientRect( SRect );
// Użyjemy prostokąta obszaru roboczego // i podzielimy go przez 10, aby obliczyć // indeksy x i y siatki, int x = ( point.x * 10 ) / Rect.right; int y = ( point.y * 10 ) / Rect.bottom;
// Oznaczamy element siatki jako odpowiadający
// lewemu lub prawemu przyciskowi myszy lub zerujemy go,
if( m_nGrid[x][y] == nFlag )
m_nGrid[x][y] = 0; else
m_nGrid[x][y] = nFlag;
// Wymuszamy odrysowanie okna. InvalidateRect( NULL, FALSE ); UpdateWindow();
}
return;
}
// Pobieramy DC obszaru roboczego okna. CClientDC ClientDC( this );
CString strlnfo;
// Formatujemy napis wyjściowy, strInfo.Format(
"X:%d Y:%d %s
point.x, point.y, 1pszText );
// Rysujemy napis w oknie.
ClientDC.TextOut( point.x, point.y, strInfo);
}
void CMouseDemolView::OnFileMouseGriddisplay()
{
// Ustawiamy program w tryb MOUSE_SHOWGRID. if( m_n!nfoMode != MOUSE_SHOWGRID ){
m_n!nfoMode = MOUSE_SHOWGRID;
InvalidateRect( NULL, TRUE );
UpdateWindow();
}
void CMouseDemo1View::OnUpdateFileMouseGriddisplay( CCmdUI* pCmdUI)
{
// Jeśli program jest w trybie MOUSE_SHOWGRID, // umieszczamy znaczek obok polecenia menu. pCmdUI->SetCheck( m nlnfoMode == MOUSE SHOWGRID
}
void CMouseDemolView::OnFileMousePositioninformation()
{
// Ustawiamy program w tryb MOUSE_SHOWINFO. if( m_n!nfoMode != MOUSE_SHOWINFO ){
m_n!nfoMode = MOUSE_SHOWINFO;
InvalidateRect( NULL, TRUE ) ;
UpdateWindow();
}
}
void
CMouseDemolView: : OnUpdateFileMousePositioninformation () CCmdUI* pCmdUI)
{
// Jeśli program jest w trybie MOUSE_SHOWINFO, // umieszczamy znaczek obok polecenia menu. pCmdUI->SetCheck( m nlnfoMode == MOUSE SHOWINFO
}
Zdarzenia myszy nie związane z obszarem roboczym
W tym momencie możesz sobie zadawać pytanie, gdzie jest obszar nie będący obszarem roboczym okna? Najprostsza odpowiedź jest jednocześnie najpełniejsza: obszary nierobocze to wszystkie obszary okna otaczające obszar roboczy. Dla ilustracji otwórz aplikację WordPad. Obszar roboczy to duży biały obszar, w którym możesz wprowadzać tekst. Do obszaru nieroboczego należą te części okna, które otaczają obszar roboczy, czyli belka tytułowa, paski przewijania itd.
Około połowy zdefiniowanych w Windows komunikatów wejściowych odnosi się do obszaru nieroboczego. Komunikaty myszy posiadające w nazwie przedrostek NC odnoszą się właśnie do tego obszaru (ang. non-client area). Na przykład, Windows otrzymuje
komunikat WM_NCLBUTTONDOWN w momencie gdy użytkownik wybiera polecenie menu, zmienia rozmiar okna czy je minimalizuje. Różnice pomiędzy tymi operacjami wynikają jedynie z położenia wskaźnika myszy w momencie kliknięcia przycisku.
Niesystemowe komunikaty myszy są przeznaczone do wykorzystania przez aplikację -tj. są przeznaczone dla nas. Z pomocą ClassWizarda reagowanie na podstawowy zestaw komunikatów jest bardzo proste. Tabela 8.2 zawiera listę komunikatów myszy odnoszących się do obszaru nieroboczego.
Tabela 8.2. Komunikaty myszy odnoszące się do obszaru nieroboczego
Komunikat
Generowany gdy
WM NCLBUTTONDOWN-Został wciśnięty lewy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM NCLBUTTONUP-Został wciśnięty lewy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM NCRBUTTONDOWN-Został wciśnięty prawy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM NCRBUTTONUP-Został wciśnięty prawy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM NCMBUTTONDOWN-Został wciśnięty środkowylewy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM NCMBUTTONUP-Został wciśnięty środkowy przycisk myszy w momencie,
gdy wskaźnik znajdował się w obszarze nieroboczym okna.

WM_NCMOUSEMOVE-Wskaźnik myszy zmienił położenie w obszarze nieroboczym okna.
WM NCHITTEST-Komunikat pytający: "W którym miejscu okna znajduje się wskaźnik myszy?" Przedrostek NC w nazwie tego komunikatu nie oznacza, że chodzi wyłącznie o obszar nieroboczy, choć głównie to interesuje system. Zamiast tego komunikat zwraca kod testu trafienia (ang. hit test) identyfikujący położenie wskaźnika myszy w oknie - na przykład na ramce, na pasku menu czy w obszarze roboczym.

Jednym z podstawowych sposobów przekazywania użytkownikowi informacji zwrotnej jest zmiana kształtu wskaźnika myszy. Powszechnie używany wskaźnik w kształcie klepsydry jest jednocześnie najprostszy do wyświetlenia, gdyż MFC oferuje w tym celu dwie specjalne funkcje: BeginWaitCursor (), która powoduje wyświetlenie wskaźnika w kształcie klepsydry, zaś EndWaitCursor () przywraca standardowy wskaźnik aplikacji. Te funkcje są powszechnie stosowane w przypadku dłuższych zadań, które mogą zająć nieco więcej czasu.
Powinieneś wiedzieć, że nie jest to jedyna metoda wyświetlania wskaźnika w kształcie klepsydry. Możesz wyświetlić taki wskaźnik także poprzez obsługę komunikatu WM_SETCURSOR. Jeśli program musi reagować na wszystkie komunikaty, mimo że wyświetla wskaźnik w kształcie klepsydry, obsługa komunikatu WM_SETCURSOR jest jedynym
rozwiązaniem. Windows wysyła ciągły strumień tych komunikatów do każdego z okien, w których obszarze znajduje się wskaźnik myszy, powodując ciągłą aktualizację jego kształtu. Poświęć kilka minut i przekonaj się o tym za pomocą programu Spy+ + .
Funkcja służąca do zmiany kształtu wskaźnika myszy to funkcja Windows API Set-Cursor (). Na przykład, oto fragment kodu wyświetlający wskaźnik w kształcie strzałki skierowanej w górę:
::SetCursor(::LoadCursor( AfxGetInstanceHandle
IDC UPARROW));
Demonstracyjny program zmieniający kształt wskaźnika myszy
Aby zademonstrować sposób zmiany kształtu wskaźnika myszy wewnątrz aplikacji, napisaliśmy specjalny program o nazwie MouseDemo2. Program ładuje szesnaście wskaźników myszy i wybiera je w zależności od położenia myszki w oknie.
Aplikacja sprawdza, w którym miejscu okna widoku znajduje się wskaźnik myszy. Podzielenie widoku na 16 prostokątnych obszarów pozwala na podjęcie decyzji, który ze wskaźników powinien zostać wybrany. Gdy uruchomisz program i przesuniesz myszkę w obrębie okna widoku, zobaczysz zmiany kształtu wskaźnika. Oprócz tego w górnej części okna wyświetlana jest nazwa aktualnie stosowanego kształtu wskaźnika.
MouseDemo2
Położenie na płytce CD-ROM: Rozdz08\MouseDemo2 Nazwa programu: MouseDemo2.exe
Moduł kodu źródłowego w tekście: MouseDemo2View.cpp
Listing 8.2. Najważniejsze fragmenty kodu źródłowego z pliku MouseDemo2 View. cpp
// MouseDemo2View.cpp : implementation of // the CMouseDemo2View class
CMouseDemo2View.cpp : implementation
the CMouseDemo2View cllas
//
//////////////////////////////
// CMouseDemo2View consrution/destrucion
CMouseDemo2View: :CMouseDemo2View()
{
// Lista ładowanych w programie identyfikatorów
// wskaźników.
static char *szCursor[] = {
IDC_ARROW, IDC_IBEAM, IDC_WAIT,
IDC_CROSS, IDCJJPARROW, IDC_SIZENWSE,
IDC_SIZENESW, IDC_SIZEWE, IDC_SIZENS,
IDC_SIZEALL, IDC_NO, IDC_APPSTARTING,
IDC HELP, IDC ARROW, IDC ARROW, IDC ARROW };

// Ładujemy wskaźniki. for( int i=0; i<16; i++ ) m_hCursor[i] =
::LoadCursor( NULL, szCursor[i] );
}
////////////////////////////////
//CMouseDemo2View: : ~CMouseDemo2View
{
}
// CMouseDemo2View message handlers
BOOL CMouseDemo2View: :OnSetCursor (CWnd* pWnd, UINT nHitTest, UINT message)
{
// Sprawdzamy, czy wskaźnik znajduje się w obszarze
// roboczym okna.
if( nHitTest == HTCLIENT ){
// Odczytujemy położenie myszki (we // współrzędnych ekranu). POINT pt; GetCursorPos( &pt );
// Zamiana współrzędnych ekranu //na współrzędne obszaru roboczego. ScreenToClient( Spt );
// Stworzona przez nas funkcja zwracająca
// numer obszaru, w którym znajduje się wskaźnik.
int nCursor = GetcursorRegion( &pt );
// Ustawiamy kształt wskaźnika. ::SetCursor( m_hCursor[nCursor] ) ;
// Zwracana wartość TRUE informuje, że obsłużyliśmy // ten komunikat, return( TRUE );
// Wywołujemy domyślną funkcję OnSetCursor(). return CYiew::OnSetCursor(pWnd, nHitTest, message);
}
int CMouseDemo2View: : GetcursorRegion ( POINT *lpPt )
{
// Potrzebujemy rozmiarów obszaru roboczego, aby móc // wyznaczyć numer regionu (od O do 3 dla x // oraz od O do 3 dla y) . RECT Rect; GetClientRect ( SRect ) ;
// Dzielimy szerokość obszaru roboczego na cztery, // aby otrzymać współrzędną x regionu, int x =
( lpPt->x * 4 ) / Rect.right; if ( x > 3 ) x = 3;
// Dzielimy wysokość obszaru roboczego na cztery, // aby otrzymać współrzędna y regionu, int y =
( lpPt->y * 4 ) / Rect.bottom; if( y > 3 ) y = 3;
// Zwracamy indeks, który należy do przedziału // od O do 15. return( y * 4 + x );
}
void CMouseDemo2View: : OnMouseMove (UINT nFlags, CPoint point;
{
// Lista nazw wskaźników, które wyświetlamy // w górnej części obszaru roboczego okna. static CString strCursorf] = {
"IDC_ARROW", "IDC_IBEAM", "IDC_WAIT", "IDC_CROSS", "IDC_UPARROW", "IDC_SIZENWSE "IDC_SIZENESW", "IDC_SIZEWE", " IDC_SIZENS "IDC_SIZEALL", "IDC_NO", " IDC_APPSTARTING "IDC_HELP", "IDC_ARROW", "IDC_ARROW", "IDC_ARROW" };
// Pobieramy numer regionu, w którym
// znajduje się wskaźnik. Jest to liczba
// z zakresu od O do 15 reprezentująca jeden
// z szesnastu załadowanych wskaźników.
int nCursor =
GetCursorRegion ( spoint );
// Pobranie kontekstu urządzenia dla
// obszaru roboczego, dzięki czemu możemy
// coś narysować w oknie.
CClientDC ClientDC( this );
CString strlnfo;
// Formatowanie napisu. strlnfo = "Wskaźnik:" + strCursor [nCursor] +
// Narysowanie napisu w obszarze roboczym okna. ClientDC.TextOut( O, O,
strlnfo, strInfo.GetLength () );
// Wywołanie domyślnej funkcji OnMouseMove(). CView::OnMouseMove(nFlags, point);
}
Przechwytywanie wskaźnika myszy
Gdy nabierzesz nieco doświadczenia w programowaniu Windows, z pewnością będziesz odczuwać potrzebę wychwytywania wszystkich komunikatów myszy i przekazywania ich do konkretnego okna. Nazywa się to przechwytywaniem myszy. Najczęstszym zastosowaniem tej techniki jest wychwycenie par komunikatów związanych z wciśnięciem i zwolnieniem przycisku myszy. Jeśli nie przechwycisz myszy, mogą w takich przypadkach pojawić się dziwne i trudne do wykrycia błędy.
Na przykład, załóżmy, że zaimplementowałeś funkcję obsługi komunikatu WM_LBUTTON-DOWN. Wewnątrz tej funkcji stosujesz zmienną logiczną m_bciicked (prawda/fałsz) określającą stan lewego przycisku myszy. Jeśli lewy przycisk zostanie wciśnięty, tj. otrzymasz komunikat WM_LBUTTONDOWN, ustawiasz wartość zmiennej na TRUE. Jeśli lewy przycisk myszy nie jest wciśnięty, zmienna powinna mieć wartość FALSE.
Wyobraź sobie teraz, że użytkownik wcisnął lewy przycisk myszy i przed zwolnieniem go przesunął wskaźnik poza obszar roboczy okna. Jeśli w funkcji obsługi komunikatu WM_LBUTTONDOWN nie przechwycisz wskaźnika myszy, wtedy w takiej sytuacji Twoja aplikacja nie otrzyma komunikatu WM_LBUTTONUP. Zmienna m_bciicked będzie wciąż miała wartość TRUE, mimo że przycisk faktycznie został zwolniony. Aplikacja nie będzie zsynchronizowana ze stanem myszy, co może prowadzić do wszelkich niepożądanych efektów.
Aby zrozumieć przechwytywanie myszy, najlepiej zobaczyć to w działaniu. Uruchom dowolną aplikację i otwórz jej okno dialogowe wyboru plików. Myszka może być prze-chwytywana przez dwie kontrolki okien dialogowych: listy oraz pola edycji. Przechwytywanie myszy ma miejsce, gdy klikniesz któryś z elementów listy lub tekst w polu edycji. Gdy przy wciśniętym przycisku myszy przeciągniesz wskaźnik poza obszar kontrolki, zawartość kontrolki ulegnie przewinięciu. To przewijanie jest możliwe właśnie dzięki przechwyceniu przez kontrolkę wskaźnika myszy.
Kolejnym miejscem, w którym możesz zobaczyć w działaniu przechwytywanie myszy, to większość z okien do edycji tekstu (włącznie z edytorem w Visual C++). Te okna przewijają swoją zawartość w momencie, gdy klikniesz w oknie pełnym tekstu i przeciągniesz wskaźnik myszy poza granicę okna. Ponieważ okno przechwyciło wskaźnik myszy, może otrzymywać wszystkie pochodzące od niej komunikaty, łącznie z komunikatem WM_MOUSEMOVE.
Gdy zobaczyłeś, jak to działa w praktyce, prawdopodobnie chcesz sprawdzić, czy sam potrafisz przechwycić wskaźnik myszy. To łatwe; wywołaj po prostu funkcję cwnd: : SetCapture (). Jak wspomnieliśmy, zwykle robi się to w odpowiedzi na komunikat wciśnięcia przycisku myszy. Aby zwolnić przechwycenie - coś co musisz zrobić - wywołaj funkcję cwnd: :ReleaseCapture (). Dobrym momentem do zwolnienia wskaźnika jest odpowiedź na komunikat zwolnienia przycisku myszy (WM__LBUTTONUP).
Oto kod wygenerowany przez ClassWizarda przedstawiający typowy scenariusz prze-chwytywania i zwalniania wskaźnika myszy. Mysz jest przechwytywana w momencie wciśnięcia lewego przycisku myszy (w odpowiedzi na komunikat WM_LBUTTONDOWN) i zwalniana w momencie zwolnienia tego przycisku (w odpowiedzi na komunikat WM_
LBUTTONUP):
Rozdział 8. Mysz i klawiatura
167
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point; {
// Przechwycenie myszy
SetCapture();
CFrameWnd::OnLButtonDown(nFlags, point);
}
void CMainFrame::OnLButtonUp(UINT nFlags, CPoint point) {
// Zwolnienie myszy
ReleaseCapture();
CFrameWnd::OnLButtonUp(nFlags, point);
}
Co istotne, gdy przechwycisz mysz, Windows nie przekazuje żadnych komunikatów testu trafienia (WM_NCHITTEST) ani ustawiania kształtu wskaźnika (WM_SETCURSOR). Założenie jest takie, że gdy już posiadasz pełną kontrolę nad myszką, możesz sam zmienić wskaźnik wtedy, gdy to będzie potrzebne. Jeśli chcesz zmienić wskaźnik myszy w momencie gdy Twój program ją przechwycił, najprawdopodobniej uczynisz to w odpowiedzi na komunikat WM_MOUSEMOVE, jedyny komunikat otrzymywany w momencie, gdy mysz jest przechwycona.
Ograniczanie ruchów myszy
Kolejną możliwością udostępnianą przez Windows jest ograniczanie ruchów wskaźnika myszy. Nie jest to coś, z czego będziesz często korzystał, ale aby nasza dyskusja była kompletna, musimy o tym wspomnieć. Ograniczanie ruchów wskaźnika wiąże się ze zdefiniowaniem prostokąta obszaru ruchu, czyli prostokąta, w którym będzie się mógł poruszać wskaźnik myszy.
Jednym z oczywistych zastosowań ograniczenia ruchów wskaźnika jest ograniczenie przez Windows ruchów wskaźnika do obszaru ekranu. Gdyby tak nie było, użytkownik mógłby przypadkowo przesunąć wskaźnik daleko poza granice ekranu, a następnie mieć kłopoty ze znalezieniem go. Spotkaliśmy się z takimi sterownikami ekranu, w których wskaźnik myszy przesunięty poza górną krawędź pojawiał się ponownie u dołu ekranu, a przesunięty poza boczną krawędź pojawiał się z drugiej strony. Choć osobiście nie preferujemy tego rodzaju zachowania wskaźnika, jednak z pewnością znajdzie się wiele osób, którym to będzie odpowiadać.
Kiedy więc program powinien ograniczać ruchy wskaźnika? Oczywiście wtedy, gdy trzeba zabezpieczyć użytkownika przed przesunięciem wskaźnika poza zadany obszar. Na przykład, program zabezpieczający mógłby ograniczać ruch wskaźnika wyłącznie do obszaru okna dialogowego wpisywania hasła. Ciężko jest znaleźć przykłady zastosowań, gdyż nie często się je spotyka. Na przykład program Paintbrush w Windows 3.1 ograniczał ruchy wskaźnika podczas rysowania kształtów takich ja krzywe Beziera, lecz my uznaliśmy to raczej za denerwujące niż użyteczne.
Twórcy MFC z pewnością także uznali, że możliwość ograniczania ruchu wskaźnika nie będzie zbyt często wykorzystywana. Nie dołączyli więc żadnej funkcji składowej reprezentującej funkcję API służącą do tego celu - : :ClipCursor (). Co ciekawe jednak, mimo że MFC nie udostępnia tej funkcji w żadnej ze swoich klas, w kilku przypadkach samo ogranicza ruchy wskaźnika myszy. Podczas zmiany rozmiaru paska narzędzi OLE ruch wskaźnika myszy jest ograniczony do obszaru roboczego okna nadrzędnego. Takie zastosowanie ograniczenia ma sens, ponieważ rozmiar okien potomnych powinien być ograniczony rozmiarami okna nadrzędnego.
Odczyt danych z klawiatury
Obsługa danych wprowadzanych z klawiatury jest prosta i oczywista. Gdy wiesz już, które komunikaty obsłużyć, pozostaje jedynie stworzyć dla nich funkcje obsługi. Pracując z danymi wprowadzonymi z klawiatury, możesz zdecydować się na obsłużenie od jednego do trzech komunikatów. Dla każdego wciskanego klawisza Windows generuje trzy komunikaty. Pierwszym z nich jest WM_KEYDOWN, sygnalizujący, że został wciśnięty klawisz. Następnym wysyłanym komunikatem jest WM_CHAR, zawierający wirtualny kod wciśniętego klawisza. Ostatnim komunikatem jest WM_KEYUP, wysyłanym w momencie zwolnienia klawisza. Dzięki tym trzem komunikatom możesz w pełni kontrolować użycie klawiatury.
Mamy zamiar omówić także kilka mniej oczywistych aspektów odczytu danych z klawiatury. Zaczniemy od przyjrzenia się, w jaki sposób sprzętowe kody klawiszy są zamieniane na kody widocznych znaków. Następnie omówimy ogólne zagadnienia interfejsu użytkownika odnoszące się do korzystania z klawiatury, a mianowicie co trzeba zrobić, gdy okno znajdzie się w ognisku wprowadzania.
Klawiatura fizyczna
Wykorzystanie klawiatury w programie MS-DOS często wiązało się z posiadaniem niezbędnej wiedzy o sposobie generowania sprzętowych kodów klawiatury, zwanych fizycznymi kodami klawiszy (ang. scan code). Choć funkcje biblioteki C czasu rzeczywistego automatycznie tłumaczyły te kody, programiści MS-DOS-a chcący uzyskać lepszą wydajność wykorzystywali je bezpośrednio. Fizyczne kody klawiszy nie odnoszą się do jakiegokolwiek zestawu znaków; zamiast tego stanowią po prostu 8-bitowe kody określające konkretny klawisz. Na przykład, klawisz opisany literą Y na anglojęzycznej klawiaturze ma fizyczny kod 15 (szesnastkowo). Nie trzeba wielkiej wyobraźni - ani kodu - aby stworzyć tablicę konwersji kodów fizycznych na znaki ASCII. To właśnie robiło więc wielu programistów MS-DOS-a.
Dobra znajomość klawiatury była wymagana dlatego, ponieważ programiści MS-DOS-a musieli ignorować około połowy generowanych fizycznych kodów klawiszy. Każdy klawisz na klawiaturze posiada w rzeczywistości dwa kody fizyczne. Pierwszy z nich wskazuje, że klawisz został wciśnięty, podczas gdy drugi wskazuje, że klawisz został zwolniony. Różnica pomiędzy tymi kodami wynosi 80 szesnastkowo. Na przykład,
w przypadku wspomnianego przed chwilą klawisza Y kod wciśnięcia to 15 szesnastkowo, podczas gdy kod zwolnienia to 95 szesnastkowo. Z tych dwóch kodów bardziej interesujący jest kod wciśnięcia. Odczytując fizyczne kody, kod zwolnienia - posiadający ustawiony bit znaku -jest zwykle ignorowany.
Problem z fizycznymi kodami klawiszy polega na tym, że są one bardzo ściśle zależne od sprzętowego, niskopoziomowego wejścia danych. Aby poprawnie dokonywać konwersji kodów fizycznych, musisz wziąć pod uwagę klawiaturę aktualnie zainstalowaną w systemie. Większość rodzajów klawiatur różni się w zależności od języka kraju, w którym są wykorzystywane.
Na przykład, klawiatury szwajcarsko-niemieckie uwzględniaj ą zarówno język jak i kraj. Na tych klawiaturach klawisze Y i Z są zamienione miejscami w stosunku do klawiatur anglojęzycznych. W rezultacie program MS-DOS opierający się na konwersji fizycznych kodów klawiszy zgodnie z układem klawiatury anglojęzycznej nie będzie poprawnie działał przy użyciu klawiatury szwajcarskiej.
Gdy mówimy, że fizyczne kody klawiszy są zależne od sprzętu, w rzeczywistości mamy na myśli że są zależne od różnego układu klawiszy przyjętego w różnych krajach. Aby program zależny od kodów fizycznych mógł działać na całym świecie, wymagane jest użycie około dwóch tuzinów tablic konwersji, i to przy założeniu, że korzystamy jedynie z alfabetu łacińskiego. W przypadku innych klawiatur, takich jak klawiatura z rosyjską cyrylicą czy znakami arabskimi, a także różne klawiatury azjatyckie, pojawia się cała grupa nowych problemów.
Oprócz uzależnienia od sprzętu, fizyczne kody klawiszy są tak niskopoziomowe, że nie biorą pod uwagę stanu klawiatury, takiego jak wciśnięcie klawisza Shift czy klawisza Caps Lock. W rezultacie, w celu zwykłego rozróżnienia pomiędzy małą a wielką literą konieczne jest wykonanie dodatkowej, złożonej pracy. Na przykład, program zależny od kodów fizycznych nie mógłby bez dodatkowej pracy rozróżnić wciśniętej litery Y od litery y. Kody fizyczne nie są zależne od wielkości liter, ponieważ same klawiatury nie są zależne od wielkości liter.
Dla obu problemów Windows stosuje różne rozwiązania. Aby uniknąć uzależnienia od specyficznych dla krajów układów klawiatury, sterowniki klawiatury konwertują kody fizyczne na standardowy zestaw kodów klawiszy wirtualnych (ang. Wirtual key code). Ten zestaw kodów składa się na tak zwaną logiczną klawiaturą Windows. W celu rozwiązania problemu stanu klawiatury Windows udostępnia funkcje pomocnicze przeznaczone do konwersji kodów klawiszy wirtualnych na znaki drukowalne. Przy tej ostatniej konwersji tworzone jest rozróżnienie pomiędzy małymi a wielkimi literami, wykorzystywanymi podczas czytania tekstu.
Logiczna klawiatura Windows
Klawiatura logiczna jest abstrakcją służącą ukryciu różnic pomiędzy klawiaturami używanymi w krajach korzystających z alfabetu łacińskiego. Nigdzie w dokumentacji Windows nie znajdziesz układu klawiatury logicznej. To co znajdziesz, to definicje kodów wirtualnych (VK_). Konwersja fizycznych kodów klawiszy na kody klawiszy wirtualnych jest zadaniem sterownika klawiatury.
W szczególności, Windows wywołuje sterownik klawiatury do zamiany gołych kodów sprzętowych (fizycznych) na kody klawiszy wirtualnych. Te kody klawiszy wirtualnych są przekazywane aplikacji za pomocą komunikatów. Ponieważ dla każdego klawisza istnieją dwa kody fizyczne - tj. kod wciśnięcia i kod zwolnienia klawisza - występują także dwa komunikaty. Komunikat WM_KEYDOWN wskazuje, że klawisz został wciśnięty, zaś komunikat WM_KEYUP wskazuje, że klawisz został zwolniony.
Choć kody klawiszy wirtualnych rozwiązują jeden z problemów, nie rozwiązują jednak drugiego. Rozwiązują problem różnych układów klawiatur, gdyż to sterownik klawiatury zajmuje się koniecznym tłumaczeniem kodów fizycznych na kody wirtualne.
Pozostaje jednak jeszcze problem polegający na tym, że kody wirtualne są wciąż kodami niskiego poziomu. W szczególności, musimy wykonać mnóstwo pracy, aby określić, czy litery mają być małe czy wielkie, gdyż kody wirtualne nie zależą, od stanu klawiszy modyfikujących, do których należą Shift, Ctrl oraz Alt. Kody klawiszy wirtualnych ignorują także stan klawiszy przełączających, którymi są Caps Lock, Num Lock oraz Scroll Lock.
Choć nie dostarczają wystarczających informacji do odczytu znaków z klawiatury, jednak kody klawiszy wirtualnych są przydatne do użycia jako kody klawiszy akceleratorów. Istnieją dwa typy akceleratorów: VIRTKEY (klawisze wirtualne) oraz ASCII. Zalecamy, abyś zawsze używał akceleratorów VIRTKEY, które -jak sugeruje nazwa - odnoszą się do klawiszy wirtualnych. Drugi rodzaj akceleratorów, ASCII, jest tworzony dla innego typu komunikatów klawiatury, które omówimy za chwilę.
Akcelerator to klawisz lub kombinacja klawiszy interpretowana przez program jako polecenie. Z punktu widzenia programu rezultat wyboru polecenia w menu i wciśnięcia kombinacji klawiszy akceleratora jest taki sam, ponieważ w obu przypadkach Windows generuje identyczne komunikaty. Możesz połączyć oba sposoby w umyśle użytkownika, dodając nazwę akceleratora do nazwy polecenia w menu. Na przykład, często spotyka się kombinację Ctrl+V obok polecenia Wklej w menu Edycja. Choć oba rodzaje poleceń są dla użytkownika podobne, jednak z punktu widzenia programu są definiowane w różny sposób. Menu są definiowane jako zasoby menu, zaś akceleratory są definiowane jako zasoby akceleratorów.
Komunikaty znaków drukowalnych
Windows API zawiera funkcję służącą do konwersji komunikatów kodów klawiszy wirtualnych na komunikaty WM_CHAR. Nazywamy je komunikatami klawiszy drukowalnych, ponieważ większość z nich odnosi się do małych i wielkich liter, cyfr oraz znaków przestankowych. Oznacza to, że odnoszą się one do znaków, które można zobaczyć na ekranie lub na wydruku.
Funkcją Windows API dokonującą konwersji kodów klawiszy wirtualnych na komunikaty WM_CHAR jest funkcja : :TranslateMessage (). Dopóki jednak nie korzystasz bezpośrednio z Windows SDK, prawdopodobnie nigdy nie zobaczysz tej funkcji, a ponieważ MFC wywołuje ją wewnętrznie w swojej pętli komunikatów, nie musisz się już o to troszczyć.
To co ważne, to fakt, że ta funkcja umieszcza komunikaty WM_CHAR w kolejce komunikatów Twojej aplikacji. Każdy otrzymany komunikat WM_CHAR będzie poprzedzony komunikatem WM_KEYDOWN i zakończony komunikatem WM_KEYUP. Znaki drukowalne otrzymasz, odbierając komunikat WM_CHAR, możesz więc bezpiecznie zignorować powiązane z nim komunikaty klawiszy wirtualnych.
Jeśli ignorujesz komunikaty WM_KEYDOWN, po co Windows kłopocze się ich wysyłaniem? Te komunikaty są przeznaczone dla wszystkich klawiszy, które nie mają odpowiadającego im znaku drukowalnego. Powinniśmy wspomnieć, że takie klawisze pełnią raczej rolę poleceń niż danych oraz że najłatwiejszym sposobem obsługi poleceń wprowadzanych z klawiatury jest użycie tabeli akceleratorów. Jednak nie zawsze można użyć tabeli akceleratorów. W szczególności, przyjęło się, że w danej chwili może być aktywna tylko jedna tabela akceleratorów. Ta tabela będzie wykorzystywana przez okno ramki aplikacji. Wszystkie inne okna muszą przetwarzać niskopoziomowe polecenia klawiatury bezpośrednio, bez ułatwień płynących z zastosowania tabeli akceleratorów.
Tabela 8.3 zawiera listę kodów klawiszy wirtualnych dla klawiszy nie związanych ze znakami drukowalnymi. Gdy nie możesz tworzyć pozycji tabeli akceleratorów, w celu wykrycia momentu wciśnięcia tych klawiszy musisz oprzeć się na komunikacie WM_
KEYDOWN.
Tabela 8.3. Kody klawiszy wirtualnych dla klawiszy nie związanych ze znakami drukowalnymi
Kod klawisza wirtualnego
Klawisz
VK_MENU-Menu (tylko w klawiaturach Windows 95/98)

VK_APPS-Application (tylko w klawiaturach Windows 95/98)

VK_CONTROL-Control

VK_DELETE-Delete

VK_DOWN-Kursor w dół
VK_END-End

VK_F1 do VK_F12-F1 do F12

VK_HOME-Home

VK_INSERT-Insert

VK_LEFT-Kursor w lewo
VK_PAUSE-Pause

VK_NEXT-Page Down

VK_PRIOR-Page U p

VK_SNAPSHOT-Print Screen

VK_RIGHT-Kursor w prawo

VK_SHIFT-Shift

VK UP-Kursor w górę




Podsumowując, dane wprowadzane z klawiatury zaczynają jako fizyczne kody klawiszy. Sterownik klawiatury Windows konwertuje je na kody niezależne od sprzętu: kody klawiszy wirtualnych. Zwykle aplikacje Windows przesyłają komunikaty kodów wirtualnych do funkcji Windows API generującej komunikaty WM_CHAR wtedy, gdy wciśniętym klawiszem jest klawisz oznaczony znakiem drukowalnym. W przypadku znaków nie-drukowalnych - takich jak klawisze funkcyjne czy klawisze nawigacyjne - musisz oprzeć się na komunikatach klawiszy wirtualnych, WM_KEYDOWN.
Teraz gdy wiesz już, jakie komunikaty klawiatury Windows może przekazać do Twojego programu, nadszedł czas, aby zająć się zagadnieniami wprowadzania danych związanymi ze stanem aplikacji. W pozostałych sekcjach tego rozdziału opiszemy, w jaki sposób aplikacja daje znać użytkownikowi, że okno znajduje się w ognisku wprowadzania. Można w tym celu użyć różnych technik, które nazwiemy ogólnie odzwierciedlaniem ogniska wprowadzania.
Odzwierciedlanie ogniska wprowadzania
Gdy tworzysz obsługę klawiatury w klasie okna, musisz zapewnić wizualną wskazówkę informującą użytkownika, że taka obsługa ma miejsce. Ponieważ okno przyjmuje informacje od klawiatury tylko wtedy, gdy znajduje się w ognisku wprowadzania (ang. input focus), odpowiednim momentem wyświetlenia tej wizualnej wskazówki jest chwila otrzymania ogniska wprowadzania. Oprócz tego, gdy okno traci ognisko wprowadzania, także powinieneś w jakiś sposób wizualnie to zaznaczyć.
Aby pomóc Ci w budowie okien wyświetlających właściwe wskazówki, w następnych sekcjach opiszemy trzy sposoby, w jakie okno może ogłosić fakt posiadania ogniska wprowadzania. Zaczniemy od tworzenia punktów wstawiania, pełniących w Windows rolę kursorów tekstowych. Następnie opowiemy o prostokątach ogniska, zwykle występujących w kontrolkach okien dialogowych, ale nadających się do zastosowania także w innych sytuacjach. Na koniec omówimy powiązania pomiędzy stanem zaznaczenia okna a ogniskiem wprowadzania klawiatury.
Tworzenie i zarządzanie kursorami klawiatury
Kursor klawiatury, czyli punkt wstawiania, to migająca bitmapa informująca użytkownika, w którym miejscu pojawi się następny znak wprowadzony z klawiatury.
Tabela 8.4 zawiera listę ośmiu funkcji składowych klasy cwnd służących do tworzenia i zarządzania kursorami klawiatury. Ta grupa funkcji jest niewielka i łatwa w zrozumieniu. Jedyny problem z kursorami tekstowymi wiąże się z odpowiednim doborem chwil ich tworzenia, wyświetlania, ukrywania i niszczenia.
Kursor klawiatury należy tworzyć wtedy, gdy okno otrzyma ognisko wprowadzania. Choć możesz mieć ochotę stworzyć go wcześniej, powinieneś się jej oprzeć. W końcu kursor tekstowy jest potrzebny tylko wtedy, gdy możesz odczytywać dane z klawiatury. Bez ogniska wprowadzania okno i tak nie otrzyma żadnych komunikatów od klawiatury. Oprócz tworzenia nowego kursora musisz umieścić go w odpowiednim miejscu okna i sprawić, by stał się widoczny. Oto fragment kodu wykonujący to w odpowiedzi na komunikat WM_SETFOCUS:
Tabela 8.4. Funkcje składowe klasy CWndprzeznaczone
do tworzenia i zarządzania kursorami tekstowymi
Funkcja
Opis
CreateCaret ()-Tworzy kursor tekstowy używając dostarczonej bitmapy. CreateGrayCaret(}-Tworzy jednolity szary kursor tekstowy, we wskazanym rozmiarze. CreateSolidCaret()-Tworzy jednolity czarny kursor tekstowy we wskazanym rozmiarze.
GetCaretPos()-Zwraca położenie kursora. Z powodu zastosowania lokalnego stanu wprowadzania w Windows 95, Windows 98 oraz Windows NT, lokalizacja kursora może być odczytana tylko wtedy, jeśli jest on zawarty w oknie stworzonym przez aktualny wątek.

DestroyCaret-Niszczy kursor. Aby uniknąć pozostawienia aplikacji w nieznanym stanie, niszcz kursor w momencie utraty przez okno ogniska wprowadzania (i twórz go, gdy odzyskasz to ognisko).

HideCaret()-Sprawia, że kursor staje się niewidoczny.
SetCaretPos(]-Przenosi kursor we wskazane miejsce okna. Gdy okno zawierające kursor jest przewijane, kursor powinien zostać przewinięty razem z danymi.

ShowCaret()-Sprawia, że kursor staje się widoczny.

void CMainFrame: :OnSetFocus (CWnd* pOldWnd) { // Najpierw wywołaj domyślną funkcję obsługi CFrameWnd: :OnSetFocus (pOldWnd) ; // Stwórz, umieść i wyświetl kursor CreateSolidCaret (0, d cyLineHeight ) ; SetCaretPos (d ptCaretLocation) ; ShowCaret ( ) ; }
O ile sensownym miejscem tworzenia kursora jest funkcja obsługi komunikatu WM_SETFOCUS, o tyle logicznym momentem jego niszczenia jest chwila, gdy okno traci ognisko wprowadzania. W odpowiedzi na komunikat WM_KILLFOCUS powinieneś najpierw ukryć kursor, a następnie go zniszczyć. Oto ilustrujący to fragment kodu:
void CMainFrame::OnKillFocus(CWnd* pNewWnd)
{
// Najpierw wywołaj domyślną funkcję obsługi CFrameWnd::OnKillFocus(pNewWnd);
// Odczytaj i zachowaj bieżącą pozycję kursora d_ptCaretLocation = GetCaretPos ();
// Usuń kursor HideCaret();
} DestroyCaret();
Jedyny inny przypadek, w którym możesz mieć do czynienia z kursorem tekstowym, wiąże się z rysowaniem poza zwykłą obsługą komunikatu WM_PAINT. Jeśli kursor znajduje się w oknie, musisz go ukryć przed przystąpieniem do jakiegokolwiek rysowania poza obsługą komunikatu WM_PAINT. W przeciwnym razie kursor mógłby pozostawić śmieci na ekranie. Gdy zakończysz rysowanie poza obsługą komunikatu WM_PAINT, powinieneś ponownie wyświetlić kursor. Oto fragment kodu przedstawiający całą procedurę:
void CMainFrame::OnChart(UINT nChar, UINT nReptCnt, UINT nFlags) {
CSize sizeTextBox;
// Pobranie położenia kursora CPoint pt = GetCaretPos();
// Ukrycie kursora przed pobraniem kontekstu urządzenia. HideCaret(};
// Rysowanie poza obsługą komunikatu WM_PAINT // Nawiasy klamrowe są potrzebne do zniszczenia // automatycznej zmiennej CłientDC {
CClientDC ClientDC(this);
CłientDC.TextOut(pt.x, pt.y, (LPCTSTR)snChar, l);
sizeTextBox = CłientDC.GetTextExtent( (LPCTSTR)&nChar, 1);
}
// Przesunięcie położenia kursora pt.x += sizeTextBox.ex; SetCaretPos(pt);
// Wyświetlenie kursora ShowCaret();
}
Ten fragment kodu w rzeczywistości nie robi nic więcej poza ukryciem kursora tekstowego. Pokazuje, w jaki sposób możesz odpowiedzieć na komunikat WM_CHAR zarówno w celu wypisania wprowadzonego znaku, jak i odpowiedniego przesunięcia kursora tekstowego. Obiekty rysowane w ten sposób nie będą stanowić stałej zawartości okna, chyba że będą rysowane także w odpowiedzi na komunikat WM_PAINT. Ponieważ nasz kod w żadnym miejscu nie zachowuje tekstu, nie będzie można go trwale wyrysować. (Gdy zrozumiesz, dlaczego ten fragment kodu jest niepełny, wtedy naprawdę zrozumiesz znaczenie komunikatu WM_PAINT).
W celu przetestowania swojego kodu tworzącego kursor tekstowy wymuś odebranie ogniska wprowadzania od Twojego programu. W tym celu uaktywnij Menedżera zadań (Ctrl+Esc) lub uaktywnij jakąś inną aplikację działającą w systemie. Gdy przełączysz się do innego programu, kursor tekstowy powinien zniknąć bez śladu. Gdy przełączysz się z powrotem, kursor powinien pojawić się w tym samym miejscu co poprzednio.
Kursor tekstowy jest zwany także punktem wstawiania, gdyż dokładnie wskazuje użytkownikowi miejsce, w którym pojawi się następny znak wprowadzony z klawiatury. Użytkownicy oczekują, że będą mogli przemieszczać kursor w obrębie okna, korzystając z klawiszy nawigacyjnych. Innym sposobem przedstawienia obecności ogniska wprowadzania jest prostokąt ogniska wprowadzania, który omówimy za chwilę.
Prostokąt ogniska wprowadzania
Prostokąt ogniska wprowadzania stanowi alternatywny sposób wskazywania położenia ogniska wprowadzania. W odróżnieniu od kursorów prostokąty ogniska nie migają. Zamiast tego stanowią pewien rodzaj zaznaczenia przyciągającego wzrok użytkownika ku określonej części okna. To, czy użyjesz kursora tekstowego czy prostokąta ogniska, zależy od tego, co chcesz podkreślić. Najczęstszymi przykładami zastosowania prostokątów ogniska są okna dialogowe. W przypadku wszystkich nieedycyjnych kontrolek stosowane są właśnie prostokąty ogniska, przemieszczane - za pomocą klawisza Tab lub myszki - pomiędzy różnymi kontrolkami dialogu.
Prostokąty ogniska nie są jednak przeznaczone wyłącznie dla kontrolek okien dialogowych, choć z pewnością jest to najbardziej oczywiste i najczęstsze zastosowanie. Możesz użyć ich wszędzie tam, gdzie kursor tekstowy z jakichś względów byłby nieodpowiedni. Jedną z różnic pomiędzy kursorami tekstowymi a prostokątami ogniska jest to, że kursory tekstowe są zawsze jednolite, zaś prostokąty ogniska są puste, obejmujące wskazywane obiekty. Jeśli chcesz wskazać ognisko jako jednolite lub migające, użyj kursora tekstowego. Jeśli chcesz subtelniejszego, obejmującego wskaźnika, użyj zamiast tego prostokąta ogniska.
Rysowanie prostokąta ogniska jest bardzo łatwe. Po prostu wywołujesz funkcję MFC CDC: : DrawFocusRect (). Poniższy przykład pokazuje, w jaki sposób można narysować prostokąt ogniska obejmujący blok tekstu:
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point) {
CClientDC ClientDC(this);
// Rysowanie linii tekstu w miejscu wskaźnika myszy CString str;
str.Format("Prostokąt ogniska dookoła tego tekstu."); ClientDC.TextOut(point.x, point.y, str);
// Obliczenie szerokości i wysokości tekstu CSize sizeText = ClientDC.GetTextExtent(str);
// Obliczenie marginesów dookoła prostokąta ogniska int cxMargin = GetSystemMetrics( SM_CXBORDER ) * 4; int cyMargin = GetSystemMetrics( SM_CYBORDER ) * 1;
// Obliczenie rozmiaru prostokąta ogniska
CRect rTextBox;
rTextBox.left = point.x - cxMargin;
rTextBox.top = point.y - cyMargin;
rTextBox.right = point.x + sizeText.cx + cxMargin;
rTextBox.bottom = point.y + sizeText.cy + cyMargin;
// Rysowanie prostokąta ogniska dookoła bloku tekstu ClientDC.DrawFocusRect(&rTextBox);
CFrameWnd::OLButtonDown(nFlags, point);
}
Powyższy fragment kodu rysuje prostokąt ogniska w odpowiedzi na komunikat
WM_LBUTTONDOWN. Pokazaliśmy tu jedynie jak można wywołać funkcję coc: : Draw-FocusRext (), jednak w rzeczywistości obsługa tego rodzaju komunikatów nie jest zwykłym miejscem rysowania prostokątów ognisk.
Zwykle prostokąt ogniska powinieneś rysować wtedy, gdy okno znajdzie się w ognisku wprowadzania. Powód powinien być oczywisty: ognisko powinno być rysowane wtedy, gdy okno faktycznie je posiada. Jak zapewne pamiętasz z poprzedniej dyskusji, oznacza to, że prostokąt ogniska należy rysować w odpowiedzi na komunikat WM_SETFOCUS. Oto fragment kodu programu MFC przedstawiający odpowiedni sposób:
void CMainFrame::OnSetFocus(CWnd* pOldWnd) {
CFrameWnd::OnSetFocus(pO1dWnd);
CClientDC ClientDC(this);
// Rysowanie prostokąta ogniska
CRect rFocus;
rFocus.left = d_ptFocusRect.x;
rFocus.top = d_ptFocusRect.y;
rFocus.right = d_ptFocusRect.x + d_cxlcon;
rFocus.bottom = d_ptFocusRect.y + d_cylcon;
ClientDC.DrawFocusRect(&rFocus);
}
Jeśli narysujesz prostokąt ogniska, do Ciebie należy także usunięcie go. Ponieważ prostokąt ogniska służy jedynie pokazaniu, że okno posiada ognisko wprowadzania, logicznym momentem usunięcia prostokąta ogniska jest odpowiedź na komunikat odebrania ogniska wprowadzania. Co ciekawe, prostokąt ogniska jest usuwany dokładnie tym samym wywołaniem, które służy do jego rysowania: CDC: :DrawFocusRect (). Poniższy fragment kodu usuwa prostokąt ogniska, który narysowaliśmy przed momentem. Jest on wywoływany w odpowiedzi na - a cóżby innego - komunikat WM_KILLFOCUS:
void CMainFrame::OnKillFocus(CWnd* pNewWnd)
{ CFrameWnd::OnKillFocus(pNewWnd); CClientDC ClientDC(this);
// Usuwanie prostokąta ogniska
CRect rFocus;
rFocus.left = d_ptFocusRect.x;
rFocus.top = d_ptFocusRect.y;
rFocus.right = d_ptFocusRect.x + d_cxIcon;
rFocus.bottom = d_ptFocusRect.y + d_cyIcon;
ClientDC.DrawFocusRect(&rFocus) ;
}
Możesz zastanawiać się, jak to się dzieje, że ten sam kod może służyć do rysowania i jednocześnie do usuwania prostokąta ogniska. Aby wyjaśnić, jak to jest możliwe, musimy opisać, jak działa funkcja CDC : : DrawFocusRect (). Funkcja po prostu odwraca kolory zestawu pikseli, używając arytmetyki boolowskiej. Podczas rysowania prostokąta odwracane są kolory pikseli. Przy usuwaniu prostokąta kolory tych samych pikseli są odwracane
ponownie, co w efekcie powoduje całkowite usunięcie śladu po prostokącie ogniska. Tego typu operacje nazywa się często operacjami boolowskimi (logicznymi), korzystającymi z operacji rastrowych GDI (czyli tzw. kodów ROP - ang. Raster OPeratiori).
Jak dotąd opisaliśmy, w jaki sposób można narysować prostokąt ogniska w odpowiedzi na komunikat WM_SETFOCUS i usunąć go po otrzymaniu komunikatu WM_KILLFOCUS. Jednak istnieje jeszcze jeden komunikat, który może zaburzyć poprawne działanie prostokąta ogniska. Aby w pełni móc obsługiwać prostokąt ogniska, musisz odrysować go także w odpowiedzi na komunikat WM_PAINT.
Prostokąt ogniska musi zostać narysowany w funkcji obsługi komunikatu WM_PAINT z tego samego powodu, z jakiego są rysowane inne obiekty w obszarze roboczym okna. Nie możesz przewidzieć zawczasu jakie zdarzenia zewnętrzne spowodują zniszczenie zawartości obszaru roboczego. Może być to choćby wygaszacz ekranu uruchamiający się po dziesięciu minutach bezczynnej pracy komputera. Bez względu na powód do ponownego narysowania prostokąta ogniska służy ta sama funkcja, którą omawialiśmy przed chwilą: coc: :DrawFocusRect o . W przypadku komunikatu WM_PAINT musisz jednak sprawdzić wcześniej, czy okno znajduje się w ognisku wprowadzania. Jeśli tak nie jest, nie powinieneś rysować prostokąta ogniska. Oto funkcja obsługi komunikatu WM_PAINT sprawdzająca przed narysowaniem prostokąta ogniska, czy okno posiada ognisko wprowadzania:
void CMainFrame : :OnPaint ( ) {
CPaintDC PaintDC (this) ; // kontekst urządzenia dla obszaru roboczego
// Prostokąt ogniska rysujemy tylko wtedy, gdy okno posiada // ognisko wprowadzania if (GetFocus () == this) {
CRect rFocus;
rFocus.left = d_ptFocusRect . x;
rFocus. top = d_ptFocusRect . y;
rFocus. right = d_ptFocusRect .x + d_cxIcon;
rFocus . bottom = d_ptFocusRect .y + d_cyIcon;
// Rysowanie prostokąta ogniska ClientDC.DrawFocusRect (SrFocus) ;
}
}
Oba mechanizmy informowania o posiadaniu ogniska wprowadzania - kursor tekstowy i prostokąt ogniska - są względnie łatwe w utworzeniu i wykorzystaniu. Jednak istnieje także trzeci element - stan zaznaczenia - blisko powiązany z ogniskiem wprowadzania klawiatury. Przez stan zaznaczenia rozumiemy wyświetlanie w oknie obiektów zaznaczonych przez użytkownika. W celu zachowania zgodności z regułami odnoszącymi się do interfejsu użytkownika w Windows stan zaznaczenia musi być widoczny tylko wtedy, gdy okno posiada ognisko wprowadzania. We wszystkich innych przypadkach stan zaznaczenia obiektów musi być niewidoczny.
Stan zaznaczenia i ognisko wprowadzania
W aplikacjach Windows powszechnym trybem operacji jest tryb obiekt-czynność. Oznacza to, że użytkownik najpierw zaznacza obiekt, a następnie wybiera operację odnoszącą się do tego obiektu. Na przykład, aby zmienić formatowanie słowa w programie przetwarzania tekstu, użytkownik najpierw zaznacza słowo, a dopiero potem wybiera rodzaj formatowania. Choć ten tryb działania dla doświadczonych użytkowników Windows jest oczywisty, jednak w przypadku innych, znakowych środowisk, można napotkać na przeciwny tryb, tj. tryb czynność-obiekt.
Obiekty mogą być zaznaczane za pomocą myszy lub klawiatury. Bez względu na sposób zaznaczenia programy Windows zmieniają kolor zaznaczonych obiektów pomagając, użytkownikowi w odróżnieniu ich od innych obiektów. O wyborze koloru tekstu opowiemy w rozdziale 9., jednak na razie wystarczy, że do odczytu kolorów systemowych służy funkcja : :GetSysColor () wywoływana z odpowiednim indeksem elementu. Następnie musisz wykonać operację bitowej różnicy symetrycznej (XOR) w celu wyznaczenia koloru zaznaczonego tekstu. Służą do tego poniższe linie kodu:
COLORREF d_crForeground = GetSysColor(COLOR_WINDOWTEXT); COLORREF d_crBackground = GetSysColor(COLOR_WINDOW); COLORREF d_crSelectFore = d_crForeground A OxOOFFFFFF; COLORREF d_crSelectBack = d_crBackground A OxOOFFFFFF;
Ogólnie, gdy okno traci ognisko wprowadzania, powinno ukryć stan zaznaczenia. Gdy ognisko zostanie ponownie odzyskane, okno powinno ponownie wyświetlić stan zaznaczenia. Mówimy "ogólnie", ponieważ w kilku sytuacjach okno powinno zachować stan zaznaczenia mimo utraty ogniska wprowadzania.
Aby lepiej zorientować się, kiedy stan zaznaczenia powinien być ukryty, a kiedy powinien być widoczny, uruchom kilka z popularnych aplikacji Windows. Jeśli przyjrzysz się uważnie, zauważysz wiele małych wizualnych wskazówek wskazujących, że okno jest aktywne i posiada ognisko wprowadzania. Pewne z nich - na przykład zmiany koloru belek tytułowych - są obsługiwane przez Windows. Inne zmiany stanu - na przykład trzy zmiany omawiane w tym rozdziale - muszą zostać obsłużone przez aplikację. Jednak zamiast niezmiennie trzymać się zasad, powinieneś wybrać taki sposób, jaki będzie najlepszy dla użytkownika.
Podsumowanie
W odróżnieniu od poziomu abstrakcji oferowanego przez MFC w przypadku wielu innych aspektów programowania Windows, obsługa klawiatury i myszy wymaga raczej dość niskopoziomowego kodowania programu. Windows przekazuje komunikaty nie tylko dotyczące ruchów i kliknięć myszką, ale także po trzy różne komunikaty dla każdego wciśniętego klawisza!
Oczywiście, wraz ze złożonością mamy do dyspozycji większą elastyczność. Powinieneś dobrze zapoznać się z koncepcjami i technikami opisanymi w tym rozdziale. Ponieważ interakcja z użytkownikiem dotyczy praktycznie wszystkich aplikacji, będziesz miał wiele okazji, aby to przetrenować.

Wyszukiwarka

Podobne podstrony:
Megatutorial 3 3 Mysz i klawiatura
utk7 klawiatura mysz
TI 99 08 19 B M pl(1)
zestawy cwiczen przygotowane na podstawie programu Mistrz Klawia 6
ei 05 08 s029
Wyklad 2 PNOP 08 9 zaoczne
Egzamin 08 zbior zadan i pytan
niezbednik wychowawcy, pedagoga i psychologa 08 4 (1)
Kallysten Po wyjęciu z pudełka 08

więcej podobnych podstron