文章目录
前言
今天开个新坑,开始学习D3D,教程为https://www.youtube.com/channel/UCsyHonfwHi4fLb2lkq0DEAA,先从做一个Windows窗口开始。
创建窗口前的VS设置
为了适应接下来的工程,我们要对VS进行一些设置,首先将字符集设置为多字节字符集。
将VS的多处理器编译打开。
设置代码速度优先。
给Release下配置预处理器定义:NDEBUG,NDEBUG宏将调试时使用到的assert代码定义为空的,提高Release版本的程序运行效率。
运行时库选择静态库就可以。
浮点模型设置为快速模型。
C++标准使用最新的。
最后,将子系统设置为窗口。
Windows窗口简介
窗口分为两个部分,一个是窗口本身,一个是消息处理。
创建Windows窗口
Windows程序入口函数
每个程序都有一个入口,WinMain函数是Windows应用程序的入口程序,代码如下。
#include < Windows.h>
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
return 0;
}
要编写Windows窗口程序,需要引用Windows.h。关于WinMain的几个参数(被chill形容为junk),我现在能做出的解释如下:
- HInstance是一个用来指向应用程序实例信息的指针
- 第一个HInstance参数hInstance指向当前应用程序实例的信息。
- 第二个HInstance参数hPrevInstance指向先前应用程序实例的信息,值得注意的是这个参数总是为NULL,也许是属于Windows那么多年以来的历史遗留。
- 第三个LPSTR参数用来接收程序开始运行时,用户通过控制台端输入的额外参数。LPSTR只是char *的类型别名。C/C++的main函数也有类似的参数。
- 第四个int参数用来决定我们这个程序的窗体用何种方法展示出来。比如说传入0(预处理宏为:SW_HIDE)的意思为:隐藏当前窗体并打开另一个窗体。具体有那些方式可以参考微软的MSDN。
- 在函数名WinMain前方还有一个CALLBACK。CALLBACK是函数修饰符,他的作用是告诉C++这个函数要用一个叫stdcall的调用约定。这个调用约定使得函数在栈上传递参数的方法与标准C的调用约定(cdecl)略有不同。WindowsAPI使用的应该都是stdcall调用约定。
注册窗体
当然,如果你直接将上面的代码复制到你的文件中编译,你不会得到一个窗口,因为要实现一个窗口,还有很多事情要做。即使是写个控制台程序,为了把控制台窗口拿给你看,系统在幕后也做了很多工作。要创建一个窗口,我们需要将使用一个叫Register的函数向系统注册我们将要创建的窗空的信息(样式啥的),然后我们才可以用另一个函数实例化一个窗口出来。这个过程有点像假如说Windows是一个C++类(但是实际上窗口不是C++类),我们去实例化它了以后才有。
为了创建窗体,我们将使用RegisterClassEx()
函数,Ex表示他是标准的创建函数,其需要一个叫WNDCLASSEX
结构的指针,WNDCLASSEX
结构里包含了窗体的信息参数(MSDN)。WindowAPI很多都需要某个结构的指针,这也算是它们的特点,相比于直接将结构拷贝过去,传指针显然效率更好。
ATOM RegisterClassExA(
const WNDCLASSEXA *unnamedParam1
);
typedef struct tagWNDCLASSEXA {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXA, *PWNDCLASSEXA, *NPWNDCLASSEXA, *LPWNDCLASSEXA;
首先我们定义一个WNDCLASSEX
结构的变量wc。
const auto className = "CurtainXT's";
// 注册窗体类型
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 = className;
wc.hIconSm = nullptr;
RegisterClassEx(&wc);
这些信息里面值得一提的是:wc.cbSize = sizeof(wc)
存储了这个结构体的大小,wc.style = CS_OWNDC
代表我们会为这个Class的每一个窗口都有一个设备上下文,使得可以窗口中显示窗口,wc.lpfnWndProc = DefWindowProc
是一个Windows Procedure指针,代表你想要这个窗口使用何种消息处理机制,这里我们先使用默认消息处理机制,wc.hbrBackground = nullptr
设置为null是因为我们将使用DX来渲染窗口,所以不需要系统再帮我们提前渲染一次。
实例化并显示窗体
为了将窗口实例化,我们使用CreateWindowEx()
函数(MSDN链接),该函数参数会定义窗口创造时的样式和行为,比如长宽,创建的位置,是否允许拖拽、透明等等等。
HWND CreateWindowExA(
DWORD dwExStyle,
LPCSTR lpClassName,
LPCSTR lpWindowName,
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
CreateWindowEx()
会返回一个HWND类型的值,这是创建的窗口的handle,我们用这个handle在代码中操作这个窗口。
// 创建窗体实例
HWND hWnd = CreateWindowEx(
0, className, "Hello World",
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
200, 200, 640, 480,
nullptr, nullptr,
hInstance, nullptr
);
这里面的参数查看MSDN了解详情。如果你代码写到这里,点击运行会发现依旧啥都没有,这是因为为了让我们看到窗口,我们还需要将窗口显示出来,我们使用ShowWindow()
实现这一点(MSDN链接)。
BOOL ShowWindow(
HWND hWnd,
int nCmdShow
);
// 将窗体显示出来
ShowWindow(hWnd, SW_SHOW);
现在我们的窗口终于出来了,可是这个窗体啥都干不了,连关闭都要靠停止调试,所以接下来我们将学习窗口的消息处理。
窗口消息处理
事件驱动编程简介
在学习窗口消息之前,先学习一下什么是事件驱动编程(Event Driven Programming),简单来说,事件驱动编程写出来的程序只要在用户操作产生事件时它们才会进行相应的处理,如果什么事件都没有