Просмотр исходного кода

feat: 添加窗口封装和信息转发

nicetry12138 1 год назад
Родитель
Сommit
3e8807f614

BIN
图形学/DirectX学习/Image/009.png


+ 113 - 0
图形学/DirectX学习/README.md

@@ -353,3 +353,116 @@ case WM_LBUTTONDOWN:
 
 ![](Image/008.png)
 
+### 封装Window
+
+由于该项目只有一个窗口,所以直接做成一个单例类
+
+```cpp
+class Window
+{
+private:
+	// singleton manages registration/cleanup of window class
+	class WindowClass;
+
+	
+private:
+	static LRESULT CALLBACK HandleMsgSetup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+	static LRESULT CALLBACK HandleMsgThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+	LRESULT HandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+}
+```
+
+`WindowClass` 是窗口的实例类,由 `Window` 来管理
+
+`WindowClass` 是单例类,所以会在获取 `GetInstance` 时构建和注册一个窗口
+
+```cpp
+Window::WindowClass::WindowClass() noexcept : hInst(GetModuleHandle(nullptr))
+{
+	WNDCLASSEX wc = { 0 };
+	wc.cbSize = sizeof(wc);
+	wc.style = CS_OWNDC;
+	wc.lpfnWndProc = HandleMsgSetup;
+	// ... 其他注册内容
+	RegisterClassEx(&wc);
+}
+```
+
+在 `Window` 类创建的时候会通过 `WindowClass` 构建和注册一个窗口,再由 `Window` 来创建出窗口
+
+```cpp
+Window::Window(int InWidth, int InHeight, const wchar_t* InName) noexcept
+{
+	RECT Wr;
+	Wr.left = 100;
+	Wr.right = InWidth + Wr.left;
+	Wr.top = 100;
+	Wr.bottom = InHeight + 100;
+
+	// AdjustWindowRect 会根据样式重新计算 RECT 中各个参数的值
+	AdjustWindowRect(&Wr, WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, FALSE);
+
+	// 重新设置过 RECT 参数,所以这里不能直接使用 InWidth 和 InHeight
+	hWnd = CreateWindow(
+		WindowClass::GetName(), InName,
+		WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
+		CW_USEDEFAULT, CW_USEDEFAULT, Wr.right - Wr.left, Wr.bottom - Wr.top,
+		nullptr, nullptr, WindowClass::GetInstance(), this
+	);
+
+	ShowWindow(hWnd, SW_SHOWDEFAULT);
+}
+```
+
+这里需要注意的是 `wc.lpfnWndProc = HandleMsgSetup` 绑定的窗口信息函数
+
+```cpp
+LRESULT Window::HandleMsgSetup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
+{
+	if (msg == WM_NCCREATE)
+	{
+		const CREATESTRUCTW* const pCreate = reinterpret_cast<CREATESTRUCTW*>(lParam);
+		Window* const pWnd = static_cast<Window*>(pCreate->lpCreateParams);
+		SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pWnd));
+		SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&Window::HandleMsgThunk));
+		return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
+	}
+	return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+```
+
+当 `WM_NCCREATE` 被触发的时候,重新设置 `GWLP_USERDATA` 和 `GWLP_WNDPROC`,也就将消息的回调函数设置成了 `Window::HandleMsgThunk`
+
+为什么要切换信息回调函数呢?
+
+1. 组织性:`HandleMsgSetup` 专注于窗口创建时的设置工作,而 `HandleMsgThunk` 用于处理窗口的常规消息。这种分离使得代码更加清晰和易于管理
+2. 安全性:在窗口创建期间,可能会收到各种消息,但在窗口类与窗口句柄关联之前,这些消息不应该被传递到窗口类的实例。`HandleMsgSetup` 确保只有在关联建立之后,消息才会被转发到窗口类的实例
+3. 效率:一旦窗口创建完成并且关联建立,`HandleMsgThunk` 将直接转发消息到窗口类的实例,无需每次都检查 `WM_NCCREATE` 消息。这提高了消息处理的效率
+4. 灵活性:如果将来需要在窗口创建过程中添加更多的初始化代码,只需修改 `HandleMsgSetup` 函数即可,而不会影响到常规消息处理的代码
+
+这里不得不提到 `WM_NCCREATE` ,这个宏的 `NCCREATE` 可以拆分成 `NC` 和 `CREATE`,后者 `CREATE` 就是创建的意思;前者 `NC` 表示的是 `No-Client`
+
+![](Image/009.png)
+
+以上图为例,图中标题栏的边框,最大化最小化按钮,以及其他UI元素,这个边框称为 `Window`的 `no-client` 区域。
+
+`WM_NCCREATE` 在 `WM_CREATE` 之前发送
+
+在这之前的消息都会被 `HandleMsgSetup` 吃掉,因为客户端一般不用处理这之前的消息
+
+通过 `SetWindowLongPtr` 就设定了 `GWLP_USERDATA` 用户自定义数据中存储的 `Window` 对象指针
+
+```cpp
+LRESULT Window::HandleMsgThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
+{
+	Window* const pWnd = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+	return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
+}
+```
+
+在 `HandleMsgThunk` 函数中,通过 `GetWindowLongPtr` 的方式将 `Window` 对象的指针从 `GWLP_USERDATA` 中取出,并将消息转发到 `HandleMsg` 中
+
+由于 `WINAPI` 回调函数需要符合特定的签名,并且必须能够通过全局访问,因此它们不能直接绑定到类的非静态成员函数,这个函数必须是一个全局函数或静态成员函数
+
+所以通过上面的方法,将全局事件分发到成员函数 `Window::HandleMsg` 中
+

