win32窗口编程

本文详细介绍了如何使用VisualStudio在Windows10环境下创建一个最简单的窗口应用程序,包括WinMain函数的用法、窗口注册、消息循环的实现,以及如何处理WM_CLOSE和自定义消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

准备

操作系统:Windows10或以上

编译器:Microsoft Visual Studio
在这里插入图片描述
在这里插入图片描述

创建一个最简单的窗口

Just as every C application and C++ application must have a main function as its starting point, every Windows desktop application must have a WinMain function. WinMain has the following syntax.

int WINAPI WinMain(
   _In_ HINSTANCE hInstance,
   _In_opt_ HINSTANCE hPrevInstance,
   _In_ LPSTR     lpCmdLine,
   _In_ int       nCmdShow
);

WinMain函数是Windows窗口应用程序的主函数,就像控制台console程序一样都有一个主函数main();。WinMain函数有四个参数:

  • hInstance:A handle to the current instance of the application. 当前应用实例的一个操作。
  • hPrevInstance:A handle to the previous instance of the application. This parameter is always NULL. 这个参数不是很常用。
  • lpCmdLine:The command line for the application, excluding the program name. To retrieve the entire command line, use the GetCommandLine function. 用来接收命令行的参数。
  • nCmdShow:Controls how the window is to be shown. This parameter can be any of the values that can be specified in the nCmdShow parameter for the ShowWindow function. 这个参数决定着窗口将以怎样的形式展现出来。

使用 Visual Studio 创建一个空项目:
在这里插入图片描述
确定好项目名称以及所在位置:
在这里插入图片描述
Source Files(源文件)右击->Add(添加)->New Item:
在这里插入图片描述
在这里插入图片描述

以下便是一个最简单的窗口应用程序代码:
在这里插入图片描述

运行以上代码之前记得修改项目的属性:Solution Explorer栏下右击项目名称:Properties—>Configuration Properties—>Linker—>System—>SubSystem改成Windows。
在这里插入图片描述
在这里插入图片描述
温馨提示:如果您开发的不是窗口应用程序(console控制台程序),记得改为Console,这里设置不好是不能成功编译运行的。
在这里插入图片描述


编译成功后运行以上代码,你会发现什么都没有出现,这是因为它是最简单的窗口应用程序,里面还没有任何内容。

注册窗口类、创建窗口实例并显示窗口

#include <Windows.h>

int CALLBACK WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	const LPCWSTR pClassName = L"pClassName";         //指定的字符串常数,前面需要加一个大写字母 L
	
	//register window class 注册窗口类
	WNDCLASSEX wc = { 0 };
	wc.cbSize = sizeof(wc);    //初始化成员变量
	wc.style = CS_OWNDC;
	wc.lpfnWndProc = DefWindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = nullptr;
	wc.hCursor = nullptr;
	wc.hbrBackground = nullptr;
	wc.lpszMenuName = nullptr;
	wc.lpszClassName = pClassName;
	wc.hIconSm = nullptr;
	RegisterClassEx(&wc);
	
	//create window instance
	HWND hWnd = CreateWindowEx(
		0, pClassName,
		L"Happy Hard Window",      //窗口标题
		WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
		200, 200, 640, 480,        //窗口位置(200,200),大小(640,480)。
		nullptr, nullptr, hInstance, nullptr
	);

	//展示窗口
	ShowWindow(hWnd, SW_SHOW);
	
	while (true) {};      //让程序进入死循环,让窗口一直显示
	
	return 0;
}

在这里插入图片描述
编译并运行,会发现有一个窗口,这时并不能对窗口进行任何操作。

消息循环

对以上窗口不仅能进行任何操作,是因为我们还没写代码对事件进行处理。这里的事件指的就是鼠标操作或者键盘输入。

Windows is about windows. And Windows is about Messages.

在没有任何操作的时候,窗口的画面会一直不变;一旦用户输入了操作,窗口也会做相应的改变。对于不同的事件,会产生不同的响应,并重新刷新图形界面窗口,反馈给使用者。游戏也是这样,只不过游戏的画面的刷新是实时的,即使没有鼠标键盘操作,游戏画面也在刷新,包括子弹的运动、NPC的动作等。

在这里插入图片描述
在这里插入图片描述
将上一步的代码中的 while(true) 死循环改成以下代码:

