当我们在使用鼠标或键盘时,操作系统会将这些动作转换为一个消息(message),并将其发送到相应的消息队列中。这个消息包含了一些信息,例如动作的类型、坐标、时间戳等等。
在Windows系统中,每个窗口都有一个消息队列,操作系统将接收到的消息按照先后顺序依次存储在该队列中,等待程序读取和处理
每个线程只有一个消息队列,消息队列是属于线程的,每个线程在创建窗口时才会分配一个消息队列,并不是每个线程都有消息队列。当消息被发送到一个窗口时,系统会将消息放入该窗口所属的线程的消息队列中,线程可以通过GetMessage函数从消息队列中获取消息并进行处理。
有一点要注意:一个窗口只能属于一个线程,但一个线程可以拥有多个窗口
消息处理函数用于处于各种事件消息的函数,也称为窗口过程函数。当一个窗口收到一个事件消息时,Windows操作系统会调用该窗口的消息处理函数来处理该消息
通常其语法形式如下:
LRESULT CALLBACK WndProc(HWND hWnd, //窗口句柄UINT message, //消息类型//wParam和lParam是消息的参数WPARAM wParam, LPARAM lParam)
当用户进行鼠标点击等操作时,操作系统会将消息发送到应用程序的消息队列中,然后应用程序的消息循环会处理这些消息,例如将其传递给某个特定窗口的消息处理函数
在处理窗口消息时,通常需要检查消息是由哪个窗口发送的,以便正确地响应消息。一个应用程序可以拥有多个窗口,并且每个窗口都有自己的消息处理函数
消息循环会不断地从系统队列中获取消息,然后将消息传递给相应的消息处理函数进行处理。在Windows操作系统中,消息循环通常是由函数GetMessage和DispatchMessage进行实现的
以下代码是Visual Studio提供的默认消息函数:
while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg); //翻译消息DispatchMessage(&msg); //分发消息}}
WinMain是Windows程序的入口函数, WinMain的返回值是一个整数,表示程序的退出状态码。在WinMain函数内部,我们可以创建窗口、初始化程序并执行消息循环等操作
它的语法格式如下:
int WINAPI WinMain(
HINSTANCE hInstance, //当前模块的句柄
HINSTANCE hPrevInstance, //废弃,置NULL
LPSTR lpCmdLine, //命令行参数,包含应用程序启动时传递的所有命令行参数
int nCmdShow //窗口显示方式,指定应用程序最初如何显示
);
CreateWindow函数是Windows API中用于创建一个窗口的函数,返回一个HWND类型的窗口句柄,该句柄可以用于操作该窗口,如显示、隐藏、移动、调整大小等
每当使用CreateWindow创建窗口时,操作系统会在内核生成一个窗口对象,该窗口对象包含了窗口的状态信息(如窗口大小, 位置, 标题), 同时也包含了窗口的过程函数指针,用于处理窗口的消息
其语法格式如下:
HWND CreateWindow(LPCWSTR lpClassName, //指定窗口类名LPCWSTR lpWindowName, //指定窗口标题DWORD dwStyle, //指定窗口的样式,如是否可见、是否有边框、是否可以调整大小等//指定窗口的位置和尺寸。 int x, int y,int nWidth,int nHeight,HWND hWndParent, //指定父窗口句柄,若没有父窗口,则为NULLHMENU hMenu, //指定菜单句柄,若没有,则为NULLHINSTANCE hInstance, //指定应用程序实例句柄,即应用程序的模块句柄LPVOID lpParam //指定窗口创建时传递给消息处理函数的参数
);
以下是常见的dwStyle参数值及其含义:
WS_OVERLAPPEDWINDOW
:创建一个拥有各种窗口风格的窗体,包括标题,系统菜单,边框,最小化和最大化按钮等WS_POPUP
:创建没有标题栏和边框的弹出式窗口。WS_CHILD
:创建一个子窗口。WS_VISIBLE
:使窗口可见。WS_DISABLED
:禁用窗口和它的子窗口。WS_MINIMIZEBOX
:包含最小化按钮。WS_MAXIMIZEBOX
:包含最大化按钮。WS_THICKFRAME
:包含可调整大小的边框。WS_CAPTION
:创建带有标题栏的窗口。WS_SYSMENU
:创建带有系统菜单的窗口ShowWindow函数用于显示或隐藏指定窗口,如果函数成功,返回值为非零值,否则返回值为零,其语法格式如下:
BOOL ShowWindow(HWND hWnd, //要显示或隐藏的窗口的句柄int nCmdShow //指定窗口如何显示的常量
);
常用的nCmdShow
常量包括:
SW_HIDE
:隐藏窗口
SW_SHOW
:显示窗口。
SW_SHOWDEFAULT
:使用窗口类中定义的默认值显示窗口。
SW_SHOWMAXIMIZED
:显示窗口并最大化。
SW_SHOWMINIMIZED
:显示窗口并最小化。
GetMessage函数用于从当前线程的消息队列中获取消息。如果当前线程的消息队列为空,即没有任何消息时,GetMessage函数将阻塞当前线程,直到消息队列中有消息为止。GetMessage函数会把获取到的消息复制到由lpMsg参数指向的MSG结构体中。
如果函数检索到了一个消息,则返回非零值;如果函数调用期间发生错误,则返回-1;如果函数检索到了一个WM_QUIT消息,则返回0。
其语法格式如下:
BOOL GetMessage(LPMSG lpMsg, //指向MSG结构体的指针,用于获取消息HWND hWnd, //窗口句柄,指定窗口消息的范围。若指定为NULL,则获取调用线程的所有消息UINT wMsgFilterMin, //指定检索的最小消息值。通常设置为UINT wMsgFilterMax //指定检索的最大消息值。通常设置为0
);
DispatchMessage函数是用于分发消息给消息处理函数处理的函数。当GetMessage函数从消息队列中取出一条消息时,就会将这条消息交给DispatchMessage函数来处理,DispatchMessage函数会根据消息的类型和参数,找到对应窗口的消息处理函数
其语法格式如下:
DispatchMessageW(_In_ CONST MSG *lpMsg //指向消息结构体的指针
);
TranslateMessage函数用于将键盘或鼠标的输入消息转换成字符消息,具体来说就是将输入的消息转换成WM_CHAR或WM_DEADCHAR消息, 然后交给窗口过程函数处理
其语法格式如下:
TranslateMessage(_In_ CONST MSG *lpMsg //指向消息结构体的指针
);
RegisterClass函数用于向Windows操作系统注册一个新的窗口类。注册窗口类之后,可以使用CreateWindow函数创建该窗口类的窗口
其语法格式如下:
RegisterClassW(_In_ CONST WNDCLASSW *lpWndClass //指向窗口类结构体的指针
);
以下是常见的Windows消息类型:
WM_CREATE
:创建窗口时发送的消息WM_DESTROY
:销毁窗口时发送的消息WM_PAINT
:绘制窗口内容时发送的消息WM_MOUSEMOVE
:鼠标移动时发送的消息WM_LBUTTONDOWN
:鼠标左键按下时发送的消息WM_RBUTTONDOWN
:鼠标右键按下时发送的消息WM_KEYDOWN
:键盘按下时发送的消息WM_COMMAND
:当一个控件被点击或选择时发送的消息WM_NOTIFY
:控件通知父窗口的消息WM_SIZE
:窗口大小改变时发送的消息如下代码是Visual Studio提供的Windows应用程序默认模板
include "framework.h"
include "WindowsProject1.h"define MAX_LOADSTRING 100// 全局变量:
HINSTANCE hInst; //当前实例
WCHAR szTitle[MAX_LOADSTRING]; //窗口标题
WCHAR szWindowClass[MAX_LOADSTRING]; //定义窗口类的名称// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);//入口函数
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow)
{//这两行代码表示这两个参数没有使用,避免出现编译器警告UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: 在此处放置代码。DWORD DLLAdress = (DWORD)hInstance;TCHAR str[255];wsprintf(str, TEXT("当前模块地址是:%x"),DLLAdress);OutputDebugString(str); //输出内容至VS输出窗口中,便于调试程序时查看// 初始化全局字符串LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// 执行应用程序初始化:if (!InitInstance(hInstance, nCmdShow)){return FALSE;}MSG msg; //定义消息结构体HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1)); //加载加速键表//进入消息循环while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg); //翻译消息DispatchMessage(&msg); //分发消息}}return (int)msg.wParam; //返回消息参数wParam}//
函数: MyRegisterClass()
目标: 注册窗口类。
ATOM MyRegisterClass(HINSTANCE hInstance)
{ wcscpy_s(szWindowClass, MAX_LOADSTRING, L"我的第一个窗口程序"); //设置窗口类名WNDCLASS wndclass = { 0 }; //定义了窗口类的结构体,用于存储窗口类的属性wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; //指定窗口的背景颜色为默认的背景颜色wndclass.lpszClassName = szWindowClass; //定义窗口类名wndclass.hInstance = hInstance; //定义窗口类的实例句柄,此处表示这个窗口类属于当前应用程序实例的wndclass.lpfnWndProc = WndProc; //定义窗口过程函数(消息处理函数)return RegisterClass(&wndclass); //注册窗口类,之后可以使用CreateWindow函数创建该窗口类的窗口
}// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // 将实例句柄存储在全局变量中wcscpy_s(szTitle,MAX_LOADSTRING, L"窗口标题"); //设置窗口标题//创建窗口HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow); //显示窗口UpdateWindow(hWnd); //更新窗口return TRUE;
}
//
// 消息处理函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
////窗口(消息)处理函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);// 分析菜单选择:switch (wmId){case IDM_ABOUT: //DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: 在此处添加使用 hdc 的任何绘图代码...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default://默认的窗口处理函数return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;
}
子窗口是指在一个父窗口中的独立的窗口。子窗口可以是任何标准窗口类,如按钮、编辑框、列表框等,也可以是自定义的窗口类
例如,在一个应用程序的主窗口中,可以创建多个子窗口来显示不同的工具栏、状态栏、文本编辑区等。子窗口的消息处理函数与普通窗口的消息处理函数相同,但需要在创建时指定父窗口句柄
在创建子窗口时使用CreateWindow函数,系统为函数的第一个参数提供了默认值,例如以下代码,第一个参数的值为"BUTTON",即代表当前创建的窗口为编辑框,第二个参数即为编辑框的标题, 第三个参数需设置成WS_CHILD来表示是子窗口,第九个参数表示为控件标识,可以理解成控件的编号(唯一的)
关于控件标识的解释:例如一个窗口有两个按钮, 若要捕捉指定按钮的消息, 可以通过控件标识来进行捕捉
define IDC_BUTTON1 1 CreateWindowA("BUTTON","设置",WS_CHILD | WS_VISIBLE, 100,100,50,50,hWnd, (HMENU)IDC_BUTTON1, //控件标识hInst,NULL,
);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{ switch (message){case WM_COMMAND: { int wmId = LOWORD(wParam); //LOWORD从一个整数中提取其低16位的值// 分析菜单选择:switch (wmId){case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;case IDC_BUTTON1: //点击按钮1要执行的代码MessageBoxA(0, "设置编辑框文本", "提示", 0);SetDlgItemText(hWnd, IDC_EDIT1, L"Hello World"); //设置指定控件的文本内容break;case IDC_BUTTON2: //点击按钮2要执行的代码TCHAR EditText[100];GetDlgItemText(hWnd, IDC_EDIT1, EditText, 100); //获取指定控件的文本内容MessageBox(0, EditText, L"获取编辑框文本", 0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: 在此处添加使用 hdc 的任何绘图代码...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;case WM_CREATE://创建一个编辑框CreateWindowA("EDIT","这是编辑框", WS_CHILD | WS_VISIBLE, 0,0,150,100,hWnd,(HMENU)IDC_EDIT1,hInst,NULL,);//创建一个按钮1CreateWindowA("BUTTON","设置",WS_CHILD | WS_VISIBLE,100,100,50,50,hWnd,(HMENU)IDC_BUTTON1,hInst,NULL,);//创建一个按钮2CreateWindowA("BUTTON","获取",WS_CHILD | WS_VISIBLE,40,100,50,50,hWnd,(HMENU)IDC_BUTTON2,hInst,NULL,);break;default://默认的窗口处理函数return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}
执行结果如下:
上一篇:【小猫爪】AUTOSAR学习笔记16-Memory Stack之Nvm模块
下一篇:Redis——几种部署方式(主从复制流程、redis cluster为什么是16384个solt位),Redis持久化(默认RDB与AOF)简述