> 文章列表 > WINDOWS消息

WINDOWS消息

WINDOWS消息

WINDOWS消息

Unit01消息队列

01消息队列概念

  • 消息队列是用于存放消息的队列
  • 消息在队列中先进先出
  • 所有窗口程序都有消息队列
  • 程序(GetMessage)可以从队列中获消息

02消息队列分类

  • 系统消息队列:由系统维护的消息队列(这个队列非常庞大),存放系统产生的所有消息,例如鼠标、键盘等
  • 程序消息队列:属于每一个应用程序(线程)的消息队列,由应用程序(线程)维护

03消息和队列关系

  • 消息和消息队列的关系
    • 当鼠标、键盘产生消息时,会将消息放到系统消息队列
    • 系统会根据存放的消息,找到对应程序的消息队列
    • 将消息投递到程序的消息队列中
  • 根据消息和消息队列之间使用关系,将消息分成两类:
    • 队列消息:消息的发送和获取,都是通过消息队列完成
    • 非队列消息:消息的发送和获取,是直接调用消息的窗口处理完成(就是消息不进队列直接调用消息处理函数对消息进行处理)

在这里插入图片描述

  • 队列消息:消息发送后,首先放入队列,然后通过消息循环,从队列当中完成
    • GetMessage:从消息队列中获取消息
    • PostMessage:将消息投递到消息队列
    • 常见队列消息:WM_PAINT、键盘、鼠标、定时器
  • 非队列消息:消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息
    • SendMessage:直接将消息发送给窗口的处理函数,并等待处理结果
    • 常见非队列消息:WM_CREATE、WM_SIZE等

在这里插入图片描述

04深谈GetMessage原理

  • 在程序(线程)消息队列超找消息,如果队列中有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出消息,否则从队列中取出消息返回
  • 如果程序(线程)消息队列中没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中。
  • 如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,取得消息返回处理
  • 如果没有重新绘制的区域,检查定时器如果有到时得定时器,产生WM_TIMER,返回处理执行
  • 如果没有到时的定时器,整理程序的资源、内存等
  • GetMessage会继续等待下一条消息。PeekMessage会返回FALSE,交出程序的控制权
  • 注意:GetMessage如果获取到时WM_QUIT,函数或返回FALSE

05WM_PAINT消息

  • 产生时间:当窗口需要绘制的时候
  • 附带信息(对于此消息,附带消息没用)
    • wParam:0
    • lParam:0
  • 专职用法:用于绘图
#include <windows.h>
HANDLE g_hOutput = 0;
void OnPaint(HWND hWnd){char* pszText = "WM_PAINT\\n";WriteConsole(g_hOutput,pszText,strlen(pszText),NULL,NULL);
}
//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_PAINT:OnPaint(hWnd);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}
  • 窗口无效区域:需要重新绘制的区域
BOOL InvalidateRect(  HWND hWnd,           // handle to window 窗口句柄CONST RECT* lpRect,  // rectangle coordinates 区域的矩形坐标(如果为NULL指的是整个窗口)BOOL bErase          // erase state 重绘前是否先擦除
);

示例代码:

#include <windows.h>
HANDLE g_hOutput = 0;
void OnPaint(HWND hWnd){char* pszText = "WM_PAINT\\n";WriteConsole(g_hOutput,pszText,strlen(pszText),NULL,NULL);
}
//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_PAINT:OnPaint(hWnd);break;case WM_LBUTTONDOWN://点击鼠标的左键消息InvalidateRect(hWnd,NULL,TRUE);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}
  • 消息处理步骤
    • 开始绘图
    HDC BeginPaint(HWND hwnd,            // handle to window 绘制窗口句柄LPPAINTSTRUCT lpPaint // paint information 绘图参数的BUFF
    );//返回绘图设备句柄HDC
    
    • 正式绘图
    • 结束绘图
    BOOL EndPaint(HWND hWnd,                  // handle to window 绘制窗口句柄CONST PAINTSTRUCT *lpPaint  // paint data 绘图参数的指针BeginPaint返回
    );
    

