资讯动态

用Dev-C++和Win32 API编写你的第一个Windows图形界面程序

发布时间:2026/9/18 15:12:16 来源:尧图企业网站定制
我见过太多 C 初学者学了大半年还只会跟黑乎乎的终端窗口打交道偶尔有人问C 到底能不能写带界面的程序当然能而且用 Dev-C 就能轻松做到。今天我就带你从零开始用 Win32 API 搭一个真正能运行的 Windows 窗口程序——界面上有文本框、有命令按钮按钮一点就能读取用户输入的内容并给出反馈。这可能是你写出的第一个像样的 GUI 程序但它背后的消息驱动机制、控件创建逻辑以后换到 MFC、Qt 里也完全通用。我之所以选 Dev-C 来讲是因为它体积小、安装快、开箱即用自带 MinGW 的 GCC 编译器写 Win32 程序完全不需要额外配置。文章会从一个最简单的窗口骨架讲起一步步加上文本框和按钮最后做成一个能统计输入字符数的小工具。不管是学生交课程设计还是上班族想快速验证一个桌面端想法都可以直接照着做、抄着改。1. 开发环境准备用 Dev-C 搭一个 Windows 窗口项目1.1 为什么从 Win32 API 入手而不是 MFC 或 Qt很多新手在选图形界面框架时会纠结MFC 不是也支持 C 吗Qt 不是更流行吗为什么偏要用最原始的 Win32 API我个人的看法是学框架前先把地基打牢。Win32 API 是 Windows 系统提供的原生 C 语言接口你直接调用操作系统创建窗口、处理消息所有底层机制都摆在眼前没有任何封装和隐藏。而 MFC、Qt 这类框架虽然开发效率高但它们内部做了大量包装消息循环、消息映射、事件分发全都被框架接管了出了问题你很难定位原因。从实际效果看Win32 API 写出的程序编译后非常轻量一个窗口程序才几十 KB不依赖庞大的 DLL 或框架运行时。用 Dev-C 自带的 MinGW 工具链编译也不需要额外装一大堆扩展库。对于课程设计、毕业设计里的带界面小程序用 Win32 API 完全可以交差而且能把原理说得很清楚。如果你以后真要干 Windows 桌面开发再走上 MFC、Qt、C# 的路线会惊讶地发现所有框架本质上还是在做同一件事注册窗口、创建窗口、处理消息。今天我们学的这套东西就是你理解所有 GUI 框架的钥匙。1.2 Dev-C 里创建 Windows 项目的两种方式Dev-C 我推荐用 Orwell Dev-C 5.11 或小熊猫 Dev-C社区维护版两者都内置了 MinGW 编译器默认支持 Win32 开发。安装过程很简单一路 Next 就行这里不赘述。装好之后有两种建项目的方式直接用模板打开 Dev-C点击文件 - 新建 - 项目在弹窗里选择Windows Application项目名称填 MyFirstWinApp。Dev-C 会生成一个基于 WinMain 的窗口模板并且已经帮你处理好了 GUI 程序的链接参数。从空项目自己搭新建Empty Project手动添加一个 main.cpp 源文件然后进入工具 - 编译器选项 - 编译器页面在连接器命令行里加上-mwindows参数确保编译出的程序是窗口程序而不是控制台程序。两种方式我建议新手走第一种先把路跑通但如果你想彻底搞懂每个环节第二种更合适。因为用空项目时你被迫手动写全部代码最终对程序的理解会比模板生成的深得多。这里有个关键点必须强调一下如果创建的是控制台项目WinMain 程序也能编译运行但运行时会额外弹出一个黑乎乎的 cmd 窗口非常难看。-mwindows参数就是告诉链接器这是一个 Windows 图形程序不要链接控制台子系统这一步配置错了后面所有界面都会变形。注意如果选中Windows Application模板Dev-C 会自动设置好链接选项不会再弹控制台窗口。如果自己建空项目千万别漏掉-mwindows否则程序会带一个多余的黑窗口。2. 窗口程序的核心机制事件驱动与消息循环2.1 WinMain 入口与窗口类的注册Windows 图形程序的入口不是 main而是 WinMain。它的函数签名长这样int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)新手看到这些参数容易懵我解释一下hInstance当前应用程序的实例句柄你可以理解为程序自己的身份证号后面创建窗口、加载图标都要用到它。hPrevInstance早期 16 位 Windows 的遗留参数现在永远是 NULL不用管。lpCmdLine命令行参数类似 main 函数里的 argv比如你从命令行启动程序时传给它的额外参数。nCmdShow窗口初始显示方式最大化、最小化、普通显示由系统在启动时传入。WinMain 内部要做的第一件事不是直接创建窗口而是注册窗口类。窗口类相当于一个模板它定义了窗口用哪个窗口过程、什么图标、什么背景颜色、什么光标等。只有先把这个模板登记到系统里CreateWindow 才能按模板创建出真正的窗口。窗口类用结构体 WNDCLASS 描述一般只需要填以下几个字段WNDCLASS wc { 0 }; wc.lpfnWndProc WndProc; // 窗口过程函数所有事件都发到这里处理 wc.hInstance hInstance; // 应用实例句柄 wc.lpszClassName MyWindowClass; // 窗口类名称要求全局唯一 wc.hbrBackground (HBRUSH)(COLOR_WINDOW 1); // 窗口背景色 wc.hCursor LoadCursor(NULL, IDC_ARROW); // 鼠标光标 wc.hIcon LoadIcon(NULL, IDI_APPLICATION); // 窗口图标填好之后调用RegisterClass(wc)注册。为什么窗口类这么重要想象一下你要批量生产一批同款商品肯定先做一个模具窗口类就是模具。每次 CreateWindow 时Windows 系统都会去查这个模具取出窗口过程、背景颜色等信息然后创建出符合要求的窗口。2.2 CreateWindow 创建主窗口注册完窗口类就可以创建主窗口了。CreateWindow 的完整签名相当长但核心参数不算多HWND hWnd CreateWindow( MyWindowClass, // 窗口类名 第一个窗口程序, // 窗口标题栏文字 WS_OVERLAPPEDWINDOW, // 窗口样式常见标准窗口样式 CW_USEDEFAULT, CW_USEDEFAULT, // 窗口初始位置 500, 400, // 窗口宽和高 NULL, // 父窗口句柄这里是主窗口不需要父窗口 NULL, // 菜单句柄这里用不到 hInstance, // 应用实例句柄 NULL // 附加参数暂时用不到 );其中CW_USEDEFAULT表示窗口位置由系统自行安排。WS_OVERLAPPEDWINDOW是组合样式包含标题栏、系统菜单、最大化和最小化按钮、可拉伸边框等是一个标准的顶层窗口外观。创建窗口后还需要调用ShowWindow(hWnd, nCmdShow)让窗口显示出来UpdateWindow(hWnd)触发一次绘制。很多新手忘了 ShowWindow结果程序运行了却看不到任何窗口任务管理器里却能看到进程存在这种情况非常经典。2.3 消息循环与窗口过程函数的分工Windows 窗口程序是事件驱动的程序不是在顺序执行完一套逻辑就退出而是启动后进入一个无限循环等待用户的操作。理解消息循环最好的方式是类比成一个客服电话中心程序就像客服人员不知道用户什么时候会打电话进来只能一直守着电话。每一次来电就是一个消息可能是鼠标移动、键盘按键、按钮点击、窗口重绘等。程序收到消息后会根据消息类型交给对应的处理逻辑处理完又回到等待状态。消息循环长这样MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); // 把键盘按下/抬起的消息转换为字符消息 DispatchMessage(msg); // 把消息分发给对应窗口的窗口过程 }GetMessage从消息队列中取出消息当取到 WM_QUIT 时返回的值为 0循环才会退出程序随之结束。TranslateMessage负责把 WM_KEYDOWN 这类原始键盘消息翻译成更容易处理的 WM_CHAR 字符串消息。DispatchMessage则根据消息中的窗口句柄把消息派发给那个窗口的窗口过程函数。窗口过程函数就是真正干活的地方它的签名是LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)所有发给该窗口的消息都会汇聚到这个函数里你用 switch 判断 message 是什么类型然后分别处理。处理不了的未知消息一定要交给DefWindowProc做默认处理否则窗口的缩放、关闭、绘制等基础行为都会异常。可以说消息循环是窗口程序的心脏窗口过程是大脑。消息循环保证程序不死窗口过程保证程序知道遇到什么事情该干什么。3. 给主窗口加上文本框和命令按钮3.1 控件本质上也是窗口很多新手第一次接触控件这个概念时总把它们当成某种特殊的 UI 组件觉得很高深。但实际上Win32 里的文本框、按钮、列表、滚动条等控件本质上也全都是窗口。它们同样有窗口类名有窗口过程有自己的句柄 HWND。比如文本框的窗口类名就是 EDIT按钮的窗口类名就是 BUTTON。创建控件本质上就是调用一次带特定窗口类名的 CreateWindow 函数把它挂到父窗口上。理解这一点特别重要。它意味着你创建控件时用的 API 和创建主窗口完全一样只是窗口类名换成了系统预置的类名样式换成了子窗口样式并且要指定一个父窗口句柄。为了方便管理我们通常在主窗口的WM_CREATE消息里创建所有子控件。因为 WM_CREATE 在主窗口刚刚创建完成、即将显示之前触发此时主窗口句柄已经可用非常适合做子控件的初始化。3.2 用 CreateWindow 创建控件并设置样式下面我在主窗口的 WM_CREATE 里创建一个输入用的文本框、一个命令按钮、一个输出用的文本框#define IDC_EDIT_INPUT 1001 #define IDC_BUTTON_RUN 1002 #define IDC_EDIT_OUTPUT 1003 HWND hEditInput; HWND hEditOutput; case WM_CREATE: // 输入文本框 hEditInput CreateWindow(EDIT, , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT, 20, 20, 240, 30, hWnd, (HMENU)IDC_EDIT_INPUT, GetModuleHandle(NULL), NULL); // 命令按钮 CreateWindow(BUTTON, 统计字符, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 270, 20, 100, 30, hWnd, (HMENU)IDC_BUTTON_RUN, GetModuleHandle(NULL), NULL); // 输出文本框多行、只读 hEditOutput CreateWindow(EDIT, , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_READONLY, 20, 70, 350, 150, hWnd, (HMENU)IDC_EDIT_OUTPUT, GetModuleHandle(NULL), NULL); break;这里有三个地方非常容易踩坑我展开说一下。第一控件 ID 的传递。创建主窗口时 CreateWindow 的第七个参数是菜单句柄但创建子控件时这个位置被复用为控件 ID。系统约定当窗口是子窗口时这个参数不再是菜单句柄而是一个整数 ID用来自动识别是哪个控件。因为它的原型是 HMENU所以常见做法就是在传参时用(HMENU)IDC_BUTTON_RUN强制类型转换。这个 ID 是后面处理按钮事件的唯一依据必须保证唯一。第二样式必须带 WS_CHILD子窗口和 WS_VISIBLE可见。WS_CHILD 表明它是父窗口的子窗口WS_VISIBLE 让它在创建时就直接显示。如果漏掉 WS_VISIBLE程序运行后控件不会显示出来但你检查代码又看不出问题是非常经典的隐形坑。第三实例句柄要传 GetModuleHandle(NULL)。在主窗口的 CreateWindow 里我们可以直接使用 WinMain 的 hInstance但在窗口过程里没有 hInstance 参数所以要用GetModuleHandle(NULL)获取当前进程的实例句柄再传给控件创建的调用。这一步漏了或者传错创建控件就会失败。3.3 常用控件样式速查表学习控件样式时不用死记用到哪个查哪个即可。下面是 Win32 里最常用的几个控件样式控件常见窗口类名常用样式说明文本框EDITES_LEFT文本左对齐ES_MULTILINE允许多行显示ES_READONLY只读不能输入ES_PASSWORD密码模式输入显示为星号ES_AUTOHSCROLL文本超出宽度自动横向滚动按钮BUTTONBS_PUSHBUTTON普通命令按钮BS_DEFPUSHBUTTON默认按钮按回车会触发BS_CHECKBOX复选框BS_AUTORADIOBUTTON单选框静态文本STATICSS_LEFT文字左对齐显示下拉框COMBOBOXCBS_DROPDOWN可编辑的下拉框上表这几个类名和样式覆盖了日常绝大多数小工具开发需求。想要更多样式直接在 MSDN 里搜 Edit Control Styles 或 Button Styles 就能看到完整列表。4. 让命令按钮真正干活控件交互逻辑4.1 WM_COMMAND 命令消息怎么用窗口界面画出来了但按钮点了没反应这是新手最常问的问题。要让命令按钮响应点击需要在窗口过程里处理WM_COMMAND消息。当用户点击按钮时系统会向父窗口发送 WM_COMMAND 消息。这个消息的两个参数里携带了关键信息LOWORD(wParam)控件的 ID用来判断是哪个控件发送的命令。HIWORD(wParam)通知码用来判断发生了什么操作。对按钮来说最常见的通知码是BN_CLICKED表示按钮被点击了。所以一个标准的按钮事件处理片段是这样case WM_COMMAND: if (LOWORD(wParam) IDC_BUTTON_RUN HIWORD(wParam) BN_CLICKED) { // 在这里写按钮点击后的逻辑 } break;注意这里必须先判断 ID再判断通知码。有的同学只判断了 ID结果按钮还没点击其他类型的通知码进来时也执行了逻辑导致程序行为怪异。控件 ID 的判断为什么必须用 LOWORD因为 wParam 是一个 64 位整数高 16 位和低 16 位分别存放不同信息。LOWORD宏取出低 16 位HIWORD宏取出高 16 位这两个宏在 windows.h 里已经定义好了直接用即可。4.2 读写文本框内容的核心函数要在按钮点击后读取文本框里的文字最常用的函数是一对老搭档GetWindowText和SetWindowText。char buffer[256]; GetWindowText(hEditInput, buffer, 256);GetWindowText接收三个参数目标窗口句柄、一个字符缓冲区的指针、缓冲区最大长度。函数会把文本框里的文字复制到缓冲区里并确保以 \0 结尾。这里最需要注意的就是缓冲区大小一定要足够大如果文本内容超过缓冲区长度会被截断。SetWindowText更简单两个参数目标窗口句柄和要设置的字符串指针。它既可以给文本框设置文字也可以给按钮、静态文本、窗口标题栏设置文字。SetWindowText(hEditOutput, 这是输出结果);写代码的时候我习惯给这两个 API 配上英文名记忆GetWindowText是从窗口拿文本SetWindowText是往窗口塞文本一个负责读一个负责写作用对象全是 HWND 句柄不需要关心控件到底是文本框还是别的什么窗口非常通用。4.3 实战功能字符统计器理论讲完我给它落实成一个小功能用户在输入文本框里写一句话点击统计字符按钮后程序读取文本框内容算出字符数拼接成一段结果文字显示到下方的只读文本框里。按钮点击后的完整逻辑如下case WM_COMMAND: if (LOWORD(wParam) IDC_BUTTON_RUN HIWORD(wParam) BN_CLICKED) { char buffer[256]; char result[512]; // 1. 从输入框读取内容 GetWindowText(hEditInput, buffer, 256); // 2. 拼接结果字符串 wsprintf(result, 你输入的内容是\r\n%s\r\n\r\n共 %d 个字符, buffer, (int)lstrlen(buffer)); // 3. 把结果显示到输出框 SetWindowText(hEditOutput, result); } break;这里用到了wsprintf做字符串格式化。%s用来插入字符串%d用来插入整数\r\n是 Windows 里标准的换行符表示回车加换行。在只读多行文本框里需要\r\n才能正确换行只写\n可能显示不出换行效果这点要记住。lstrlen是 Windows API 提供的字符串长度函数等价于 C 库里的strlen专门用来统计 ANSI 字符串的字符数。因为这里统计的是用户输入的文本所以把结果类型转成 int 输出。运行这个程序的效果就是你在输入框里随便敲几个字一点按钮下方就会显示你输入了什么、以及一共几个字符。虽然功能很小但读取用户输入 - 处理 - 显示结果这条链路已经完整跑通了任何一个桌面小工具的基本交互都是这个套路。5. 完整代码与运行效果演示5.1 可以直接复制的完整代码为了让你少走弯路我把前面所有代码整合成一个完整可运行的程序。你不需要理解每一行重点是先在 Dev-C 里跑起来再回头对照注释理解#include windows.h // 定义控件 ID #define IDC_EDIT_INPUT 1001 #define IDC_BUTTON_RUN 1002 #define IDC_EDIT_OUTPUT 1003 // 全局控件句柄方便在窗口过程函数中访问 HWND hEditInput; HWND hEditOutput; // 窗口过程函数声明 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); // 程序入口 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 1. 定义窗口类 WNDCLASS wc { 0 }; wc.lpfnWndProc WndProc; wc.hInstance hInstance; wc.lpszClassName MyWindowClass; wc.hbrBackground (HBRUSH)(COLOR_WINDOW 1); wc.hCursor LoadCursor(NULL, IDC_ARROW); wc.hIcon LoadIcon(NULL, IDI_APPLICATION); wc.style CS_HREDRAW | CS_VREDRAW; // 2. 注册窗口类 if (!RegisterClass(wc)) { MessageBox(NULL, 窗口类注册失败, 错误, MB_OK | MB_ICONERROR); return 0; } // 3. 创建主窗口 HWND hWnd CreateWindow( MyWindowClass, 字符统计小工具, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 420, 280, NULL, NULL, hInstance, NULL); if (hWnd NULL) { MessageBox(NULL, 窗口创建失败, 错误, MB_OK | MB_ICONERROR); return 0; } // 4. 显示窗口 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 5. 消息循环 MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return msg.wParam; } // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // 创建输入文本框 hEditInput CreateWindow(EDIT, , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT, 20, 20, 240, 30, hWnd, (HMENU)IDC_EDIT_INPUT, GetModuleHandle(NULL), NULL); // 创建命令按钮 CreateWindow(BUTTON, 统计字符, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 270, 20, 110, 30, hWnd, (HMENU)IDC_BUTTON_RUN, GetModuleHandle(NULL), NULL); // 创建输出文本框多行只读 hEditOutput CreateWindow(EDIT, , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_READONLY, 20, 70, 360, 150, hWnd, (HMENU)IDC_EDIT_OUTPUT, GetModuleHandle(NULL), NULL); break; case WM_COMMAND: // 判断按钮是否被点击 if (LOWORD(wParam) IDC_BUTTON_RUN HIWORD(wParam) BN_CLICKED) { char buffer[256]; char result[512]; GetWindowText(hEditInput, buffer, 256); wsprintf(result, 你输入的内容是\r\n%s\r\n\r\n共 %d 个字符, buffer, (int)lstrlen(buffer)); SetWindowText(hEditOutput, result); } break; case WM_DESTROY: // 用户点击关闭按钮时退出消息循环 PostQuitMessage(0); break; default: // 其他消息交给系统默认处理 return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }这段代码我讲两处容易忽略的细节。其一wc.style CS_HREDRAW | CS_VREDRAW表示窗口尺寸改变时强制重绘水平、垂直方向的内容这样窗口拉伸后文字和控件不会花屏。新手常漏掉这个字段结果一拉大窗口界面就变得很奇怪。其二WM_DESTROY 里必须调用PostQuitMessage(0)。它会给消息循环发送 WM_QUIT 消息让 GetMessage 返回 0循环退出程序结束。如果你不处理 WM_DESTROY 或漏了 PostQuitMessage点击关闭按钮后窗口看起来消失了但进程还在后台驻留。5.2 编译运行步骤代码准备好之后按下面的顺序操作在 Dev-C 里新建一个Windows Application项目或者手动建空项目并链接-mwindows。把上面的完整代码粘贴到 main.cpp 里保存文件。按下 F11编译并运行或点击菜单栏的运行 - 编译运行。弹出窗口后在输入框里输入任意文字点击统计字符按钮观察输出区结果。如果你项目类型和代码都没问题运行效果应该是窗口标题显示字符统计小工具上面是一个可输入的单行文本框中间是统计字符按钮下面是一个多行只读区域。点击按钮后下方区域立即显示输入内容和字数统计结果。实测下来整个编译过程不会有任何 warning生成的 exe 文件就只有几十 KB拷到别的 Windows 电脑上也能直接运行因为它只依赖系统自带的 user32.dll。这也是 Win32 API 程序一个很大的优势——部署极其简单。6. 新手最容易踩的坑常见问题排查6.1 编译阶段的报错与解决我在带新人写这个例子的过程中编译阶段十有八九会遇到下面几个报错这里直接给出一张对照表报错信息原因解决办法[Error] ld returned 1 exit status链接失败最常见是项目类型不对没有-mwindows确认选了 Windows Application 模板或在链接参数里加-mwindowsundefined reference to WinMain16编译器没找到程序入口WinMain 拼写错误或项目类型选了 Console检查入口函数名必须为 WinMain确认项目类型为 WindowsWndProc was not declared in this scope窗口过程函数没有在 WinMain 前面声明在文件顶部添加LRESULT CALLBACK WndProc(...);函数声明cannot convert int to HMENUCreateWindow 里控件 ID 传参类型不匹配使用(HMENU)IDC_BUTTON_RUN做强制转换ld returned 1 exit status这个报错是所有新手最容易遇到的通用报错它的真实含义是链接失败但 Dev-C 给出的错误提示不够直观。遇到它先看错误窗口里前面的具体信息再对照上表检查项目类型。6.2 界面运行时的诡异问题编译通过只是第一步运行阶段也有很多坑我在实操中记录下几个高频问题。控件创建了却看不到。这个基本可以断定是创建控件时漏了WS_VISIBLE样式或者控件位置设到了窗口可视范围之外。调试时可以先把窗口位置调到屏幕中间再把控件坐标设得小一些比如 20, 20这样能快速排除坐标问题。按钮点击完全没反应。检查三处第一WM_COMMAND 里判断的控件 ID 是否和创建控件时传的 ID 一致第二是否漏了HIWORD(wParam) BN_CLICKED的通知码判断导致消息进了却不满足条件第三WM_COMMAND 的 break 是否正确。我在实际代码审查里经常看到 IDC_BUTTON_RUN 定义是 1002但创建时误写成了 1003这种错误肉眼很难查一旦怀疑就必须逐一比对。窗口一闪而过直接退出。通常是因为消息循环写错了比如 GetMessage 判断条件不对或者 WinMain 里在消息循环之前提前 return 了。还有可能创建窗口失败返回 NULL但没有弹提示程序直接 return 退出。排查时建议把每一步的返回值都打印或用 MessageBox 显示很快就能定位。6.3 中文乱码问题在 Dev-C 环境里写中文最烦人的就是乱码。这里分两种场景第一种源码里写的中文窗口标题、按钮文字在运行时显示成问号或乱码。这通常是因为源文件的编码和编译器默认读取的编码不一致。Dev-C 5.11 默认使用系统 ANSI 编码在中文 Windows 上是 GBK如果你用 UTF-8 编码保存了源文件编译时就会出问题。解决办法是编辑器右下角或文件 - 另存为时把编码格式改成ANSI或系统默认再重新编译。第二种编译时报错说有无效字符。这也是编码问题。稳妥的做法是让源文件保存为 ANSI 编码或者全部使用英文界面文字。另外新版小熊猫 Dev-C 对编码的处理更友好可以在编译器选项里设置执行文件使用 UTF-8这也不失为一条出路。有一个经验供参考如果项目要在国内外不同系统上分发建议界面用英文或者代码里统一使用宽字符版本 APIMessageBoxW、CreateWindowW 等但那需要写L中文这种宽字符串难度会稍高一些。我们这个入门项目直接用 GBK 中文系统是最省事的方案。6.4 界面不美观怎么办默认控件的字体是系统小字体界面看起来确实有点简陋。想在入门阶段让界面稍微能看一点最直接的办法是创建控件后给它设置一个更大的字体HFONT hFont CreateFont(20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, Microsoft YaHei); SendMessage(hEditInput, WM_SETFONT, (WPARAM)hFont, TRUE); SendMessage(hEditOutput, WM_SETFONT, (WPARAM)hFont, TRUE);WM_SETFONT 消息用于给控件设置字体第二个参数传字体句柄TRUE 表示立即重绘。实测下来设置一个 20 像素的微软雅黑字体后整个窗口的观感会有非常明显的提升界面上不再有上世纪的感觉。字体句柄用完记得用DeleteObject(hFont)释放程序退出前别忘。7. 窗口程序下一步还能玩出什么扩展思路7.1 从纯代码创建到对话框资源我们目前的例子是全部用代码创建控件控件坐标和大小全都硬编码在 WM_CREATE 里。这种做法简单直接适合入门和教学。但真实项目里更常见的是用**对话框Dialog**方式在资源文件 .rc 里用脚本描述控件的布局、位置、文本然后在程序里通过 DialogBox 或 CreateDialog 加载。这样做的好处是布局调整不用改代码而且 Dev-C 内置的资源编辑器可以帮助可视化拖拽控件。不过对话框方式需要先理解 .rc 资源脚本的基础语法又要引入新的头文件和资源编译流程对新手来说跨度稍大。我的建议很简单先用今天这篇文章的方法把代码创建的方式玩熟然后再过渡到对话框资源你会豁然开朗。7.2 从消息机制到一切GUI框架Win32 程序里的一切交互都可以从消息的角度去理解。鼠标点击会产生 WM_LBUTTONDOWN键盘输入会产生 WM_CHAR窗口被遮挡后恢复会产生 WM_PAINT控件状态变化会产生 WM_COMMAND。你掌握了消息机制再去看 MFC 的 ON_BN_CLICKED、Qt 的 signal/slot会发现它们本质上是给 WM_COMMAND 这类底层消息包了一层漂亮的壳。因此我强烈建议你在跑通今天的例子后自己动手改一改把按钮文字改成复制、在输出框里显示输入内容的逆序、加一个清空按钮、再做一个小型记事本。这些练习能帮你把消息分发和控件 API 用得滚瓜烂熟哪怕以后不再碰 Win32 API这套界面逻辑与业务逻辑分离的思维习惯也会一直伴随你。我在实际教人入门时第一课就是让他们亲手写出这个文本框 按钮的程序。能把这条链路完整走通Windows 窗口编程就算真正入门了。后面再碰到任何框架你都可以底气十足地对自己说这不过是在另一套外衣下做消息处理罢了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价