Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Displaying multiple simultaneous Views of the same Document

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?

like image 498
GrahamS Avatar asked Apr 07 '11 16:04

GrahamS


2 Answers

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.

like image 60
Slauma Avatar answered Oct 09 '22 05:10

Slauma


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.

like image 26
Jerry Coffin Avatar answered Oct 09 '22 04:10

Jerry Coffin