ScreenToClient无法获取窗口客户区坐标,画图类应用绘制问题求助
解决ScreenToClient无法获取正确客户区坐标的问题
首先先确认MSDN对ScreenToClient的定义:
新坐标相对于指定窗口客户区的左上角。
结合你做画图应用的场景,我发现几个可能导致坐标异常的问题,以及对应的解决方案:
一、错误的消息处理时机:在WM_PAINT里处理实时绘制
WM_PAINT是窗口重绘消息,它的触发时机是窗口需要刷新的时候(比如被遮挡后恢复、窗口大小改变),这个时机里去检测鼠标状态并绘制是不合理的:
- 此时鼠标的位置和状态可能和用户实际操作不同步
- 频繁触发WM_PAINT会导致绘制逻辑混乱,还容易出现闪烁
正确的做法是把绘制逻辑拆分到鼠标消息里:
WM_LBUTTONDOWN:按下左键时,标记开始绘制(比如设置bDrawing = TRUE),调用SetCapture(hWnd)捕获鼠标(确保鼠标移出窗口时仍能收到移动消息),同时记录初始坐标WM_MOUSEMOVE:如果bDrawing为TRUE,再去获取鼠标坐标并绘制WM_LBUTTONUP:松开左键时,标记停止绘制(bDrawing = FALSE),调用ReleaseCapture()释放鼠标捕获
二、ScreenToClient的正确调用方式
你需要确保传递的是正确的窗口句柄,并且POINT参数是传地址(因为ScreenToClient会直接修改这个结构体里的坐标值):
POINT pt; // 获取屏幕坐标 GetCursorPos(&pt); // 转换为当前窗口的客户区坐标 ScreenToClient(hWnd, &pt); // 此时pt.x和pt.y就是相对于客户区左上角的坐标了
三、GetKeyState的判断逻辑错误
你代码里用GetKeyState(VK_LBUTTON) & 256来判断左键是否按住,这是错误的:
GetKeyState返回值的**最高位(0x8000)**表示当前按键是否被按下- 256(0x100)对应的是次高位,用来表示按键的切换状态(比如Caps Lock、Num Lock这类)
正确的判断应该是:
if ((GetKeyState(VK_LBUTTON) & 0x8000) && !bStopDrawing)
四、WM_PAINT的正确职责
WM_PAINT应该负责重绘所有已经绘制的内容,而不是实时绘制。你可以维护一个缓冲区(比如一个内存DC或者点的数组),在鼠标消息里把绘制的内容记录到缓冲区,然后在WM_PAINT里把缓冲区的内容复制到窗口DC上,这样能避免闪烁,也能保证窗口重绘时内容不会丢失。
举个简化的代码示例:
// 全局变量,标记是否正在绘制 BOOL bDrawing = FALSE; // 内存DC,用来保存绘制内容 HDC hMemDC = NULL; HBITMAP hMemBitmap = NULL; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // 初始化内存DC和位图 { HDC hDC = GetDC(hWnd); RECT rc; GetClientRect(hWnd, &rc); hMemDC = CreateCompatibleDC(hDC); hMemBitmap = CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top); SelectObject(hMemDC, hMemBitmap); // 初始化内存DC为白色背景 PatBlt(hMemDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, WHITENESS); ReleaseDC(hWnd, hDC); } break; case WM_LBUTTONDOWN: bDrawing = TRUE; SetCapture(hWnd); // 记录初始点 POINT ptStart = { LOWORD(lParam), HIWORD(lParam) }; // 在内存DC上绘制初始点 SetPixel(hMemDC, ptStart.x, ptStart.y, RGB(0,0,0)); InvalidateRect(hWnd, NULL, FALSE); break; case WM_MOUSEMOVE: if (bDrawing) { POINT pt; GetCursorPos(&pt); ScreenToClient(hWnd, &pt); // 在内存DC上绘制点 SetPixel(hMemDC, pt.x, pt.y, RGB(0,0,0)); InvalidateRect(hWnd, NULL, FALSE); } break; case WM_LBUTTONUP: bDrawing = FALSE; ReleaseCapture(); break; case WM_PAINT: { PAINTSTRUCT ps; HDC hDC = BeginPaint(hWnd, &ps); // 把内存DC的内容复制到窗口DC BitBlt(hDC, 0, 0, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, hMemDC, 0, 0, SRCCOPY); EndPaint(hWnd, &ps); } break; case WM_DESTROY: // 释放资源 DeleteObject(hMemBitmap); DeleteDC(hMemDC); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
内容的提问来源于stack exchange,提问作者Itsenough1
相关产品推荐
相关产品推荐

