【问题标题】:Displaying multiple simultaneous Views of the same Document显示同一文档的多个同时视图
【发布时间】:2011-04-07 16:32:30
【问题描述】:

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

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

CSingleDocTemplate 似乎只允许连接一个文档、一个框架和一个视图。我仍然想要一个 SDI 应用程序,但我想要一个文档和两个不同的视图——这难道不是一个好的 Doc/View 架构的全部意义吗?

【问题讨论】:

  • 注意:我见过MSDN article on "Adding Multiple Views to a Single Document",但它侧重于能够在两个或多个视图之间切换,而不是同时显示它们。
  • 这是我讨厌微软工具的一件事——当你完全按照他们设想的方式做事时,它们很棒,但要超越界限,BAM!事情变得困难了一个数量级。

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


【解决方案1】:

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 调用将处理这两个视图。

【讨论】:

  • 感谢@Slauma,这听起来正是我所需要的。有一件事我有点困惑。你说“向 CSingleDocTemplate 添加一个视图(不管是哪个视图)”。这样就可以告诉 Document 关于一个 View 的信息,但是 Document 是如何了解其他 View 的呢?即要调用CDocument::UpdateAllViews()GetNextView() 正常工作,文档需要了解所有视图,但我在您的代码中看不到任何CDocument::AddView() 调用。
  • @GrahamS:我在答案中附加了一个简短的编辑部分。
【解决方案2】:

根据评论修改:

好的,您需要的是静态拆分窗口。创建它的最简单方法(我知道)是从 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 的基础。然后,我们将获取该视图的文件,并将它们添加到我们的原始项目中。为了便于连接,我们希望确保第二个项目的文档类与第一个项目使用相同的名称。

此时,我们有了第二个视图的代码,但它没有连接到其他任何东西,因此它创建的视图将不可见。为了让它可见,我们需要将它的头文件包含到 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”,但是(我认为)应该很明显地做出哪些更改重新排列视图。

当然,从那里开始,您必须在每个视图中编写代码 以执行该视图应该执行的任何操作 - 但由于每个视图仍然是一个单独的正常视图, 每个都是相当独立的,所以发展是正常的。唯一显着的区别是您必须遵循使文档无效的规则,并且(尤其是如果其中一个视图的更新成本很高)您可能需要考虑使用提示来判断数据的哪一部分已失效,因此您可以编写每个视图以仅更新需要的内容,而不是每次都重新绘制所有数据。

【讨论】:

  • 感谢@Jerry,+1,@Slauma 解决方案的方便扩展。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-04
  • 2013-10-04
相关资源
最近更新 更多