MSG msg;     //msg结构体
while (GetMessage(&msg, nullptr, 0, 0) > 0) {
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

这时候就可以移动窗口和最小化以及关闭窗口,但是在关闭窗口的时候,虽然窗口是关闭了,但是程序的进程依然在运行着。这是因为当单击关闭窗口的按钮的时候,程序并不知道你是想要关闭窗口还是关闭整个进程。

  • msg 结构体中的成员变量:
    在这里插入图片描述

在前面的代码中,我们设置了 wc.lpfnWndProc = DefWindowProc; default Window procedure,意味着所有的消息都将做出默认行为,包括关闭窗口。默认的关闭窗口,不连同进程一起关闭。因为一个进程可能会有多个窗口,父窗口、子窗口等,当我们关闭子窗口的时候,我们并不希望把整个进程以及父窗口都关闭。

想要让窗口对于不同的消息,做出我们想要的行为,就需要自定义一个消息处理机制:

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)    //WndProc函数名随意起
{
	return DefWindowProc(hWnd, msg, wParam, lParam);
}
... ...
wc.lpfnWndProc = WndProc;

在函数体中添加 switch 语句,对 msg 进行switch,对于不同的情况case,做出不同的反应:

switch (msg)
{
case WM_CLOSE:
	PostQuitMessage(69);
	break;
}

全部代码:

#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_CLOSE:
		PostQuitMessage(69);
		break;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

int CALLBACK WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	const LPCTSTR pClassName = L"pClassName";
	//register window class 注册窗口类
	WNDCLASSEX wc = { 0 };
	wc.cbSize = sizeof(wc);
	wc.style = CS_OWNDC;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = nullptr;
	wc.hCursor = nullptr;
	wc.hbrBackground = nullptr;
	wc.lpszMenuName = nullptr;
	wc.lpszClassName = pClassName;
	wc.hIconSm = nullptr;
	RegisterClassEx(&wc);
	//create window instance
	HWND hWnd = CreateWindowEx(
		0, pClassName,
		L"Happy Hard Window",
		WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
		200, 200, 640, 480,
		nullptr, nullptr, hInstance, nullptr
	);
	ShowWindow(hWnd, SW_SHOW);

	//message bump
	MSG msg;
	BOOL gResult;
	while ((gResult = GetMessage(&msg, nullptr, 0, 0)) > 0)
	{
		TranslateMessage(&msg);          //后文会讲到这个函数的作用
		DispatchMessage(&msg);           //这个函数用于派遣消息,让窗口对消息做出快速反应
	}

	if (gResult == -1)
	{
		return -1;
	}
	else
	{
		return msg.wParam;     //The exit code given in the PostQuitMessage function.
	}
}
  • DispatchMessage:Dispatches a message to a window procedure. It is typically used to dispatch a message retrieved by the GetMessage function.
  • PostQuitMessage:Indicates to the system that a thread has made a request to terminate (quit). It is typically used in response to a WM_DESTROY message.

这时候再来编译运行,当关闭窗口的时候,进程也会被关闭,并返回 69值:
在这里插入图片描述

窗口消息

窗口消息除了 WM_CLOSE 以外,还有很多:(大概有好几千条)
在这里插入图片描述

为了方便编程,可以专门进行以下操作:
创建一个头文件 WindowsMessageMap.h 和 一个 .cpp 的源文件 WindowsMessageMap.cpp

在这里插入图片描述
WindowsMessageMap.h 内容如下:

#pragma once
#include <unordered_map>
#include <Windows.h>
#include <string>

class WindowsMessageMap
{
public:
	WindowsMessageMap() noexcept;
	std::string operator()(DWORD msg, LPARAM lp, WPARAM wp) const noexcept;
private:
	std::unordered_map<DWORD, std::string> map;
};

WindowsMessageMap.cpp的内容如下:在这里可以做一个实验,通过网络查找,对经常出现的消息列举在map中,为了实时显示窗口的操作,将消息打印出来。

#include "WindowsMessageMap.h"
#include <string>
#include <sstream>
#include <iomanip>

// secret messages
#define WM_UAHDESTROYWINDOW 0x0090
#define WM_UAHDRAWMENU 0x0091
#define WM_UAHDRAWMENUITEM 0x0092
#define WM_UAHINITMENU 0x0093
#define WM_UAHMEASUREMENUITEM 0x0094
#define WM_UAHNCPAINTMENUPOPUP 0x0095

#define REGISTER_MESSAGE(msg){msg,#msg}

