【DirectX11-Tutorial】编写第一个窗口程序creating a window

本系列主要参考此博客的文章l,同时会加上一点个人实践过程。

========================================== 分割线 ==========================================

这篇文章主要分为三个部分,首先是看一下创建窗口完整的代码,然后了解程序运作的主要细节,最后如何修改这些代码。

接下来开始第一个窗口程序,上一个程序中使用WinMain()函数开始整个程序,这一次将结合另一个函数WinProc(),这个函数将处理程序每一个系统运行中的传来的信息。

第一个窗口程序

下面就是运行一个窗口的全部代码:

// include the basic windows header file

#include <windows.h>

#include <windowsx.h>

// the WindowProc function prototype

LRESULT CALLBACK WindowProc(HWND hWnd,

                         UINT message,

                         WPARAM wParam,

                         LPARAM lParam);

// the entry point for any Windows program

int WINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPSTR lpCmdLine,

                   int nCmdShow)

{

    // the handle for the window, filled by a function

    HWND hWnd;

    // this struct holds information for the window class

    WNDCLASSEX wc;

    // clear out the window class for use

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information

    wc.cbSize = sizeof(WNDCLASSEX);

    wc.style = CS_HREDRAW | CS_VREDRAW;

    wc.lpfnWndProc = WindowProc;

    wc.hInstance = hInstance;

    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

    wc.lpszClassName = L"WindowClass1";

    // register the window class

    RegisterClassEx(&wc);

    // create the window and use the result as the handle

    hWnd = CreateWindowEx(NULL,

                          L"WindowClass1",    // name of the window class

                          L"Our First Windowed Program",   // title of the window

                          WS_OVERLAPPEDWINDOW,    // window style

                          300,    // x-position of the window

                          300,    // y-position of the window

                          500,    // width of the window

                          400,    // height of the window

                          NULL,    // we have no parent window, NULL

                          NULL,    // we aren't using menus, NULL

                          hInstance,    // application handle

                          NULL);    // used with multiple windows, NULL

    // display the window on the screen

    ShowWindow(hWnd, nCmdShow);

    // enter the main loop:

    // this struct holds Windows event messages

    MSG msg;

    // wait for the next message in the queue, store the result in 'msg'

    while(GetMessage(&msg, NULL, 0, 0))

    {

        // translate keystroke messages into the right format

        TranslateMessage(&msg);

        // send the message to the WindowProc function

        DispatchMessage(&msg);

    }

    // return this part of the WM_QUIT message to Windows

    return msg.wParam;

}

// this is the main message handler for the program

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    // sort through and find what code to run for the message given

    switch(message)

    {

        // this message is read when the window is closed

        case WM_DESTROY:

            {

                // close the application entirely

                PostQuitMessage(0);

                return 0;

            } break;

    }

    // Handle any messages the switch statement didn't

    return DefWindowProc (hWnd, message, wParam, lParam);

}

运行这个代码,会得到下图的结果:

Our First Windowed Program

第一个窗口程序

Building the Window

以上程序中的代码,只有三个步骤用于创建窗口,其余则让程序保持正常运行。三个步骤分别是:

1. Register the window class.

2. Create the window.

3. Show the window.

这些步骤仅仅归结于以下内容:

RegisterClassEx();

CreateWindowEx();

ShowWindow();

下来快速看看,其中包含的细节:

1. Registering the Window Class

简单地说,窗口类是系统处理各种参数变量的结构基础。不需要了解其中的细节,只需要知道从定义上讲它并不是一个C++类。而是一种特定属性窗口的模板。

The Window Class

窗口类

在上图中,"window 1" 和"window 2"用"window class 1"定义基本属性,而 "window class 2"同样"window 3" 和"window 4"作为基本属性的定义。每一个窗口都有其自己单独的属性,例如窗口大小、位置、内容等。但是基本属性仍然是窗口类。在此步骤中,将注册窗口类,这意味着让系统创建一个窗口类,为了实现这一环节有如下代码:

// this struct holds information for the window class

WNDCLASSEX wc;

// clear out the window class for use

ZeroMemory(&wc, sizeof(WNDCLASSEX));

// fill in the struct with the needed information

wc.cbSize = sizeof(WNDCLASSEX);

wc.style = CS_HREDRAW | CS_VREDRAW;

wc.lpfnWndProc = WindowProc;

wc.hInstance = hInstance;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

wc.lpszClassName = L"WindowClass1";

// register the window class

RegisterClassEx(&wc);

