(1) 响应 WM_MOUSEMOVE 消息获得鼠标位置;
(2) 响应 WM_PAINT 将鼠标位置输出到窗口中;
(3) 学习二者之间的关键步骤:调用 InvalidateRect() 以通知窗口重绘。
零. 课堂视频
在窗口上跟踪输出鼠标位置-Win32版
一、关键知识点
1. BeginPaint() 对比 GetDC()
两个API都能得到指定窗口的 DC (设备上下文),供程序后续在 DC 上画图、输出文字等。二者区别在于,BeginPaint() 是在窗口收到 WM_PAINT 消息后使用。窗口收到 WM_PAINT 意味着:这个窗口当前展现的内容已经失效,确实需要重画。而 GetDC() 是直接获得一个窗口的DC,然后直接开画。
再往前推一步:一个程序有机会响应某个窗口的消息(比如这里的 WM_PAINT),通常就意味着这个窗口是该程序自己创建出来的。
二者在参数上也有明显不同,BeginPaint() 除窗口句柄外,还需要先创建一个 PAINTSTRCT 的变量。
2. EndPaint()
GetDC() 调用后,要记得配套地写上 ReleaseDC(),类似,我们从 BeginPaint() 得到一个 DC,在完成使用该 DC 之后,要记得配套调用 EndPaint()。
3. WM_MOUSEMOVE 消息
坐标点在该消息的响应函数的 lParam 参数,该参数的类型的是 LPARAM, 它是Windows开发库定义的一个宏。其中 L 表示 “long” ,也就是长整型。然而,在 Windows 上,哪怕是 64 位的系统,long 其实仍然一个 int ,因此还是四字节。当它用作 WM_MOUSEMOVE 的参数时,从左到右,前两个字节(低字),存储鼠标移动后的 x 坐标,高字存储 y 坐标。
对一个整数(int,long)取低字,可使用宏 LOWORD(),其中 LO 是 “low / 低” 的缩写,对应的取高字使用 HIWORD(), “HI” 是 “High / 高” 的缩写。
4. InvalidateRect
函数原型(声明)为:
BOOL InvalidateRect( HWND hWnd, const RECT *lpRect, BOOL bErase );
InvalidateRect 用于让指定窗口(hWnd)的指定区域(lpRect,说明见下)的绘图内容失效。
如果 lpRect 为空指针,则让整个窗口的(严格讲是客户区,即不包容标题栏、边框等)的绘图内容都失效。这样,窗口就会收到 WM_PAINT 消息,然后程序调用处理该消息的函数,开始重绘。
bErase 为真时,如果该窗口设置了合适的默认背景刷子(Brush,类似于 Pen,也是一种绘图资源 ), 系统将保证在重绘前,使用该刷子(特定颜色、样式),“刷掉” 原有内容。注意,窗口并不一定有合适的背景刷子。在我们的示例程序中,以下这一行保证了这一点:
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
其中的 RECT 是一个结构(struct),定义为:
struct RECT
{
long left, top, right, bottom;
};
即,通过 左、上、右、下 位置,定义一个矩形区域。
三、完整代码
#include <string>
#include <sstream> // stringstream
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h>
#include <windows.h>
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
/* Make the class name into a global variable */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; /* No menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szClassName, /* Classname */
_T("Code::Blocks Template Windows App"), /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
544, /* The programs width */
375, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
int xPos, yPos; // 用于记录鼠标的坐标x和y值
LRESULT CALLBACK OnMouseMove(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
xPos = LOWORD(lParam); // 低字 是 x
yPos = HIWORD(lParam); // 高字 是 y
::InvalidateRect(hwnd, nullptr, true); // true: 宣告窗口整个区域的现有展现内容失效,并且自动擦除
return DefWindowProc (hwnd, message, wParam, lParam);
}
LRESULT CALLBACK OnPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint(hwnd, &ps);
std::stringstream ss;
ss << "[" << xPos << " | " << yPos << "]-Hello Win32-来自d2school的南老师";
auto txt = ss.str();
::TextOut(hDC, xPos, yPos, txt.c_str(), txt.size());
::EndPaint(hwnd, &ps);
return DefWindowProc (hwnd, message, wParam, lParam);
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_MOUSEMOVE:
return OnMouseMove(hwnd, message, wParam, lParam);
case WM_PAINT :
return OnPaint(hwnd, message, wParam, lParam);
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}