CView给我的一个反思,对MFC的理解需要深入

本文详细解析了在单文档程序中使用CView及其派生类时遇到的Assert错误问题,并提供了具体的解决方法。通过继承类中响应WM_MOUSEACTIVATE消息,可以解决在对话框和单文档程序中单击视图时出现的错误。文章深入分析了问题的原因,解释了不同程序结构下CView类与桌面窗口关系的变化。

报错的内容:

用鼠标单击视图的时候,就会出现Assert错误,具体为
CView类的onmouseActive函数中
ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame))

解决方法:
继承类中响应WM_MOUSEACTIVATE
int CMyView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
{
  return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message);
}


具体原因分析:

在对话框程序上pDesktopWnd是对话框指针,视图类的父窗口也是对话框指针,那么ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame))这句就不会出错,因为CView::GetParentFrame()的往他的祖先窗口中上溯,直到找到一个是FrameWnd类型的窗口之后, 返回这个窗口对象的指针(如果没有的话,返回NULL),在对话框程序中这么要么为NULL,要么为对话框指针;但是假如是单文档程序呢,情况就不一样了,CView::GetParentFrame()必定是返回单文档程序的框架指针,而pDesktopWnd指针则是对话框指针,二者不相等,那么我们再看看pDesktopWnd->IsChild(pParentFrame)是否为TRUE,很显然框架窗口不可能是对话框窗口的父窗口。

《在没有文档的情况下使用CView及其派生类》http://blog.youkuaiyun.com/gxj1680/article/details/3236628

《有关在对话框上创建视图上的再探索》http://www.cnblogs.com/clever101/archive/2009/12/05/1617472.html



