在CodeBolcks+Windows API下的C++编程教程——使用向导新建的Win32 GUI程序代码详解
0.前言
我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。
了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已经编写完成的文章(目录)”:
学习编程从游戏开始——编程计划(目录) - lexyao - 博客园
这是一篇专题文章,这篇文章是用来讲解下面这篇文章用到的知识的,我在这篇文章中讲解程序使用的例子就是在下面这篇文章中创建的aTetris项目:
在CodeBolcks+Windows API下的C++编程教程——用向导创建一个Windows GUI项目(aTetris) - lexyao - 博客园
在这篇文章里,我主要讲述以下几个方面的内容:
- 文件内容来源
- 向导创建的Win32 GUI程序代码
- 使用向导新建的Win32 GUI程序代码详解
- 结束语
1.文件内容来源
任何开发环境下编写C++程序都是从向导创建的程序开始的。不管在哪一个开发环境,向导创建的Win32 GUI程序的文件内容是一样的。写这篇文章的目的就是为了让初学者理解向导创建的Win32 GUI程序中的代码。
关于向导创建的Win32 GUI程序代码解释的内容来自Microsoft的文档。由于文档中包含了其他的内容,直接转载会让阅读者花费事件去找重点,所以,我复制了其中我认为有用的部分。
想阅读原文完整的内容请点击下面的网址:
演练:创建传统的 Windows 桌面应用程序 (C++) | Microsoft Learn
2.向导创建的Win32 GUI程序代码
在CodeBlocks中点击主菜单[File->New->Projects],选择新建“Win32 GUI project”,向导就会帮助你创建一个基础的Win32 GUI程序。aTetris项目就是这样开始的。
向导创建的Win32 GUI程序只有一个代码文件,代码的全部内容如下:
1 #if defined(UNICODE) && !defined(_UNICODE) 2 #define _UNICODE 3 #elif defined(_UNICODE) && !defined(UNICODE) 4 #define UNICODE 5 #endif 6 7 #include <tchar.h> 8 #include <windows.h> 9 10 /* Declare Windows procedure */ 11 LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); 12 13 /* Make the class name into a global variable */ 14 TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp"); 15 16 int WINAPI WinMain (HINSTANCE hThisInstance, 17 HINSTANCE hPrevInstance, 18 LPSTR lpszArgument, 19 int nCmdShow) 20 { 21 HWND hwnd; /* This is the handle for our window */ 22 MSG messages; /* Here messages to the application are saved */ 23 WNDCLASSEX wincl; /* Data structure for the windowclass */ 24 25 /* The Window structure */ 26 wincl.hInstance = hThisInstance; 27 wincl.lpszClassName = szClassName; 28 wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */ 29 wincl.style = CS_DBLCLKS; /* Catch double-clicks */ 30 wincl.cbSize = sizeof (WNDCLASSEX); 31 32 /* Use default icon and mouse-pointer */ 33 wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); 34 wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 35 wincl.hCursor = LoadCursor (NULL, IDC_ARROW); 36 wincl.lpszMenuName = NULL; /* No menu */ 37 wincl.cbClsExtra = 0; /* No extra bytes after the window class */ 38 wincl.cbWndExtra = 0; /* structure or the window instance */ 39 /* Use Windows's default colour as the background of the window */ 40 wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; 41 42 /* Register the window class, and if it fails quit the program */ 43 if (!RegisterClassEx (&wincl)) 44 return 0; 45 46 /* The class is registered, let's create the program*/ 47 hwnd = CreateWindowEx ( 48 0, /* Extended possibilites for variation */ 49 szClassName, /* Classname */ 50 _T("Code::Blocks Template Windows App"), /* Title Text */ 51 WS_OVERLAPPEDWINDOW, /* default window */ 52 CW_USEDEFAULT, /* Windows decides the position */ 53 CW_USEDEFAULT, /* where the window ends up on the screen */ 54 544, /* The programs width */ 55 375, /* and height in pixels */ 56 HWND_DESKTOP, /* The window is a child-window to desktop */ 57 NULL, /* No menu */ 58 hThisInstance, /* Program Instance handler */ 59 NULL /* No Window Creation data */ 60 ); 61 62 /* Make the window visible on the screen */ 63 ShowWindow (hwnd, nCmdShow); 64 65 /* Run the message loop. It will run until GetMessage() returns 0 */ 66 while (GetMessage (&messages, NULL, 0, 0)) 67 { 68 /* Translate virtual-key messages into character messages */ 69 TranslateMessage(&messages); 70 /* Send message to WindowProcedure */ 71 DispatchMessage(&messages); 72 } 73 74 /* The program return-value is 0 - The value that PostQuitMessage() gave */ 75 return messages.wParam; 76 } 77 78 79 /* This function is called by the Windows function DispatchMessage() */ 80 81 LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 82 { 83 switch (message) /* handle the messages */ 84 { 85 case WM_DESTROY: 86 PostQuitMessage (0); /* send a WM_QUIT to the message queue */ 87 break; 88 default: /* for messages that we don't deal with */ 89 return DefWindowProc (hwnd, message, wParam, lParam); 90 } 91 92 return 0; 93 }
3.使用向导新建的Win32 GUI程序代码详解
以下是从Microsoft文档中摘录的内容。
3.1 代码在 Windows 桌面应用程序中开始运行的位置
1.正如每个 C 应用程序和 C++ 应用程序在起始点必须具有 main
函数,每个 Windows 桌面应用程序也必须具有 WinMain
函数。 WinMain
具有以下语法。
int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow );
有关此函数的参数和返回值的信息,请参阅 WinMain 入口点。
2.Windows 桌面程序需要 <windows.h>
。 你还会经常看到 #include <tchar.h>
。 这是为了更轻松地编写可与 char
或 wchar_t
配合使用的应用。 其工作方式是,在代码中改用 TCHAR
宏,如果在项目中定义了 UNICODE
符号,则该宏最终解析为 wchar_t
,否则解析为 char
。 如果总是在启用 UNICODE 的情况下生成,则不需要 TCHAR
,可以直接使用 wchar_t
。 以下代码在文件顶部显示这两个 #include
语句。
#include <windows.h>
#include <tchar.h>
3.除了 WinMain
函数之外,每个 Windows 桌面应用程序还必须具有一个窗口过程函数。 该函数称为 WndProc
,但可以在代码中为其指定任何名称。 WndProc
具有以下语法。
LRESULT CALLBACK WndProc(
_In_ HWND hWnd,
_In_ UINT message,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
在此函数中编写代码,以处理发生事件时应用程序从 Windows 收到的消息。 例如,如果用户在应用程序中选择“确定”按钮,Windows 会向你发送一条消息。 在 WndProc
函数内编写代码来执行任何适当的工作。 此过程称为处理事件。 你只需处理与应用程序相关的事件。
3.2 向 WinMain 函数添加功能
1.在 WinMain
函数中,需要捕获有关主窗口的一些基本信息。 可以通过填充 WNDCLASSEX 类型的结构来完成此操作。 该结构包含有关窗口的信息,例如应用程序图标、窗口的背景色、标题栏中显示的名称等。 重要的是,它包含一个指向窗口过程的函数指针,该窗口过程处理 Windows 发送到应用的消息。 下面的示例演示一个典型 WNDCLASSEX
结构:
WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
2.填充 WNDCLASSEX
结构后,向 Windows 注册该结构,使其了解你的窗口以及如何向该窗口发送消息。 使用 RegisterClassEx 函数,并将窗口类结构作为参数传递。 使用 _T
宏是因为我们根据上面有关 Unicode 的讨论使用 TCHAR
类型。 下面的代码演示如何注册窗口类。
if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; }
3.接下来,使用 CreateWindowEx 函数创建窗口。
static TCHAR szWindowClass[] = _T("DesktopApp"); static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application"); //CreateWindowEx 的参数说明: //WS_EX_OVERLAPPEDWINDOW :可选的扩展窗口样式。 //szWindowClass:应用程序的名称 //szTitle:标题栏中显示的文本 //WS_OVERLAPPEDWINDOW:要创建的窗口类型 //CW_USEDEFAULT、CW_USEDEFAULT:初始位置 (x, y) //500, 100: 初始大小 (width, length) //NULL:此窗口的父级 //NULL:此应用程序没有菜单栏 //hInstance:WinMain 中的第一个参数 //NULL:此应用程序中不使用 HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindowEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; }
此函数返回一个 HWND
,它是窗口句柄。 句柄有点像指针。 Windows 使用它来跟踪你创建的窗口。
4.至此,窗口已创建完毕,但仍需要告知 Windows 使其可见。 这就是这段代码的作用:
// The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
显示的窗口只是一个空白矩形,因为尚未实现 WndProc
函数。 应用程序尚未处理 Windows 正在向其发送的消息。
5.为了处理消息,我们首先添加所谓的“消息循环”来侦听 Windows 发送的消息。 当应用程序收到消息时,此循环将该消息调度到 WndProc
函数进行处理。 消息循环类似于以下代码:
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam;
有关消息循环中的结构和函数的详细信息,请参阅 MSG、GetMessage、TranslateMessage 和 DispatchMessage。
用于创建应用程序主窗口并侦听 Windows 发送给应用的消息的基本 WinMain
函数类似于以下代码:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // Store instance handle in our global variable hInst = hInstance; // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application dows not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; }
3.3 在 WndProc
函数中处理消息
1.要处理应用程序收到的消息,可以在 WndProc
函数中实现 switch
语句。
要处理的一条重要消息是 WM_PAINT。 当必须更新其显示窗口的一部分时,应用程序会收到 WM_PAINT
消息。 当用户将窗口移到你的窗口前面,然后又将其移开时,可能会发生该事件。 当你的窗口第一次显示时,它会收到此消息,让你有机会显示你的应用程序 UI。 当 Windows 发送这些事件时,你的应用程序会发现它们。 第一次显示窗口时,必须更新整个窗口。
要处理 WM_PAINT
消息,请首先调用 BeginPaint,然后处理所有的逻辑以在窗口中布局文本、按钮和其他控件。 然后调用 EndPaint。 对于此应用程序,BeginPaint()
和 EndPaint()
之间的代码会在你在 WinMain()
中创建的窗口中显示 Hello, Windows desktop!
。 在以下代码中,TextOut 函数在窗口中的指定位置显示文本。
PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application-specific layout section. EndPaint(hWnd, &ps); break; }
在前面的代码中,HDC
是与窗口工作区关联的设备上下文的句柄。 在窗口中绘图时可以使用它来引用其工作区。 BeginPaint
和 EndPaint
函数用于准备并完成工作区中的绘制。 BeginPaint
返回用于在工作区进行绘制的显示设备上下文的句柄;EndPaint
结束绘制请求并释放设备上下文。
2.应用程序通常还处理许多其他消息。 例如,首次创建窗口时发送 WM_CREATE,关闭窗口时发送 WM_DESTROY。 以下代码显示基本但完整的 WndProc
函数:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0; }
4.结束语
向导为我们搭建了一个程序的基础框架,更多的内容需要我们自己去编写。剩下的工作就看程序员自己的发挥了。