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

发布于 2024-10-31 04:44:14 字数 316 浏览 1 评论 0原文

如何说服 MFC 文档/视图体系结构让我同时显示同一文档的两个不同视图?

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

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

How do I persuade the MFC Doc/View architecture to let me simultaneously display two different views of the same document?

For example, say my CDocument sub-class represents an archive of some description.
I want a UI where the names of all the entries in that archive are presented in a CListView sub-class in the left hand pane, while the details of the currently selected entry are displayed in a CEditView sub-class in the right hand pane.

The CSingleDocTemplate only seems to allow for connecting up one document, one frame and one view. I still want an SDI application, but I want one document and two different views - isn't that the whole point of a good Doc/View architecture?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

嘿哥们儿 2024-11-07 04:44:14

SDI 的意思是“单一文档界面”,它限制您一次只能查看一个文档,但不限制您可以为此文档打开的视图数量。

在 SDI 应用程序中打开多个视图的最常见方法可能是分割窗口

您向 CSingleDocTemplate 添加一个视图(无论是哪一个)

pDocTemplate = new CSingleDocTemplate(
    IDR_MYRESOURCEID,
    RUNTIME_CLASS(CMyDoc),
    RUNTIME_CLASS(CMyFrameWnd),
    RUNTIME_CLASS(CMyListView));

您的框架窗口都会获取 CSplitterWnd m_wndSplitter 的一个实例,并且您会重载 OnCreateClient code> virtual function:

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< /code> 传递给 OnCreateClient 包含对当前文档的引用 m_pCurrentDoc(Framewindow 知道该文档)。 MFC 在CView::OnCreate (ViewCore.cpp) 中使用此函数将视图添加到文档中:m_pCurrentDoc->AddView(this) 并设置文档指针<视图中的代码>m_pDocument

因此,后续对文档的 UpdateAllViews 的调用将处理这两个视图。

SDI means "Single Document Interface", it restricts you to only one single document at a time but not in the number of views you can open for this document.

The probably most common approach to open multiple views in an SDI application are Splitter Windows.

You add one view to the CSingleDocTemplate (it doesn't matter which one)

pDocTemplate = new CSingleDocTemplate(
    IDR_MYRESOURCEID,
    RUNTIME_CLASS(CMyDoc),
    RUNTIME_CLASS(CMyFrameWnd),
    RUNTIME_CLASS(CMyListView));

Your frame window gets an instance of a CSplitterWnd m_wndSplitter and you overload the OnCreateClient virtual function:

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;
}

This example creates a splitter window with one row and two columns. On the left side in the splitter is a view of type CMyListView and on the right side is a view of type CMyEditView.

You can even nest multiple splitter windows one in each other to create arbitrary complex view collections in the frame window.

Here is a small tutorial which shows how to work with splitter windows in a SDI application:

http://www.codeproject.com/KB/splitter/splitterwindowtutorial.aspx

Edit

Wiring up of the views you add to the splitter with the document does MFC internally: CCreateContext* pContext which is passed into OnCreateClient contains a reference m_pCurrentDoc to the current document (the Framewindow knows about this document). MFC uses this in CView::OnCreate (ViewCore.cpp) to add the View to the Document: m_pCurrentDoc->AddView(this) and to set the document pointer m_pDocument in the View.

Therefore subsequent calls of UpdateAllViews of your document will take care of both views.

迷乱花海 2024-11-07 04:44:14

根据评论修改:

好的,您想要的是静态拆分窗口。 (据我所知)创建此窗口的最简单方法是从 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”位于下部窗格中,但是(我认为)应该相当明显地进行哪些更改来重新排列视图。

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

Revised based on comment:

Okay, what you're after is a static splitter window. The easiest way (I know of) to create this is to start with an SDI MFC project, and tell it that you want a splitter window (in the AppWizard, under "User Interface Features", check "Split Window"). That will create a dynamic splitter -- i.e., it starts with only one pane, and you can create a second by dragging the splitter bar -- but when you do, you'll just get two identical views (though you can scroll them separately from each other).

Then we have to do a little work to turn that from a dynamic splitter into a static splitter. It's probably best to start by looking at the code for the dynamic splitter. If you look in that app's CMainFrame, you'll find that it has:

CSplitterWnd m_wndSplitter;

If you look in the main-frame's OnCreateClient, you'll find something like this:

return m_wndSplitter.Create(this,
    2, 2,               // TODO: adjust the number of rows, columns
    CSize(10, 10),      // TODO: adjust the minimum pane size
    pContext);

This is what we need to change -- the Create is what creates the dynamic splitter. We need to get rid of that, and create a static splitter instead. The first step to do that is create another view class -- right now, we have only one view class, but we want two, one for each pane.

The easiest way (that I know of) to create our second view class is to run a second copy of VS, and create another (separate) application. We'll tell it to base the view class for that application off of CListView. We'll then take the files for that view, and add them to our original project. To make it easy to hook things up, we want to make sure this second project uses the same name for its document class as the first one did though.

At that point, we have the code for our second view, but it's not connected to anything else, so the view it creates won't be visible. To make it visible, we need to include its header into the CMainframe.cpp (or whatever name it has in your target project). We then get back to the OnCreateClient, and replace the code quoted above with something like this:

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);

For the moment, I've created a horizontal split, with the "OriginalView" in the upper pane, and the "ListBaseView" in the lower pane, but (I think) it should be fairly obvious what changes to make to rearrange the views.

From there, of course, you'll have to write the code in each view to do whatever it is that view is supposed to do -- but since each is still a separate, normal view, each is reasonably independent, so the development is about like normal. The only significant difference is that you must follow the rules for invalidating your document, and (especially if one of the views is expensive to update) you may want to consider using hints to tell what part of the data has been invalidated so you can write each view to update only what's needed instead of just re-drawing all its data every time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文