MFC这个章节里,不能忽视的是对话框的开发。如果把 MFC 程序比作一栋办公楼,那对话框就是「会客室」—— 它是程序与用户面对面交流的地方:用户在这里输入数据,程序在这里展示信息,彼此的互动都从这个空间开始。今天围绕这个「会客室」,讲透了它的设计、交流规则、数据传递,以及如何让它真正「用起来」。

一、对话框编辑器

记得第一次打开 VC++ 的对话框编辑器时,我总想起老家木匠做衣柜的场景 —— 木匠先在纸上画好隔板位置、抽屉大小,对话框编辑器就像这张图纸:拖个按钮像钉个挂钩,放个输入框像留个抽屉,调整布局像摆家具。你不用写一行代码,就能用鼠标拖曳出对话框的模样:标题栏是「会客室门牌」,按钮是「呼叫铃」,输入框是「留言本」。

这种「所见即所得」的设计,在当年可是个新鲜事。上世纪 90 年代末,我刚接触编程时,写一个简单的输入框界面,得用纯 API 函数一行行定义坐标、尺寸,就像用文字描述衣柜的每个部件尺寸,稍不注意就会出现按钮重叠、文字超出边框的情况。有次为了让一个查询框居中显示,调试了一下午坐标计算代码,最后发现是把屏幕分辨率的宽高搞反了。而有了对话框编辑器后,鼠标拖动就能对齐控件,还能直接在界面上预览字体大小,效率提升不止一倍。

编辑器里还有个很贴心的功能 —— 控件对齐工具。就像给家具安装定位器,选中几个按钮,点一下「左对齐」,它们就整整齐齐排成一列。这在做数据录入界面时特别有用,那些密密麻麻的输入框和标签,靠手动调整永远会有细微偏差,用对齐工具处理后,界面瞬间变得清爽规整。我记得当年做一个员工信息登记系统,光是对话框里的控件就有三十多个,全靠这个功能节省了大半天时间。

对话框的消息处理函数:会客室里的应答规则

但光有房间还不够,得有人回应客人的需求。用户点击按钮、输入文字时,对话框会收到「消息」—— 就像客人按铃、递纸条,程序必须知道该怎么接话。比如用户点了「确定」按钮,对话框会收到BN_CLICKED消息,我们写的OnOK()函数就是「应答话术」。

消息处理有个很关键的逻辑 —— 消息映射。它就像会客室的服务指南,明确记录着「客人按红色按钮要送茶水,按蓝色按钮要拿纸笔」。MFC 把这个过程封装得很巧妙,我们不用去管底层的消息分发机制,只需在 ClassWizard 里关联控件和函数就行。

这让我想起早年做项目时的趣事:有次忘了写关闭按钮的消息处理,用户点了十几次都没反应,最后打电话来问「是不是程序冻住了」。后来排查时发现,按钮确实放在了对话框上,但没给它绑定消息处理函数,就像按铃后铃铛响了,却没人听到。还有一次更乌龙,把「确定」和「取消」的消息处理函数写反了,用户点「确定」没保存数据,点「取消」反而保存了,测试人员拿着打印出来的操作记录来找我时,我脸都红了。从那以后,每次写完消息处理,我都会像检查门窗是否锁好一样,逐个按钮点一遍才放心。

其实消息处理背后是 MFC 的消息循环机制,就像会客室门口的接待员,不断接收客人的请求并指引给对应的服务员。这个机制在当时比其他开发框架要高效得多,有些早期的编程工具处理消息时会出现卡顿,就像接待员同时接了太多电话,顾此失彼,而 MFC 能有条不紊地处理多个消息,哪怕用户快速点击多个按钮,也能按顺序响应。

二、对话框数据交换与校验

用户在对话框里填的信息(比如年龄、邮箱),怎么传到程序里?又怎么确保填的是有效信息?这就得靠 DDX(对话框数据交换)和 DDV(对话框数据校验)。

