Now you can begin, in small ways at first, to take advantage of the document/view architecture in MFC. The basic idea is that storing and manipulating data is separated from viewing it. A document object stores your data structures and is associated with a view object that knows how to display the data and manage user interaction with it, such as selecting and editing the data.
Figure 6 shows the document/view relationship conceptually. Drawing in MFC is managed by the view's OnDraw member function. Migrating to this model involves moving your WM_PAINT code into that function.
The document and view are closely associated objects . The view has a way to access the document's contents, which it must do to display the document's data and to inform the document of editing changes. The document also has a way of informing the view when the display needs to be updated. This might seem redundant, except that documents sometimes support multiple views of the same data. For example, the New Window command on the window menu in many applications supports opening a second frame window on the same document. Another example is splitter windows, like those in many applications, including Microsoft Excel™. When view A informs the document of an edit, you can call the document's UpdateAllViews member function to alert all related views that they must update themselves to obtain the changes. Multiple views usually show the same data in different ways. For example, one view of a hypothetical document might show its data in tabular form, while another shows the data as a graph.
Structurally, a view is a child window embedded in the client area of a frame window. In an SDI application, picture a child window that covers the entire client area of the application frame window. In an MDI application, views of documents are embedded in MDI child window frames. (The MDI child frames, in turn, are embedded in a special child window itself embedded in the application's main frame window. This child is called MDICLIENT; it manages the currently open MDI children.) When splitter windows are used, the client area real estate is divided among multiple view windows. The document keeps a list of pointers to all of the views of its data.
MFC's CView class is the base class for all view classes, including CScrollView, a view that manages scrolling for you; CFormView, a view based on a dialog template resource; CRecordView, a form view associated with a CRecordset object for database access; and other view classes. CView wraps the view window's HWND and supplies member functions for drawing, printing, and other actions. (Printing is discussed later, in "Moving Your Printing Code to MFC." Other view classes are discussed later in "Use a Different View Class.")
The most interesting CView member function is OnDraw. CView also has an OnPaint function, which the framework calls in response to the WM_PAINT message. But instead of putting your painting code in OnPaint, you put it in OnDraw. OnPaint creates and prepares an appropriate device context (an object of class CDC), then calls your OnDraw override, passing it a pointer to the prepared CDC object.
Figure 7 describes the drawing process.
The only part of the process you're responsible for is writing the drawing code in OnDraw.
Here is a simple OnDraw function that draws some text at a given location within the view:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int x, y;
x = y = 0;
pDC->TextOut(x, y, "Hello, world!");
}
The key features of this code are:
What's different from C about this TextOut call? The syntax. First, there is no HDC-type parameter. Second, instead, TextOut here is a member function of MFC class CDC, not the familiar GDI call. You're passed a pointer to a CDC object, which wraps the HDC. You call MFC's version of TextOut through that pointer. CDC::TextOut does call the familiar Windows API function underneath, but the call you make is somewhat simplified. You'll see more of this in Make GDI Calls the MFC Way.
Tip The syntax shown is for accessing class members. In C++, classes and structures are closely related, so the syntax is like that for accessing struct members in C. A class and a struct are the same thing, with different keywords and default accessibility constraints. Both can have member variables and member functions, and sections of either can be declared public, protected, or private.
The GetDocument call returns a pointer to a CDocument-derived class. The example doesn't use that pointer for anything, but it could use it to access the data stored in the view's associated document and to call document member functions.
The ASSERT_VALID macro is part of MFC's diagnostic support, used to validate the document's internal state before attempting to do anything with the pDoc pointer. You'll learn about this macro later, in "Hook Up MFC Diagnostics."
In SHOWDIB, when you move your painting code to OnDraw, you have to make some changes to it, as shown in the following code from SHOWDIB:
void CShowDibView::OnDraw(CDC* pDC)
{
HWND hWnd = GetSafeHwnd(); // add these lines
HDC hDC = pDC->GetSafeHdc();
/* If we have updated more than once, the rest of our
* window is not in some level of degradation worse than
* our redraw... we need to redraw the whole area
*/
if (UpdateCount > 1) {
::ValidateRect(hWnd, NULL); // replace Begin/EndPaint
UpdateCount = 0;
::InvalidateRect(hWnd, (LPRECT) (NULL), 1);
return; // change break to return
}
// remove BeginPaint
AppPaint(hWnd,
hDC,
::GetScrollPos(hWnd,SB_HORZ),
::GetScrollPos(hWnd,SB_VERT) );
// remove EndPaint
// delete break statement here
}
After moving the painting code to OnDraw, delete the WM_PAINT case from WindowProc. You can also remove the definition:
PAINTSTRUCT ps;
from WindowProc. If not, you're warned about an unreferenced variable.
Aside from the few changes described below, this version of OnDraw makes minimal changes.
Proper MFC usage, as you'll see later in Make GDI Calls the MFC Way, would differ quite a bit from what you see here. This code ignores the pointer to a CDC class object passed as a parameter, except to obtain a safe HDC to pass to the AppPaint function. The CDC object encapsulates a fully prepared device context.
The most essential change to the original C code is to remove the calls to BeginPaint and EndPaint. BeginPaint has been called before OnDraw is called; EndPaint will be called when OnDraw completes. To preserve the original logic, it's necessary to replace the first BeginPaint/EndPaint pair with a call to ValidateRect.
Moving the code from a switch statement into a function requires deleting the two break statements, replacing the first one with a return statement. This preserves the original logic (although it wasn't very elegant).
Notice the calls to GetSafeHwnd and GetSafeHdc at the beginning of OnDraw. The Windows API function calls have been disambiguated with the scope resolution operator. That too will change in Make GDI Calls the MFC Way.