Korzystanie z wprowadzania myszy
W tej sekcji omówiono zadania związane z wprowadzaniem danych za pomocą myszy.
- Śledzenie kursora myszy
- Rysowanie linii za pomocą myszy
- przetwarzanie komunikatu podwójnego kliknięcia
- Zaznaczanie linii tekstu
- używanie kółka myszy w dokumencie z osadzonymi obiektami
- Pobieranie liczby linii przewijania kółka myszy
Śledzenie kursora myszy
Aplikacje często wykonują zadania, które obejmują śledzenie położenia kursora myszy. Większość aplikacji rysunkowych, na przykład, śledzi położenie kursora myszy podczas operacji rysunku, co umożliwia użytkownikowi rysowanie w obszarze klienta okna przez przeciągnięcie myszy. Aplikacje do przetwarzania wyrazów śledzą również kursor, umożliwiając użytkownikowi wybranie wyrazu lub bloku tekstu, klikając i przeciągając mysz.
Śledzenie kursora zwykle obejmuje przetwarzanie komunikatów WM_LBUTTONDOWN, WM_MOUSEMOVEi WM_LBUTTONUP. Okno określa, kiedy rozpocząć śledzenie kursora, sprawdzając położenie kursora podane w parametrze lParam komunikatu WM_LBUTTONDOWN. Na przykład aplikacja do przetwarzania tekstu rozpocznie śledzenie kursora tylko wtedy, gdy komunikat WM_LBUTTONDOWN pojawił się, gdy kursor znajdował się na linii tekstu, ale nie wtedy, gdy przeszedł za koniec dokumentu.
Okno śledzi położenie kursora, przetwarzając strumień komunikatów WM_MOUSEMOVE opublikowanych w oknie w miarę poruszania się myszą. Przetwarzanie komunikatu WM_MOUSEMOVE zazwyczaj wiąże się z powtarzalnym malowaniem lub operacją rysowania w obszarze klienta. Na przykład aplikacja rysunkowa może wielokrotnie wyrysowywać linię w miarę poruszania się myszą. Okno używa komunikatu WM_LBUTTONUP jako sygnału, aby zatrzymać śledzenie kursora.
Ponadto aplikacja może wywołać funkcję TrackMouseEvent, aby system wysyłał inne komunikaty przydatne do śledzenia kursora. System wysyła komunikat WM_MOUSEHOVER, kiedy kursor znajduje się nad obszarem klienta na określony czas. Publikuje komunikat WM_MOUSELEAVE, gdy kursor opuszcza obszar klienta. Komunikaty WM_NCMOUSEHOVER i WM_NCMOUSELEAVE są odpowiednimi komunikatami dla obszarów nieklientowych.
Rysowanie linii za pomocą myszy
W przykładzie w tej sekcji pokazano, jak śledzić kursor myszy. Zawiera fragmenty procedury obsługi okna, która umożliwia użytkownikowi rysowanie linii w obszarze klienta okna poprzez przeciąganie myszy.
Gdy procedura okna odbiera komunikat WM_LBUTTONDOWN, przechwytuje mysz oraz zapisuje współrzędne kursora, wykorzystując je jako punkt początkowy linii. Kluczowym elementem jest również użycie funkcji ClipCursor, aby kontrolować kursor, ograniczając go do obszaru klienta podczas operacji rysowania linii.
Podczas pierwszego komunikatu WM_MOUSEMOVE procedura okna rysuje linię od punktu początkowego do bieżącej pozycji kursora. Podczas kolejnych komunikatów WM_MOUSEMOVE procedura okna wymazuje poprzednią linię, rysując ją za pomocą odwróconego koloru pióra. Następnie rysuje nowy wiersz od punktu początkowego do nowej pozycji kursora.
Komunikat WM_LBUTTONUP sygnalizuje koniec operacji rysowania. Procedura okna zwalnia przechwytywanie myszy i uwalnia mysz z obszaru klienta.
LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc; // handle to device context
RECT rcClient; // client area rectangle
POINT ptClientUL; // client upper left corner
POINT ptClientLR; // client lower right corner
static POINTS ptsBegin; // beginning point
static POINTS ptsEnd; // new endpoint
static POINTS ptsPrevEnd; // previous endpoint
static BOOL fPrevLine = FALSE; // previous line flag
switch (uMsg)
{
case WM_LBUTTONDOWN:
// Capture mouse input.
SetCapture(hwndMain);
// Retrieve the screen coordinates of the client area,
// and convert them into client coordinates.
GetClientRect(hwndMain, &rcClient);
ptClientUL.x = rcClient.left;
ptClientUL.y = rcClient.top;
// Add one to the right and bottom sides, because the
// coordinates retrieved by GetClientRect do not
// include the far left and lowermost pixels.
ptClientLR.x = rcClient.right + 1;
ptClientLR.y = rcClient.bottom + 1;
ClientToScreen(hwndMain, &ptClientUL);
ClientToScreen(hwndMain, &ptClientLR);
// Copy the client coordinates of the client area
// to the rcClient structure. Confine the mouse cursor
// to the client area by passing the rcClient structure
// to the ClipCursor function.
SetRect(&rcClient, ptClientUL.x, ptClientUL.y,
ptClientLR.x, ptClientLR.y);
ClipCursor(&rcClient);
// Convert the cursor coordinates into a POINTS
// structure, which defines the beginning point of the
// line drawn during a WM_MOUSEMOVE message.
ptsBegin = MAKEPOINTS(lParam);
return 0;
case WM_MOUSEMOVE:
// When moving the mouse, the user must hold down
// the left mouse button to draw lines.
if (wParam & MK_LBUTTON)
{
// Retrieve a device context (DC) for the client area.
hdc = GetDC(hwndMain);
// The following function ensures that pixels of
// the previously drawn line are set to white and
// those of the new line are set to black.
SetROP2(hdc, R2_NOTXORPEN);
// If a line was drawn during an earlier WM_MOUSEMOVE
// message, draw over it. This erases the line by
// setting the color of its pixels to white.
if (fPrevLine)
{
MoveToEx(hdc, ptsBegin.x, ptsBegin.y,
(LPPOINT) NULL);
LineTo(hdc, ptsPrevEnd.x, ptsPrevEnd.y);
}
// Convert the current cursor coordinates to a
// POINTS structure, and then draw a new line.
ptsEnd = MAKEPOINTS(lParam);
MoveToEx(hdc, ptsBegin.x, ptsBegin.y, (LPPOINT) NULL);
LineTo(hdc, ptsEnd.x, ptsEnd.y);
// Set the previous line flag, save the ending
// point of the new line, and then release the DC.
fPrevLine = TRUE;
ptsPrevEnd = ptsEnd;
ReleaseDC(hwndMain, hdc);
}
break;
case WM_LBUTTONUP:
// The user has finished drawing the line. Reset the
// previous line flag, release the mouse cursor, and
// release the mouse capture.
fPrevLine = FALSE;
ClipCursor(NULL);
ReleaseCapture();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
break;
// Process other messages.
}
}
Przetwarzanie wiadomości dwukrotnego kliknięcia
Aby otrzymywać wiadomości o dwukrotnym kliknięciu, okno musi należeć do klasy okien, która ma styl klasy CS_DBLCLKS. Ten styl można ustawić podczas rejestrowania klasy okna, jak pokazano w poniższym przykładzie.
BOOL InitApplication(HINSTANCE hInstance)
{
WNDCLASS wc;
wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_IBEAM);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWClass";
return RegisterClass(&wc);
}
Komunikat podwójnego kliknięcia jest zawsze poprzedzony komunikatem przyciśnięcia przycisku. Z tego powodu aplikacje zazwyczaj używają komunikatu o dwukrotnym kliknięciu, aby rozszerzyć zadanie rozpoczęte podczas komunikatu o naciśnięciu przycisku.
Wybieranie linii tekstu
Przykład w tej sekcji jest pobierany z prostej aplikacji do przetwarzania wyrazów. Zawiera on kod, który umożliwia użytkownikowi ustawienie położenia karetki przez kliknięcie dowolnego miejsca w wierszu tekstu i wybranie (wyróżnienie) wiersza tekstu przez dwukrotne kliknięcie w dowolnym miejscu w wierszu.
LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc; // handle to device context
TEXTMETRIC tm; // font size data
int i, j; // loop counters
int cCR = 0; // count of carriage returns
char ch; // character from input buffer
static int nBegLine; // beginning of selected line
static int nCurrentLine = 0; // currently selected line
static int nLastLine = 0; // last text line
static int nCaretPosX = 0; // x-coordinate of caret
static int cch = 0; // number of characters entered
static int nCharWidth = 0; // exact width of a character
static char szHilite[128]; // text string to highlight
static DWORD dwCharX; // average width of characters
static DWORD dwLineHeight; // line height
static POINTS ptsCursor; // coordinates of mouse cursor
static COLORREF crPrevText; // previous text color
static COLORREF crPrevBk; // previous background color
static PTCHAR pchInputBuf; // pointer to input buffer
static BOOL fTextSelected = FALSE; // text-selection flag
size_t * pcch;
HRESULT hResult;
switch (uMsg)
{
case WM_CREATE:
// Get the metrics of the current font.
hdc = GetDC(hwndMain);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwndMain, hdc);
// Save the average character width and height.
dwCharX = tm.tmAveCharWidth;
dwLineHeight = tm.tmHeight;
// Allocate a buffer to store keyboard input.
pchInputBuf = (LPSTR) GlobalAlloc(GPTR,
BUFSIZE * sizeof(TCHAR));
return 0;
case WM_CHAR:
switch (wParam)
{
case 0x08: // backspace
case 0x0A: // linefeed
case 0x1B: // escape
MessageBeep( (UINT) -1);
return 0;
case 0x09: // tab
// Convert tabs to four consecutive spaces.
for (i = 0; i < 4; i++)
SendMessage(hwndMain, WM_CHAR, 0x20, 0);
return 0;
case 0x0D: // carriage return
// Record the carriage return, and position the
// caret at the beginning of the new line.
pchInputBuf[cch++] = 0x0D;
nCaretPosX = 0;
nCurrentLine += 1;
break;
default: / displayable character
ch = (char) wParam;
HideCaret(hwndMain);
// Retrieve the character's width, and display the
// character.
hdc = GetDC(hwndMain);
GetCharWidth32(hdc, (UINT) wParam, (UINT) wParam,
&nCharWidth);
TextOut(hdc, nCaretPosX,
nCurrentLine * dwLineHeight, &ch, 1);
ReleaseDC(hwndMain, hdc);
// Store the character in the buffer.
pchInputBuf[cch++] = ch;
// Calculate the new horizontal position of the
// caret. If the new position exceeds the maximum,
// insert a carriage return and reposition the
// caret at the beginning of the next line.
nCaretPosX += nCharWidth;
if ((DWORD) nCaretPosX > dwMaxCharX)
{
nCaretPosX = 0;
pchInputBuf[cch++] = 0x0D;
++nCurrentLine;
}
ShowCaret(hwndMain);
break;
}
SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);
nLastLine = max(nLastLine, nCurrentLine);
break;
// Process other messages.
case WM_LBUTTONDOWN:
// If a line of text is currently highlighted, redraw
// the text to remove the highlighting.
if (fTextSelected)
{
hdc = GetDC(hwndMain);
SetTextColor(hdc, crPrevText);
SetBkColor(hdc, crPrevBk);
hResult = StringCchLength(szHilite, 128/sizeof(TCHAR), pcch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 0, nCurrentLine * dwLineHeight,
szHilite, *pcch);
ReleaseDC(hwndMain, hdc);
ShowCaret(hwndMain);
fTextSelected = FALSE;
}
// Save the current mouse-cursor coordinates.
ptsCursor = MAKEPOINTS(lParam);
// Determine which line the cursor is on, and save
// the line number. Do not allow line numbers greater
// than the number of the last line of text. The
// line number is later multiplied by the average height
// of the current font. The result is used to set the
// y-coordinate of the caret.
nCurrentLine = min((int)(ptsCursor.y / dwLineHeight),
nLastLine);
// Parse the text input buffer to find the first
// character in the selected line of text. Each
// line ends with a carriage return, so it is possible
// to count the carriage returns to find the selected
// line.
cCR = 0;
nBegLine = 0;
if (nCurrentLine != 0)
{
for (i = 0; (i < cch) &&
(cCR < nCurrentLine); i++)
{
if (pchInputBuf[i] == 0x0D)
++cCR;
}
nBegLine = i;
}
// Starting at the beginning of the selected line,
// measure the width of each character, summing the
// width with each character measured. Stop when the
// sum is greater than the x-coordinate of the cursor.
// The sum is used to set the x-coordinate of the caret.
hdc = GetDC(hwndMain);
nCaretPosX = 0;
for (i = nBegLine;
(pchInputBuf[i] != 0x0D) && (i < cch); i++)
{
ch = pchInputBuf[i];
GetCharWidth32(hdc, (int) ch, (int) ch, &nCharWidth);
if ((nCaretPosX + nCharWidth) > ptsCursor.x) break;
else nCaretPosX += nCharWidth;
}
ReleaseDC(hwndMain, hdc);
// Set the caret to the user-selected position.
SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);
break;
case WM_LBUTTONDBLCLK:
// Copy the selected line of text to a buffer.
for (i = nBegLine, j = 0; (pchInputBuf[i] != 0x0D) &&
(i < cch); i++)
{
szHilite[j++] = pchInputBuf[i];
}
szHilite[j] = '\0';
// Hide the caret, invert the background and foreground
// colors, and then redraw the selected line.
HideCaret(hwndMain);
hdc = GetDC(hwndMain);
crPrevText = SetTextColor(hdc, RGB(255, 255, 255));
crPrevBk = SetBkColor(hdc, RGB(0, 0, 0));
hResult = StringCchLength(szHilite, 128/sizeof(TCHAR), pcch);
if (FAILED(hResult))
{
// TODO: write error handler
}
TextOut(hdc, 0, nCurrentLine * dwLineHeight, szHilite, *pcch);
SetTextColor(hdc, crPrevText);
SetBkColor(hdc, crPrevBk);
ReleaseDC(hwndMain, hdc);
fTextSelected = TRUE;
break;
// Process other messages.
default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}
Używanie kółka myszy w dokumencie z obiektami osadzonymi
W tym przykładzie założono, że dokument Microsoft Word zawiera różne osadzone obiekty:
- Arkusz kalkulacyjny programu Microsoft Excel
- Osadzona kontrolka pola listy, która przewija się w odpowiedzi na koło
- Osadzona kontrolka pola tekstowego, która nie odpowiada na koło
Komunikat MSH_MOUSEWHEEL jest zawsze wysyłany do okna głównego w programie Microsoft Word. Jest to prawdą, nawet jeśli osadzony arkusz kalkulacyjny jest aktywny. W poniższej tabeli opisano sposób obsługi komunikatu MSH_MOUSEWHEEL w zależności od orientacji.
Skupienie się na | Obsługa jest następująca |
---|---|
Dokument programu Word | Program Word przewija okno dokumentu. |
Osadzony arkusz kalkulacyjny programu Excel | Program Word publikuje komunikat w programie Excel. Musisz zdecydować, czy osadzona aplikacja powinna odpowiadać na komunikat, czy nie. |
Osadzona kontrolka | Do aplikacji należy wysłanie komunikatu do osadzonej kontrolki, która ma fokus, oraz sprawdzenie kodu powrotnego, aby ustalić, czy kontrolka go obsłużyła. Jeśli kontrolka nie obsłużyła tego, aplikacja powinna przewijać okno dokumentu. Jeśli na przykład użytkownik kliknął pole listy, a następnie przerzucił koło, ta kontrolka przewija się w odpowiedzi na obrót koła. Gdyby użytkownik kliknął pole tekstowe, a następnie obrócił koło, cały dokument przewijałby się. |
W poniższym przykładzie pokazano, jak aplikacja może obsługiwać dwa komunikaty dotyczące kół.
/************************************************
* this code deals with MSH_MOUSEWHEEL
*************************************************/
#include "zmouse.h"
//
// Mouse Wheel rotation stuff, only define if we are
// on a version of the OS that does not support
// WM_MOUSEWHEEL messages.
//
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL WM_MOUSELAST+1
// Message ID for IntelliMouse wheel
#endif
UINT uMSH_MOUSEWHEEL = 0; // Value returned from
// RegisterWindowMessage()
/**************************************************
INT WINAPI WinMain(
HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
INT nCmdShow)
{
MSG msg;
BOOL bRet;
if (!InitInstance(hInst, nCmdShow))
return FALSE;
//
// The new IntelliMouse uses a Registered message to transmit
// wheel rotation info. So register for it!
uMSH_MOUSEWHEEL =
RegisterWindowMessage(MSH_MOUSEWHEEL);
if ( !uMSH_MOUSEWHEEL )
{
MessageBox(NULL,"
RegisterWindowMessag Failed!",
"Error",MB_OK);
return msg.wParam;
}
while (( bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
if (!TranslateAccelerator(ghwndApp,
ghaccelTable,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return msg.wParam;
}
/************************************************
* this code deals with WM_MOUSEWHEEL
*************************************************/
LONG APIENTRY MainWndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
static int nZoom = 0;
switch (msg)
{
//
// Handle Mouse Wheel messages generated
// by the operating systems that have built-in
// support for the WM_MOUSEWHEEL message.
//
case WM_MOUSEWHEEL:
((short) HIWORD(wParam)< 0) ? nZoom-- : nZoom++;
//
// Do other wheel stuff...
//
break;
default:
//
// uMSH_MOUSEWHEEL is a message registered by
// the mswheel dll on versions of Windows that
// do not support the new message in the OS.
if( msg == uMSH_MOUSEWHEEL )
{
((int)wParam < 0) ? nZoom-- : nZoom++;
//
// Do other wheel stuff...
//
break;
}
return DefWindowProc(hwnd,
msg,
wParam,
lParam);
}
return 0L;
}
Pobieranie liczby linii przewijania kółka myszy
Poniższy kod umożliwia aplikacji pobieranie liczby linii przewijania przy użyciu funkcji SystemParametersInfo.
#ifndef SPI_GETWHEELSCROLLLINES
#define SPI_GETWHEELSCROLLLINES 104
#endif
#include "zmouse.h"
/*********************************************************
* FUNCTION: GetNumScrollLines
* Purpose : An OS independent method to retrieve the
* number of wheel scroll lines
* Params : none
* Returns : UINT: Number of scroll lines where WHEEL_PAGESCROLL
* indicates to scroll a page at a time.
*********************************************************/
UINT GetNumScrollLines(void)
{
HWND hdlMsWheel;
UINT ucNumLines=3; // 3 is the default
OSVERSIONINFO osversion;
UINT uiMsh_MsgScrollLines;
memset(&osversion, 0, sizeof(osversion));
osversion.dwOSVersionInfoSize =sizeof(osversion);
GetVersionEx(&osversion);
if ((osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) ||
( (osversion.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
(osversion.dwMajorVersion < 4) ) )
{
hdlMsWheel = FindWindow(MSH_WHEELMODULE_CLASS,
MSH_WHEELMODULE_TITLE);
if (hdlMsWheel)
{
uiMsh_MsgScrollLines = RegisterWindowMessage
(MSH_SCROLL_LINES);
if (uiMsh_MsgScrollLines)
ucNumLines = (int)SendMessage(hdlMsWheel,
uiMsh_MsgScrollLines,
0,
0);
}
}
else if ( (osversion.dwPlatformId ==
VER_PLATFORM_WIN32_NT) &&
(osversion.dwMajorVersion >= 4) )
{
SystemParametersInfo(SPI_GETWHEELSCROLLLINES,
0,
&ucNumLines, 0);
}
return(ucNumLines);
}