WindowsMessageMap::WindowsMessageMap() noexcept
	:
	map({
		REGISTER_MESSAGE(WM_CREATE),
		REGISTER_MESSAGE(WM_DESTROY),
		REGISTER_MESSAGE(WM_MOVE),
		REGISTER_MESSAGE(WM_SIZE),
		REGISTER_MESSAGE(WM_ACTIVATE),
		REGISTER_MESSAGE(WM_SETFOCUS),
		REGISTER_MESSAGE(WM_KILLFOCUS),
		REGISTER_MESSAGE(WM_ENABLE),
		REGISTER_MESSAGE(WM_SETREDRAW),
		REGISTER_MESSAGE(WM_SETTEXT),
		REGISTER_MESSAGE(WM_GETTEXT),
		REGISTER_MESSAGE(WM_GETTEXTLENGTH),
		REGISTER_MESSAGE(WM_PAINT),
		REGISTER_MESSAGE(WM_CLOSE),
		REGISTER_MESSAGE(WM_QUERYENDSESSION),
		REGISTER_MESSAGE(WM_QUIT),
		REGISTER_MESSAGE(WM_QUERYOPEN),
		REGISTER_MESSAGE(WM_ERASEBKGND),
		REGISTER_MESSAGE(WM_SYSCOLORCHANGE),
		REGISTER_MESSAGE(WM_ENDSESSION),
		REGISTER_MESSAGE(WM_SHOWWINDOW),
		REGISTER_MESSAGE(WM_CTLCOLORMSGBOX),
		REGISTER_MESSAGE(WM_CTLCOLOREDIT),
		REGISTER_MESSAGE(WM_CTLCOLORLISTBOX),
		REGISTER_MESSAGE(WM_CTLCOLORBTN),
		REGISTER_MESSAGE(WM_CTLCOLORDLG),
		REGISTER_MESSAGE(WM_CTLCOLORSCROLLBAR),
		REGISTER_MESSAGE(WM_CTLCOLORSTATIC),
		REGISTER_MESSAGE(WM_WININICHANGE),
		REGISTER_MESSAGE(WM_SETTINGCHANGE),
		REGISTER_MESSAGE(WM_DEVMODECHANGE),
		REGISTER_MESSAGE(WM_ACTIVATEAPP),
		REGISTER_MESSAGE(WM_FONTCHANGE),
		REGISTER_MESSAGE(WM_TIMECHANGE),
		REGISTER_MESSAGE(WM_CANCELMODE),
		REGISTER_MESSAGE(WM_SETCURSOR),
		REGISTER_MESSAGE(WM_MOUSEACTIVATE),
		REGISTER_MESSAGE(WM_CHILDACTIVATE),
		REGISTER_MESSAGE(WM_QUEUESYNC),
		REGISTER_MESSAGE(WM_GETMINMAXINFO),
		REGISTER_MESSAGE(WM_ICONERASEBKGND),
		REGISTER_MESSAGE(WM_NEXTDLGCTL),
		REGISTER_MESSAGE(WM_SPOOLERSTATUS),
		REGISTER_MESSAGE(WM_DRAWITEM),
		REGISTER_MESSAGE(WM_MEASUREITEM),
		REGISTER_MESSAGE(WM_DELETEITEM),
		REGISTER_MESSAGE(WM_VKEYTOITEM),
		REGISTER_MESSAGE(WM_CHARTOITEM),
		REGISTER_MESSAGE(WM_SETFONT),
		REGISTER_MESSAGE(WM_GETFONT),
		REGISTER_MESSAGE(WM_QUERYDRAGICON),
		REGISTER_MESSAGE(WM_COMPAREITEM),
		REGISTER_MESSAGE(WM_COMPACTING),
		REGISTER_MESSAGE(WM_NCCREATE),
		REGISTER_MESSAGE(WM_NCDESTROY),
		REGISTER_MESSAGE(WM_NCCALCSIZE),
		REGISTER_MESSAGE(WM_NCHITTEST),
		REGISTER_MESSAGE(WM_NCPAINT),
		REGISTER_MESSAGE(WM_NCACTIVATE),
		REGISTER_MESSAGE(WM_GETDLGCODE),
		REGISTER_MESSAGE(WM_NCMOUSEMOVE),
		REGISTER_MESSAGE(WM_NCLBUTTONDOWN),
		REGISTER_MESSAGE(WM_NCLBUTTONUP),
		REGISTER_MESSAGE(WM_NCLBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_NCRBUTTONDOWN),
		REGISTER_MESSAGE(WM_NCRBUTTONUP),
		REGISTER_MESSAGE(WM_NCRBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_NCMBUTTONDOWN),
		REGISTER_MESSAGE(WM_NCMBUTTONUP),
		REGISTER_MESSAGE(WM_NCMBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_KEYDOWN),
		REGISTER_MESSAGE(WM_KEYUP),
		REGISTER_MESSAGE(WM_CHAR),
		REGISTER_MESSAGE(WM_DEADCHAR),
		REGISTER_MESSAGE(WM_SYSKEYDOWN),
		REGISTER_MESSAGE(WM_SYSKEYUP),
		REGISTER_MESSAGE(WM_SYSCHAR),
		REGISTER_MESSAGE(WM_SYSDEADCHAR),
		REGISTER_MESSAGE(WM_KEYLAST),
		REGISTER_MESSAGE(WM_INITDIALOG),
		REGISTER_MESSAGE(WM_COMMAND),
		REGISTER_MESSAGE(WM_SYSCOMMAND),
		REGISTER_MESSAGE(WM_TIMER),
		REGISTER_MESSAGE(WM_HSCROLL),
		REGISTER_MESSAGE(WM_VSCROLL),
		REGISTER_MESSAGE(WM_INITMENU),
		REGISTER_MESSAGE(WM_INITMENUPOPUP),
		REGISTER_MESSAGE(WM_MENUSELECT),
		REGISTER_MESSAGE(WM_MENUCHAR),
		REGISTER_MESSAGE(WM_ENTERIDLE),
		REGISTER_MESSAGE(WM_MOUSEWHEEL),
		REGISTER_MESSAGE(WM_MOUSEMOVE),
		REGISTER_MESSAGE(WM_LBUTTONDOWN),
		REGISTER_MESSAGE(WM_LBUTTONUP),
		REGISTER_MESSAGE(WM_LBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_RBUTTONDOWN),
		REGISTER_MESSAGE(WM_RBUTTONUP),
		REGISTER_MESSAGE(WM_RBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_MBUTTONDOWN),
		REGISTER_MESSAGE(WM_MBUTTONUP),
		REGISTER_MESSAGE(WM_MBUTTONDBLCLK),
		REGISTER_MESSAGE(WM_PARENTNOTIFY),
		REGISTER_MESSAGE(WM_MDICREATE),
		REGISTER_MESSAGE(WM_MDIDESTROY),
		REGISTER_MESSAGE(WM_MDIACTIVATE),
		REGISTER_MESSAGE(WM_MDIRESTORE),
		REGISTER_MESSAGE(WM_MDINEXT),
		REGISTER_MESSAGE(WM_MDIMAXIMIZE),
		REGISTER_MESSAGE(WM_MDITILE),
		REGISTER_MESSAGE(WM_MDICASCADE),
		REGISTER_MESSAGE(WM_MDIICONARRANGE),
		REGISTER_MESSAGE(WM_MDIGETACTIVE),
		REGISTER_MESSAGE(WM_MDISETMENU),
		REGISTER_MESSAGE(WM_CUT),
		REGISTER_MESSAGE(WM_COPYDATA),
		REGISTER_MESSAGE(WM_COPY),
		REGISTER_MESSAGE(WM_PASTE),
		REGISTER_MESSAGE(WM_CLEAR),
		REGISTER_MESSAGE(WM_UNDO),
		REGISTER_MESSAGE(WM_RENDERFORMAT),
		REGISTER_MESSAGE(WM_RENDERALLFORMATS),
		REGISTER_MESSAGE(WM_DESTROYCLIPBOARD),
		REGISTER_MESSAGE(WM_DRAWCLIPBOARD),
		REGISTER_MESSAGE(WM_PAINTCLIPBOARD),
		REGISTER_MESSAGE(WM_VSCROLLCLIPBOARD),
		REGISTER_MESSAGE(WM_SIZECLIPBOARD),
		REGISTER_MESSAGE(WM_ASKCBFORMATNAME),
		REGISTER_MESSAGE(WM_CHANGECBCHAIN),
		REGISTER_MESSAGE(WM_HSCROLLCLIPBOARD),
		REGISTER_MESSAGE(WM_QUERYNEWPALETTE),
		REGISTER_MESSAGE(WM_PALETTEISCHANGING),
		REGISTER_MESSAGE(WM_PALETTECHANGED),
		REGISTER_MESSAGE(WM_DROPFILES),
		REGISTER_MESSAGE(WM_POWER),
		REGISTER_MESSAGE(WM_WINDOWPOSCHANGED),
		REGISTER_MESSAGE(WM_WINDOWPOSCHANGING),
		REGISTER_MESSAGE(WM_HELP),
		REGISTER_MESSAGE(WM_NOTIFY),
		REGISTER_MESSAGE(WM_CONTEXTMENU),
		REGISTER_MESSAGE(WM_TCARD),
		REGISTER_MESSAGE(WM_MDIREFRESHMENU),
		REGISTER_MESSAGE(WM_MOVING),
		REGISTER_MESSAGE(WM_STYLECHANGED),
		REGISTER_MESSAGE(WM_STYLECHANGING),
		REGISTER_MESSAGE(WM_SIZING),
		REGISTER_MESSAGE(WM_SETHOTKEY),
		REGISTER_MESSAGE(WM_PRINT),
		REGISTER_MESSAGE(WM_PRINTCLIENT),
		REGISTER_MESSAGE(WM_POWERBROADCAST),
		REGISTER_MESSAGE(WM_HOTKEY),
		REGISTER_MESSAGE(WM_GETICON),
		REGISTER_MESSAGE(WM_EXITMENULOOP),
		REGISTER_MESSAGE(WM_ENTERMENULOOP),
		REGISTER_MESSAGE(WM_DISPLAYCHANGE),
		REGISTER_MESSAGE(WM_STYLECHANGED),
		REGISTER_MESSAGE(WM_STYLECHANGING),
		REGISTER_MESSAGE(WM_GETICON),
		REGISTER_MESSAGE(WM_SETICON),
		REGISTER_MESSAGE(WM_SIZING),
		REGISTER_MESSAGE(WM_MOVING),
		REGISTER_MESSAGE(WM_CAPTURECHANGED),
		REGISTER_MESSAGE(WM_DEVICECHANGE),
		REGISTER_MESSAGE(WM_PRINT),
		REGISTER_MESSAGE(WM_PRINTCLIENT),
		REGISTER_MESSAGE(WM_IME_SETCONTEXT),
		REGISTER_MESSAGE(WM_IME_NOTIFY),
		REGISTER_MESSAGE(WM_NCMOUSELEAVE),
		REGISTER_MESSAGE(WM_EXITSIZEMOVE),
		REGISTER_MESSAGE(WM_UAHDESTROYWINDOW),
		REGISTER_MESSAGE(WM_DWMNCRENDERINGCHANGED),
		REGISTER_MESSAGE(WM_ENTERSIZEMOVE),
		})
{}

