win32api之Windows应用程序(五)

发布时间 2023-03-22 21:11:07作者: 亨利其实很坏

什么是消息队列

当我们在使用鼠标或键盘时,操作系统会将这些动作转换为一个消息(message),并将其发送到相应的消息队列中。这个消息包含了一些信息,例如动作的类型、坐标、时间戳等等。

在Windows系统中,每个窗口都有一个消息队列,操作系统将接收到的消息按照先后顺序依次存储在该队列中,等待程序读取和处理

每个线程只有一个消息队列,消息队列是属于线程的,每个线程在创建窗口时才会分配一个消息队列,并不是每个线程都有消息队列。当消息被发送到一个窗口时,系统会将消息放入该窗口所属的线程的消息队列中,线程可以通过GetMessage函数从消息队列中获取消息并进行处理。

有一点要注意:一个窗口只能属于一个线程,但一个线程可以拥有多个窗口

image-20230227162213354


什么是消息处理函数

消息处理函数用于处于各种事件消息的函数,也称为窗口过程函数。当一个窗口收到一个事件消息时,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;
}

执行结果如下:

动画