(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); // 低字 是 xyPos = 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;
}