了解每一个类,但是不需要花费太多时间在这些不必要的细节上面。

WNDCLASSEX wc;

这是包含窗口类的信息的结构。我们不涉及其所有内容,如一些不需要在游戏编程中涉及的内容。如果你想要结构完整的概括,可以在 WNDCLASSEX 下 MSDN 库中找到它。

顺便说一句, ‘EX‘ 是表明这是加长版结构的 WNDCLASS,基本上是相同的除了不会涉及到的一些其他地方。CreateWindowEx() 和 RegisterClassEx() 的功能也是一样的。

ZeroMemory(&wc, sizeof(WNDCLASSEX));

这个函数可以初始化数组内存为NULL,第一个参数指向数值的初始位置,第二个参数标识数组的长度。使用这两个参数可以很快的初始化。

wc.cbSize = sizeof(WNDCLASSEX);

用于指定内容计算结构的大小,使用sizeof()运算符来实现。

wc.style = CS_HREDRAW | CS_VREDRAW;

使用这个语句我们可以存储窗口的样式,虽然可以自行插入一下参数但是在游戏编程中几乎用不到。其可以插入的值能在WNDCLASSEX 下 MSDN 库中找到。现在,使用 CS_HREDRAW 和CS_VREDRAW的OR逻辑。这两个参数告诉 Windows,如果它移动垂直或水平则重绘窗口。这是适用于窗口的语句,而不是游戏。稍后将重置此值,当我们进入全屏游戏。

wc.lpfnWndProc = WindowProc;

这个值告诉窗口类当从系统受到指令时该使用什么语句。在程序中,这个函数语句是WindowProc(),但是它也可以是其他适合的语句,例如WndProc()、WinProc()或者ASDF()。

wc.hInstance = hInstance;

上一篇文章提到过这个语句,用来处理应用的副本,只是将值传递到WinMain()。

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

此语句存储窗口类的默认鼠标图像。这是通过使用具有两个参数的函数的返回值 LoadCursor()函数。第一个参数是 hInstance,存储应用程序的指针图形。不设置这一点,所以它设置为 NULL。第二个是包含默认的鼠标指针值。在 LoadCursor() 下 MSDN 库中还有其他。

wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

此语句包含的‘brush‘ 将用于窗口的背景颜色。画笔在这里用于指示背景的颜色。COLOR_WINDOW 指示绘制画笔的窗口颜色,在示例中为白色 。

wc.lpszClassName = L"WindowClass1";

这是正在建设的窗口类的名称。当生成一个类的时候,命名它为"WindowClass1"。用于使窗口名字显示正确。

‘L‘ 出现在字符串,只需告诉编译器这个字符串应由 16 位 Unicode 字符,而不是通常的 8 位 ANSI 字符。

RegisterClassEx(&wc);

此函数最后注册窗口类。唯一的参数是结构和系统休息是存放的地址。

2. Create the Window

接下来就是创建一个窗口,现在有了自己创建的窗口类,可以创建基于这个类的窗口。

// create the window and use the result as the handle

hWnd = CreateWindowEx(NULL,

                      L"WindowClass1",    // name of the window class

                      L"Our First Windowed Program",   // title of the window

                      WS_OVERLAPPEDWINDOW,    // window style

                      300,    // x-position of the window

                      300,    // y-position of the window

                      500,    // width of the window

                      400,    // height of the window

                      NULL,    // we have no parent window, NULL

                      NULL,    // we aren't using menus, NULL

                      hInstance,    // application handle

                      NULL);    // used with multiple windows, NULL

以上只是一个函数来创建一个窗口。此函数具有大量的参数,以下是函数原型:

HWND CreateWindowEx(DWORD dwExStyle,

                    LPCTSTR lpClassName,

                    LPCTSTR lpWindowName,

                    DWORD dwStyle,

                    int x,

                    int y,

                    int nWidth,

                    int nHeight,

                    HWND hWndParent,

                    HMENU hMenu,

                    HINSTANCE hInstance,

                    LPVOID lpParam);

DWORD dwExStyle,

第一个参数在RegisterClass() 更新为RegisterClassEx()的时候进行添加,第四个参数dwStyle,用来添加更多选项的窗口样式。不会涉及其他几个参数,但可以在 CreateWindowEx() 下 MSDN 库中找到它们。

LPCTSTR lpClassName,

This is the name of the class our window will use. Because we only have one class, we will use what we have, L"WindowClass1". This string also uses 16-bit Unicode characters, thus we put an ‘L‘ in front of it.

