窗口数据/函数

数据类型

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
  1. 只要有无效区域 就会不停地产生消息WM_PAINT
  2. InvalidateRect 设定无效区域 产生消息WM_PAINT
  3. InvalidateRect()是强制系统进行重画,但是不一定就马上进行重画

    1. 因为InvalidateRect()只是通知系统,此 时的窗口已经变为无效
    2. 强制系统调用WM_PAINT,而这个消息只是Post(寄送)就是将该消息放入消息队列
    3. 当执行到WM_PAINT消息时才会对敞口进行重绘
  4. ValidateRect(HWND,RECT)使rc有效 清除消息队列中的WM_PAINT消息
  5. 可是使用ValidateRect设定区域为有效.或者 BeginPaint/EndPaint 设置所有无效区域全部有效
  6. UpdateWindow只向窗体发送WM_PAINT消息

    1. 在发送之前判断GetUpdateRect(hWnd,NULL,TRUE)看有无可绘制的客户区域
    2. 如果没有,则不发送WM_PAINT
    3. 发送即不经过消息队列,直接发送到对应窗口,因此此函数可以立即更新窗口
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
  • 通常只需要绘制无效区域即可,当然也可以客户区全部重绘。

鼠标操作

操作逻辑:

  1. 考虑到窗口最大化最小化隐藏等操作,我们只能在 PAINT中绘制图像
  2. 我们把捕捉到的鼠标操作保存起来,调用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

  1. 区分大小写和SHIFT+数字
  2. 只响应字符按键和部分控制符
  3. 有很多控制字符不响应 不响应的键如下:

    • Tab键
    • Caps Lock大小写切换键
    • ESC键
    • F1~F12
    • SHIFT
    • CTRL
    • ALT
    • 方向键
    • 方向键上方键盘区
    • Num Lock键
  4. WM_CHAR是由WM_KEYDOWN消息TranslateMessage()之后产生的,然后再发送给窗口过程

    比如说按下E键后产生WM_KEYDOWN消息,经过translate()处理后产生变成WM_KEYDOWNWM_CHAR 2 个消息传递给窗口过程
  5. 如果用这个消息处理字符那么就不建议在WM_KEYDOWN中再有相关的处理代码
case WM_CHAR: {
    wsprintfA(szBuf, "获得字符: %d_%c\r\n", wParam, wParam);
    OutputDebugStringA(szBuf);
    return 0;
}

WM_KEYDOWN

  1. 接受键盘按下的操作
  2. 这里接收到的是 微软的虚拟按键
  3. 只有只有大写,就算用户输入的是小写也会自动转成大写
  4. 响应所有按键消息(Alt键,Print Screen,SysRq,截屏键 不响兴)
case WM_KEYDOWN: {
    wsprintfA(szBuf, "按键按下: %d_%c\r\n", wParam, wParam);
    OutputDebugStringA(szBuf);
    return 0;
}

WM_KEYUP

  1. 接受键盘弹起的操作
  2. 与WM_KEYDOWN一样接收到的时虚拟按键都是大写的
case WM_KEYUP: {
    wsprintfA(szBuf, "按键弹起: %d_%c\r\n", wParam,wParam);
    OutputDebugStringA(szBuf);
    return 0;
}

计时器 SetTimer

描述

  1. 窗口运行后每隔一段时间就触发一次该消息
  2. 该消息由系统发出
  3. 该消息设计的初衷:在空闲时保持程序热运行.可以维护资源
  4. 根据 设计的初衷该消息有以下几个特性

    1. 触发的时间和频率没有规律
    2. 如果窗口频繁响应其他消息特别是高优先级的消息,那么出发的频率会很低甚至一直不触发(一直在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;
}

实例

把字符串画到界面上

  1. 获取窗口句柄 HWND
  2. 根据HWND获取 dc(描述位置)
  3. 根据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);

获取键盘消息

  1. 我们设计一个可以获取大小写的消息处理机制
  2. 设计中如果对大小写敏感建议只在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);
}
Last modification:October 26, 2018
如果觉得我的文章对你有用,请随意赞赏