用户
搜索

该用户从未签到

i春秋作家

Rank: 7Rank: 7Rank: 7

16

主题

27

帖子

249

魔法币
收听
0
粉丝
0
注册时间
2018-1-1

i春秋签约作者

发表于 2020-7-21 00:45:35 82337

本文原创作者flag0,本文属于i春秋原创奖励计划,未经许可禁止转载!

绘图消息

WM_PAINT

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // WM_PAINT
  WPARAM wParam,   // not used
  LPARAM lParam    // not used
);

DrawText在矩形中绘制文本。

int DrawText(
  HDC hDC,          // handle to DC
  LPCTSTR lpString, //文本缓冲区
  int nCount,       //文本长度
  LPRECT lpRect,    //矩形
  UINT uFormat      //文本对其格式,详情参考MSDN
  //DT_LEFT 左对齐
  //DT_RIGHT 右对齐
  //DT_TOP  上对齐
  //DT_CENTER 水平居中对齐
  //DT_VCENTER 垂直居中对齐
  //...
 );

DC:在DOS系统中对屏幕抽象成了一个内存,直接往内存空间中写入即可在屏幕上进行输出。在Windows系统中将屏幕抽象成了DC,操作DC即可在屏幕上进行输出。

BeginPaint 获取DC

HDC BeginPaint(
  HWND hwnd,            // handle to window:窗口句柄
  LPPAINTSTRUCT lpPaint // paint information(很少会用到)
);

_RECT矩形结构体,两个对角线上的点确定一个矩形。

typedef struct _RECT { 
  //左上角
  LONG left; 
  LONG top;
  //右下角
  LONG right; 
  LONG bottom; 
} RECT, *PRECT; 

客户区

不同类的窗口,样式几乎一致,标题栏、菜单栏、滚动条、状态栏等这些位置都叫做非客户区,除此之外就是客户区了。

绘图的坐标以客户区的左上角为原点,建立的坐标系。

GDI对象资源泄露

打开任务管理器中详细信息中的GDI对象,不停打开缩小窗口,观察值会一直增加,会造成GDI资源泄露。

1592963918002

这里的原因是由BeginPaint获取DC的时候申请的资源没有释放导致的。

调用EndPaint释放即可。

BOOL EndPaint(
    HWND hWnd,// handle to window
    CONST PAINTSTRUCT *lpPaint  // paint data
);

调用BeginPaint获取DC时,要和EndPaint配对使用。

case WM_PAINT://绘图消息
{
    //获取DC句柄
    PAINTSTRUCT ps;
    HDC hDC= BeginPaint(hwnd, &ps);
    //绘制文本
    char szBuff[] = { "Hello World" };

    RECT rc = {
        0,0,    //左上角
        800,600 //右下角
    };

    DrawText(
        hDC,
        szBuff,
        strlen(szBuff),
        &rc,
        DT_CENTER
    );

    //释放资源
    EndPaint(hwnd, &ps);
}

系统发送WM_PAINT消息的时机

  • 窗口创建
  • 尺寸修改
  • 最小化最大化
  • 遮盖的部分重现

DebugView 查看输出信息

使用OutPutDebugString()输出调试信息,用DebugView监听即可看到。

监视->监视Win32 监视当前用户(推荐用)

监视->Capture Global Win32 监视所有用户

过滤功能:

1592965406742

输入要过滤的字符

1592965458071

则会只监听含有该字符的消息。

F5调试启动,OutPutDebugString会输出到VS窗口中,如果是Crtl + F5 直接启动,才会输出到DebugView里面。

系统不发送WM_PAINT消息时绘图

  1. 手动调用api InvalidateRect发送WM_PAINT消息,手动制造一块需要绘制的区域(手动产生无效区)。
  2. GetDC 换一种拿到DC的方式,可以在任何地方都可以画(该方法不发送WM_PAINT消息)。

InvalidateRect函数将矩形添加到指定窗口的更新区域。更新区域代表必须重新绘制的窗口工作区的一部分。(手动制造一块需要重新绘制的区域,发送WM_PAINT消息)。

BOOL InvalidateRect(
    HWND hWnd,           // handle to window
    CONST RECT* lpRect, //需要重新绘制的矩形大小,填NULL整个客户区重新绘制
    BOOL bErase          //是否擦除背景
);

Demo

InvalidateRect(hWnd, NULL, TRUE);

GetDC函数检索指定窗口的客户区域或整个屏幕的显示设备上下文(DC)的句柄。您可以在后续的GDI函数中使用返回的句柄来提取DC。

HDC GetDC(  
    HWND hWnd   // handle to window
);

Demo