std::string WindowsMessageMap::operator()(DWORD msg, LPARAM lp, WPARAM wp) const noexcept
{
	constexpr int firstColWidth = 25;
	const auto i = map.find(msg);

	std::ostringstream oss;
	if (i != map.end())
	{
		oss << std::left << std::setw(firstColWidth) << i->second << std::right;
	}
	else
	{
		std::ostringstream padss;
		padss << "Unknown message: 0x" << std::hex << msg;
		oss << std::left << std::setw(firstColWidth) << padss.str() << std::right;
	}
	oss << "   LP: 0x" << std::hex << std::setfill('0') << std::setw(8) << lp;
	oss << "   WP: 0x" << std::hex << std::setfill('0') << std::setw(8) << wp << std::endl;

	return oss.str();
}

通过include将WindowsMessageMap.h头文件引进WinMain()函数所在的源文件中:
在这里插入图片描述

如果输出的string类型出现了乱码,需要自己转换以下 ConvertToLPWSTR()。如果没有乱码问题,省去LPWSTR ConvertToLPWSTR()函数即可。
在这里插入图片描述

当按键按下的时候来修改窗口的标题,以确定程序是否运行正常:
在这里插入图片描述

运行结果如下:
在这里插入图片描述

在Output栏下可以看到打印的消息,我们可以看到 WM_KEYDOWN(按键按下)、WM_NCMOUSEMOVE(鼠标移动)等消息被实时地打印出来。

当大写字母 F 键被按下的时候,窗口的标题发生了相应的变化。
在这里插入图片描述

WM_KEYDOWN 有个类似的消息是 WM_CHAR,他们两个主要的区别就是,前者对大小写不敏感,接收一切按键的按下,可以用于游戏角色的移动;而后者对于大小写敏感,用于文本输入。 前文代码中的 TranslateMessage() 的其中一个作用就是将 WM_KEYDOWN 的消息转换成 WM_CHAR 的消息。

添加以下代码,通过输入文本来改变窗口标题:
在这里插入图片描述

添加以下代码,当鼠标左键点击的时候,将窗口标题改为当前鼠标的坐标:(注意添加#include <sstream>)。
在这里插入图片描述
在这里插入图片描述
本期文章到此结束,后面随缘更新后续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码星人1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值