第一个Window窗口程序

381 阅读3分钟

在使用vs2019创建新项目的时候,我们选择Windows桌面向导。之后,选择空项目即可。

主函数入口

在桌面程序里,我们的主函数入口不在是main函数了,而是WinMain函数或者wWinMain函数。如下所示:

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow)
{
	return 0;
}

这两个函数的不同之处在于第三个参数cmdLine,第一个使用ANSI编码,而第二个使用Unicode编码。所以,这里我们主要使用后者。

窗口初始化

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
	WNDCLASSEX wndClass = { 0 };
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hInstance = hInstance;
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = L"Dx11BookWindowClass";

	if (!RegisterClassEx(&wndClass))
		return -1;

	HWND hwnd = CreateWindowW(L"Dx11BookWindowClass", L"Blank Win32 Window",
		WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, hInstance, NULL);

	if (!hwnd) return -1;

	ShowWindow(hwnd, cmdShow);
    
    return 0;
}

窗口类由WNDCLASSEX所定义,包含窗口的各种属性。

创建完WNDCLASSEX结构后,调用函数RegisterClassEx()来注册窗口类。返回值为0,则表示注册失败。

接下来调用函数CreateWindowW来创建窗口。

如果创建成功,调用ShowWindow显示窗口。

注意GUI程序是事件驱动类型的程序,意思是当一个事件发生后,程序接收到它的通知,采取一些行动来响应。该过程一直运行,直到退出事件发生。但是,在视频游戏中,应用程序是实时的,意味着无论一些事件是否发生,都不会阻止程序在其生命期间执行许多任务。如果游戏控制器上用户按下按钮,它会被游戏循环中的更新阶段所检测到,游戏再来响应该事件。如果没有事件发生,游戏也一直不断地渲染当前游戏状态,执行逻辑更新,查找并响应网络数据,播放声音,等等。

程序循环

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
	// ...
    
	MSG msg = { 0 };
	while (msg.message != WM_QUIT) {
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		} else {
        	// Update
            // Draw
        }
	}

	return static_cast<int>(msg.wParam);
}

程序循环就是一个无限的循环,直到用户跳出该循环为止。由接收WM_QUIT事件来发生结束指令。

MSG是一个用于持有来自于操作系统中window消息的结构,它用于程序响应这些消息。

PeeKMessage函数取得相关窗口的消息。

PeeKMessage是非阻塞函数;当系统无消息时,返回FALSE,继续执行后续的代码。

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
	WNDCLASSEX wndClass = { 0 };
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hInstance = hInstance;
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = L"Dx11BookWindowClass";

	if (!RegisterClassEx(&wndClass))
		return -1;

	HWND hwnd = CreateWindowW(L"Dx11BookWindowClass", L"Blank Win32 Window",
		WS_OVERLAPPEDWINDOW, 0, 0, 640, 480,
		NULL, NULL, hInstance, NULL);

	if (!hwnd) return -1;

	ShowWindow(hwnd, cmdShow);

	MSG msg = { 0 };
	while (msg.message != WM_QUIT) {
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return static_cast<int>(msg.wParam);
}

窗口过程

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	PAINTSTRUCT ps;
	HDC hDC;

	switch (message) {
	case WM_PAINT:
		hDC = BeginPaint(hwnd, &ps);
		EndPaint(hwnd, &ps);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;
}

DefWindowProc函数只被那些我们没有自定义响应代码的消息所调用。