使用鼠标输入
本部分介绍与鼠标输入关联的任务。
跟踪鼠标光标
应用程序通常执行涉及跟踪鼠标光标位置的任务。 例如,大多数绘图应用程序在绘制操作期间跟踪鼠标光标的位置,从而允许用户通过拖动鼠标在窗口的工作区中绘图。 Word处理应用程序还可以跟踪光标,使用户能够通过单击并拖动鼠标来选择单词或文本块。
跟踪游标通常涉及处理 WM_LBUTTONDOWN、 WM_MOUSEMOVE和 WM_LBUTTONUP 消息。 窗口通过检查WM_LBUTTONDOWN消息的 lParam 参数中提供的游标位置来确定何时开始跟踪 光标 。 例如,仅当光标位于文本行上时发生 WM_LBUTTONDOWN 消息时,字处理应用程序才会开始跟踪游标,但如果它已超过文档的末尾,则不会开始跟踪光标。
窗口通过处理在鼠标移动时发布到窗口的 WM_MOUSEMOVE 消息流来跟踪光标的位置。 处理 WM_MOUSEMOVE 消息通常涉及工作区中的重复绘制或绘图操作。 例如,绘图应用程序可能会在鼠标移动时重复重新绘制线条。 窗口使用 WM_LBUTTONUP 消息作为停止跟踪光标的信号。
此外,应用程序可以调用 TrackMouseEvent 函数,让系统发送对跟踪游标有用的其他消息。 当光标悬停在工作区上一定时间段时,系统会发布 WM_MOUSEHOVER 消息。 当光标离开工作区时,它会发布 WM_MOUSELEAVE 消息。 WM_NCMOUSEHOVER和WM_NCMOUSELEAVE消息是非工作区的相应消息。
使用鼠标绘制线条
本部分中的示例演示如何跟踪鼠标光标。 它包含窗口过程的某些部分,使用户能够通过拖动鼠标在窗口的工作区中绘制线条。
当窗口过程收到 WM_LBUTTONDOWN 消息时,它会捕获鼠标并保存光标的坐标,并使用坐标作为线条的起点。 它还使用 ClipCursor 函数在线条绘制操作期间将光标限制在工作区。
在第一 个WM_MOUSEMOVE 消息期间,窗口过程将绘制一条从起始点到光标当前位置的线条。 在后续 WM_MOUSEMOVE 消息期间,窗口过程通过用倒笔颜色在上一行上绘制来擦除上一行。 然后,它从起始点到光标的新位置绘制一条新线。
WM_LBUTTONUP消息表示绘图操作结束。 窗口过程释放鼠标捕获,并从工作区中释放鼠标。
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.
}
}
处理双击消息
若要接收双击消息,窗口必须属于具有 CS_DBLCLKS 类样式的窗口类。 注册窗口类时设置此样式,如以下示例所示。
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);
}
双击消息前面始终带有按钮关闭消息。 出于此原因,应用程序通常使用双击消息来扩展它在按钮关闭消息期间开始的任务。
选择文本行
本部分中的示例取自一个简单的字处理应用程序。 它包含的代码使用户能够通过单击文本行上的任意位置来设置插入点的位置,并通过双击该行上的任意位置选择 (突出显示) 文本行。
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;
}
在包含嵌入对象的文档中使用鼠标滚轮
此示例假定 Microsoft Word 文档包含各种嵌入对象:
- Microsoft Excel 电子表格
- 滚动以响应滚轮的嵌入列表框控件
- 不响应滚轮的嵌入文本框控件
MSH_MOUSEWHEEL消息始终发送到 Microsoft Word 中的main窗口。 即使嵌入的电子表格处于活动状态,也是如此。 下表说明了如何根据焦点处理MSH_MOUSEWHEEL消息。
重点在于 | 处理方式如下: |
---|---|
Word文档 | Word滚动文档窗口。 |
嵌入式 Excel 电子表格 | Word将邮件发布到 Excel。 必须决定嵌入式应用程序是否应响应消息。 |
嵌入式控件 | 由应用程序将消息发送到具有焦点的嵌入式控件,并检查返回代码以查看控件是否处理了它。 如果控件未处理它,则应用程序应滚动文档窗口。 例如,如果用户单击列表框,然后滚动滚轮,则该控件将滚动以响应滚轮旋转。 如果用户单击文本框,然后旋转滚轮,则整个文档将滚动。 |
以下示例演示应用程序如何处理两个滚轮消息。
/************************************************
* 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;
}
检索鼠标滚轮滚动行数
以下代码允许应用程序使用 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);
}