+ 2 - 1
图形学/DirectX学习/src/hd3d/hd3d/Test/WindowsMessageMap.h

@@ -19,8 +19,9 @@
 ******************************************************************************************/
 #pragma once
 #include <unordered_map>
-#include <Windows.h>
 #include <string>
+//#include <Windows.h>
+#include "../ChiliWin.h"
 
 class WindowsMessageMap
 {

+ 41 - 66
图形学/DirectX学习/src/hd3d/hd3d/WinMain.cpp

@@ -1,6 +1,8 @@
-#include <Windows.h>
+//#include <Windows.h>
+#include "ChiliWin.h"
 #include <cstdlib>
 #include <sstream>
+#include "Window.h"
 #include "Test/WindowsMessageMap.h"
 
 inline std::wstring to_wide_string(const std::string& input) //string to wstring
@@ -10,78 +12,51 @@ inline std::wstring to_wide_string(const std::string& input) //string to wstring
 	return wide;
 }
 
-LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
-	static WindowsMessageMap mm;
-
-	OutputDebugString(to_wide_string(mm(msg, lParam, wParam)).c_str());
-
-	switch (msg)
-	{
-	case WM_CLOSE:
-		PostQuitMessage(69);
-		break;
-	case WM_KEYDOWN:
-		if (wParam == 'F') {
-			// 当 F 键按下,修改窗口的 Title
-			SetWindowText(hWnd, L"Reset Title");
-		}
-		break;
-	case WM_CHAR:
-	{
-		static std::string title;
-		title.push_back((char)wParam);
-		SetWindowText(hWnd, to_wide_string(title).c_str());
-		break;
-	}
-	case WM_LBUTTONDOWN:
-	{
-		POINTS pt = MAKEPOINTS(lParam);
-		std::ostringstream oss;
-		oss << "(" << pt.x << ", " << pt.y << ")";
-		SetWindowText(hWnd, to_wide_string(oss.str()).c_str());
-		break;
-	}
-	default:
-		break;
-	}
-	return DefWindowProc(hWnd, msg, wParam, lParam);
-}
+//LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+//	static WindowsMessageMap mm;
+//
+//	OutputDebugString(to_wide_string(mm(msg, lParam, wParam)).c_str());
+//
+//	switch (msg)
+//	{
+//	case WM_CLOSE:
+//		PostQuitMessage(69);
+//		break;
+//	case WM_KEYDOWN:
+//		if (wParam == 'F') {
+//			// 当 F 键按下,修改窗口的 Title
+//			SetWindowText(hWnd, L"Reset Title");
+//		}
+//		break;
+//	case WM_CHAR:
+//	{
+//		static std::string title;
+//		title.push_back((char)wParam);
+//		SetWindowText(hWnd, to_wide_string(title).c_str());
+//		break;
+//	}
+//	case WM_LBUTTONDOWN:
+//	{
+//		POINTS pt = MAKEPOINTS(lParam);
+//		std::ostringstream oss;
+//		oss << "(" << pt.x << ", " << pt.y << ")";
+//		SetWindowText(hWnd, to_wide_string(oss.str()).c_str());
+//		break;
+//	}
+//	default:
+//		break;
+//	}
+//	return DefWindowProc(hWnd, msg, wParam, lParam);
+//}
 
 int WINAPI WinMain(HINSTANCE hInstance,
 	HINSTANCE hPrevInstance,
 	LPSTR lpCmdLine,
 	int nCmdSHow) {
 
-	const wchar_t* pClassName = L"hw3dbutts";
-
-	// 注册类
-	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);
-
-	// 创建窗口
-	HWND hWnd = CreateWindowEx(
-		WS_EX_RIGHTSCROLLBAR,
-		pClassName,
-		L"Hello World",
-		WS_SYSMENU | WS_CAPTION | WS_MAXIMIZEBOX,
-		200, 200, 640, 480,
-		nullptr, nullptr, hInstance, nullptr
-	);
 
