Now let's say some things about taking control of the MDI. Let's review some of the material from the Document-View section of Chapter 5: Software Design Patterns.
As the user's interface with the program, the CView has two different roles. On the one hand, it's the user's channel to the program's data. And on the other hand, the CView is a graphical Windows object that sits inside two levels of frame windows.
In terms of data, we have the hierarchy shown in Table 23.7. We include the CMainFrame here because we do sometimes keep program data in there.
Application class |
MFC parent class |
Defined in |
---|---|---|
CPopApp |
CWinApp |
Pop.* |
CMainFrame |
CMDIFrameWnd |
MainFrm.* |
CPopDoc |
CDocument |
PopDoc.* |
CPopView |
CView |
PopView.* |
And in terms of the window tree we have the hierarchy shown in Table 23.8. Note that the use of a CSplitterWnd adds an extra layer.
Application class |
MFC parent class |
Defined in |
---|---|---|
CMainFrame |
CMDIFrameWnd |
MainFrm.* |
CChildFrame |
CMDIChildWnd |
ChildFrm.* |
CSplitterWnd |
CSplitterWnd |
ChildFrm.* |
CPopView |
CView |
PopView.* |
As we summarized it in Tables 23.2 and 23.4, there are a number of special MFC functions for getting a pointer to any one of these application classes from inside any of the other ones. Looking back at those tables you'll recall that the method for getting from the CPopApp down to the CPopViews is pretty ugly. But it's easier to move up the hierarchy of the window tree.
For reasons that we'll discuss shortly, we need a way for CMainFrame to count the number of open CChildFrame windows. So we write a CPopApp::getMDIChildCount() method that looks like this. Remember that a CMDIChild is what we call a child frame; in our program it's a CChildFrame object.
int CMainFrame::getMDIChildCount() { int count = 0; CMDIChildWnd *next, *current = MDIGetActive(); //Current focus child wnd. if (!current) return 0; while (TRUE) //We'll exit this endless loop with a return inside it. { count++; MDINext(); //Move the child frame activation to the cyclically next one. next = MDIGetActive(); if (next == current) return count; } }
To give our Pop Framework a good behavior, we don't want to ever waste screen space by showing the user the boring gray MDI client window that shows up when no views at all are open. What we want is the following.
If there is only one child frame open, we want it to automatically maximize into the main frame.
If there is more than one child frame open, we want them to automatically tile themselves into the main frame.
If we resize the main frame, we want the child frames to retile so as to continue filling the frame.
You might think that a feature like this would be built into the MDI, but it isn't. This isn't necessarily a failing, this just doesn't happen to be something that the designers anticipated someone wanting to do. And, frankly, it isn't all that important a feature for the Pop Framework. It's developer gold-plating again. After all, why would we want to open more than one game document in the first place? And since we're using splitter windows anyway, we don't have much reason for opening a second view of a given doc either. Well, looking at how it works is a good way to gain a little more understanding of how MDI works. But if you're in a hurry, feel free to skip the rest of this section for now. You can always come back to it later.
What we've done here is to add a new BOOL _autotile variable to our CMainFrame class. We give it a default value of TRUE, and we put a menu item controlling it as Windows | Autotile. Our CMainFrame handles this menu item's messages.
The MFC call for tiling a window 'vertically' is CMDIMainFrame::MDITile(MDITILE _VERTICAL). We need for our CMainFrame object to call this method whenever we do any of these five things listed in the first column of Table 23.9. In the second column we list the method that we need to override to make our autotiling work.
Action |
MFC method to override |
---|---|
Open a new child frame view of an open document with Window | New |
CMainFrame::OnWindowNew |
Open a new document with File | New |
CPopApp::OnFileNew |
Open a saved document with File | Open |
CPopApp::OnFileOpen |
Resize the main frame window |
CMainFrame::OnSize |
Close a child frame window |
CChildFrame::OnDestroy |
To make things more complicated, if there is only one open child frame, we don't want to call MDITile, because that puts our frame inside the client area of the main frame, and we have a wasteful extra caption bar. Instead, if we only have one child frame open, we need to make sure that this one view is maximized.
In order to distinguish between the case where we have one maximized view and the case where we have a number of views that we want to tile, we need a way to count how many views are open. And this is where we have to use that CMainFrame::getMDIChildCount() method.
As an example of the changes we had to make, we print our version of CMainFrame::OnWindowNew below. (In the Pop Framework the menu selection for invoking OnWindowNew is in fact labeled Window | Additional View of Current Game, rather than with the more standard label Window | New.)
void CMainFrame::OnWindowNew() { CMDIFrameWnd::OnWindowNew(); //Call base class handler. //Our code. RR. /* Keep tiling so the child frames stay "stuck" to the outer window. The CMainFrame::getMDIChildCount() is a helper function I added. Gets the number of existing child frames, including this one. If there are more than one child, then I tile, otherwise I don't tile because whenever I have only one child, I've maximized it. You don't want to tile when a single window is maximized as this brings its caption bar back down into the MDI client area. */ if (_autotile && getMDIChildCount() > 1) // MDITile(MDITILE_VERTICAL); }
We make a similar change to the CMainFrame::OnSize, the CPopApp::OnFileNew(), CPopApp::OnFileOpen(), and the CChildFrame::OnDestroy().
In the case of CChildFrame::OnDestroy() we have to work a little harder. This is the method that gets called when you kill off a child frame. Here it's possible that we had two open child frames and are now closing one of them. If only one child view is going to remain, then we need to maximize the single remaining child frame. If you're interested in how this works, you can look at the code in the Pop Framework's ChildFrame.cpp.