在Windows编程中,通常涉及到三个坐标系,分别为屏幕坐标系、窗口坐标系和客户区坐标系。下面来看一张三种坐标系的说明图:
【屏幕坐标系、窗口坐标系和客户区坐标系示意图】
从图中我们可以看到,最外边的矩形,是屏幕。里面的一个矩形是窗口。在窗口处理标题栏外的是客户区部分。屏幕坐标系就是左上角作为屏幕的原点。向右为屏幕坐标系X轴正向,向左为负向,向上为屏幕坐标系负向,向下为屏幕坐标正向。
窗口坐标系和客户区坐标系与屏幕坐标系的XY轴走向是一样的。当然,这一切都是默认的坐标系映射模式下的。我们不深入说其他的坐标映射模式。这是防止一些朋友误会这个坐标系走向。
窗口坐标系和客户区坐标系与屏幕坐标系不一样的地方就在于起点不一样而已。从图中加粗的坐标轴的交叉点可以看到三个坐标系的原点。
知道坐标系的走向和原点有什么意义呢?这个是非常有意义的。你所处理的坐标,经常会在坐标系间转换。不过,很少用到窗口坐标系。因为一般主要处理客户区的消息,很少处理非客户区的消息。所以,主要是客户区坐标和屏幕坐标的转换。
所谓的坐标转换,也就是转换一个坐标参考的起点。当你的鼠标光标位于客户区中,同时具备三种坐标系的坐标。不过在客户区中,主要是使用客户区坐标。好比地球坐标、太阳系坐标和银河系坐标,我们在地球中,自然使用地球坐标最合适了。我们在确定光标在客户区的位置时,只需要相对于客户区原点就可以定位了。
但是,如果要用窗口坐标系和屏幕坐标系来计算光标的位置时,我们就需要用到这两个坐标系的坐标。因此,就要将客户区的坐标转换为这两个坐标系的坐标。同理,我们也可能需要从窗口坐标和屏幕坐标转换为客户区坐标。
转换的函数有:ClientToScreen(将客户区坐标转换为屏幕坐标)和ScreenToClient(将屏幕坐标转换为客户区坐标)。一般不会需要窗口坐标,就没有相关的转换函数。如果需要,自己想办法吧。
我们将客户区中的光标坐标、客户区原点坐标和光标的屏幕坐标都显示出来,然后看看他们的关系。鼠标光标的屏幕坐标的获取,就是使用ClientToScreen来将客户区的坐标转换得来。而客户区原点坐标,如果是相对于客户区本身,自然是(0,0),然而我们需要知道客户区原点在屏幕坐标系的坐标,所以也将原点转换为屏幕坐标。
结果我们发现,客户区原点在屏幕的坐标加上鼠标光标在客户区中的坐标就等于鼠标光标在屏幕的坐标。由此,也说明了坐标转换其实就是相对原点的切换罢了。在处理坐标转换的时候,在大脑时刻有这样一张坐标系图,那么坐标也不是什么事了。
下面是程序执行的效果截图:
【程序效果图:客户区原点坐标+鼠标光标在客户区的坐标=鼠标光标在屏幕上的坐标】
下面是完整的实现代码:#include "windows.h"
#include <tchar.h>
TCHAR tip[100]=_T("");
TCHAR txt[100]=_T("C++技术网www.cjjjs.com");
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static POINT ptScreen,ptClient,ptOrg;//静态变量,默认初始化为0
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);
TextOut(hdc,2,10,txt,lstrlen(txt));
wsprintf(txt,_T("客户区:(%d , %d),客户区原点屏幕坐标:(%d , %d),屏幕:(%d , %d)"),ptClient.x,ptClient.y,ptOrg.x,ptOrg.y,ptScreen.x,ptScreen.y);
TextOut(hdc,2,30,tip,lstrlen(tip));
EndPaint(hwnd,&ps);
return 0;
case WM_MOUSEMOVE:
{
//客户区鼠标移动时lParam携带的是客户区坐标
ptClient.x = LOWORD(lParam);
ptClient.y = HIWORD(lParam);
ptScreen.x=ptClient.x;
ptScreen.y=ptClient.y;
ClientToScreen(hwnd,&ptScreen);//将客户区坐标转换为屏幕坐标
ptOrg.x=ptOrg.y=0;
ClientToScreen(hwnd,&ptOrg);//将窗口的
InvalidateRect(hwnd,NULL,TRUE);
ScreenToClient()
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;//跳出到默认处理
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
TCHAR ClassName[] = _T("MyClass");
TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
WNDCLASS wndClass;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WinProc;
wndClass.lpszClassName = ClassName;
wndClass.lpszMenuName=NULL;
wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
if(!RegisterClass(&wndClass))return 0;
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,350,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}