-	// 展示窗口
-	ShowWindow(hWnd, SW_SHOW);
+	Window wnd(800, 300, L"Donkey Fart Box");
+	//Window wnd2(300, 800, L"Donkey Fart Box");	// 直接通过构造函数创建第二个窗口
 
 	// 消息处理
 	MSG msg;

+ 111 - 0
图形学/DirectX学习/src/hd3d/hd3d/Window.cpp

@@ -0,0 +1,111 @@
+#include "Window.h"
+#include "resource.h"
+
+Window::WindowClass Window::WindowClass::wndClass;
+
+Window::WindowClass::WindowClass() noexcept
+	:
+	hInst(GetModuleHandle(nullptr))
+{
+	WNDCLASSEX wc = { 0 };
+	wc.cbSize = sizeof(wc);
+	wc.style = CS_OWNDC;
+	wc.lpfnWndProc = HandleMsgSetup;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+	wc.hInstance = GetInstance();
+	wc.hIcon = static_cast<HICON>(LoadImage(
+		GetInstance(), MAKEINTRESOURCE(IDI_ICON1),
+		IMAGE_ICON, 32, 32, 0
+	));
+	wc.hCursor = nullptr;
+	wc.hbrBackground = nullptr;
+	wc.lpszMenuName = nullptr;
+	wc.lpszClassName = GetName();
+	wc.hIconSm = static_cast<HICON>(LoadImage(
+		GetInstance(), MAKEINTRESOURCE(IDI_ICON1),
+		IMAGE_ICON, 16, 16, 0
+	));
+	RegisterClassEx(&wc);
+}
+
+Window::WindowClass::~WindowClass()
+{
+	UnregisterClass(wndClassName, GetInstance());
+}
+
+const wchar_t* Window::WindowClass::GetName() noexcept
+{
+	return wndClassName;
+}
+
+HINSTANCE Window::WindowClass::GetInstance() noexcept
+{
+	return wndClass.hInst;
+}
+
+Window::Window(int InWidth, int InHeight, const wchar_t* InName) noexcept
+{
+	RECT Wr;
+	Wr.left = 100;
+	Wr.right = InWidth + Wr.left;
+	Wr.top = 100;
+	Wr.bottom = InHeight + 100;
+
+	// AdjustWindowRect 会根据样式重新计算 RECT 中各个参数的值
+	AdjustWindowRect(&Wr, WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, FALSE);
+
+	// 重新设置过 RECT 参数,所以这里不能直接使用 InWidth 和 InHeight
+	hWnd = CreateWindow(
+		WindowClass::GetName(), InName,
+		WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
+		CW_USEDEFAULT, CW_USEDEFAULT, Wr.right - Wr.left, Wr.bottom - Wr.top,
+		nullptr, nullptr, WindowClass::GetInstance(), this
+	);
+
+	ShowWindow(hWnd, SW_SHOWDEFAULT);
+}
+
+Window::~Window()
+{
+	DestroyWindow(hWnd);
+}
+
+LRESULT Window::HandleMsgSetup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
+{
+	// use create parameter passed in from CreateWindow() to store window class pointer at WinAPI side
+	if (msg == WM_NCCREATE)
+	{
+		// extract ptr to window class from creation data
+		const CREATESTRUCTW* const pCreate = reinterpret_cast<CREATESTRUCTW*>(lParam);
+		Window* const pWnd = static_cast<Window*>(pCreate->lpCreateParams);
+		// set WinAPI-managed user data to store ptr to window instance
+		SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pWnd));
+		// set message proc to normal (non-setup) handler now that setup is finished
+		SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&Window::HandleMsgThunk));
+		// forward message to window instance handler
+		return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
+	}
+	// if we get a message before the WM_NCCREATE message, handle with default handler
+	return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+
+LRESULT Window::HandleMsgThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
+{
+	// retrieve ptr to window instance
+	Window* const pWnd = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+	// forward message to window instance handler
+	return pWnd->HandleMsg(hWnd, msg, wParam, lParam);
+}
+
+LRESULT Window::HandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
+{
+	switch (msg) {
+	case WM_CLOSE:
+		// 不在这里退出窗口,而是发送推出信息给 WinMain, 由 WinMain 来触发 Window 的析构函数 进而关闭窗口
+		PostQuitMessage(0);
+		return 0;
+	}
+
+	return DefWindowProc(hWnd, msg, wParam, lParam);
+}