示例代码:

#include <windows.h>
HANDLE g_hOutput = 0;
void OnPaint(HWND hWnd){char* pszText = "WM_PAINT\\n";WriteConsole(g_hOutput,pszText,strlen(pszText),NULL,NULL);//这个结构体内部的内容不用管PAINTSTRUCT ps = {0};//开始绘图HDC hdc = BeginPaint(hWnd, &ps);//正式绘图TextOut(hdc, 100, 100, "hello", 5);//结束绘图EndPaint(hWnd, &ps);//以上绘图的代码,必须放在处理WM_PAINT消息时调用
}
//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_PAINT:OnPaint(hWnd);break;case WM_LBUTTONDOWN://点击鼠标的左键消息InvalidateRect(hWnd,NULL,TRUE);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

Unit02键盘消息

01键盘消息分类

  • WM_KEYDOWN按键被按下时产生
  • WM_KEYUP按键被放开时产生
  • WM_SYSKEYDOWN系统按键下时产生,比如:ALT、F10
  • WM_SYSKEYUP系统按键放开时产生
  • 这些消息附带信息:
    • WPARAM:按键的Virtual Key(键码值世界通用)
    • LPARAM:按键的参数,例如按下次数
#include <windows.h>
#include <stdio.h>
HANDLE g_hOutput = 0;void OnKeyDown(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_KEYDOWN:键码值=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnKeyUp(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_KEYUP:键码值=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_KEYDOWN:OnKeyDown(hWnd,wParam);break;case WM_KEYUP:OnKeyUp(hWnd,wParam);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

图中笔误TransMessage应该是TranslateMessage
笔误TransMessage应该是TranslateMessage

02字符消息(WM_CHAR)

  • TreanslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符没有此消息
  • 附带信息:
    • WPARAM:输入的字符的ASCII字符编码值
    • LPARAM:按键相关参数(这个参数在此没什么用)
  • TranslateMessage函数执行过程的伪代码:
TranslateMessage(&Msg){if(nMsg.message != WM_KEYDOWN){return;}//根据nMsg.wParam(键码值)可以获取哪个按键被按下if(不可见字符的按键){return;}//查看CapsLock(大写锁定键)是否处于打开状态if(打开)//65是A的键码值(示例按下的是A键)PostMessage(nMsg.hwnd,WM_CHAR,65,...);else//97是a的键码值PostMessage(nMsg.hwnd,WM_CHAR,97,...);
}

示例代码

#include <windows.h>
#include <stdio.h>
HANDLE g_hOutput = 0;void OnKeyDown(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_KEYDOWN:键码值=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnKeyUp(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_KEYUP:键码值=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnChar(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_KEYUP:键码值=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_CHAR:OnChar(hWnd,wParam);break;case WM_KEYDOWN:OnKeyDown(hWnd,wParam);break;case WM_KEYUP:OnKeyUp(hWnd,wParam);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述

Unit03鼠标消息

01鼠标消息分类

  • 基本鼠标消息
    • WM_LBUTTONDOWN鼠标左键按下
    • WM_LBUTTONUP鼠标左键抬起
    • WM_RBUTTONDOWN鼠标右键按下
    • WM_RBUTTONUP鼠标右键抬起
    • WM_MOUSEMOVE鼠标移动消息
  • 双击消息
    • WM_LBUTTONDBLCLK鼠标左键双击
    • WM_RBUTTONDBLCLK鼠标右键双击
  • 滚轮消息
    • WM_MOUSEWHEEL鼠标滚轮消息

02鼠标基本消息

  • 附带信息
    • WPARAM:其他按键的状态,例如Ctrl/shift等
    • LPARAM:
      • 鼠标的位置,窗口客户坐标系
        • LOWORDx坐标的位置
        • HIWORDy坐标的位置
  • 一般情况鼠标按下或抬起成对出现,在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息
#include <windows.h>
#include <stdio.h>HANDLE g_hOutput = 0;void OnLButtonDown(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONDOWN:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnLButtonUp(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONUP:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnMouseMove(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_MOUSEMOVE:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_MOUSEMOVE:OnMouseMove(hWnd,wParam,lParam);break;case WM_LBUTTONDOWN:OnLButtonDown(hWnd,wParam,lParam);break;case WM_LBUTTONUP:OnLButtonUp(hWnd,wParam,lParam);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

03鼠标双击消息

  • 附带信息
    • WPARAM:其他按键的状态,例如Ctrl/shift等
    • LPARAM:
      • 鼠标的位置,窗口客户坐标系
        • LOWORDx坐标的位置
        • HIWORDy坐标的位置
  • 消息产生顺序
    • 以左键双击为例
      • WM_LBUTTINDOWN
      • WM_LBUTTONUP
      • WM_LBUTTONBLACK
      • WM_LBUTTONUP
  • 注意:使用时需要在注册窗口类的时候加上CS_DBLCLKS风格
#include <windows.h>
#include <stdio.h>HANDLE g_hOutput = 0;void OnLButtonDown(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONDOWN:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnLButtonUp(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONUP:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnMouseMove(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_MOUSEMOVE:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));//WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnButtonDoubleClick(HWND hWnd,WPARAM wParam,LPARAM lParam){char* szText = "WM_LBUTTONDBLCLK\\n";WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_LBUTTONDBLCLK:OnButtonDoubleClick(hWnd,wParam,lParam);break;case WM_MOUSEMOVE:OnMouseMove(hWnd,wParam,lParam);break;case WM_LBUTTONDOWN:OnLButtonDown(hWnd,wParam,lParam);break;case WM_LBUTTONUP:OnLButtonUp(hWnd,wParam,lParam);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述
在这里插入图片描述

04鼠标滚动消息

  • 附带信息
    • WPARAM:
      • LOWORD其他按键的状态
      • HIWORD滚轮的偏移量,通过正负值表示滚动的方向(正:向前滚动,负:向后滚动)
    • LPARAM:鼠标当前的位置,屏幕坐标系
      • LOWORDx坐标的位置
      • HIWORDy坐标的位置
  • 使用:通过偏移量获取滚动的方向距离
#include <windows.h>
#include <stdio.h>HANDLE g_hOutput = 0;void OnLButtonDown(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONDOWN:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnLButtonUp(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_LBUTTONUP:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnMouseMove(HWND hWnd,WPARAM wParam,LPARAM lParam){char szText[256] = {0};sprintf(szText,"WM_MOUSEMOVE:其他按键状态:%d, X=%d,Y=%d\\n",wParam,LOWORD(lParam),HIWORD(lParam));//WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnButtonDoubleClick(HWND hWnd,WPARAM wParam,LPARAM lParam){char* szText = "WM_LBUTTONDBLCLK\\n";WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}void OnMouseWheel(HWND hWnd, WPARAM wParam){short nDelta = HIWORD(wParam);char szText[256] = {0};sprintf(szText,"WM_MOUSEWHEEL:nDelta=%d\\n", nDelta);WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_MOUSEWHEEL:OnMouseWheel(hWnd, wParam);break;case WM_LBUTTONDBLCLK:OnButtonDoubleClick(hWnd,wParam,lParam);break;case WM_MOUSEMOVE:OnMouseMove(hWnd,wParam,lParam);break;case WM_LBUTTONDOWN:OnLButtonDown(hWnd,wParam,lParam);break;case WM_LBUTTONUP:OnLButtonUp(hWnd,wParam,lParam);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述

Unit04定时器消息

01定时器的消息介绍

  • 产生时间:在程序中创建定时器,当达到时间间隔时,定时器会向程序发送一个WM_TIMER消息(实际上是GetMessage发送的消息)。定时器的精度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达消息。
  • 附带信息:
    • wParam:定时器ID
    • lParam:定时器处理函数指针

02创建销毁定时器

  • 创还能定时器
UINT_PTR SetTimer(HWND hWnd,//定时器窗口句柄UINT_PTR nIDEvent,//定时器IDUINT uElapse,//时间间隔TIMERPROC lpTimerFunc//定时器处理函数指针(一般不使用,为NULL)
);//创建成功返回非0
  • 关闭定时器
BOOL KillTimer(HWND hWnd,//定时器窗口句柄UINT_PTR uIDEvent//定时器ID
);

示例代码:

#include <windows.h>
#include <stdio.h>HANDLE g_hOutput = 0;void OnTimer(HWND hWnd,WPARAM wParam){char szText[256] = {0};sprintf(szText,"WM_TIMER:定时器ID=%d\\n",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}
//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_TIMER:OnTimer(hWnd, wParam);break;case WM_CREATE:SetTimer(hWnd, 1, 1000, NULL);SetTimer(hWnd, 2,2000, NULL);break;case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{AllocConsole();g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";wc.lpszMenuName = NULL;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述

Unit05菜单资源

01菜单分类

  • 窗口的顶层菜单(窗口title下面的一长条菜单)
  • 弹出式菜单(右键菜单)
  • 系统菜单(点击窗口title或者logo弹出的菜单)
  • 注意:HMENU(菜单句柄)类型表示菜单,ID表示菜单项

02资源相关

  • 资源脚本文件:*.rc文件
  • 编译器:RC.EXE
    在这里插入图片描述

03菜单资源使用

  • 添加菜单资源
  • 加载菜单资源
    • 注册窗口类时设置菜单
    • 创建窗口传参设置菜单
    • 在主窗口WM_CREATE消息中利用SetMenu函数设置菜单
//加载资源
HMENU LoadMenu(HINSTANCE hInstance,//handle to moduleLPCTSTR lpMenuName//menu name or resource identifier
); 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

示例代码:

#include <windows.h>
#include "resource.h"//***********************
//保存全局窗口实例,为了第三种方法加载菜单中使用SetMenu()函数中获取实例
HINSTANCE g_hIns = 0;
//第三种方法加载菜单在创建窗口消息中触发加载菜单
void OnCreate(HWND hWnd){HMENU hMenu = LoadMenu(g_hIns, (char*)IDR_MENU1);SetMenu(hWnd, hMenu);
}
//***********************//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){//*****************//第三种加载菜单的的方法:在创建窗口消息中加载菜单case WM_CREATE:OnCreate(hWnd);break;//****************case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{//将当前窗口实例保存到全局变量中g_hIns = hIns;//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";//第一种加载菜单的方法:(char*)IDR_MENU1wc.lpszMenuName = NULL;//(char*)IDR_MENU1;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//***********************//第二种加载菜单的方法:在创建窗口时使用LoadMenu()函数加载菜单//HMENU  hMenu = LoadMenu(hIns,(char*)IDR_MENU1);//在内存创建窗口,添加菜单//HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);//***********************//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述

04 命令消息(WM_CIMMADN)处理

  • WM_CIMMADN点击菜单项发出的消息
  • 附带信息
    • WPARAM:
      • HIWORD:对于菜单为0(说明没有用)
      • LOWORD:菜单项的ID
    • LPARAM:对于菜单为0

示例代码:

#include <windows.h>
#include "resource.h"//***********************
//保存全局窗口实例,为了第三种方法加载菜单中使用SetMenu()函数中获取实例
HINSTANCE g_hIns = 0;
//第三种方法加载菜单在创建窗口消息中触发加载菜单
void OnCreate(HWND hWnd){HMENU hMenu = LoadMenu(g_hIns, (char*)IDR_MENU1);SetMenu(hWnd, hMenu);
}
//***********************
void OnCommand(HWND hWnd,WPARAM wParam){switch(LOWORD(wParam)){case ID_NEW:MessageBox(hWnd, "新建被点击", "Infor", MB_OK);break;case ID_EXIT:MessageBox(hWnd, "退出被点击", "Infor", MB_OK);break;case ID_ABOUT:MessageBox(hWnd, "关于被点击", "Infor", MB_OK);break;}
}
//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){case WM_COMMAND:OnCommand(hWnd,wParam);break;//*****************//第三种加载菜单的的方法:在创建窗口消息中加载菜单case WM_CREATE:OnCreate(hWnd);break;//****************case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{//将当前窗口实例保存到全局变量中g_hIns = hIns;//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";//第一种加载菜单的方法:(char*)IDR_MENU1wc.lpszMenuName = NULL;//(char*)IDR_MENU1;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//***********************//第二种加载菜单的方法:在创建窗口时使用LoadMenu()函数加载菜单//HMENU  hMenu = LoadMenu(hIns,(char*)IDR_MENU1);//在内存创建窗口,添加菜单//HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);//***********************//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

在这里插入图片描述

05 菜单项状态

06上下文菜单

  • 显示上下文菜单(弹出式菜单)
BOOL TrackPopupMenu(  HMENU hMenu,   	//菜单句柄UINT uFlags,   	//显示方式int x,   			//鼠标水平位置,屏幕坐标系int y, 				//鼠标垂直位置,屏幕坐标系int nReserved, 	//保留,必须为0HWND hWnd,   	//处理菜单消息的窗口句柄const RECT* prcRect //NULL,此参数可忽略
); //TrackPopupMenu是阻塞函数
  • 处理上下文菜单弹出的两种方式
    • WM_RBUTTONUP:鼠标右键抬起消息为窗口坐标系,要使用需将其转成屏幕坐标系坐标,可通过ClientToScreen函数进行转换
    • WM_CONTEXTMENU
      • WPARAM:右键点击的窗口句柄
      • LPARAM:对于菜单为0
        • HIWORD:X坐标,屏幕坐标
        • LOWORD:Y坐标,屏幕坐标
      • WM_CONTEXTMENU消息是在WM_RBUTTONUP消息之后产生
BOOL ClientToScreen(  HWND hWnd,       // handle to windowLPPOINT lpPoint  // screen coordinates
);

示例代码1:通过WM_RBUTTONUP消息进行弹出,这种方式显示的上下文菜单有一点问题,因为我们所需要的鼠标位置是屏幕坐标系下的坐标,而通过lParam获取的是客户窗口坐标系的坐标,所以导致鼠标右键点击出现的上下文菜单和鼠标的位置有一定的距离,偏差较大

#include <windows.h>
#include "resource.h"//***********************
//保存全局窗口实例,为了第三种方法加载菜单中使用SetMenu()函数中获取实例
HINSTANCE g_hIns = 0;
//第三种方法加载菜单在创建窗口消息中触发加载菜单
void OnCreate(HWND hWnd){HMENU hMenu = LoadMenu(g_hIns, (char*)IDR_MENU1);SetMenu(hWnd, hMenu);
}
//***********************
void OnCommand(HWND hWnd,WPARAM wParam){switch(LOWORD(wParam)){case ID_NEW:MessageBox(hWnd, "新建被点击", "Infor", MB_OK);break;case ID_EXIT:MessageBox(hWnd, "退出被点击", "Infor", MB_OK);break;case ID_ABOUT:MessageBox(hWnd, "关于被点击", "Infor", MB_OK);break;}
}void OnRButtonUp(HWND hWnd,LPARAM lParam){HMENU hMain = LoadMenu(g_hIns, (char*)IDR_MENU1);//GetSubMenu函数获取hMain大菜单[文件0,帮助1]中的小菜单[文件0]HMENU hPopup = GetSubMenu(hMain,0);TrackPopupMenu(hPopup,TPM_CENTERALIGN|TPM_VCENTERALIGN,LOWORD(lParam),HIWORD(lParam),0,hWnd,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){//上下文菜单弹出的时机:在鼠标右键抬起时弹出上下文菜单case WM_RBUTTONUP:OnRButtonUp(hWnd,lParam);break;case WM_COMMAND:OnCommand(hWnd,wParam);break;//*****************//第三种加载菜单的的方法:在创建窗口消息中加载菜单case WM_CREATE:OnCreate(hWnd);break;//****************case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{//将当前窗口实例保存到全局变量中g_hIns = hIns;//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";//第一种加载菜单的方法:(char*)IDR_MENU1wc.lpszMenuName = NULL;//(char*)IDR_MENU1;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//***********************//第二种加载菜单的方法:在创建窗口时使用LoadMenu()函数加载菜单//HMENU  hMenu = LoadMenu(hIns,(char*)IDR_MENU1);//在内存创建窗口,添加菜单//HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);//***********************//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}

示例代码2:通过WM_CONTEXTMENU方式弹出

#include <windows.h>
#include "resource.h"//***********************
//保存全局窗口实例,为了第三种方法加载菜单中使用SetMenu()函数中获取实例
HINSTANCE g_hIns = 0;
//第三种方法加载菜单在创建窗口消息中触发加载菜单
void OnCreate(HWND hWnd){HMENU hMenu = LoadMenu(g_hIns, (char*)IDR_MENU1);SetMenu(hWnd, hMenu);
}
//***********************
void OnCommand(HWND hWnd,WPARAM wParam){switch(LOWORD(wParam)){case ID_NEW:MessageBox(hWnd, "新建被点击", "Infor", MB_OK);break;case ID_EXIT:MessageBox(hWnd, "退出被点击", "Infor", MB_OK);break;case ID_ABOUT:MessageBox(hWnd, "关于被点击", "Infor", MB_OK);break;}
}void OnContextMenu(HWND hWnd,LPARAM lParam){HMENU hMain = LoadMenu(g_hIns, (char*)IDR_MENU1);//GetSubMenu函数获取hMain大菜单[文件0,帮助1]中的小菜单[文件0]HMENU hPopup = GetSubMenu(hMain,0);TrackPopupMenu(hPopup,TPM_LEFTALIGN|TPM_TOPALIGN,LOWORD(lParam),HIWORD(lParam),0,hWnd,NULL);
}//窗口处理函数(自定义,处理函数)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
{switch(msgID){//上下文菜单弹出的时机case WM_CONTEXTMENU:OnContextMenu(hWnd,lParam);break;case WM_COMMAND:OnCommand(hWnd,wParam);break;//*****************//第三种加载菜单的的方法:在创建窗口消息中加载菜单case WM_CREATE:OnCreate(hWnd);break;//****************case WM_DESTROY:PostQuitMessage(0);//可以使GetMessage函数返回0break;}return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow)
{//将当前窗口实例保存到全局变量中g_hIns = hIns;//注册窗口类WNDCLASS wc = {0};wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.hCursor = NULL;wc.hIcon = NULL;wc.hInstance = hIns;wc.lpfnWndProc = WndProc;wc.lpszClassName = "Main";//第一种加载菜单的方法:(char*)IDR_MENU1wc.lpszMenuName = NULL;//(char*)IDR_MENU1;wc.style = CS_HREDRAW|CS_VREDRAW;RegisterClass(&wc);//将以上所有赋值全部写入操作系统内核//***********************//第二种加载菜单的方法:在创建窗口时使用LoadMenu()函数加载菜单//HMENU  hMenu = LoadMenu(hIns,(char*)IDR_MENU1);//在内存创建窗口,添加菜单//HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);//***********************//在内存创建窗口HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);//显示窗口ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);//消息循环MSG nMsg = {0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理}return 0;
}