HDC hDc = GetDC(hwnd);

GetDC对应的是ReleaseDC来释放空间。

int ReleaseDC(
    HWND hWnd,  // handle to window  
    HDC hDC     // handle to DC
);

无效区

当有无效区的时候窗口需要重新绘制,当重新绘制时,会产生无效区。

无效区是一块矩形区域,是需要重绘的区域。

系统在检测到无效区存在时会发送WM_PAINT消息。

在调用BeginPaint的时候,会自动清除无效区中的矩形,而GetDC不会清除,所以在WM_PAINT消息下使用GetDC会导致无限循环。

BeginPaint和GetDC的区别:

  1. BeginPaint会清除无效区,GetDC不会。
  2. BeginPaint绘制区域不超过无效区。
  3. GetDC无视无效区。
  4. BeginPaint只能在PAINT消息中使用。

简要区分:BeginPaint在WM_PAINT消息中使用,其他地方用GetDC。

键盘消息

1592903502909

虚拟码:键盘的驱动程序会把扫描码转换成虚拟码(与硬件无关),其中数字按键和ASCII码中的值是一样的,字母按键和ASCII码(大写)中的值是一样的,虚拟码没有大小写之分,都是大写。

扫描码:键盘上的每个键都对应一个扫描码(硬件上),各厂商可能不一样。

监听键盘消息

WM_KEYDOWN键盘按下(在WM_KEYDOWN消息中无法区分大小写)。

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // WM_KEYDOWN 
  WPARAM wParam,   // virtual-key code 虚拟码
  LPARAM lParam    // key data 扫描码
);

区分大小写:

  • 方法1:判断Caps键有没有打开 或者 Shift键有没有按下
  • 方法2:使用TranslateMessageAPI来转换成WM_CHAR消息,在WM_CHAR消息中可以区分大小写

TranslateMessage(&msg);//只为WM_CHAR消息服务,将WM_KEYDOWN转化为WM_CHAR

BOOL TranslateMessage(  
    CONST MSG *lpMsg   // message information
);

Demo

//获取并处理消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//拿到所有消息
{
    //将WM_KEYDOWN消息转换为WM_CHAR消息
    TranslateMessage(&msg);
    //调用回调函数
    DispatchMessage(&msg);
}
//回调函数内部   
case WM_CHAR:
{
    char szBuff[MAXBYTE] = { 0 };
    wsprintf(szBuff, "CHAR: %c", wParam);
    OutputDebugString(szBuff);
    return 0;
}

处理中文

case WM_CHAR:
{
    static char szBuff[MAXBYTE] = { 0 };
    static int nIdx = 0;
    szBuff[nIdx++] = wParam;
    char szBuffFmt[MAXBYTE] = { 0 };
    wsprintf(szBuffFmt, "[FW] char: %s", szBuff);
    OutputDebugString(szBuffFmt);
    return 0;
}

定时器消息

SetTimer设置定时器

UINT_PTR SetTimer(
  HWND hWnd,              // handle to window
  UINT_PTR nIDEvent,      // 定时器ID
  UINT uElapse,           // 时间间隔(毫秒)
  TIMERPROC lpTimerFunc   // 当时间到了以后执行回调函数,可以设置为NULL
  //当时间到了以后会发送WM_TIMER消息
 );

WM_TIMER

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // WM_TIMER
  WPARAM wParam,   // timer identifier
  LPARAM lParam    // timer callback (TIMERPROC)
);

KillTimer关闭定时器

BOOL KillTimer(
    HWND hWnd,          // handle to window
    UINT_PTR uIDEvent   // timer identifier
);

Demo

//设置计时器
SetTimer(hWnd, 2, 1000, NULL);
//回调函数内监听消息
case WM_TIMER:
{
    char szBuff[MAXBYTE] = { 0 };
    static int nCount = 0;
    nCount++;
    wsprintf(szBuff, "Timer: %d", nCount);
    OutputDebugString(szBuff);
    return 0;
}
case WM_RBUTTONDOWN:
{
    KillTimer(hwnd, 2);
    return 0;
}
使用道具 举报 回复
大佬大佬!
使用道具 举报 回复
mark一下
使用道具 举报 回复
发表于 2020-7-22 11:27:55
学习了学习了
使用道具 举报 回复
发表于 2020-7-23 16:03:36
get了,谢谢师傅分享。
使用道具 举报 回复
发表于 2020-7-23 20:02:51
win32画框框 了解一下消息机制就好了  还是mfc香
路漫漫,
使用道具 举报 回复
还是mfc香,哈哈哈哈哈
使用道具 举报 回复
这个系列后面还有没有?好强
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册