OpenGL 编程/GLStart/Tut1
本章将介绍使用 Win32 API 进行编程。
首先打开 Dev – C++。打开后,点击顶部菜单栏上的文件,新建,项目。确保已选中基本选项卡,然后点击 Windows 应用程序。然后给它起一个名字,然后点击确定。在下一个对话框中,选择您计算机上的保存位置,然后点击保存。
完成此操作后,您的项目将加载,您将获得一个带有默认源文件的默认源文件,其中包含 Windows 代码。右键单击源文件上方的 main.cpp 选项卡,然后点击关闭。当它询问您是否要保存更改时,点击否。
要向项目添加新的源文件,请转到顶部菜单栏,然后点击文件,新建,源文件。它会询问您是否要将源文件添加到项目中。点击是。现在您将有一个空白的源文件可供编辑。在我们开始编码之前,点击文件,保存。它会询问您要将源文件保存到哪里。确保您将其保存到您放置 Dev – C++ 项目的相同位置。将其命名为类似“winmain”的内容,然后点击保存。现在我们已经准备好了一个空白的源文件,我们可以开始为我们的窗口编码了。
源文件的第一行应该包含 Windows 标头“windows.h”。
#include <windows.h>
此标头包含到目前为止我们在 Windows 中编程所需的所有函数和结构。
在我们继续之前,我们需要讨论一些关于 Windows 事件的事情。
与标准的命令行程序不同,Windows 程序同时发生许多事件。一个例子是窗口必须不断地重新绘制自身的背景。我们现在不用担心这个问题。但是,还有一些其他事件,例如用户按下窗口上的按钮,用户用鼠标右键单击等。我们将在本课中关注的事件是用户点击窗口右上角的 X 按钮以退出程序。我们必须告诉操作系统(Windows)在用户执行此操作时,程序应该退出。处理这些类型事件的 Windows 中的函数称为窗口过程。我们接下来将定义此函数。还要注意,Windows 不会将这些操作称为“事件”,而是将它们称为消息。消息是事件。我们将在课程中使用“消息”一词。现在让我们开始为窗口过程编码。
窗口过程的返回值是消息处理的结果,该结果将返回给 WinMain() 函数(稍后将详细介绍)。
LRESULT CALLBACK WindowProc( HWND hwnd, // handle of window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
我将在稍后解释这些参数。但这是您声明窗口过程的方式。因此,在包含 Windows 标头的下方,请将其放入其中
LRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
请注意,您可以根据需要命名函数。在这里,我们将其命名为“WinProc”。容易记住,对吧?在创建实际窗口时,我们需要这个名称。现在让我解释一下不同的参数
HWND hWnd 是“窗口句柄”的缩写。基本上,这是实际的窗口对象本身。此句柄在其内部存储窗口。
UINT msg 是我们将要处理的实际消息。我们可以处理的消息成千上万,但我们将在本课中只关注一个。
WPARAM wParam 和 LPARAM lParam 是不同类型的消息信息。在本课中,我们不会使用这些变量。我会在其他时间解释它们。
现在在消息过程中,我们需要一种方法来定义如何处理特定消息。我们将使用 switch 命令对这些消息进行分类
switch(msg) {
现在我们将为我们要处理的唯一消息插入一个 case 语句,即用户退出程序时。此消息的 ID 值为 WM_DESTROY。当我们在其 case 语句中时,我们放入一个函数来退出程序。我们将使用 PostQuitMessage() 函数,该函数接受一个退出代码作为其单个参数,该代码为 0
case WM_DESTROY: PostQuitMessage(0); break; default: break; }
在上面,我们放置了退出消息,然后结束了 switch 语句。现在我们已经处理了该消息,我们完成了窗口过程。我们还需要做的一件事是将消息处理的结果返回给 WinMain 函数。为此,我们返回 DefWindowProc() 函数
LRESULT DefWindowProc( HWND hWnd, // handle to window UINT Msg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
如您所见,此函数接受与窗口过程本身相同的参数。因此,您只需用与窗口过程声明中相同的参数填写它即可
return DefWindowProc(hWnd,msg,wParam,lParam); }
现在这最终完成了我们的窗口过程。现在让我们继续进行 WinMain() 函数。
WinMain
WinMain() 函数有点类似于常规 C 或 C++ 程序中的 main() 函数。它是所有 Windows 程序的入口点。但设置起来要复杂一些。以下是它的原型
int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window );
让我解释一下参数
HINSTANCE hInstance 用于跟踪窗口。让我更好地解释一下。它跟踪窗口的实例或外观。这是一个很难解释的概念。我们稍后将进一步讨论。
HINSTANCE hPrevInstance 实际上没有使用。
LPSTR lpCmdLine 指定程序的命令行参数。我们不用担心这个。
int nCmdShow 是窗口显示的方式。我们现在还没有搞乱它。
在消息过程类型之后,键入 WinMain() 函数的初始化
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
现在在 WinMain() 函数中,我们必须声明三个非常重要的变量。将其放在 WinMain() 初始化之后
HWND hWnd; WNDCLASSEX wcex; MSG msg;
让我解释一下这些变量
HWND hWnd 我们已经在消息过程中解释过了。它是存储窗口本身的句柄。
WNDCLASSEX wcex 是实际的窗口结构。您定义的此结构设置窗口的某些属性。我们将在一分钟内填充此结构。
MSG msg 是与消息过程中的消息结构类似的结构。此特定结构包含诸如实际消息本身之类的字段。我们稍后将在 WinMain() 函数中使用它。
现在我们有了这些变量,让我们定义 WNDCLASSEX 结构。以下是 WNDCLASSEX 结构的原型
typedef struct _WNDCLASSEX { // wc UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX;
让我向您展示如何填写这些属性
- UINT cbSize 是结构本身的大小。- UINT style 是窗口样式。为此有很多选择。我们将使用 CS_HREDRAW 和 CS_VREDRAW 来允许窗口在水平和垂直方向上调整大小。- WNDPROC lpfnWndProc 是您要链接的消息过程的名称。我们已经在开头创建了它,所以在此处放置它的名称(WinProc)。- int cbClsExtra 和 int cbWndExtra 将不会使用,因此它们都将设置为 0。- HANDLE hInstance 是我们在声明 WinMain 时创建的实例句柄。只需在此属性中放入我们为其指定的名称(hInstance)即可。- HICON hIcon 是我们要用于程序的大图标。为此,我们将使用 LoadIcon() 函数加载一个标准图标,该函数接受以下参数:图标的 HINSTANCE(我们将其设置为 NULL)和图标名称(设置为 IDI_APPLICATION 以获得标准 Windows 图标)- HCURSOR hCursor 是我们要使用的鼠标光标。为此,我们使用 LoadCursor() 函数,该函数接受以下参数:光标的 HINSTANCE(设置为 NULL)和光标名称(IDC_ARROW 用于标准 Windows 箭头)- HBRUSH hbrBackground 是我们要用于背景的颜色。我们通过使用 GetStockObject() 函数来获取此颜色,该函数接受刷子名称作为参数(我们将其设置为 GRAY_BRUSH),并将其类型转换为 HBRUSH 类型。- LPCTSTR lpszMenuName 标识我们要在程序中使用的菜单。由于我们没有菜单,因此将其设置为 NULL。- LPCTSTR lpszClassName 是一个字符串,用于标识窗口的 Windows 类名称。我们稍后在创建窗口时将需要这个名称。将其设置为“WinClass”。- HICON hIconSm 是用于程序的小图标。将其设置为 NULL 以获得默认图标。
因此,以下是定义好的整个结构,准备键入
wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WinProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL,IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL,IDC_ARROW); wcex.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH); wcex.lpszMenuName = NULL; wcex.lpszClassName = "WinClass"; wcex.hIconSm = NULL;
在定义 WNDCLASSEX 结构之后,我们必须使用 RegisterClassEx() 函数将其注册,该函数接受指向我们要注册的 WNDCLASSEX 结构的指针作为其单个参数
RegisterClassEx(&wcex);
现在我们继续创建窗口。还记得我们在 WinMain() 函数开头创建的 HWND(窗口句柄)吗?好吧,我们将使用 CreateWindow() 函数创建的窗口将其设置为相等
HWND CreateWindow( LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu or child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data );
让我解释一下这些参数
- LPCTSTR lpClassName 是我们在定义 WNDCLASSEX 结构(“WinClass”)时定义的窗口类的名称 - LPCTSTR lpWindowName 是显示在窗口标题栏上的字符串。现在,将其设置为“窗口” - DWORD dwStyle 指定某些窗口样式。有很多,但现在我们将将其设置为 WS_OVERLAPPEDWINDOW,以便窗口显示带有边框和最小化、最大化和关闭按钮,就像标准的 Windows 应用程序一样。 - int x 和 int y 是从屏幕左上角开始的窗口的 X 和 Y 位置。现在我们将其设置为 0 和 0。 - int nWidth 和 int nHeight 是窗口的像素高度和宽度。现在将其设置为 400 和 400。 - HWND hWndParent 是指向父窗口的窗口句柄。由于我们只创建一个窗口,因此将其设置为 NULL 以指示没有父窗口。 - HMENU hMenu 用于标识要使用的菜单。由于我们没有,因此将其设置为 NULL。 - HANDLE hInstance 是我们设置为 WNDCLASSEX 结构(hInstance)的实例句柄。 - LPVOID lpParam 不需要,因此将其设置为 NULL。
这是传递了正确参数的整个函数
hWnd = CreateWindow("WinClass","My Window", WS_OVERLAPPEDWINDOW,0,0,400,400,NULL,NULL, hInstance,NULL);
为了安全起见,让我们确保窗口已成功创建。如果 CreateWindow() 函数成功,窗口句柄 (hWnd) 将具有与之关联的窗口。如果 CreateWindow() 不成功,则窗口句柄将具有 NULL 值。因此,首先使用 IF 语句检查窗口句柄中是否为 NULL 值
if(hWnd == NULL) {
现在,如果窗口句柄为 NULL,我们需要告诉用户错误并关闭程序。首先,为了通知用户问题,我们可以使用弹出消息框。为此,我们使用 MessageBox() 函数
int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box );
第一个参数是消息框来自的窗口的窗口句柄。由于如果窗口句柄为 NULL,我们正在检查它,因此在此处输入 NULL。第二个参数 lpText 是消息框对话框中的主要文本。为此,输入“错误:无法创建窗口”。第三个参数 lpCaption 是消息框顶部的标题标题。将其设置为“错误”。最后一个参数 uType 是消息框样式。在这里,您可以识别用户可以点击消息框上的按钮类型以及出现在消息框上的图标。现在,将其设置为 MB_OK,以使其只显示一个确定按钮。创建消息框后,我们必须退出程序。就像标准的 C++ main() 函数一样,当发生错误时,我们输入“return -1”而不是“return 0”以告诉操作系统我们正在使用错误退出程序。因此,在消息框后输入“return 0”
MessageBox(NULL,"Error: Unable to create Window","ERROR",MB_OK); return -1; }
现在,假设窗口已成功创建,我们需要实际显示它。我们使用 ShowWindow() 函数来实现这一点
BOOL ShowWindow( HWND hWnd, // handle of window int nCmdShow // show state of window );
第一个参数是我们想要显示的窗口的句柄 (hWnd)。第二个参数是我们 在 WinMain() 函数 (nShowCmd) 初始化时创建的变量
ShowWindow(hWnd,nShowCmd);
紧随其后,我们应该使用 UpdateWindow() 函数处理对窗口的任何更新,该函数将要更新的窗口的句柄 (hWnd) 作为单个参数
UpdateWindow(hWnd);
现在我们的窗口已显示,我们需要防止程序退出,这样我们的窗口会一直停留在屏幕上。为此,我们创建一个主循环。我将执行循环的方式是创建一个条件为 1 的 while 循环,以便循环无限
while(1) {
现在,当循环正在进行时,我们需要某种方法来检查用户是否退出了程序。为此,我们必须检查发送的消息,看看其中是否有一个退出消息。为此,我们使用 PeekMessage() 函数
BOOL PeekMessage( LPMSG lpMsg, // pointer to structure for message HWND hWnd, // handle to window UINT wMsgFilterMin, // first message UINT wMsgFilterMax, // last message UINT wRemoveMsg // removal flags );
- LPMSG lpMsg 是指向我们在 WinMain() 函数 (msg) 开始时创建的消息结构的指针 - HWND hWnd 是我们想要处理其消息的窗口。您可以在此处输入 NULL 以选择默认窗口 - UINT wMsgFilterMin 和 UINT wMsgFilterMax 指定要处理的消息的最小和最大范围。由于我们想要处理所有消息,因此在这两个参数中都输入 0 - UINT wRemoveMsg 确定消息在处理后如何处理。我们将使用值 PM_REMOVE 告诉它在处理消息后将其删除。
由于 PeekMessage() 函数返回一个布尔值以确定其是否成功,因此让我们将其放入 IF 语句中以确保其成功运行
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
如果处理了一条消息,我们只需要检查该消息是否为退出消息。我们可以通过检查我们之前创建的 MSG 结构 (msg) 的消息属性来做到这一点。我们要检查的消息是 WM_QUIT 消息。如果是,那么我们必须退出程序当前所在的循环
if(msg.message == WM_QUIT) break;
如果未处理退出消息,我们仍然必须处理其他消息。为此,我们使用 TranslateMessage() 函数来解释消息。然后,在那之后,我们使用 DispatchMessage() 函数来执行消息。这两个函数都将指向 MSG 结构的指针作为单个参数
TranslateMessage(&msg); DispatchMessage(&msg); } }
这两个花括号结束了我们的主循环。在完成 WinMain() 函数之前,我们所做的最后一件事是向操作系统返回一个值。我们返回 0 作为值,因为到目前为止,程序已成功运行
return 0;
}
现在编译并运行程序,您应该得到这个不错的结果