dx12学习笔记_00_窗口

1. 创建窗口类[待完善, 需添加流程图片]

(大)部分内容参考 https://docs.microsoft.com/zh-cn/windows/desktop/learnwin32/creating-a-window

Every window must be associated with a window class, even if your program only ever creates one instance of that class. It is important to understand that a window class is not a “class” in the C++ sense.

通过填充 WNDCLASS 结构体 wc 来注册一个新窗口类,其中:

lpfnWndProc 是一个指向应用程序定义函数的指针.这个procedure(函数)也定义了窗口的各种行为. 我们这里就指向第三步的WindowProc 函数

hInstance 是窗口的一个句柄.句柄这东西,也是一种特殊的指针,可以用来引用其他东西.

lpszClassName 定义了窗口类的名字

就一个窗口而言,其实有这三个就够了, 其他还有7个成员可以用来对窗口的风格进行调整,目前来说不需要.

但是要记住, 这个WNDCLASS是一个结构体, 构造时并没有相应的构造缺省参数,所以要么就把总共10个参数都定义一遍, 要么就先初始化WNDCLASS wc={}, 再去只定义我们上面提到的三个参数

之后, 把WNDCLASS这个结构体的地址传给注册函数RegisterClass:

1
RegisterClass(&wc);

再通过 CreateWindowEx/CreateWindow 创建窗口实例, 返回值赋给句柄变量mhMainWnd . CreateWindowEx和CreateWindowEx都可以创建窗口, 区别只是在于Ex版本的函数可以扩展窗口的风格. 其实这里我们也不需要, 毕竟主要内容在窗口内部…

CreateWindow 的参数比较多, 这里只说几个常用的:

第一个参数lpClassName, 窗口类的名字,要和前面结构体中lpszClassName 一样

第二个参数lpWindowName, 可以理解为title的内容

倒数第二个参数hInstance, 这个hInstance是一个句柄型数据,用来区别一个程序的不同实例(多开),这个参数就是我们wWinMain中的第一个参数.

当然,这个wWinMain是主函数,我们应该将所有代码放到这个主函数里执行.说到这个wWinMain, 它的第二个参数hPrevInstance非常有趣(时泪), 具体可以看看Raymond的这篇博客

最后,创建完了窗口别忘了show出来

1
2
ShowWindow(hwnd, nCmdShow);
return true;

这样, 一个传统的WIN32窗口就创建好了, 如果你想使用.NET CORE, 可以参考MSDN上关于CoreWindow的介绍, 在这种窗口下, 一些函数的名称和使用方法会和本系列博文中介绍的传统方法有所不同.

2. 消息循环

作为一个GUI程序,必须响应用户事件和系统事件. Windows使用一个消息模型来进行事件的响应.

比如按下左键,那么触发事件0x0201 WM_LBUTTONDOWN,

应用程序需要抓取到所有的消息并分配(dispatch)给正确的窗口. 对于每个创建窗口的线程, 都需要维护一个消息队列 , 该队列存储着该线程上产生的所有操作. 通过 GetMessage (只返回True/False)拉取一条消息,这将删除队列头部的一条消息,当队列中没有操作时,这个函数会停止,不过这并不会把你的程序搞死(如果要在后台工作请创建子线程(我猜的)). 如果方法调用成功,它会自动填充msg结构体,这其中包含了窗口和消息代码.

一些例子里则使用PeekMessage 来判断消息队列是否有消息(只返回True/False),参数设置为PM_NOREMOVE的情况下不会从队列删除消息,设置为PM_REMOVE时则效果和GetMessage差不多.

GetMessage的第二个参数是要一个hWnd, 用来指定获取哪个窗口的消息,如果这里填NULL, 那么调用线程的窗口就是需要被Get消息的窗口.

对于GetMessage/PeekMessage得到的msg, 一般是用两个函数:

1
2
TranslateMessage(&msg); 
DispatchMessage(&msg);

来进行消息的解析(分析是哪个窗口,鼠标点击位置…), 从而实现对窗口实现各种操作.

顾名思义,一个函数翻译消息, 一个函数是分发消息–到正确的窗口.

把它们放到主函数里:

1
2
3
4
5
6
7

MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

这个地方也可以写成判断msg是否为WM.QUIT来进行循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while(msg.message != WM_QUIT)
{
//分发消息,消息可以改变动画渲染中的某些参数,比如人物运动,
//相机视角等等...,消息的处理将放在下面消息处理(Procedure)来说
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
//在这里进行动画计算和渲染
}
}

3. Procedure

DispatchMessage 调用了目标窗口的procedure(由操作系统完成),即:

1
LRESULT CALLBACK (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

hwnd就是目标窗口的句柄,一般来说这个Procedure里包含了一个大switch,囊括了你希望窗口响应的各种操作.

当没有任何操作需要响应时,返回DefWindowProc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
//这个WM_PAINT消息只在窗口大小发生变化和窗口被遮挡等情况下才会触发,正常放着
//不管是没法用这个消息来刷新窗口的
case WM_PAINT:
{
//这里将窗口全部填充RGB(150,170,179)的蓝灰色
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HBRUSH hBrush = CreateSolidBrush(RGB(150,170,179));
FillRect(hdc, &ps.rcPaint, hBrush);
EndPaint(hwnd, &ps);
}
return 0;

}
return DefWindowProc(hwnd, uMsg, wParam, lParam);