窗口数据/函数
数据类型
HINSTANCE 是应用程序实例句柄,
HWND 是窗口对象句柄,
HANDLE 是任意对象的句柄,
CWnd 是MFC中的窗口类。
HDC 窗口对象中用于绘制界面的句柄
PAINTSTRUCT BeginPaint中用来存储hdc相关信息的结构体
RECT 描述hdc中的一块矩形区域
无效区域
Windows内部为每个窗口保存一个绘图信息结构,这个结构包含了包围无效区域的最小矩形的坐标以及其它信息,这个矩形就叫做无效矩形
刷新界面/rc
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-invalidaterect
https://docs.microsoft.com/zh-cn/windows/desktop/api/winuser/nf-winuser-validaterect
- 只要有无效区域 就会不停地产生消息
WM_PAINT
InvalidateRect
设定无效区域 产生消息WM_PAINT
InvalidateRect()
是强制系统进行重画,但是不一定就马上进行重画- 因为
InvalidateRect()
只是通知系统,此 时的窗口已经变为无效 - 强制系统调用
WM_PAINT
,而这个消息只是Post
(寄送)就是将该消息放入消息队列 - 当执行到WM_PAINT消息时才会对敞口进行重绘
- 因为
ValidateRect(HWND,RECT)
使rc有效 清除消息队列中的WM_PAINT消息- 可是使用ValidateRect设定区域为有效.或者
BeginPaint/EndPaint
设置所有无效区域全部有效 UpdateWindow只向窗体发送WM_PAINT消息
- 在发送之前判断
GetUpdateRect(hWnd,NULL,TRUE)
看有无可绘制的客户区域 - 如果没有,则不发送WM_PAINT
- 发送即不经过消息队列,直接发送到对应窗口,因此此函数可以立即更新窗口
- 在发送之前判断
InvalidateRect(hwnd, &clientRC, true);//清除之前的背景(BeginPaint在操作)
InvalidateRect(hwnd, &clientRC, false);//清除之前的背景(BeginPaint在操作)
ValidateRect(hwnd, &clientRC);//声明rc有效
获取dc的3种方法
dc注意及时释放
/*
建议
获取书写区的dc
一般是菜单下面开始的第一行
*/
hdc = GetDC(hwnd);
ReleaseDC(HWND hWnd, HDC hdc);//释放dc
/*
获取整个窗口的dc
起始位置是非客户区(标题,菜单)
*/
hdc = GetWindowDc(hwnd);
ReleaseDC(HWND hWnd, HDC hdc);//释放dc
/*
WM_PAINT中绘制界面一定要用这个
虽然ReleaseDC也可以刷新出绘制的界面
但是系统收不到EndPaint会认为没有绘制完成
系统会不停地WM_PAINT
BeginPaint获取的dc是无效区域的dc
在这个dc所做的修改会被认为是暂未完成或者是备用空间
除非把这个dc声明为有效EndPaint
否则系统会不停地WM_PAINT
*/
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
EndPaint ( hWnd , &ps );//声明dc绘制结束,被认为是有效的
窗口消息
窗口绘制
WM_NCPAINT
- 发送者: 系统/内核
- 意义: 绘制非客户区
- 建议: 不建议用户使用/重写
WM_PAINT
- 发送者: 系统/内核/另一个程序
- 意义: 创建窗口的请求
- 建议: 不建议手动发送 该消息,如果需要刷新窗口那么发送
update
消息即可 - 发送时机: 最小化,最大化,显示界面时,有无效区域时
- 如果在处理
WM_PAINT
消息之前显示区域中的另一个区域变为无效,则Windows计算出一个包围两个区域的新的无效区域 - Windows不会将多个
WM_PAINT
消息都放在消息队列中 - 如果无效区域始终不能设置成有效,那么WM_PAINT还会产生
- 可以调用函数
ValidateRect
将其变为有效。 BeginPaint/EndPaint
中内部就包含了重置无效区域的操作- 一旦处理WM_PAINT消息,需要重置无效区域为有效。(可以通过
BeginPaint/EndPaint
,或者是ValidateRect
) - 通常只需要绘制无效区域即可,当然也可以客户区全部重绘。
鼠标操作
操作逻辑:
- 考虑到窗口最大化最小化隐藏等操作,我们只能在
PAINT
中绘制图像 - 我们把捕捉到的鼠标操作保存起来,调用
PAINT
绘制界面
WM_LBUTTONDOWN
作用: 接受鼠标左键按下的操作
case WM_LBUTTONDOWN:
{
OutputDebugStringA("WM_LBUTTONDOWN");
POINT pt;
ptBegin.x = LOWORD(lParam);
ptBegin.y = HIWORD(lParam);
char szBuf[256] = { 0 };
wsprintfA(szBuf, "wParam = %p, xPos = %d, yPos = %d", wParam, xPos, yPos);
OutputDebugStringA(szBuf);
}
WM_LBUTTONUP
作用: 接受鼠标左键弹起的操作
case WM_LBUTTONUP:{
OutputDebugStringA("WM_LBUTTONUP");
ptEnd.x = LOWORD(lParam);
ptEnd.y = HIWORD(lParam);
}
WM_MOUSEMOVE
作用: 接受鼠标 移动 的操作
case WM_MOUSEMOVE:{
OutputDebugStringA("WM_MOUSEMOVE");
ptEnd.x = LOWORD(lParam);
ptEnd.y = HIWORD(lParam);
}
键盘操作
WM_CHAR
- 区分大小写和SHIFT+数字
- 只响应字符按键和部分控制符
有很多控制字符不响应 不响应的键如下:
- Tab键
- Caps Lock大小写切换键
- ESC键
- F1~F12
- SHIFT
- CTRL
- ALT
- 方向键
- 方向键上方键盘区
- Num Lock键
WM_CHAR
是由WM_KEYDOWN
消息TranslateMessage()
之后产生的,然后再发送给窗口过程比如说按下E键后产生
WM_KEYDOWN
消息,经过translate()
处理后产生变成WM_KEYDOWN
,WM_CHAR 2
个消息传递给窗口过程- 如果用这个消息处理字符那么就不建议在
WM_KEYDOWN
中再有相关的处理代码
case WM_CHAR: {
wsprintfA(szBuf, "获得字符: %d_%c\r\n", wParam, wParam);
OutputDebugStringA(szBuf);
return 0;
}
WM_KEYDOWN
- 接受键盘按下的操作
- 这里接收到的是 微软的虚拟按键
- 只有只有大写,就算用户输入的是小写也会自动转成大写
- 响应所有按键消息(Alt键,Print Screen,SysRq,截屏键 不响兴)
case WM_KEYDOWN: {
wsprintfA(szBuf, "按键按下: %d_%c\r\n", wParam, wParam);
OutputDebugStringA(szBuf);
return 0;
}
WM_KEYUP
- 接受键盘弹起的操作
- 与WM_KEYDOWN一样接收到的时虚拟按键都是大写的
case WM_KEYUP: {
wsprintfA(szBuf, "按键弹起: %d_%c\r\n", wParam,wParam);
OutputDebugStringA(szBuf);
return 0;
}
计时器 SetTimer
描述
- 窗口运行后每隔一段时间就触发一次该消息
- 该消息由系统发出
- 该消息设计的初衷:在空闲时保持程序热运行.可以维护资源
根据 设计的初衷该消息有以下几个特性
- 触发的时间和频率没有规律
- 如果窗口频繁响应其他消息特别是高优先级的消息,那么出发的频率会很低甚至一直不触发(一直在
WM_PAINT
)
5.建议在窗口生成时 设置定时器
SetTimer函数的原型
UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器
UINT nElapse, // 时间间隔,单位为毫秒
TIMERPROC lpTimerFunc // 回调函数
);
关键代码
//设置定时器
UINT_PTR t = SetTimer(hWnd,123,200,NULL);
//检测是否设置成功
if (t == 0)
{
OutputDebugStringA("MyTimerProc error");
}
//设置消息处理
case WM_TIMER:{
OutputDebugStringA("WM_TIMER");
return 0;
}
实例
把字符串画到界面上
- 获取窗口句柄
HWND
- 根据
HWND
获取 dc(描述位置) - 根据
dc
绘制字符串
//获取dc
HWND hCalc = FindWindowW(NULL, L"扫雷");
HDC hDc = GetWindowDC(hCalc);
//设置我们需要的绘画框
RECT rc;
rc.left = 0;
rc.right = 100;
rc.top = 0;
rc.bottom = 100;
//绘制字符串
DrawText(hDc,
_T("Hello World!"),
_tcslen(_T("Hello World!")),
&rc,
DT_LEFT);
//释放dc
ReleaseDC(hCalc, hDc);
绘制线条
PAINTSTRUCT ps;
//获取hdc
HDC hdc = BeginPaint(hwnd, &ps);
//保存之前的坐标
POINT pt;
//移动到新坐标并且把之前的坐标保存到pt中
MoveToEx(hdc, ptBegin.x, ptBegin.y, &pt);
//绘制线条
LineTo(hdc, ptEnd.x, ptEnd.y);
/*
刷新界面绘图
我们之前利用hdc绘制了线条但是界面update时不会显示这个线条
需要我们强制显示
*/
RECT clientRC;
GetClientRect(hwnd, &clientRC);
InvalidateRect(hwnd, &clientRC, true);//清除之前的背景
//InvalidateRect(hwnd, &clientRC, false);//之前的界面作为背景依然保留
//释放dc
EndPaint(hwnd, &ps);
获取键盘消息
- 我们设计一个可以获取大小写的消息处理机制
- 设计中如果对大小写敏感建议只在
WM_CHAR
中处理字符
//+++++++++消息处理回调函数
case WM_KEYDOWN: {
wsprintfA(szBuf, "按键按下: %d_%c\r\n", wParam, wParam);
OutputDebugStringA(szBuf);
return 0;
}
case WM_CHAR: {
wsprintfA(szBuf, "获得字符: %d_%c\r\n", wParam, wParam);
OutputDebugStringA(szBuf);
return 0;
}
case WM_KEYUP: {
wsprintfA(szBuf, "按键弹起: %d_%c\r\n", wParam,wParam);
OutputDebugStringA(szBuf);
return 0;
}
//+++++++++++++消息循环
//线程拥有消息队列
MSG msg;
while (GetMessage(&msg,
NULL,
0,
0) > 0)
{
/*
转换按下键盘的消息
WM_KEYDOWN->WM_KEYDOWN+WM_CHAR
*/
TranslateMessage(&msg);
//派发消息
DispatchMessage(&msg);
}