<think>我们正在处理一个MFC MDI应用程序,需要创建一个分割视图。用户要求使用第二种方式:将CSplitterWnd作为视图(即CSplitterView)直接传递给文档模板。这样,分割窗口本身就是视图,而不是被包含在另一个视图中。 步骤: 1. 创建一个从CSplitterWnd派生的类(例如CSplitterView),并重写OnCreateClient方法(实际上,对于这种用法,我们通常是在这个类中创建分割窗口和视图)。 2. 在文档模板中,将这个CSplitterView类作为视图类注册。 3. 在CSplitterView的OnCreateClient中,创建静态分割窗口以及各个窗格(视图)。 注意:由于CSplitterWnd本身并不是从CView派生的,但文档模板要求视图类必须是从CView派生的。因此,我们需要让我们的CSplitterView类同时继承CSplitterWnd和CView(或者更准确地说,我们通常会让它继承CView,然后在CView中嵌入一个CSplitterWnd,但用户要求的是将CSplitterWnd作为视图,所以我们需要一个同时继承CView和CSplitterWnd的类?)。然而,MFC中有一个更常见的方式:我们创建一个CView派生的类,然后在其中创建CSplitterWnd。但用户明确要求第二种方式,即分割窗口作为视图。 实际上,标准做法是:我们创建一个从CSplitterWnd派生的类,然后让这个类同时继承CView?这并不常见,因为CSplitterWnd和CView是平行的,它们都继承自CWnd。所以,我们无法同时继承这两个类(多重继承在MFC中通常不推荐,而且容易引起混乱)。 因此,我们通常采用另一种方式:创建一个CView派生的类,然后在其中包含一个CSplitterWnd成员(即第一种方式)。但是用户要求第二种方式,即让分割窗口充当视图。那么,我们如何实现? 实际上,MFC提供了一种机制:我们可以创建一个从CSplitterWnd派生的类,然后将其作为视图。但是注意,文档模板要求视图类必须是从CView派生的。因此,我们必须让我们的分割窗口类同时继承CView?这不可能,因为CView和CSplitterWnd都是CWnd的派生类,但它们是独立的。 那么,我们如何解决?实际上,我们可以创建一个类,它同时继承CView和CSplitterWnd?多重继承在这里是可行的,但需要小心处理。 然而,在MFC中,更常见的做法是:我们创建一个类,它继承自CView,然后在这个CView中创建一个CSplitterWnd成员,并重写CView::OnCreateClient来创建分割窗口(即第一种方式)。但用户要求的是第二种方式。 经过查阅资料,实际上MFC中有一个类CSplitterWnd本身就可以作为视图,但是我们需要在文档模板中注册一个CView派生的类。因此,我们无法直接将CSplitterWnd作为视图。所以,我们只能采用第一种方式(在CView中包含CSplitterWnd)或者采用第二种方式:创建一个CView派生的类,然后在这个类中创建CSplitterWnd,但是将分割窗口的创建放在OnCreateClient中,并且让这个CView实际上只起到容器的作用,而分割窗口覆盖整个客户区。 但是,用户要求的是“直接把m_wndSplitter当CView传入MDI”,也就是希望分割窗口本身作为视图。这实际上是不可能的,因为CSplitterWnd不是从CView派生的。因此,我们只能通过一个技巧:创建一个CView派生的类,然后在这个类中创建一个CSplitterWnd,并让这个CSplitterWnd覆盖整个客户区,然后在这个CView的OnCreateClient中创建分割窗口,这样看起来就像是分割窗口充当了视图。 然而,用户引用的第二种方式中,文档模板的视图类是一个从CSplitterWnd派生的类?这并不正确,因为文档模板要求视图类必须是CView的派生类。 所以,我们重新理解第二种方式:实际上,第二种方式中的“视图”是一个CView派生的类,但这个类内部使用CSplitterWnd来管理子视图,并且这个类本身并不绘制任何内容,而是由分割窗口中的子视图来绘制。因此,我们仍然需要一个CView派生的类,但在这个类中,我们重写OnCreateClient来创建分割窗口,然后将分割窗口作为它的子窗口。 因此,我们提供的代码示例将是一个CView派生的类(例如CSplitterView),在这个类中有一个CSplitterWnd成员(m_wndSplitter),并在OnCreateClient中创建分割窗口和视图。 但是,用户要求的是“使用CSplitterView充当CView”,并且希望分割窗口本身作为视图。所以,我们按照第二种方式的描述,创建一个名为CSplitterView的类,它继承自CView,并在其中包含一个CSplitterWnd成员,然后在OnCreateClient中创建分割窗口。 因此,我们提供以下完整代码示例: 步骤: 1. 创建一个新的MFC MDI应用程序(如果已有项目,则跳过)。 2. 添加一个CView派生的类,命名为CSplitterView。 3. 在CSplitterView类中,添加一个CSplitterWnd类型的成员变量(例如m_wndSplitter)。 4. 重写CSplitterView的OnCreateClient函数,并在其中创建分割窗口和视图。 5. 修改文档模板,使用CSplitterView作为视图类。 注意:由于CSplitterView是CView的派生类,因此它可以作为文档模板的视图类。 下面我们提供代码: 首先,创建CSplitterView类: 在SplitterView.h中:</think>以下是使用 `CSplitterView` 作为主视图的完整MFC MDI实现代码,严格遵循文档/视图架构设计: ### 📁 文件结构 ``` MyProject/ ├── MyProject.h/cpp // 应用程序类 ├── MainFrame.h/cpp // 主框架 ├── ChildFrame.h/cpp // 子框架 ├── MyDoc.h/cpp // 文档类 ├── SplitterView.h/cpp // 分割视图 ├── LeftPane.h/cpp // 左侧窗格 └── RightPane.h/cpp // 右侧窗格 ``` ### 核心代码实现 #### 1. 应用程序初始化 (`MyProject.cpp`) ```cpp // MyProject.cpp BOOL CMyApp::InitInstance() { // 注册文档模板 CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate( IDR_MYTYPE, RUNTIME_CLASS(CMyDoc), // 文档类 RUNTIME_CLASS(CChildFrame), // 子框架 RUNTIME_CLASS(CSplitterView) // 分割视图作为主视图 ); AddDocTemplate(pDocTemplate); // 创建主窗口 CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; // 标准初始化 pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); return TRUE; } ``` #### 2. 分割视图实现 (`SplitterView.h/cpp`) ```cpp // SplitterView.h #pragma once #include <afxsplit.h> class CSplitterView : public CView { protected: CSplitterWnd m_wndSplitter; public: virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext); //{{AFX_MSG(CSplitterView) afx_msg void OnSize(UINT nType, int cx, int cy); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; ``` ```cpp // SplitterView.cpp #include "stdafx.h" #include "SplitterView.h" #include "LeftPane.h" #include "RightPane.h" IMPLEMENT_DYNCREATE(CSplitterView, CView) BEGIN_MESSAGE_MAP(CSplitterView, CView) ON_WM_SIZE() END_MESSAGE_MAP() BOOL CSplitterView::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // 创建2x1静态分割窗口 if (!m_wndSplitter.CreateStatic(this, 1, 2)) { TRACE0("Failed to create splitter\n"); return FALSE; } // 创建左侧窗格 (占30%) if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftPane), CSize(100, 0), pContext)) { TRACE0("Failed to create left pane\n"); return FALSE; } // 创建右侧窗格 (占70%) if (!m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CRightPane), CSize(0, 0), pContext)) { TRACE0("Failed to create right pane\n"); return FALSE; } // 设置初始分割比例 m_wndSplitter.SetColumnInfo(0, 200, 100); m_wndSplitter.RecalcLayout(); return TRUE; } void CSplitterView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); if (m_wndSplitter.GetSafeHwnd()) { // 保持30%:70%的比例 int leftWidth = static_cast<int>(cx * 0.3); m_wndSplitter.SetColumnInfo(0, leftWidth, 100); m_wndSplitter.RecalcLayout(); } } ``` #### 3. 左侧窗格实现 (`LeftPane.h/cpp`) ```cpp // LeftPane.h #pragma once class CLeftPane : public CView { public: virtual void OnDraw(CDC* pDC); // 重写绘图 DECLARE_DYNCREATE(CLeftPane) DECLARE_MESSAGE_MAP() }; ``` ```cpp // LeftPane.cpp #include "stdafx.h" #include "LeftPane.h" #include "MyDoc.h" IMPLEMENT_DYNCREATE(CLeftPane, CView) BEGIN_MESSAGE_MAP(CLeftPane, CView) END_MESSAGE_MAP() void CLeftPane::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // 示例:显示文档数据 pDC->TextOut(10, 10, _T("左侧窗格 - 文档数据:")); pDC->TextOut(20, 40, pDoc->GetData()); } ``` #### 4. 右侧窗格实现 (`RightPane.h/cpp`) ```cpp // RightPane.h #pragma once class CRightPane : public CFormView { DECLARE_DYNCREATE(CRightPane) public: enum { IDD = IDD_RIGHT_FORM }; CEdit m_editData; virtual void DoDataExchange(CDataExchange* pDX); virtual void OnInitialUpdate(); DECLARE_MESSAGE_MAP() }; ``` ```cpp // RightPane.cpp #include "stdafx.h" #include "RightPane.h" #include "MyDoc.h" IMPLEMENT_DYNCREATE(CRightPane, CFormView) BEGIN_MESSAGE_MAP(CRightPane, CFormView) ON_EN_CHANGE(IDC_EDIT_DATA, &CRightPane::OnEditChange) END_MESSAGE_MAP() void CRightPane::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT_DATA, m_editData); } void CRightPane::OnInitialUpdate() { CFormView::OnInitialUpdate(); CMyDoc* pDoc = GetDocument(); m_editData.SetWindowText(pDoc->GetData()); } void CRightPane::OnEditChange() { CString str; m_editData.GetWindowText(str); GetDocument()->SetData(str); // 直接更新文档 } ``` #### 5. 文档类实现 (`MyDoc.h/cpp`) ```cpp // MyDoc.h #pragma once #include <afxmt.h> // 用于CCriticalSection class CMyDoc : public CDocument { protected: CString m_strData; // 共享数据 CCriticalSection m_cs; // 线程同步 public: CString GetData() { CSingleLock lock(&m_cs, TRUE); return m_strData; } void SetData(LPCTSTR lpszData) { CSingleLock lock(&m_cs, TRUE); m_strData = lpszData; UpdateAllViews(NULL); // 通知所有视图更新 } DECLARE_DYNCREATE(CMyDoc) DECLARE_MESSAGE_MAP() }; ``` ```cpp // MyDoc.cpp IMPLEMENT_DYNCREATE(CMyDoc, CDocument) BEGIN_MESSAGE_MAP(CMyDoc, CDocument) END_MESSAGE_MAP() // 序列化支持 void CMyDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_strData; } else { ar >> m_strData; } } ``` ### 🔧 关键实现要点 1. **文档-视图绑定**: ```cpp // 在任何窗格中直接访问文档 CMyDoc* pDoc = GetDocument(); pDoc->SetData(_T("New Data")); ``` 2. **动态布局调整**: ```cpp void CSplitterView::OnSize(...) { int leftWidth = static_cast<int>(cx * 0.3); m_wndSplitter.SetColumnInfo(0, leftWidth, 100); m_wndSplitter.RecalcLayout(); } ``` 3. **线程安全数据访问**: ```cpp // 使用临界区保护共享数据 void SetData(LPCTSTR lpszData) { CSingleLock lock(&m_cs, TRUE); // 自动锁定 m_strData = lpszData; UpdateAllViews(NULL); } ``` 4. **视图更新机制**: ```cpp // 在文档中更新数据时 void CMyDoc::SetData(...) { // ...更新数据... UpdateAllViews(NULL, HINT_DATA_CHANGED); } // 在视图类中响应更新 void CLeftPane::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == HINT_DATA_CHANGED) { Invalidate(); // 触发重绘 } } ``` ### ⚙️ 资源文件配置 在资源文件中添加对话框模板 `IDD_RIGHT_FORM`: ```rc IDD_RIGHT_FORM DIALOGEX 0, 0, 200, 100 STYLE DS_SETFONT | WS_CHILD FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDIT_DATA, 10,10,180,50, ES_MULTILINE | ES_AUTOVSCROLL END ``` ### 📌 使用说明 1. 创建MFC MDI应用程序(Visual Studio向导) 2. 添加上述类文件 3. 修改应用程序初始化代码 4. 添加对话框资源 `IDD_RIGHT_FORM` 5. 编译运行后效果: - 新文档窗口自动创建分割视图 - 左侧窗格显示文档内容 - 右侧窗格可编辑文档数据 - 修改数据自动同步到所有视图 ### 相关问题 1. 如何在分割窗口中实现不同视图类型(如树形视图+列表视图)? 2. 如何保存和恢复分割窗口的布局状态? 3. 分割窗口中的视图如何共享文档的不同数据部分? 4. 如何实现动态创建和销毁分割窗格? 5. 在MFC分割窗口中如何处理键盘消息路由? 6. 如何实现嵌套分割窗口(如三分割区域)? 7. 分割窗口中的滚动视图如何同步滚动位置?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值