在CodeBolcks+Windows API下的C++编程教程——给你的项目中添加头文件和菜单
0.前言
我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。
了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已经编写完成的文章(目录)”:
学习编程从游戏开始——编程计划(目录) - lexyao - 博客园
这是一篇专题文章,这篇文章是用来讲解下面这篇文章用到的知识的,我在这篇文章中讲解程序使用的例子就是在下面这篇文章中创建的aTetris项目:
在CodeBolcks+Windows API下的C++编程教程——用向导创建一个Windows GUI项目(aTetris) - lexyao - 博客园
在这篇文章里,我主要讲述以下几个方面的内容:
- 使用向导给Win32 GUI程序(aTetris)新建一个头文件(.h文件)
- 在资源文件中添加菜单资源
- 将资源文件中的菜单添加到Win32 GUI程序的主窗口界面
- 事件驱动与消息循环
- 给Win32 GUI程序添加响应菜单事件的代码
- 结束语
1.使用向导给Win32 GUI程序(aTetris)新建一个头文件(.h文件)
在C和C++语言编程中,头文件有着极其重要的地位。
头文件的扩展名为.h或.hpp。头文件的内容通常是定义程序中使用的常量、变量、数据结构、类等等。
头文件可以在头文件(.h或.hpp)和程序文件(.c或.cpp)中通过指令#include引用。例如:
#include <tchar.h> #include <windows.h> #include "xTetrisApp.h"
给项目添加头文件的操作步骤如下:
第一步、点击主菜单:[File->New->File...],打开[New from template]窗口
第二步、在[New from template]窗口中选择“Files->C/C++header->Go”,启动新建文件向导
第三步、输入文件名
点击文件名栏目后面的[...]按钮选择保存文件的位置,输入文件名,然后点击[保存],得到全路径文件名。
我们要添加的资源文件是D:\CodeBlocks\Test\Tetris\aTetris.h。注意文件扩展名是h。
这里必须是全路径文件名,否则会因为找不到路径而无法创建文件。
界面中还有几个选项,需要全部选中。
最后点击[Finish]按钮完成操作。
这时你会发现文件列表中增加了一个新文件aTetris.h。
打开aTetris.h,里面只有一个防止重复定义的编译指令。
#ifndef ATETRIS_H_INCLUDED #define ATETRIS_H_INCLUDED #endif // ATETRIS_H_INCLUDED
2.在资源文件中添加菜单资源
了解Microsoft官方关于资源文件(.rc)中定义资源的说明请看:资源定义声明 - Win32 apps | Microsoft Learn
资源文件中可以定义的资源包括:图标、光标、菜单、对话框、位图、增强型图元文件、字体、快捷键表、消息表条目、字符串表条目或版本信息。
了解Microsoft官方关于资源文件(.rc)中定义菜单资源的说明请看:MENU 资源 - Win32 apps | Microsoft Learn、MENUITEM 语句 - Win32 apps | Microsoft Learn
按着Microsoft官方的约定,在资源文件中定义菜单的完整示例如下:
menuID MENU { MENUITEM "&Soup", 100 MENUITEM "S&alad", 101 POPUP "&Entree" { MENUITEM "&Fish", 200 MENUITEM "&Chicken", 201, CHECKED POPUP "&Beef" { MENUITEM "&Steak", 301 MENUITEM "&Prime Rib", 302 } } MENUITEM "&Dessert", 103 }
上面的语法定义中:
menuID :标识资源的唯一名称或 16 位无符号整数值。
早期的版本中菜单中的{ }使用的是BEGIN END,两种表示的意义是相同的。
给应用程序添加菜单需要做三个方面的工作:
- 在资源文件中添加菜单资源
- 在头文件中定义菜单资源中用到的常数
- 在创建主窗口的代码中使用菜单资源
在资源文件aTetris.rc文件中添加以下代码,这些代码是按着资源文件约定的格式编写的。
///////////////////////////////////////////////////////////////////////////// // Menu aTetris MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New...", IDM_NEW MENUITEM SEPARATOR MENUITEM "E&xit", IDM_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", IDM_ABOUT END END
在头文件aTetris.h中添加菜单资源中使用的常数,常数的数值不要与操作系统定义的常数相同,这样在两者同时使用的时候会发生冲突:
#ifndef ATETRIS_H_INCLUDED #define ATETRIS_H_INCLUDED #define IDM_NEW 40001 #define IDM_EXIT 40002 #define IDM_ABOUT 40003 #endif // ATETRIS_H_INCLUDED
3.将资源文件中的菜单添加到Win32 GUI程序的主窗口界面
在CodeBlocks中打开aTetrisMain.cpp文件,在定义主窗口结构的代码中找到了以下代码:
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { ...... wincl.lpszMenuName = NULL; /* No menu */ ...... }
上面的代码中的菜单名为NULL,所以窗口中没有菜单。将这个NULL替换成资源文件中菜单资源的名称就可以将资源中的菜单资源添加到应用程序的主窗口了。
修改aTetrisMain.cpp文件设置主窗口结构数据的代码中的菜单名称由NULL改为资源文件中的菜单资源名称_T("aTetris"):
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { ...... wincl.lpszMenuName = _T("aTetris"); //wincl.lpszMenuName = NULL; /* No menu */
......
}
作为一种好的编程习惯,可以将先将表示资源文件名的字符串定义为一个常量,然后在代码中使用这个常量。下面这段代码跟上面的代码效果是一样的:
TCHAR szMenuResource[ ] = _T("aTetris"); int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { ...... wincl.lpszMenuName = szMenuResource; //wincl.lpszMenuName = NULL; /* No menu */ ...... }
编译运行aTetris项目的主窗口中就看到主菜单了。不过你点击菜单项,没有任何操作。为什么这样呢?因为你还没有编写响应菜单指令的代码。
4.事件驱动与消息循环
在添加响应菜单指令的代码前先看一看菜单指令是怎么发挥作用的。
Windows的应用程序采用的是一种叫做事件驱动的运行机制,这个事件驱动是通过消息传递实现的。
以下是aTetrisMain.cpp文件中的主过程函数WinMain 的代码摘要:
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { HWND hwnd; /* This is the handle for our window */ MSG messages; /* Here messages to the application are saved */ WNDCLASSEX wincl; /* Data structure for the windowclass */ /* The Window structure */ wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */....../* Register the window class, and if it fails quit the program */ if (!RegisterClassEx (&wincl)) return 0; /* The class is registered, let's create the program*/ hwnd = CreateWindowEx ( 0, /* Extended possibilites for variation */ szClassName, /* Classname */ _T("多彩俄罗斯方块xTetris"), /* Title Text */ ...... ); /* Make the window visible on the screen */ ShowWindow (hwnd, nCmdShow); /* Run the message loop. It will run until GetMessage() returns 0 */ while (GetMessage (&messages, NULL, 0, 0)) { /* Translate virtual-key messages into character messages */ TranslateMessage(&messages); /* Send message to WindowProcedure */ DispatchMessage(&messages); } /* The program return-value is 0 - The value that PostQuitMessage() gave */ return messages.wParam; }
从上面的代码中可以看出应用程序主函数WinMain执行的过程:
- 定义窗口数据结构变量wincl
- 通过给wincl的成员赋值定义窗口的特性
- 使用RegisterClassEx (&wincl)注册窗口类型
- 使用CreateWindowEx 创建窗口类的实例
- 使用ShowWindow 显示窗口
- 通过while (GetMessage (&messages, NULL, 0, 0))进入消息循环,检查到有消息时将消息传递给窗口过程函数WindowProcedure
- 结束消息循环后通过return结束程序的运行
至于消息循环具体是怎么工作的,程序员可以不用关心,只需要知道消息循环在有消息的时候会把消息传递给窗口过程函数WindowProcedure就够了。
以下是aTetrisMain.cpp文件中的窗口过程函数WindowProcedure的代码:
/* This function is called by the Windows function DispatchMessage() */ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) /* handle the messages */ { case WM_DESTROY: PostQuitMessage (0); /* send a WM_QUIT to the message queue */ break; default: /* for messages that we don't deal with */ return DefWindowProc (hwnd, message, wParam, lParam); } return 0; }
在窗口过程函数WindowProcedure的参数中,
- hwnd是窗口句柄
- message是消息代码。消息代码有Windows系统预定义的,也有用户自定义的。
- wParam, lParam是消息的两个参数,它们传递的数据的意义由message决定,不同的消息对这两个参数中数据的解释是不同的
程序员编写程序是通过switch (message) 识别message的值,对于需要处理的消息值就编写响应代码,不需要的消息就不用管他,最后会通过default选项调用系统默认的消息处理函数return DefWindowProc (hwnd, message, wParam, lParam)。
我们将要给菜单添加的事件响应代码就在窗口过程函数WindowProcedure的switch (message) 中。
5.给Win32 GUI程序添加响应菜单事件的代码
要添加菜单事件响应代码,首先要明白菜单消息传递来的数据有哪些。
- message:WM_COMMAND
- LOWORD(wParam):指示所选的菜单项,它的值是在资源中为菜单项定义的ID或者弹出菜单的索引
- HIWORD(wParam):选择标记,指出菜单项的哪一部分被选中,可以是下列值的组合:MF_GRAYED、MF_DISABLED、MF_CHECKED、MF_BITMAP、 MF_POPUP、MF_HELP、 MF_SYSMENU或MF_MOUSESELECT。
- lParam:0
在菜单事件中,通常我们只需要识别处理message和LOWORD(wParam)就行了,特殊情况下才需要处理HIWORD(wParam)。
给aTetrisMain.cpp文件中的窗口过程函数WindowProcedure的switch (message) 中添加以下代码:
switch (message) /* handle the messages */ { case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_EXIT: SendMessage(hwnd, WM_CLOSE, 0, 0); return 0; case IDM_ABOUT: MessageBox(hwnd, _T("This is my aTetris by Win32 GUI\n") _T("Copyright(c) lexyao,2024"), szClassName,MB_ICONINFORMATION | MB_OK); return 0; } break; case WM_DESTROY: ...... }
编译运行aTetris项目,在aTetris中点击菜单[Help->About...]显示以下消息窗口:
在aTetris中点击菜单[File->Exit],关闭sTeris窗口,结束程序运行。
这正是我们预期的效果。
6.结束语
主菜单是窗口界面中的重要元素,应用程序的很多操作都是从主菜单中发起的。
在这一篇文章里介绍了给应用程序添加主菜单的方法。当然,这种介绍并不全面,要了解更多的相关知识,还需要查看官方的资料。
我们添加的菜单虽然简单,但充分展现了菜单的结构。你可以参照这个结构添加任意的菜单项。
添加菜单项不是目的,最终的目的添加响应菜单点击事件的代码。我们添加的代码虽然简单,但作为一个示例足够了。你可以根据你的需要让你的程序做你需要的事情。
这篇文章初步介绍了以下内容,看起来每一节都是知识点:
- 使用向导给Win32 GUI程序(aTetris)新建一个头文件(.h文件)
- 在资源文件中添加菜单资源
- 将资源文件中的菜单添加到Win32 GUI程序的主窗口界面
- 简单介绍了事件驱动与消息循环
- 给Win32 GUI程序添加响应菜单事件的代码
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现