同时显示同一文档的多个视图

时间:2011-04-07 16:32:30

标签: c++ visual-studio model-view-controller mfc

如何说服MFC Doc / View架构让我同时显示同一文档的两个不同视图?

例如,假设我的CDocument子类代表某些描述的存档 我想要一个UI,其中该存档中所有条目的名称显示在左侧窗格的CListView子类中,而当前所选条目的详细信息显示在CEditView子目录中右侧窗格中的-class。

CSingleDocTemplate似乎只允许连接一个文档,一个框架和一个视图。我仍然想要一个SDI应用程序,但我想要一个文档和两个不同的视图 - 这不是一个好的Doc / View架构的重点吗?

3 个答案:

答案 0 :(得分:7)

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的{​​{1}}包含对当前文档的引用OnCreateClient(Framewindow知道关于这个文件)。 MFC在m_pCurrentDoc(ViewCore.cpp)中使用它将视图添加到文档:CView::OnCreate并在视图中设置文档指针m_pCurrentDoc->AddView(this)

因此,随后调用您文档的m_pDocument将会同时处理这两种观点。

答案 1 :(得分:2)

根据评论修改:

好的,你所追求的是静态分割器窗口。创建它的最简单方法(我知道)是从SDI MFC项目开始,并告诉它你想要一个拆分器窗口(在应用程序向导中,在“用户界面功能”下,选中“拆分窗口”)。这将创建一个动态分割器 - 即,它只从一个窗格开始,你可以通过拖动分割条创建一个 - 但是当你这样做时,你只会得到两个相同的视图(尽管你可以单独滚动它们)彼此)。

然后我们需要做一些工作,将其从动态分割器转变为静态分割器。最好从查看动态拆分器的代码开始。如果您查看该应用的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。然后我们将获取该视图的文件,并将它们添加到我们的原始项目中。为了便于解决问题,我们希望确保第二个项目的文档类使用与第一个项目相同的名称。

此时,我们有第二个视图的代码,但连接到其他任何视图,因此它创建的视图将不可见。为了使其可见,我们需要将其标题包含在CMainframe.cpp(或其在目标项目中的任何名称)中。然后我们回到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”,但(我认为)应该相当明显要做什么更改重新安排观点。

当然,从那里开始,你必须在每个视图中编写代码来做该视图应该做的任何事情 - 但是因为每个视图仍然是一个单独的普通视图,每个都是相当独立的,所以发展就像正常一样。唯一重要的区别是您必须遵循使文档无效的规则,并且(特别是如果其中一个视图的更新成本很高)您可能需要考虑使用提示来告知数据的哪些部分已失效,因此您可以编写每个视图以仅更新所需的内容,而不是每次都重新绘制所有数据。

答案 2 :(得分:1)

也许这篇文章Multiple Views Using SDI就是您所需要的。