这个语句是使用窗口的类名称。因为只有一个类,将使用现有的类 L"WindowClass1"。这个字符串也使用 16 位 Unicode 字符,因此把 ‘L‘ 在它的前面。

LPCTSTR lpWindowName,

这是窗口的名称,它显示在窗口的标题栏中,使用 Unicode编码。

DWORD dwStyle,

可以在其中定义各种窗口的选项,比如删除最小化和最大化按钮,不可被调整,使其具有滚动条和各种很酷的事情。这些可以在 MSDN 中CreateWindowEx()下的库中找到。

将使用单个值 WS_OVERLAPPEDWINDOW,包括一个带有标准功能的基本窗口的其他值的快捷方式。

int x,

这一决定沿 x 轴的屏幕窗口的位置。.

int y,

这一决定沿 y 轴的屏幕窗口的位置。

int nWidth,

在这里我们设置窗口的初始宽度。

int nHeight,

在这里我们设置窗口的初始高度。

HWND hWndParent,

这个参数告诉 Windows 正在创建的父窗口。父窗口是一种包含其他窗口的窗口。举个例子,Microsoft Word 中,可以在同一窗口中打开多个文档,就是包括多个 ‘子‘ 窗口的父窗口。因为没有做任何与这个参数相关的内容,将此参数设置为 NULL。

HMENU hMenu,

这个语句用来处理菜单栏。没有任何菜单栏,所以这一个也是 NULL。

HINSTANCE hInstance,

此语句处理该实例,设置为 hInstance。

LPVOID lpParam

如果创建多个窗口,将使用一个参数。但是现在不需要创建,所以将其设为空值。

Return Value

这个是处理新窗口函数的返回值。将直接存储到 hWnd 变量,像这样 ︰

