我如何说服MFC文档/视图架构让我同时显示同一文档的两个不同视图?
例如,假设我的CDocument
子类表示一些描述的存档。
我想要一个UI,在该存档中的所有条目的名称都呈现在左侧窗格的CListView
子类中,而当前选择的条目的详细信息显示在右侧窗格的CEditView
子类中。
CSingleDocTemplate
似乎只允许连接一个文档,一个框架和一个视图。我仍然想要一个SDI应用程序,但是我想要一个文档和两个不同的视图——这难道不是一个好的文档/视图架构的全部意义吗?
SDI意思是"单个文档接口",它限制你一次只能打开一个文档,但不限制你可以为这个文档打开的视图的数量。
在SDI应用程序中打开多个视图最常见的方法可能是Splitter Windows。你添加一个视图到CSingleDocTemplate
(这并不重要)
pDocTemplate = new CSingleDocTemplate(
IDR_MYRESOURCEID,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyFrameWnd),
RUNTIME_CLASS(CMyListView));
你的框架窗口得到一个CSplitterWnd m_wndSplitter
的实例,你重载OnCreateClient
虚函数:
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
VERIFY(m_wndSplitter.CreateStatic(this,1,2)); // one row / two columns
VERIFY(m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CMyListView),
CSize(300,300),pContext));
VERIFY(m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CMyEditView),
CSize(300,300),pContext));
return TRUE;
}
本例创建一个包含一行和两列的拆分器窗口。在拆分器的左侧是CMyListView
类型的视图,右侧是CMyEditView
类型的视图。
你甚至可以嵌套多个拆分器窗口,在框架窗口中创建任意复杂的视图集合。
下面是一个小教程,展示了如何在SDI应用程序中使用拆分器窗口:
http://www.codeproject.com/KB/splitter/splitterwindowtutorial.aspx编辑
将您添加到拆分器的视图与文档连接在一起在内部执行MFC: CCreateContext* pContext
传递给OnCreateClient
,其中包含对当前文档的引用m_pCurrentDoc
(Framewindow知道这个文档)。MFC在CView::OnCreate
(ViewCore.cpp)中使用此方法将视图添加到文档:m_pCurrentDoc->AddView(this)
并在视图中设置文档指针m_pDocument
。
因此,随后调用文档的UpdateAllViews
将照顾这两个视图。
根据注释修改:
好的,你需要的是一个静态拆分器窗口。最简单的方法(我知道)是创建一个SDI MFC项目,并告诉它你想要一个拆分窗口(在AppWizard中,在"用户界面功能"下,选中"拆分窗口")。这将创建一个动态分条器——也就是说,它开始时只有一个窗格,你可以通过拖动分条器来创建第二个窗格——但是当你这样做时,你只会得到两个相同的视图(尽管你可以分别滚动它们)。
然后我们需要做一些工作把它从一个动态拆分器变成一个静态拆分器。最好从查看动态拆分器的代码开始。如果你查看该应用程序的CMainFrame
,你会发现它有:
CSplitterWnd m_wndSplitter;
如果你查看主机的OnCreateClient
,你会发现类似这样的内容:
return m_wndSplitter.Create(this,
2, 2, // TODO: adjust the number of rows, columns
CSize(10, 10), // TODO: adjust the minimum pane size
pContext);
这是我们需要改变的——Create
是创建动态拆分器的。我们需要摆脱它,并创建一个静态拆分器。第一步是创建另一个视图类——现在,我们只有一个视图类,但我们想要两个,每个窗格一个。
创建第二个视图类最简单的方法(据我所知)是运行第二个VS副本,并创建另一个(单独的)应用程序。我们将告诉它基于CListView
的应用程序的视图类。然后,我们将获取该视图的文件,并将它们添加到原始项目中。为了便于连接,我们希望确保第二个项目使用与第一个项目相同的名称作为其文档类。
OnCreateClient
,用下面的代码替换上面引用的代码:
CRect rect;
GetClientRect(&rect);
BOOL ret = m_wndSplitter.CreateStatic(this, 2, 1); // 2 rows, 1 column of views
// row 0, column 0 will be the "OriginalView". The initial split will be
// in half -- i.e., each pane will be half the height of the frame's client
//
m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(OriginalView), CSize(1, rect.Height()/2), pContext);
m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(ListBasedView), CSize(1, rect.Height()/2), pContext);
目前,我已经创建了一个水平分割,与"OriginalView"在上窗格,和"ListBaseView"在下窗格,但(我认为)它应该是相当明显的改变,使重新安排视图。
当然,从那里开始,你必须在每个视图中编写代码来做视图应该做的事情——但是由于每个视图仍然是一个独立的、正常的视图,每个视图都是合理独立的,所以开发就像正常一样。唯一显著的区别是必须遵循使文档无效的规则,并且(特别是如果其中一个视图的更新成本很高)您可能需要考虑使用提示来告诉哪些部分的数据已经无效,这样您可以编写每个视图只更新需要的部分,而不是每次都重新绘制所有数据。