+ 51 - 0
图形学/DirectX学习/src/hd3d/hd3d/Window.h

@@ -0,0 +1,51 @@
+#pragma once
+#include "ChiliWin.h"
+#include <optional>
+#include <memory>
+#include <string>
+#include <vector>
+
+class Window
+{
+private:
+	// singleton manages registration/cleanup of window class
+	class WindowClass
+	{
+	public:
+		static const wchar_t* GetName() noexcept;
+		static HINSTANCE GetInstance() noexcept;
+	private:
+		WindowClass() noexcept;
+		~WindowClass();
+		WindowClass(const WindowClass&) = delete;
+		WindowClass& operator=(const WindowClass&) = delete;
+		static constexpr const wchar_t* wndClassName = L"Chili Direct3D Engine Window";
+		static WindowClass wndClass;
+		HINSTANCE hInst;
+	};
+
+public:
+	Window(int InWidth, int InHeight, const wchar_t* InName) noexcept;
+	~Window();
+
+	Window(const Window&) = delete;
+	Window& operator = (const Window&) = delete;
+
+private:
+	static LRESULT CALLBACK HandleMsgSetup(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+	static LRESULT CALLBACK HandleMsgThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+	LRESULT HandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
+	 
+public:
+	//Keyboard kbd;
+	//Mouse mouse;
+private:
+	bool cursorEnabled = true;
+	int width;
+	int height;
+	HWND hWnd;
+	//std::unique_ptr<Graphics> pGfx;
+	std::vector<BYTE> rawBuffer;
+	std::string commandLine;
+};
+

+ 4 - 0
图形学/DirectX学习/src/hd3d/hd3d/hd3d.vcxproj

@@ -138,10 +138,14 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="Test\WindowsMessageMap.cpp" />
+    <ClCompile Include="Window.cpp" />
     <ClCompile Include="WinMain.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="ChiliWin.h" />
+    <ClInclude Include="resource.h" />
     <ClInclude Include="Test\WindowsMessageMap.h" />
+    <ClInclude Include="Window.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

+ 12 - 0
图形学/DirectX学习/src/hd3d/hd3d/hd3d.vcxproj.filters

@@ -21,10 +21,22 @@
     <ClCompile Include="Test\WindowsMessageMap.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="Window.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Test\WindowsMessageMap.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="ChiliWin.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
+    <ClInclude Include="Window.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>

+ 17 - 0
图形学/DirectX学习/src/hd3d/hd3d/resource.h

@@ -0,0 +1,17 @@
+#pragma once
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by hw3d.rc
+//
+#define IDI_ICON1                       101
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        102
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif