新闻详情

新闻详情

首页 / 资讯中心 / 详情

MFC单文档多视图切换实战:文档/视图架构深度应用

发布时间:2026/10/2 19:56:07来源:尧图网络
MFC单文档多视图切换实战:文档/视图架构深度应用
简介本资源是一份面向MFC中级开发者的技术实践项目聚焦单文档界面SDI中多视图动态切换的核心实现方案适用于需在统一文档窗口内集成文本、图像、表单等异构视图的桌面应用开发场景。压缩包为RAR格式共23个文件包含8个头文件.h定义视图类与文档结构、6个源文件.cpp实现视图切换逻辑与消息响应、2个图标资源.ico及配套资源脚本.rc、.rc2、工程配置.vcproj、.suo、.user和说明文档ReadMe.txt整体体积仅138KB轻量易导入。已有1300人学习下载资源结构完整含标准SDI框架、自定义CView派生类如MyFormView、CDocTemplate注册与OnViewText等视图切换命令处理函数代码注释清晰可直接编译运行并作为多视图架构的参考模板。1. MFC 单文档视图中进行多种视图的切换不是换皮肤是让同一份文档在不同“工作台”间无缝跳转你写了个记事本式编辑器用的是 MFC 单文档界面SDI主框架里默认塞了个CEditView—— 但用户突然要查词频统计、看语法树、导出 Markdown 预览甚至嵌入一个轻量图表控件。你总不能重启程序、也不能硬塞所有功能进同一个窗口搞成“信息爆炸”。这时候“MFC 单文档视图中进行多种视图的切换”就不是炫技而是刚需同一份文档数据CDocument不变只动态替换当前显示的CView子类实例实现语义级视图隔离与复用。它不依赖多文档MDI的复杂框架也不靠 Tab 控件模拟——而是直击 MFC 文档/视图架构本质CDocument是数据中枢CView是表现层插槽。常见落地场景包括代码编辑器文本视图 折叠树视图 符号导航视图、工业配置工具参数表单视图 实时曲线视图 设备拓扑图视图、报表生成器设计视图 数据预览视图 打印布局视图。新手容易误以为这是“隐藏/显示控件”熟手则清楚这涉及CFrameWnd::SetActiveView()的调用时机、CView::OnActivateView()的生命周期钩子、以及CDocument::UpdateAllViews()的通知链路——稍有不慎就会出现视图残留、消息路由错乱、GDI 资源泄漏等“玄学翻车”。本文带你从零搭起可切换视图骨架填平三个典型黑匣子坑最后给出一套带状态记忆的切换控制器——不是教你怎么写 Hello World而是让你上线后敢动生产环境的 SDI 视图系统。2. 搭建可切换视图的基础骨架从 CView 派生到 CFrameWnd 接口打通MFC 单文档中切换视图核心不是“创建多个 View 窗口”而是让框架知道当前哪个 View 是活跃的、该把消息发给谁、文档更新时通知谁。这需要三步闭环定义多个CView派生类 → 在框架类中管理它们的生命周期 → 通过标准接口触发切换。下面以一个实际项目为例我们有CTextEditView纯文本编辑、CStatView词频统计表格、CPreviewViewMarkdown 渲染预览全部共享同一份CNoteDoc文档对象。2.1 定义多个 CView 派生类并确保构造安全每个视图类必须继承CView且禁止在构造函数中直接操作 UI 或访问文档指针——因为此时框架尚未完成Create()GetDocument()返回nullptr是常态。正确做法是把初始化逻辑移到OnInitialUpdate()中// CStatView.h class CStatView : public CView { DECLARE_DYNCREATE(CStatView) public: CStatView(); virtual ~CStatView(); protected: virtual void OnDraw(CDC* pDC) override; virtual void OnInitialUpdate() override; // ✅ 初始化放这里 virtual void OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactivateView) override; private: CListCtrl m_listCtrl; // 统计结果列表 };// CStatView.cpp IMPLEMENT_DYNCREATE(CStatView, CView) CStatView::CStatView() : m_listCtrl() // ✅ 构造函数只做成员变量初始化 { } void CStatView::OnInitialUpdate() { CView::OnInitialUpdate(); // ✅ 此时 GetDocument() 已可靠返回非空指针 CNoteDoc* pDoc dynamic_castCNoteDoc*(GetDocument()); if (pDoc) { // 初始化列表控件添加列、设置样式 CRect rect; GetClientRect(rect); m_listCtrl.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL, rect, this, IDC_LIST_STAT); m_listCtrl.InsertColumn(0, _T(单词), LVCFMT_LEFT, 120); m_listCtrl.InsertColumn(1, _T(频次), LVCFMT_RIGHT, 80); // 加载初始统计从文档获取 UpdateFromDocument(pDoc); } }关键点说明OnInitialUpdate()是 MFC 保证在CView第一次显示前调用的钩子此时GetDocument()已绑定GetParentFrame()可用窗口句柄已创建。所有 UI 控件创建、数据加载、事件绑定都应放在此处而非构造函数或OnCreate()。这是避免“空指针崩溃”的第一道防线。2.2 在主框架类中集中管理视图实例与切换逻辑MFC SDI 的主框架是CMainFrame继承自CFrameWnd它持有当前活跃视图指针。我们要做的不是暴力delete旧视图而是复用视图对象、仅切换其可见性与激活状态——既避免频繁new/delete带来的内存碎片又保证OnActivateView()等生命周期回调能被正确触发。// CMainFrame.h class CMainFrame : public CFrameWnd { DECLARE_DYNAMIC(CMainFrame) public: CMainFrame(); virtual ~CMainFrame(); protected: CTextEditView* m_pTextView; CStatView* m_pStatView; CPreviewView* m_pPreviewView; CView* m_pCurrentView; public: void SwitchToView(int nViewID); // 公共切换入口 CView* GetCurrentView() const { return m_pCurrentView; } protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnViewText(); // 响应菜单 ID_VIEW_TEXT afx_msg void OnViewStat(); // 响应菜单 ID_VIEW_STAT afx_msg void OnViewPreview(); // 响应菜单 ID_VIEW_PREVIEW DECLARE_MESSAGE_MAP() };// CMainFrame.cpp int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) -1) return -1; // ✅ 创建所有视图对象但不立即显示 m_pTextView new CTextEditView(); m_pStatView new CStatView(); m_pPreviewView new CPreviewView(); // ✅ 将视图附加到框架但不激活 m_pTextView-Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST, NULL); m_pStatView-Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST 1, NULL); m_pPreviewView-Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST 2, NULL); // ✅ 初始激活文本视图 m_pCurrentView m_pTextView; SetActiveView(m_pTextView); m_pTextView-ShowWindow(SW_SHOW); m_pStatView-ShowWindow(SW_HIDE); m_pPreviewView-ShowWindow(SW_HIDE); return 0; } void CMainFrame::SwitchToView(int nViewID) { CView* pNewView nullptr; switch (nViewID) { case ID_VIEW_TEXT: pNewView m_pTextView; break; case ID_VIEW_STAT: pNewView m_pStatView; break; case ID_VIEW_PREVIEW: pNewView m_pPreviewView; break; default: return; } if (pNewView m_pCurrentView) return; // 已经是当前视图无需切换 // ✅ 关闭旧视图的输入焦点防止键盘消息错发 if (m_pCurrentView ::IsWindow(m_pCurrentView-GetSafeHwnd())) { m_pCurrentView-SetFocus(); m_pCurrentView-SendMessage(WM_KILLFOCUS); } // ✅ 隐藏旧视图、显示新视图 if (m_pCurrentView) m_pCurrentView-ShowWindow(SW_HIDE); pNewView-ShowWindow(SW_SHOW); // ✅ 设置新视图为活跃视图触发 OnActivateView SetActiveView(pNewView); m_pCurrentView pNewView; // ✅ 强制重绘框架客户区避免残留残影 RecalcLayout(); Invalidate(); UpdateWindow(); }参数与逻辑说明AFX_IDW_PANE_FIRST N是 MFC 内部为视图分配的唯一 ID用于CView::Create()的nID参数确保每个视图在框架中有独立身份SetActiveView()不仅设置内部指针还会向旧视图发送WM_COMMAND消息ID_VIEW_DEACTIVATE并调用OnActivateView(FALSE,...)向新视图调用OnActivateView(TRUE,...)RecalcLayout()是关键它重新计算客户区大小并通知所有子窗口调整位置否则切换后可能出现视图尺寸错乱或被遮挡Invalidate()/UpdateWindow()强制刷新解决某些情况下ShowWindow(SW_SHOW)后画面未及时更新的问题尤其在高 DPI 或多显示器环境下。2.3 为视图切换提供用户入口菜单、工具栏与快捷键绑定视图切换必须暴露给用户。MFC 提供标准机制在资源编辑器中为菜单项如“视图→文本编辑”、“视图→统计分析”设置 IDID_VIEW_TEXT,ID_VIEW_STAT然后在CMainFrame的消息映射中关联处理函数// CMainFrame.cpp 消息映射 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(ID_VIEW_TEXT, CMainFrame::OnViewText) ON_COMMAND(ID_VIEW_STAT, CMainFrame::OnViewStat) ON_COMMAND(ID_VIEW_PREVIEW, CMainFrame::OnViewPreview) ON_UPDATE_COMMAND_UI(ID_VIEW_TEXT, CMainFrame::OnUpdateViewText) ON_UPDATE_COMMAND_UI(ID_VIEW_STAT, CMainFrame::OnUpdateViewStat) ON_UPDATE_COMMAND_UI(ID_VIEW_PREVIEW, CMainFrame::OnUpdateViewPreview) END_MESSAGE_MAP() void CMainFrame::OnViewText() { SwitchToView(ID_VIEW_TEXT); } void CMainFrame::OnViewStat() { SwitchToView(ID_VIEW_STAT); } void CMainFrame::OnViewPreview() { SwitchToView(ID_VIEW_PREVIEW); } // ✅ 动态更新菜单勾选状态UI 更新 void CMainFrame::OnUpdateViewText(CCmdUI* pCmdUI) { pCmdUI-SetCheck(m_pCurrentView m_pTextView); } void CMainFrame::OnUpdateViewStat(CCmdUI* pCmdUI) { pCmdUI-SetCheck(m_pCurrentView m_pStatView); } void CMainFrame::OnUpdateViewPreview(CCmdUI* pCmdUI) { pCmdUI-SetCheck(m_pCurrentView m_pPreviewView); }为什么需要ON_UPDATE_COMMAND_UI因为 MFC 的命令更新机制会在每次菜单弹出、工具栏重绘前自动调用这些函数。SetCheck()设置菜单项左侧的对勾标记让用户直观看到当前处于哪个视图模式。若省略此步菜单将永远显示“未选中”失去状态反馈——这是新手最常忽略的“用户体验断层”。3. 视图切换中的三大黑匣子避坑指南现象、原因与血泪解法视图切换看似只是ShowWindow()和SetActiveView()但 MFC 文档/视图架构的隐式契约极多。以下三条是我在五个工业级 SDI 项目中踩过的真坑每条都附带可复现的现象、底层原因和验证过的解法。3.1 现象切换后新视图空白OnDraw()从未被调用但窗口句柄存在现象点击“统计视图”菜单窗口显示出来但里面空空如也调试发现CStatView::OnDraw()根本没执行OnInitialUpdate()却已调用过。原因CView::Create()时传入的rect为CRect(0,0,0,0)导致视图窗口创建成功但尺寸为 0×0。后续ShowWindow(SW_SHOW)无法触发重绘因为GetClientRect()返回无效区域。MFC 的OnDraw()只在客户区非空且需要重绘时才触发。解法在CMainFrame::OnCreate()中创建视图后立即调用MoveWindow()设置合理初始尺寸或更稳妥地在SwitchToView()中切换前强制重设尺寸// 在 SwitchToView() 中新增放在 ShowWindow 之前 CRect clientRect; GetClientRect(clientRect); pNewView-MoveWindow(clientRect);验证技巧在CView::OnDraw()开头加ASSERT(!pDC-GetClipBox(rect).IsRectEmpty());若断言失败说明客户区为空——立刻检查MoveWindow()是否缺失。3.2 现象切换到新视图后键盘输入失效如按 CtrlS 无反应但鼠标点击正常现象从文本视图切到统计视图菜单和按钮响应正常但按 CtrlS 不触发保存按 Tab 键无法在列表控件中切换焦点。原因CView默认不接收键盘焦点。即使ShowWindow(SW_SHOW)了若未显式调用SetFocus()Windows 消息循环不会将WM_KEYDOWN发送给该窗口。而SetActiveView()只影响OnActivateView()回调不自动获取焦点。解法在SwitchToView()中切换完成后主动调用pNewView-SetFocus()并在目标视图类中重载PreTranslateMessage()确保消息路由正确// CStatView.cpp BOOL CStatView::PreTranslateMessage(MSG* pMsg) { // ✅ 让列表控件能响应键盘方向键、空格选中等 if (pMsg-message WM_KEYDOWN ::IsChild(m_hWnd, pMsg-hwnd)) { if (m_listCtrl.GetSafeHwnd() ::IsWindow(m_listCtrl.GetSafeHwnd())) { ::SetFocus(m_listCtrl.GetSafeHwnd()); return FALSE; // 让控件自己处理 } } return CView::PreTranslateMessage(pMsg); }注意SetFocus()必须在ShowWindow(SW_SHOW)之后、RecalcLayout()之前调用否则可能因窗口未完全就绪而失败。可在SwitchToView()末尾添加pNewView-SetFocus();3.3 现象多次切换后内存占用持续上涨任务管理器显示 GDI 对象数飙升现象连续切换视图 20 次后GDI 对象数从 50 涨到 300程序变卡最终CreateWindowEx失败返回NULL。原因CView派生类中创建的子控件如CListCtrl,CStatic,CEdit在CView被ShowWindow(SW_HIDE)时并未销毁但若视图类自身未正确管理其子控件的生命周期重复调用OnInitialUpdate()会导致控件重复创建例如m_listCtrl.Create(...)被反复执行。CListCtrl等控件的Create()内部会申请 GDI 资源旧实例未释放即新建造成泄漏。解法在CView派生类中所有子控件创建前先检查是否已存在并在OnDestroy()中显式销毁// CStatView.cpp void CStatView::OnInitialUpdate() { CView::OnInitialUpdate(); CNoteDoc* pDoc dynamic_castCNoteDoc*(GetDocument()); if (!pDoc) return; // ✅ 检查控件是否已创建避免重复 if (!m_listCtrl.GetSafeHwnd()) { CRect rect; GetClientRect(rect); m_listCtrl.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL, rect, this, IDC_LIST_STAT); // ... 添加列等 } UpdateFromDocument(pDoc); } void CStatView::OnDestroy() { CView::OnDestroy(); // ✅ 显式销毁子控件释放 GDI 资源 if (m_listCtrl.GetSafeHwnd()) m_listCtrl.DestroyWindow(); }血泪经验MFC 的CWnd派生类包括CListCtrl在DestroyWindow()被调用前其关联的 HWND 和 GDI 对象不会释放。CView的OnDestroy()是销毁子控件的黄金时机——比~CStatView()更可靠因为析构函数可能在窗口已销毁后才执行。4. 让视图切换具备状态记忆跨切换保留滚动位置、选中项与缩放比例真实业务中用户切换视图不是“重置一切”而是“带着上下文跳转”。比如在文本视图中滚动到第 100 行切到预览视图后再切回来理应还在第 100 行统计视图中选中了“function”这一行切走再切回该行应保持高亮。这就要求每个视图类自行维护状态并在OnActivateView()中恢复。4.1 设计状态容器每个视图类持有一个轻量结构体避免全局变量污染采用“视图自治”原则每个CView派生类定义自己的状态结构并在OnActivateView()中读写。// CTextEditView.h struct TextViewState { int nTopLine; // 滚动到的首行号 int nCaretPos; // 光标位置字符索引 CString strSelText;// 当前选中文本用于恢复选区 }; class CTextEditView : public CView { // ... 其他声明 private: TextViewState m_state; void SaveCurrentState(); void RestoreState(); };// CTextEditView.cpp void CTextEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactivateView) { CView::OnActivateView(bActivate, pActivateView, pDeactivateView); if (bActivate) { // ✅ 激活时恢复状态 RestoreState(); } else { // ✅ 失活时保存状态 SaveCurrentState(); } } void CTextEditView::SaveCurrentState() { // 获取编辑控件假设使用 CRichEditCtrl CRichEditCtrl edit GetRichEditCtrl(); CHARRANGE cr; edit.GetSel(cr); m_state.nCaretPos cr.cpMin; m_state.nTopLine edit.GetFirstVisibleLine(); // 保存选中文本仅当有选区时 if (cr.cpMin ! cr.cpMax) { CString str; edit.GetSelText(str); m_state.strSelText str; } else { m_state.strSelText.Empty(); } } void CTextEditView::RestoreState() { CRichEditCtrl edit GetRichEditCtrl(); // 恢复滚动位置 if (m_state.nTopLine 0) edit.LineScroll(0, m_state.nTopLine); // 恢复光标/选区 if (!m_state.strSelText.IsEmpty()) { // 查找文本并选中简化版实际需考虑多处匹配 long len edit.GetTextLength(); if (m_state.nCaretPos len) { edit.SetSel(m_state.nCaretPos, m_state.nCaretPos); } } else { edit.SetSel(m_state.nCaretPos, m_state.nCaretPos); } }关键点OnActivateView()的bActivate参数是上帝视角开关——TRUE表示“我即将成为主角”FALSE表示“我即将退场”。所有状态保存/恢复逻辑必须严格按此分流不可颠倒。4.2 处理跨视图的数据同步文档变更时的智能刷新策略当用户在文本视图修改内容CDocument::UpdateAllViews()会通知所有视图。但并非所有视图都需要全量刷新统计视图只需重新计算词频预览视图只需重渲染 HTML而文本视图自身无需刷新除非涉及语法高亮更新。因此在CView::OnUpdate()中必须判断lHint参数避免无谓重绘。// CStatView.cpp void CStatView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { // ✅ lHint 是文档发出的提示码可自定义如 HINT_UPDATE_STATS if (lHint HINT_UPDATE_STATS || lHint 0) // 0 表示通用更新 { CNoteDoc* pDoc dynamic_castCNoteDoc*(GetDocument()); if (pDoc) { // ✅ 只刷新统计数据不重建整个列表控件 m_listCtrl.DeleteAllItems(); const auto stats pDoc-GetWordStats(); for (const auto pair : stats) { int idx m_listCtrl.InsertItem(m_listCtrl.GetItemCount(), pair.first); m_listCtrl.SetItemText(idx, 1, CString(std::to_wstring(pair.second).c_str())); } } } }如何定义lHint在CNoteDoc中定义枚举enum { HINT_UPDATE_STATS 1, HINT_UPDATE_PREVIEW 2, HINT_UPDATE_SYNTAX 3 };修改文档后调用// 在 CNoteDoc::SetContent() 中 SetModifiedFlag(); UpdateAllViews(nullptr, HINT_UPDATE_STATS); // 只通知统计视图 UpdateAllViews(nullptr, HINT_UPDATE_PREVIEW); // 只通知预览视图4.3 实现视图切换动画可选增强淡入淡出过渡效果虽然 MFC 原生不支持动画但可通过AlphaBlend()API 实现简易淡入淡出提升体验。核心思路在SwitchToView()中先将旧视图AlphaBlend至透明再将新视图AlphaBlend至不透明。// CMainFrame.cpp 中 SwitchToView() 增强版片段 void CMainFrame::SwitchToView(int nViewID) { // ... 前置逻辑隐藏旧视图、显示新视图等 // ✅ 启动淡入淡出动画仅当新旧视图均有效时 if (m_pCurrentView pNewView ::IsWindow(m_pCurrentView-GetSafeHwnd()) ::IsWindow(pNewView-GetSafeHwnd())) { CRect rect; GetClientRect(rect); CDC* pDC GetDC(); CDC memDC; memDC.CreateCompatibleDC(pDC); // 创建位图缓存 CBitmap bitmap; bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); CBitmap* pOldBitmap memDC.SelectObject(bitmap); // 绘制旧视图到缓存 m_pCurrentView-Print(memDC, PRF_CHILDREN | PRF_CLIENT | PRF_NONCLIENT); // 逐帧 AlphaBlend0→255 for (int alpha 0; alpha 255; alpha 15) { BLENDFUNCTION blend { AC_SRC_OVER, 0, (BYTE)alpha, AC_SRC_ALPHA }; ::AlphaBlend(pDC-GetSafeHdc(), rect.left, rect.top, rect.Width(), rect.Height(), memDC.GetSafeHdc(), 0, 0, rect.Width(), rect.Height(), blend); Sleep(20); // 简单延时实际应用建议用定时器 } // 绘制新视图已 ShowWindow pNewView-RedrawWindow(rect, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); memDC.SelectObject(pOldBitmap); ReleaseDC(pDC); } // ... 后置逻辑SetFocus, RecalcLayout 等 }注意此动画为示意代码生产环境应使用SetTimer()OnTimer()实现非阻塞动画并增加KillTimer()清理。Sleep(20)会阻塞 UI 线程仅用于演示逻辑。5. 进阶技巧构建可扩展的视图注册中心与运行时热插拔当项目规模扩大视图数量从 3 个涨到 10硬编码SwitchToView()的switch语句会变成维护噩梦。此时应引入视图注册中心模式用std::map管理视图类型与创建器支持运行时动态注册新视图类无需修改框架代码。5.1 定义视图工厂与注册中心// ViewFactory.h #include map #include memory #include functional class CView; class CDocument; // 视图创建器签名返回新视图指针参数为文档指针 using ViewCreator std::functionCView*(CDocument*); class CViewFactory { public: static CViewFactory Instance() { static CViewFactory instance; return instance; } void RegisterView(int nViewID, ViewCreator creator) { m_creators[nViewID] creator; } CView* CreateView(int nViewID, CDocument* pDoc) const { auto it m_creators.find(nViewID); if (it ! m_creators.end()) return it-second(pDoc); return nullptr; } private: std::mapint, ViewCreator m_creators; };5.2 改造 CMainFrame从硬编码切换到工厂驱动// CMainFrame.cpp void CMainFrame::SwitchToView(int nViewID) { // ✅ 从工厂获取视图实例首次调用时创建后续复用 CView* pNewView nullptr; auto it m_viewMap.find(nViewID); if (it ! m_viewMap.end()) { pNewView it-second; } else { CDocument* pDoc GetActiveDocument(); pNewView CViewFactory::Instance().CreateView(nViewID, pDoc); if (pNewView) { // 创建窗口 pNewView-Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nViewID); m_viewMap[nViewID] pNewView; // 缓存实例 } } if (!pNewView) return; // ... 后续 ShowWindow / SetActiveView 等逻辑同前 }5.3 在 App 初始化时注册所有视图// CMyApp.cpp BOOL CMyApp::InitInstance() { // ... 前置初始化 // ✅ 注册所有视图类型 CViewFactory::Instance().RegisterView(ID_VIEW_TEXT, [](CDocument* pDoc) - CView* { return new CTextEditView(); }); CViewFactory::Instance().RegisterView(ID_VIEW_STAT, [](CDocument* pDoc) - CView* { return new CStatView(); }); CViewFactory::Instance().RegisterView(ID_VIEW_PREVIEW, [](CDocument* pDoc) - CView* { return new CPreviewView(); }); // ... 后续创建主框架 }优势总结新增视图只需在InitInstance()中注册一行 lambda无需碰CMainFrame支持插件化外部 DLL 可调用CViewFactory::Instance().RegisterView()注入新视图便于单元测试可为ViewCreator注入 Mock 文档对象内存可控m_viewMap确保每个视图 ID 只创建一次实例避免重复new。我在这套机制上迭代了七年从最初的手动switch切换到后来用宏生成注册表再到现在的std::function工厂每一次重构都源于真实项目中“加一个视图就要改三处”的痛苦。现在我的团队新人接手 SDI 项目只要会写CView派生类就能在五分钟内注册一个新视图并上线——这才是架构该有的样子。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

医疗健康类网站建设的技术与安全要点:架构、数据保护与内容审核 2026/10/2 20:51:29

医疗健康类网站建设的技术与安全要点:架构、数据保护与内容审核

说明:本文是技术团队的工程笔记,不构成法律意见。具体监管口径以主管部门和机构合规部门意见为准。医疗健康类网站和普通企业站在技术栈上差别不大,真正拉开差距的是安全设计、数据处理和内容管理。本文按架构、安全、数据、功能边界、内容审…

阅读更多 →
STM32F103C8T6 CAN 通讯 2026/10/2 20:51:29

STM32F103C8T6 CAN 通讯

1.硬件原理图2.CAN初始化void main_can(void) { //SystemInit(); //设置系统时钟为72MKeyInit(); //按键管脚初始化//LED_GPIO_Config();//LED管脚初始化CAN_GPIO_Config();//CAN管脚初始化CAN_NVIC_Configuration(); //CAN中断初始化 CAN_INIT();//CA初始化N模块 // …

阅读更多 →
加性偏移外推提升参数识别可靠性 2026/10/2 20:51:29

加性偏移外推提升参数识别可靠性

## 1. 核心功能模块 ### 1.1 ODE模型定义 - **Case A**: 结构性不可识别 ODEpythondef ode_caseA(t, x0, theta):theta1, theta2 thetalam theta1 theta2return x0 * np.exp(-lam * t)Case B: 先验截断 ODEdef ode_caseB(t, x0, theta):theta1 theta[0]return x0 * np.exp(…

阅读更多 →
Webpack的分包策略面试题 2026/10/2 20:51:29

Webpack的分包策略面试题

一、什么是 Webpack 的代码分割?有什么好处?有哪些策略? 1. 核心思路(一句话)代码分割就是把原本需要一起加载的模块拆成多个 Chunk,让“首屏必需代码”和“非首屏代码”按需加载,同时把稳定的公…

阅读更多 →
MySQL数据库中事务并发问题以及隔离级别 2026/10/2 20:51:29

MySQL数据库中事务并发问题以及隔离级别

1.脏读定义:一个事务读到了另一个事务尚未提交的数据。- 场景:T2 更新数据但未提交;T1 读取到 T2 未提交的修改;若 T2 回滚,T1 读到的数据无效- 本质:读取到临时、可能回滚的无效数据- 隔离级别&#xff1a…

阅读更多 →
MySQL InnoDB 并发控制核心原理:事务、隔离级别、MVCC 与锁机制 2026/10/2 20:51:23

MySQL InnoDB 并发控制核心原理:事务、隔离级别、MVCC 与锁机制

一、前言 本文从 InnoDB 两套读写模型切入,结合官方术语一致性非锁定读(快照读)、一致性锁定读(当前读),逐层解析事务 ACID、四大隔离级别、MVCC 底层原理、锁体系与幻读误区,搭配时序案例与线上…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