可以把 DDX 看作「传送员」:用户在输入框里写了「25」,DDX 会把这个数字「搬」到程序的变量里;程序要显示「余额 100 元」,DDX 又会把变量里的数字「贴」到显示框里。而 DDV 是「安检员」—— 如果用户在年龄框里填「abc」,DDV 会弹出提示「请输入数字」;如果填「200」,它会提醒「年龄过大」。

当年做社保系统时,就是靠 DDV 挡住了无数无效数据。有次测试人员故意填「-5」当年龄,DDV 立刻报错,我们都笑说:「这安检员比火车站的还严。」但它也不是死板的,我们可以自定义校验规则。比如做图书管理系统时,要求 ISBN 编号必须是 13 位数字,我们就给 DDV 加了个自定义校验函数,像给安检员培训新的检查标准,确保输入的编号格式正确。

DDX 的交换过程其实很智能,它会自动识别数据类型。如果绑定的是字符串变量,就把输入框里的文字原样传过去;如果是整数变量,就自动做类型转换。但有次我把一个只能输入数字的编辑框绑定到了字符串变量,结果用户输入「123a」也能通过,后来才明白,DDX 只是负责搬运,数据格式的把关还得靠 DDV。这就像传送员只负责把包裹送到,包裹里的东西是否符合规定,还得安检员来检查。

现在想来,DDX 和 DDV 的设计理念影响了后来很多开发框架,包括现在 Web 开发里的表单绑定和验证,其实和它们的思路很像,只是换了种实现方式。

三、对话框的调用

设计好的对话框,总得让用户能打开。调用对话框就像「推门进会客室」:用DoModal()打开的是「封闭式会客室」—— 用户必须处理完这里的事(点确定或取消),才能回到主程序;用Create()打开的是「开放式会客室」—— 用户可以在对话框和主程序间来回切换,就像随时能进出的休息室。

最常用的是DoModal(),比如登录框:必须输入账号密码,否则进不了系统。早年写财务软件时,每次用户登录,都是DoModal()弹出登录框,那声「咚」的弹出音效,现在想起来还很熟悉。有次为了让登录框更友好,我们在DoModal()调用前加了个判断,如果用户勾选了「记住密码」,就自动填充账号密码,减少用户操作。

Create()则适合那些需要长期显示的对话框,比如图像处理软件里的调色板面板。用户可以一边在主窗口画画,一边在调色板里选颜色,不用反复开关。但Create()有个需要注意的地方,它创建的是非模态对话框,得手动管理生命周期,就像开放式会客室要安排人随时打扫整理,否则容易出现内存泄漏。我早年就犯过一个错,频繁创建非模态对话框却没及时销毁,程序运行久了就越来越卡,最后通过内存检测工具才找到问题所在。

两种调用方式各有适用场景,就像去银行办业务,取号时的排队叫号窗口是「封闭式」的,必须轮到你才能办理;而旁边的自助查询机是「开放式」的,随时可以去查余额。在实际开发中,我们会根据功能需求选择合适的调用方式,让用户操作更顺畅。

四、利用 ClassWizard 连接对话框与类

对话框、控件、消息、变量,怎么把它们串起来?ClassWizard 就是「管家」—— 它能自动生成连接代码:你选个输入框,告诉它「关联到 m_age 变量」,它就把 DDX 代码写好;你选个按钮,说「点它要执行 OnSave」,它就把消息映射做好。

刚用 ClassWizard 时,我总怀疑「这东西真能行?」毕竟之前手写这些代码时,光是消息映射宏的格式就经常搞错。有次手写ON_BN_CLICKED宏时,把控件 ID 写错了,结果按钮怎么点都没反应,查了半天才发现是少写了个数字。而 ClassWizard 生成的代码就像打印出来的标准答案,格式工整、没有遗漏,大大减少了这类低级错误。

它的操作过程也很直观,就像在填表:第一步选对话框资源,第二步输入类名,第三步勾选要关联的控件和消息,确定后,一个完整的对话框类就生成了。我记得第一次用它生成类时,看着自动出现的DoDataExchange函数和消息处理函数,有种「原来复杂的事情可以这么简单」的感慨。

但 ClassWizard 也不是万能的。有次我们想给一个按钮加双击消息处理,发现它默认只支持单击消息,后来查资料才知道,需要手动在消息映射里添加对应的宏。这就像管家能处理日常事务,但遇到特殊需求时,还得自己动手。不过总体来说,它把程序员从重复的代码编写中解放出来,让我们有更多精力去思考业务逻辑。


// 登录对话框类(ClassWizard自动生成框架)class CLoginDlg : public CDialog{public:CLoginDlg(CWnd* pParent = nullptr);// 关联的变量(DDX用)CString m_strUser; // 用户名CString m_strPwd; // 密码int m_nAge; // 年龄(示例)// 对话框数据enum { IDD = IDD_LOGIN_DLG }; // 对话框资源IDprotected:virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV核心// 消息映射(ClassWizard生成)DECLARE_MESSAGE_MAP()public:afx_msg void OnBnClickedOk(); // 确定按钮消息处理afx_msg void OnBnClickedCancel(); // 取消按钮消息处理};// DDX&DDV实现(ClassWizard辅助生成)void CLoginDlg::DoDataExchange(CDataExchange* pDX){CDialog::DoDataExchange(pDX);// 把IDC_USER_EDIT控件和m_strUser变量绑定DDX_Text(pDX, IDC_USER_EDIT, m_strUser);// 把IDC_PWD_EDIT控件和m_strPwd变量绑定DDX_Text(pDX, IDC_PWD_EDIT, m_strPwd);// 把IDC_AGE_EDIT控件和m_nAge变量绑定,并校验范围18-60DDX_Text(pDX, IDC_AGE_EDIT, m_nAge);DDV_MinMaxInt(pDX, m_nAge, 18, 60);// 校验用户名不为空DDV_NotEmpty(pDX, m_strUser);}// 确定按钮消息处理void CLoginDlg::OnBnClickedOk(){// 更新数据(从控件到变量)UpdateData(TRUE);// 模拟密码验证if (m_strPwd != _T("123456")){AfxMessageBox(_T("密码错误,请重新输入"));return;}CDialog::OnOK();}// 取消按钮消息处理void CLoginDlg::OnBnClickedCancel(){if (AfxMessageBox(_T("确定要取消登录吗?"), MB_YESNO) == IDYES){CDialog::OnCancel();}}// 调用对话框(在主程序中)void CMainFrame::OnLogin(){CLoginDlg dlg;// 如果有记住的用户名,预先填充dlg.m_strUser = m_strSavedUser;// 弹出对话框,用户点击确定后返回IDOKif (dlg.DoModal() == IDOK){// 获取用户输入的数据CString strInfo;strInfo.Format("欢迎,%s!年龄:%d", dlg.m_strUser, dlg.m_nAge);AfxMessageBox(strInfo);// 保存用户名(模拟记住功能)m_strSavedUser = dlg.m_strUser;}}

最后小结:

对话框是 MFC 里最「接地气」的部分 —— 它不聊高深的底层原理,只关心「用户怎么用得顺手」。从拖控件的编辑器到自动生成代码的 ClassWizard,MFC 把复杂的交互逻辑藏在背后,让我们能专注于「和用户对话」。

后来在 Web时代 和移动互联网时代,总想起 MFC 对话框的设计哲学:好的技术,就该像会客室的服务员 —— 默默把一切打理好,让用户和程序的交流自然又顺畅。现在的前端框架里,表单组件的设计和 MFC 对话框有着异曲同工之妙,比如 React 的表单控件绑定,就像简化版的 DDX,而各种表单验证库,也和 DDV 的思路相通。

MFC 对话框的这些设计,在当时是开创性的,它让程序员不用再为界面交互的基础逻辑耗费精力,能更快地做出用户需要的功能。这或许就是 MFC 能火那么多年的原因 —— 它懂技术,更懂开发者和用户的需求。而对于我们这些老程序员来说,每次想起用对话框编辑器拖控件、用 ClassWizard 生成代码的日子,就像想起20多年前斌吗的日子.......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89684.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89684.shtml
英文地址,请注明出处:http://en.pswp.cn/web/89684.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(李宏毅)deep learning(五)--learning rate

一,关于learning rate的讨论:(1)在梯度下降的过程中,当我们发现loss的值很小的时候,这时我们可能以为gradident已经到了local min0(低谷),但是很多时候,loss很小并不是因…

pytorch:tensorboard和transforms学习

tensorboard:可视化数据 在anaconda安装: pip install tensorboard2.12.0最好使用这个版本 不然后面调用会报错 因为版本过高的原因 然后还碰到了安装的时候 安装到C盘去了 但是我用的虚拟环境是在E盘:此时去C盘把那些新安装的复制过来就好了 附录我C盘的…

常用的100个opencv函数

以下是OpenCV中最常用的100个函数及其作用与注意事项的全面整理,按功能模块分类,结合官方文档与工业实践优化排序。各函数均标注Python(cv2)和C(cv::)命名,重点参数以加粗突出: &…

【C++】红黑树,详解其规则与插入操作

各位大佬好,我是落羽!一个坚持不断学习进步的大学生。 如果您觉得我的文章有所帮助,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页: 落羽的落羽 一、红黑树的概念与规则 红黑树是一种更加特殊的平衡二…

Camera相机人脸识别系列专题分析之十七:人脸特征检测FFD算法之libhci_face_camera_api.so 296点位人脸识别检测流程详解

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: Camera相机人脸识别系列专题分析之十七:人脸特征检测FFD算法之libhci_face_camera_api.so 296点位人脸识别检测流程详解 目录 一、背景 二、:FFD算法libhci_face_camera_api.s…

PostgreSQL 16 Administration Cookbook 读书笔记:第7章 Database Administration

编写一个要么完全成功要么完全失败的脚本 事务(transaction)可以实现all or nothing。不过这里指的是psql的-和--single-transaction选项。可以实现transaction wrapper: 此选项只能与一个或多个 -c 和/或 -f 选项组合使用。它会导致 psql 在…

DeepSeekMath:突破开源语言模型在数学推理中的极限

温馨提示: 本篇文章已同步至"AI专题精讲" DeepSeekMath:突破开源语言模型在数学推理中的极限 摘要 数学推理由于其复杂且结构化的特性,对语言模型构成了重大挑战。本文介绍了 DeepSeekMath 7B,该模型在 DeepSeek-Code…

实体类序列化报错:Caused by: java.lang.NoSuchMethodException: com.xx.PoJo$Item.<init>()

原实体类代码EqualsAndHashCode(callSuper true) Data public class Pojo extends BaseBean {private static final long serialVersionUID -4291335073882689552L;ApiModelProperty("")private Integer id;......private List<Item> list;AllArgsConstructo…

基于单片机病床呼叫系统/床位呼叫系统

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 该系统是以单片机STM32F103为核心的基于无线网络的医院病房呼叫系统&#xff0c;分为从机和主机两…

[黑马头条]-登录实现思路

需求分析在黑马头条项目中&#xff0c;登录有两种方式&#xff1a;一种是用户输入账号密码后登录&#xff0c;这种方式登陆后的权限很大&#xff0c;可以查看&#xff0c;也可以进行其他操作&#xff1b;另一种方式就是用户点击不登录&#xff0c;以游客的身份进入系统&#xf…

了解.NET Core状态管理:优化技巧与常见问题解决方案

前言 欢迎关注dotnet研习社&#xff0c;今天我们聊聊“ .NET Core 中的状态管理”。 在Web应用程序中&#xff0c;管理和维持状态是一个非常重要的主题&#xff0c;尤其是在无状态的环境中&#xff0c;如 HTTP 协议和 RESTful API。对于基于 .NET Core 构建的应用程序&#xff…

504网关超时可能是哪些原因导致?

在网络访问中&#xff0c;504 网关超时&#xff08;Gateway Timeout&#xff09;如同一个突然亮起的警示灯&#xff0c;打断用户的浏览或操作流程。这个 HTTP 状态码意味着服务器作为网关或代理时&#xff0c;未能在规定时间内收到上游服务器的响应。引发504错误的核心因素有哪…

ComfyUI 常见报错问题解决方案合集(持续更新ing)

前言&#xff1a; 本文汇总了 5 大高频问题 及其解决方案&#xff0c;涵盖&#xff1a; HuggingFace 认证修复&#xff08;Token 申请 手动下载指南&#xff09; ComfyUI 版本更新&#xff08;完整命令 依赖管理&#xff09; 自启动配置&#xff08;Conda 环境 权限修复&…

完美解决Linux服务器tomcat开机自启动问题

经过多次测试终于彻底解决tomcat开机自启动的问题了 PID3ps aux | grep /home/server/shichuan/ | grep java | awk {print $2} if [ -n "$PID3" ]; then 这个判断pid的方式还是可能出现启动失败的情况 # tail -n 1 /home/server/shichuan/logs/catalina.out |grep…

kotlin部分常用特性总结

<h3>Kotlin中类和对象初始化</h3><ul> <li>添加open关键字代表可以被继承</li> <li>Any 是所有类的父类,类似Object,包含 equals() hashCode() toString()方法</li> <li>constructor 关键字代表构造函数, constructor关键字可…

PHP 就业核心技能速查手册

# PHP 就业核心技能速查手册 > 高效聚焦市场所需&#xff0c;快速提升竞争力 --- ## 一、语法基础&#xff08;必会&#xff01;&#xff09; php // 1. 变量与数据类型 $price 19.99; // 浮点型 $isStock true; // 布尔型 // 2. 流程控制 foreach ($…

从混沌到秩序:数据科学的热力学第二定律破局——线性回归的熵减模型 × 最小二乘的能量最小化 × 梯度下降的负反馈控制系统,用物理定律重构智能算法的统一场论

目录 一、机器学习是什么&#xff1f; 1.1 什么是机器学习&#xff1f; 1.2 机器学习的三大类型 二、线性回归是什么&#xff1f; 2.1 通俗理解 2.2 数学表达 三、最小二乘法&#xff08;Least Squares Method&#xff09; 3.1 什么是损失函数&#xff1f; 3.2 什么是最小…

BI 数据可视化平台建设(3)—首页性能提升实践

作者&#xff1a; vivo 互联网大数据团队- Wang Lei 本文是vivo互联网大数据团队《BI 数据可视化平台建设》系列文章第3篇。 随着越来越多代码的堆积&#xff0c;平台的运行加载性能也在逐步下降&#xff0c;在不同程度上极大地影响了用户体验&#xff0c;从而导致用户流失。本…

基于Python的毕业设计选题管理系统设计与实现

基于Python的毕业设计选题管理系统设计与实现摘要本论文详细阐述了一个基于Python的毕业设计选题管理系统的设计与实现过程。该系统采用了Python的Tkinter库构建图形用户界面&#xff0c;使用SQLite数据库存储数据&#xff0c;实现了高校毕业设计选题过程中的教师出题、学生选题…

如何在HTML5页面中嵌入视频

在HTML5中嵌入视频主要使用<video>标签&#xff0c;这是一种简单且标准的方式。以下是详细步骤和示例&#xff1a; 基础实现 <!DOCTYPE html> <html> <head><title>视频嵌入示例</title> </head> <body><!-- 基础视频播放器…