当前位置: 首页 > news >正文

技术演进中的开发沉思-39 MFC系列:多重文件和多重视图

当年做MFC开发,记得有个开发场景既要看列表,又要看图片,还得能同时打开三个模块的文件。用到的便是 MFC 里 “多重文件与多重视图” 的应用场景。

那时候的 CRT 显示器还带着厚重的玻璃外壳,屏幕刷新率低得能看见滚动的波纹。我看着MFC宝典对这个场景的描述,看着桌上的我的学习笔记,联想到自己的开发习惯:“我开发的时候 —— 总不能看MFC宝典时,就把我的学习笔记收起来吧?” 瞬间,我就明白了 MFC 设计的核心逻辑。

这些技术说起来就像我们办公桌上的章法。有人习惯一张桌子只摊开一个笔记本(SDI),有人喜欢把合同、报表、草稿分三个抽屉放(MDI);有人会把一页笔记折成两半,左边写数据、右边画图表(窗口拆分),这正是 MFC 在二十年前就想明白的事:技术的本质,是把现实里的 “方便” 搬进屏幕里

一、SDI 与 MDI

这里涉及到SDI和MDI两个概念。

SDI(单文档界面)像个极简主义的书桌 —— 每次只能打开一个文件,窗口标题栏跟着文件名变,就像你捧着一本笔记本,翻页时封面总印着当前章节名。早年的记事本、画图软件都是这路数,MFC 里用 CView 和 CSingleDocTemplate 搭建,框架代码干净得像刚擦过的桌面。我记得为了练手,用 SDI 做个人通讯录,整个程序就像个随身笔记本,打开时只能看一个人的联系方式,虽然简单,但运行时内存占用只有 800KB,在当年 256MB 内存的机器上,轻快得像羽毛。


// SDI框架注册(简化版)CSingleDocTemplate* pDocTemplate;pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CMyDoc), // 数据核心RUNTIME_CLASS(CMainFrame),// 主窗口RUNTIME_CLASS(CMyView) // 视图);AddDocTemplate(pDocTemplate);

MDI(多文档界面)则是带抽屉的书桌。主窗口像书桌的桌面,每个打开的文件都是抽屉里的文件夹 —— 例如:你可以把 “设备台账” 和 “维修记录” 两个窗口叠着放,也能平铺对比。当年做监控系统时,我们用 MDI 同时显示三个车间的实时数据,就像同时拉开三个抽屉取资料,效率瞬间提上来。MDI适合多个模块比对的场景,那时候的界面风格单一,一个界面实现一个报表。现在流行的驾驶舱大屏,在某种意义上实现了多报表在同一个屏幕,在我们那个年代,只能用 MDI 窗口实现多个报表,如:调出当前参数、历史曲线和标准值对照表,同时比对满足对比数据,校准的场景,想想要是用 SDI,光切换窗口就得耽误半分钟。

它的注册代码多了个 “多文档标记”,就像给书桌加了抽屉轨道:


// MDI框架注册(简化版)CMultiDocTemplate* pDocTemplate;pDocTemplate = new CMultiDocTemplate(IDR_MYTYPE,RUNTIME_CLASS(CMyDoc),RUNTIME_CLASS(CChildFrame), // 子窗口(抽屉)RUNTIME_CLASS(CMyView));AddDocTemplate(pDocTemplate);

这两种模式的核心区别,就在于 “能不能同时伺候多个文件”。SDI 适合专注一件事,MDI 适合多任务并行 —— 就像写文章时,有人喜欢写完一篇再写下一篇,有人习惯同时开着提纲、正文、资料三个窗口。后来到了移动互联网时代,手机 APP 大多用 SDI 思路设计,因为小屏幕容不下太多窗口;但电脑端的 IDE 至今保留 MDI 影子,比如 VS 里能同时打开多个代码文件,本质上还是当年的抽屉逻辑。

二、多重视图

刚刚提到驾驶舱大屏,在2000年时,跟该场景很类似的就是。用户提需求:“同一份销售数据,我想有时看表格,有时看柱状图,最好能并排看。” 这正是多重视图(Multiple Views)要解决的问题。

那时候还没有现成的图表控件,柱状图得自己用 GDI 画。但凡我先做了个表格视图,用 CListCtrl 显示地区、销售额、同比增长这些数据;又做了个图表视图,在 OnDraw 函数里计算坐标,用 Rectangle 画柱子。切换视图时,用户总说像 “翻菜谱”—— 同样的食材,换种做法就有新味道。

它就像你手里的一份食材 —— 同样的猪肉,能做成红烧肉(表格视图)、肉丸子(图表视图)、肉燥(明细视图)。在 MFC 里,这些 “视图” 都盯着同一个 “文档”(数据核心),就像三个厨师共用一盆肉馅,有人做丸子,有人炒肉片,但原料始终是同一批。

我当时用了 CFormView 做表格,用 CChartView 画图表,切换视图时就像转动餐桌上的转盘:“您刚看的是红烧肉(表格),现在转过来的是丸子(图表)。” 实现时只需给文档绑定多个视图模板,就像给食材准备不同的菜谱:


// 给同一个文档绑定两个视图(简化思路)// 视图1:表格视图pDocTemplate1 = new CMultiDocTemplate(IDR_TABLETYPE,RUNTIME_CLASS(CSalesDoc),RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(CTableView));AddDocTemplate(pDocTemplate1);// 视图2:图表视图pDocTemplate2 = new CMultiDocTemplate(IDR_CHARTTYPE,RUNTIME_CLASS(CSalesDoc),RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(CChartView));AddDocTemplate(pDocTemplate2);

最妙的是数据同步 —— 当表格里改了某个销售额,图表会自动刷新。这就像你往肉馅里加了盐,不管是丸子还是肉片,都会变咸。有次销售经理在表格里改了华东区的数值,抬头就看见图表里的柱子长高了,惊得直说 “这比 Excel 还方便”。这种 “数据与视图分离” 的思路,后来在 Web 开发里演变成了 MVVM,二十年前的 MFC 早就埋下了伏笔。

三、窗口拆分

窗口拆分是个特别 “实用主义” 的设计。就像你把笔记本对折,左边记笔记,右边画草图;或者折成三折,分别写待办、进度、总结。MFC 里分静态拆分和动态拆分,前者像装订死的笔记本(拆分方式固定),后者像活页本(随时调整拆分比例)。

第一次用拆分是做一个参数配置界面:左边列参数列表,右边显示参数说明。静态拆分用 CSplitterWnd,在 OnCreateClient 里 “划条线” 就行,就像用尺子在笔记本上画分隔线。那个系统上线后,由于那时候信息化才开始,很多客户都说 —— 以前共工作看参数得来回翻手册,现在左边点一下,右边说明就出来了,真是很方便。


// 静态拆分窗口(左右两栏)CSplitterWnd m_wndSplitter;BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext){// 拆成1行2列return m_wndSplitter.CreateStatic(this, 1, 2) &&m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CListView), CSize(200, 0), pContext) &&m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CEditView), CSize(0, 0), pContext);}

动态拆分更灵活,就像用可移动的隔板分隔抽屉,用户可以自己拖到合适的位置。当年做日志分析工具时,用户经常把窗口拆成上下两部分,上面看实时日志,下面查历史记录,拖来拖去调整高度 —— 这场景和现在手机上 “分屏看视频 + 聊微信” 简直一模一样。有个运维大哥特别有意思,他总把拆分线拖到最上面,只留一条缝看实时日志,说这样 “有新错误一眼就能看见”。

动态拆分需要在创建时指定可拆分的行数和列数,就像告诉系统 “这个笔记本能折几折”:


// 动态拆分窗口(可上下左右拆分)m_wndSplitter.Create(this, 2, 2, CSize(10, 10), pContext);

后来接触到 Eclipse 的透视图,发现它的面板拆分逻辑和 MFC 如出一辙。技术迭代再快,那些让人用着顺手的设计,总会以不同形式留下来。

四、同源子窗口与多重文件

还有一个场景,有时候用户会说:“我想同时开两个窗口看同一个台账,一个看全部,一个看筛选后的。” 这就是同源子窗口 —— 就像复印了一份文件,原件和复印件内容相同,但可以在复印件上圈画,原件不变。它们共用一个文档对象,却有各自的视图状态。

记得一个ERP客户提出这样的需求,一个窗口看所有库存,另一个窗口筛选出 “低于安全库存” 的商品。我在文档类里加了个筛选标记,视图根据这个标记决定显示内容。用户每次盘点时,就把两个窗口并排放在大屏幕上,一边对照一边补货,比以前拿着两张打印纸核对高效多了。

而多重文件则是另一回事,以ERP为例:同时打开 “设备 A 台账”“设备 B 台账”“设备 C 台账”,就像桌上摊着三个不同的笔记本,各自独立,却能随时切换。MFC 用 CDocument 管理每个文件的数据,用 CMDIChildWnd 做每个窗口的 “容器”,就像给每个笔记本配了个文件夹。有次给汽车厂做设备管理系统,维修工要同时查看三台机床的保养记录,多重文件模式让他们不用反复打开关闭文件,光这个细节就节省了不少时间。

这两种模式的区别,就在于 “数据是不是同一个源头”。同源子窗口是 “一个数据,多个视角”,多重文件是 “多个数据,各自视角”—— 就像你既可以把一张照片放大看细节、缩小看整体(同源),也可以同时看今天和昨天拍的照片(多重文件)。

实现同源子窗口时,需要在文档类里维护视图列表,数据变化时通知所有视图更新:


// 文档类中通知所有视图更新void CMyDoc::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint){POSITION pos = GetFirstViewPosition();while (pos != NULL){CView* pView = GetNextView(pos);if (pView != pSender){pView->OnUpdate(pSender, lHint, pHint);}}}

而多重文件则依赖 MFC 的文档模板机制,每个文件对应一个文档实例,就像每个笔记本有自己的内容。

最后小结

现在回头看,MFC 的这些设计从不是炫技。当年在科研所,用户不会关心 “什么是 MDI”,他们只在乎 “能不能同时打开三个文件”;也不会问 “怎么拆分窗口”,只希望 “左边看列表,右边看详情”。

所有的技术都源于要解决的场景,我想这些代码和框架背后,是一群开发者在想:“怎么让屏幕里的操作,像在桌上办公一样自然?” 就像 SDI 是怕人分心,MDI 是怕人来回翻找,多重视图是怕人重复录入 —— 技术的温度,往往藏在这些 “怕麻烦” 的细节里。

后来到了 Web 时代,移动互联网时代,我发现 用 iframe 做多窗口,用 Vue 组件做视图切换,但骨子里的逻辑和当年的 MFC 没什么不同。毕竟,不管工具怎么变,人对 “方便” 的需求,从来没变过。就像现在我看到 React 组件时,觉得就是当年在MFC 里写视图类的方式,归根结底都是为了让数据以最合适的样子,出现在用户眼前。

http://www.dtcms.com/a/287112.html

相关文章:

  • 安全事件响应分析--基础命令
  • 【52】MFC入门到精通——(CComboBox)下拉框选项顺序与初始化不一致,默认显示项也不一致
  • pytorch:tensorboard和transforms学习
  • HTML5中的自定义属性
  • Jenkins自动化部署.NET应用实战:Docker+私有仓库+SSH远程发布
  • mysql常用总结
  • EMC杂谈-001-基础知识
  • 【面试八股文】软件测试面试题汇总
  • [黑马头条]-项目整合对象存储服务MinIO
  • 百度网盘TV版1.21.0 |支持倍速播放,大屏云看片
  • CS231n-2017 Lecture2图像分类笔记
  • 工业企业与污染库匹配数据库(1998-2014年)
  • Letter Combination of a Phone Number
  • Redis原理之集群
  • windows内核研究(驱动开发之内核编程)
  • Qt控件实战详解:深入掌握输入输出与数据展示
  • Python MCP与Excel增强智能:构建下一代数据处理和自动化解决方案
  • SpringBoot 3.0 挥别 spring.factories,拥抱云原生新纪元
  • 人该怎样活着呢?55
  • 【RK3576】Android 14 驱动开发实战指南
  • uview-ui使用u-icon文字图标展示
  • 报错:升级gcc,centos
  • 数据库第五次作业
  • 云边端协同架构下的智能计算革命
  • 从代码学习深度强化学习 - SAC PyTorch版
  • 消息队列与信号量:System V 进程间通信的基础
  • 【机器学习深度学习】为什么要将模型转换为 GGUF 格式?
  • win10连接鼠标自动关闭触摸板/win10关闭触摸板(笔记本)
  • 路由器的Serial 串口理解
  • 移除debian升级后没用的垃圾