hWnd = CreateWindowEx(NULL,

                      ...

3. Show the Window

显示窗口是比创建一个消息框容易。它需要一个具有两个参数的函数。原型 ︰

BOOL ShowWindow(HWND hWnd,

                int nCmdShow);

HWND hWnd,

这是只是处理刚刚创建的窗口,所以将系统返回值放置在这里 。

int nCmdShow

记得最后一个参数的 WinMain() 吗?这个语句就是去哪里使用它。由于游戏运行的时候需要全屏,不必要知道系统返回哪些指令,但是不管是否需要,可以知道nCmdShow返回的值在哪里总是

接下来进行支持创建窗口程序运行的其他操作,WinProc() 函数和消息循环。

Handling Windows Events and Messages

一旦创建窗口,需要保持这个窗口能够进行交互操作。当然,如果结束我们程序,像现在,它不会编译这么多。但是假定进行编译,我们依然只能在窗口被创建时看到一道闪光,并随着我们执行到Winmain()函数底端时闪光消失。

如果不退出,需要在完成创建窗口之后进入主循环。事件是系统程序的基础,这意味着我们的程序需要在系统退出之前做些事情。

当系统传递命令的时候,事件立即发生。指令被放置在事件队列之中。使用 GetMessage() 函数从队列中检索语句,使用TranslateMessage() 处理指定的语句格式,同时在选择运行代码之后使用DispatchMessage() 函数传递运行结果到WindowProc() 函数。

下图解释发生的顺序:

The Process of an Event Message

处理事件的过程分为两个部分:

1. The Main Loop

2. The WindowProc() Function

主循环包括GetMessage(), TranslateMessage() 和 DispatchMessage() 函数.而WindowProc() 函数中只包含某些消息发送时运行的代码。

1. The Main Loop

从上图中看出,这一节包括只有三个功能。每个函数是实际上很简单,不需要了解太多的细节,以下是所需的主循环的代码:

    // this struct holds Windows event messages

    MSG msg;

    // wait for the next message in the queue, store the result in 'msg'

    while(GetMessage(&msg, NULL, 0, 0))

    {

        // translate keystroke messages into the right format

        TranslateMessage(&msg);

        // send the message to the WindowProc function

        DispatchMessage(&msg);

    }

MSG msg;

MSG是一个结构,其中包含一个事件消息的资料。不会经常访问内容这个结构,下图是它所包含的内容 ︰

while(GetMessage(&msg, NULL, 0, 0))

Getmessage () 获取消息队列中指定信息的函数,同时将获取的信息存储到 msg 结构。通常返回 TRUE,除了程序即将退出的情况,返回 FALSE这样一来。此时while() 循环被打破。

Getmessage () 函数具有四个参数,以下是它的原型 ︰

BOOL GetMessage(LPMSG lpMsg,

                HWND hWnd,

                UINT wMsgFilterMin,

                UINT wMsgFilterMax);

LPMSG lpMsg,

这是一个指针,指向一个消息结构,传递结构的地址。

HWND hWnd,

这处理来自于窗口的消息。然而,你可能会注意到 这是空值。在此参数中,NULL 意味着获取任何窗口的下一条消息。与在这里使用hWnd值没有区别,但是如果我们有多个窗口就有所区别了。

UINT wMsgFilterMin, UINT wMsgFilterMax

这些参数可以用于限制要从消息队列检索消息的类型。示例,在 wMsgFilterMin 中使用 WM_KEYFIRST 和 WM_KEYLAST 的使用限制到键盘消息的消息类型。WM_KEYFIRST 和 WM_KEYLAST 是第一个和最后一个键盘消息的整数值的代名词。同样,WM_MOUSEFIRST 和 WM_MOUSELAST 限制鼠标消息的消息类型。

这些参数还有一个特殊情况。假定你想要收集任何消息,如果赋值每个参数为 ‘0‘,然后 getmessage () 进行搜索,你可能会在程序中注意到这点。

TranslateMessage(&msg);

TranslateMessage() 是一个函数,可以让某些按键转化为正确的格式。不会到这在太多的细节,因为它自动完成。其参数是 msg 结构的地址。

DispatchMessage(&msg);

DispatchMessage() basically does what it says. It dispatches the message. It dispatches it to our WindowProc() function, which we will cover next. This function also has a single parameter filled with the address
of our msg struct.

DispatchMessage() 基本上是用于消息调度。它分派给我们的 WindowProc() 函数,将在下一篇文章中介绍。此函数的参数也是 msg 结构的地址。

2. The WindowProc() Function

这里简要介绍一下流程:Getmessage () 获取消息,翻译,使用 DispatchMessage() 函数派遣它。

DispatchMessage() 函数调用适当的 WindowProc() 函数。幸运的是,我们只有一个 WindowProc() 函数,不用担心,所以东西会留相对简单。

当调用 WindowProc() 函数时,四条从MSG结构信息得到通过。以下是 WindowProc()的原型:

LRESULT CALLBACK WindowProc(HWND hWnd,

                            UINT message,

                            WPARAM wParam,

                            LPARAM lParam);

当消息进入 WindowProc 时,可以使用 uMsg 参数来确定它是什么消息。很多程序员使用 switch() 语句确定消息。下面是一个示例解释如何执行:

 // sort through and find what code to run for the message given

    switch(message)

    {

        // this message is read when the window is closed

        case WM_DESTROY:

            {

                // ...

                // ...

            } break;

    }

在这里,switch 语句查找要运行的代码。当然,只提供 WM_DESTROY语句,意味着如果任何其他消息发送,只是忽略它。关闭窗口时,将发送 WM_DESTROY 语句。所以当窗口关闭时,需要清理应用程序。

所包含的内容告诉应用程序我们执行完毕并返回"0",表明一切被清理干净。我们用如下代码进行操作:

 case WM_DESTROY:

            {

                // close the application entirely

                PostQuitMessage(0);

                return 0;

            } break;

PostQuitMessage() 函数发送消息 WM_QUIT,它有一个整数值为 ‘0‘。整数值 ‘0‘ 发送时,getmessage () 返回 FALSE。所以这个函数基本上做的是告诉我们程序完成了 WinMain() 功能正常。

接下来返回 ‘0‘。这是相当重要,因为它告诉 Windows 需要处理该消息。如果返回别的值,Windows 可能会混淆不清。

你也许会发现还有最后一个函数DefWindowProc()。这个函数的功能是处理没有执行过的消息。总之,它将为处理不返回 ‘0‘ 的消息。因此将其放置在结 WindowProc() 函数结束的为位置,用来捕获任何错过的语句。

时间: 2024-10-06 23:41:41

【DirectX11-Tutorial】编写第一个窗口程序creating a window的相关文章

Xamarin iOS编写第一个应用程序创建工程

Xamarin iOS编写第一个应用程序创建工程 在Xcode以及Xamarin安装好后,就可以在Xamarin Studio中编写程序了.本节将主要讲解在Xamarin Studio中如何进行工程的创建以及编写代码等内容XamariniOS编写第一个应用程序创建工程本文选自Xamarin iOS开发实战大学霸. 1.3.1  创建工程 XamariniOS编写第一个应用程序创建工程本文选自Xamarin iOS开发实战大学霸,很多的开发工具,在编写代码之前,都必须要创建一个工程,如Visual

微信小程序-----安装,编写第一个小程序和运行到手机端

第一步: 微信公众平台注册账号,并选择小程序,网址:mp.weixin.qq.com 填写相关信息,如:主体类型(个人或者企业) AppID  在开发中都是用的到的,服务器域名在网络请求也是用的到的. 完成信息之后,下载开发工具: 下载地址: https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html 选择自己需要的版本,下载直接安装,和平常的软件安装一样,直接运行 用微信扫码登录即可,然后点击添加项目, 输入申请的AppID

【win32汇编】0x04 第一个窗口程序

第一个win32汇编窗口程序,主要来自 win32汇编语言程序设计,然后加上了自己查阅msdn文档的一些理解,基本过程: start入口地址 --> _WinMain主函数 --> 定义一些需要用的消息实体和类实体 --> 注册窗口类 --> 建立并显示窗口 --> 消息循环 ( _ProcMain主要用于处理程序接受的消息 ) 1 ;模式定义 2 ;>>>>>>>>>>>>>>>>

DIOCP之编写第一个应用程序(三)

Client 设计功能如下: 1.建立与服务器连接 2.请求连接时,加密密码,采用Base64编码 3.时时发送心跳告诉服务器在线 4.进行相关的数据处理与交互 第一步:创建一个VCL-Forms Application(创建一个标准VCL程序) 第二步:编写一个公共单元用于与Server通讯,因为客户端一般由多个窗体组成,所以需要一个公共连接类,不能放入窗体,不然窗体关闭,这个连接也会被关闭 以下单元是彩蛋的单元,我简化了,保留部分,用于学习,这个部分主要是向服务器发送请求 unit Clie

DIOCP之编写第一个应用程序(一)

Server 设计功能如下: 1.支持客户端登录 2.连接数据库进行操作 3.推送信息 4.限制文件上传大小 第一步:创建一个VCL-Forms Application(创建一个标准VCL程序) 第二步:引用必要的单元文件:diocp_coder_tcpServer, diocp_tcp_server,  SimpleMsgPack,uMyClientContext(uMyClientContext为扩展clientcontext,自己编写),uDIOCPStreamCoder(引用编码器与解码

编写第一个裸机程序

一. ARM裸机之Makefile 1.1. Makefile 分析 led.bin: led.o arm-linux-ld -Ttext 0x0 -o led.elf $^ arm-linux-objcopy -O binary led.elf led.bin arm-linux-objdump -D led.elf > led_elf.dis gcc mkv210_image.c -o mkx210 ./mkx210 led.bin 210.bin %.o : %.S arm-linux-g

DIOCP之编写第一个应用程序(二)

构建client界面: 构建界面要比写代码更难爱,不是专业UI设计太丑,先有个界面,好写代码,客户端代码与界面设计思想:界面与数据之间分离处理,不能要接收数据的地方写代码,不然以后修改程序会死人的.

第一个单片机程序(C言语编写)

忙活了半天,预备任务做了那么多,终于要编写我们的程序代码了.假如学过 C 言语的话,你应当很轻松的跟着我的编程本人写出来,假如没学过 C 言语也没紧要,你先照着我的抄,我会在适宜的地位写出来对 C 言语语法的说明,如许抄几回后再看看说明,就应当很明确了,抄的时分必定要仔细,特别标点符号弗成以搞错.第一个单片机程序: #include <reg52.h> //包括特别功用存放器界说的头文件 sbit LED = P0^0; //位地址声明,留意:sbit 必需小写.P 大写! void main

32位汇编第二讲,编写窗口程序,加载资源,响应消息,以及调用C库函数

32位汇编第二讲,编写窗口程序,加载资源,响应消息,以及调用C库函数 (如果想看所有代码,请下载课堂资料,里面有所有代码,这里会讲解怎么生成一个窗口程序) 一丶32位汇编编写Windows窗口程序 首先我们知道32位汇编是可以调用Windows API的,那么今天我们就调用windowsAPI来写一个窗口程序 如果你有windows开发知识,那么就很理解了,如果没有,那么跟着我写,跟着步骤去写,那么也可以写出来 首先我们要编写一个窗口程序(使用SDKAPI编写)有几个步骤 1.设计窗口类 2.注