win32api之Windows应用程序(五)
什么是消息队列
当我们在使用鼠标或键盘时,操作系统会将这些动作转换为一个消息(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); //分发消息
}
}
常用API
WinMain函数
WinMain是Windows程序的入口函数, WinMain的返回值是一个整数,表示程序的退出状态码。在WinMain函数内部,我们可以创建窗口、初始化程序并执行消息循环等操作
它的语法格式如下:
int WINAPI WinMain(
HINSTANCE hInstance, //当前模块的句柄
HINSTANCE hPrevInstance, //废弃,置NULL
LPSTR lpCmdLine, //命令行参数,包含应用程序启动时传递的所有命令行参数
int nCmdShow //窗口显示方式,指定应用程序最初如何显示
);
CreateWindow
CreateWindow函数是Windows API中用于创建一个窗口的函数,返回一个HWND类型的窗口句柄,该句柄可以用于操作该窗口,如显示、隐藏、移动、调整大小等
每当使用CreateWindow创建窗口时,操作系统会在内核生成一个窗口对象,该窗口对象包含了窗口的状态信息(如窗口大小, 位置, 标题), 同时也包含了窗口的过程函数指针,用于处理窗口的消息
其语法格式如下:
HWND CreateWindow(
LPCWSTR lpClassName, //指定窗口类名
LPCWSTR lpWindowName, //指定窗口标题
DWORD dwStyle, //指定窗口的样式,如是否可见、是否有边框、是否可以调整大小等
//指定窗口的位置和尺寸。
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent, //指定父窗口句柄,若没有父窗口,则为NULL
HMENU hMenu, //指定菜单句柄,若没有,则为NULL
HINSTANCE 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
ShowWindow函数用于显示或隐藏指定窗口,如果函数成功,返回值为非零值,否则返回值为零,其语法格式如下:
BOOL ShowWindow(
HWND hWnd, //要显示或隐藏的窗口的句柄
int nCmdShow //指定窗口如何显示的常量
);
常用的nCmdShow
常量包括:
-
SW_HIDE
:隐藏窗口 -
SW_SHOW
:显示窗口。 -
SW_SHOWDEFAULT
:使用窗口类中定义的默认值显示窗口。 -
SW_SHOWMAXIMIZED
:显示窗口并最大化。 -
SW_SHOWMINIMIZED
:显示窗口并最小化。
GetMessage
GetMessage函数用于从当前线程的消息队列中获取消息。如果当前线程的消息队列为空,即没有任何消息时,GetMessage函数将阻塞当前线程,直到消息队列中有消息为止。GetMessage函数会把获取到的消息复制到由lpMsg参数指向的MSG结构体中。
如果函数检索到了一个消息,则返回非零值;如果函数调用期间发生错误,则返回-1;如果函数检索到了一个WM_QUIT消息,则返回0。
其语法格式如下:
BOOL GetMessage(
LPMSG lpMsg, //指向MSG结构体的指针,用于获取消息
HWND hWnd, //窗口句柄,指定窗口消息的范围。若指定为NULL,则获取调用线程的所有消息
UINT wMsgFilterMin, //指定检索的最小消息值。通常设置为
UINT wMsgFilterMax //指定检索的最大消息值。通常设置为0
);
DispatchMessage
DispatchMessage函数是用于分发消息给消息处理函数处理的函数。当GetMessage函数从消息队列中取出一条消息时,就会将这条消息交给DispatchMessage函数来处理,DispatchMessage函数会根据消息的类型和参数,找到对应窗口的消息处理函数
其语法格式如下:
DispatchMessageW(
_In_ CONST MSG *lpMsg //指向消息结构体的指针
);
TranslateMessage
TranslateMessage函数用于将键盘或鼠标的输入消息转换成字符消息,具体来说就是将输入的消息转换成WM_CHAR或WM_DEADCHAR消息, 然后交给窗口过程函数处理
其语法格式如下:
TranslateMessage(
_In_ CONST MSG *lpMsg //指向消息结构体的指针
);
RegisterClass
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;
}
<br>
//
// 消息处理函数: 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,
);
//创建一个按钮1
CreateWindowA(
"BUTTON",
"设置",
WS_CHILD | WS_VISIBLE,
100,
100,
50,
50,
hWnd,
(HMENU)IDC_BUTTON1,
hInst,
NULL,
);
//创建一个按钮2
CreateWindowA(
"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;
}
执行结果如下: