Hi, and welcome to my very first article here at CodeProject. I have tried to make this article as clear and professional-looking as possible, but as always, typing and context errors or outright bugs may still remain. If you happen to stumble upon one, feel free to contact me or leave a note on the comments section.
Now, the article itself discusses, like its topic states, about using different types of view classes without the doc/view framework. In a nutshell, we will be creating a standard frame window application, and force it to use a custom view class. To add an extra degree of difficulty, we will use a
Anyone who has worked with
CFormView or similar view classes know how irritating it is that their constructors and Create-member methods are stashed into a private section of the class' declaration. And to make things even worse, it is impossible to alter this without hacking the MFC source files. Most inconvenient..
A while ago, I was working on a project which started as a normal dialog-based solution. Further down the road, it became apparent that the application needs a toolbar and a menu. Well, adding those to a dialog window proved out to be quite difficult, so I took a different type of approach: changed my dialog to a
CFormView-derived class. After a few more hours of debugging, I got the code to compile ok, but oh what a hell broke loose when I tried to run it... Asserts, access violations, unhandled exceptions, you name it..
Making MFC framework do the hard job
So let's quit the rambling, and get to work. First, you need to have a standard MFC SDI application, so fire up your IDE and create one. It doesn't need to be special in any way, if you have Visual Studio, just use its wizards to create it. Remember, however, to uncheck the doc/view architecture check-box!
As I am a Visual Studio user myself, all my examples from now on will conform to it. If you use a different IDE, follow its procedures to accomplish the same. Once your application skeleton is created, throw away the pregenerated child view class' source files. We won't be needing those. Instead, create a new formview (dialog) resource and place one or two controls into it. They can be any controls you please. Visual Studio has a template for forms from version 5.0 onwards, and you can access it by clicking the small plus sign next to the 'dialog' when inserting a new resource.
When done, create a new class for your dialog, specifying
CFormView as the base class. Name your derived class
CMainView, for example. Of course, you can use any name you think is suitable. Just remember to alter the example codes I present to match your selection.
Open up your newly created class, and change its constructors to
class CMainView : public CFormView
// Change constructor to public. It is private by default
After doing that, surf up to your main frame window class (MainFrm.h by default), and replace the original child view include command from its top. Put a command to include our view class' header instead. Browse a bit more down and add a member variable to the class, called
CMainView* m_pMainView. While at it, you can remove the old child view variable. Just remember, that the App Wizard generated code uses this variable in the
OnCmdMsg implementations of the frame window. You must alter these functions to use your view pointer instead.
Now open up the implementation file (MainFrm.cpp) and browse to its
OnCreate handler. This handler is called when the frame window is created, and we promptly alter it to do something else instead. Alter the handler to represent the following:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
// First, build the view context structure
// Designate the class from which to build the view
ccx.m_pNewViewClass = RUNTIME_CLASS(CMainView);
First off, you can see that we create a new context structure
CCreateContext ccx. Basically, this structure, as part of the framework, is responsible for dictating the view and the document used in this context. The framework creates these automatically when you register a document template and order it to create a new view for you.
In the next line, we designate our view class to work as the base of the new view to create. You could use any
CView-derived class here, as long as it is declared to support dynamic creation. If you use the Class Wizard to derive your classes from any
CView or its derivate, the necessary steps for implementing a dynamic creation are done for you.
// Using the structure, create a view
m_pMainView = DYNAMIC_DOWNCAST( CMainView, this->CreateView(&ccx) );
// Did we succeed ?
if ( !m_pMainView )
TRACE0("Creation of view failed\n");
Wait, wait, what's that horrible call ?!
DYNAMIC_DOWNCAST blah blah ?
Calm down, take a deep breath. This call is actually the framework's method to creating views into a frame window. What we do here is to order the frame window (
this -pointer) to call its member function
CreateView and specify our freshly designed
CCreateContext structure as its parameter.
The method returns a pointer to the created view's
CWnd base class, if it is successful. If it didn't succeed, it returns
NULL. Our next step in the call is to cast down from the
CWnd base class all the way to our
CMainView class. After all,
CView is derived from
CFormView is indirectly derived from
CView. The cast is perfectly legal and good.
Additionally, you can specify a new ID for the view as the second parameter of
CreateView. If you don't specify the ID, a default ID of
AFX_IDW_PANE_FIRST is used.
Here is an alternate way to accomplish the same, if you feel unfamiliar with nested function calls:
// Creating the child view step-by-step
// First order the frame window to create a new view for us
CWnd* pNewView = this->CreateView(&ccx);
// Cast down from the returned pointer
m_pMainView = DYNAMIC_DOWNCAST( CMainView, pNewView );
// Succesfull ?
if ( !m_pMainView )
TRACE0("Creation of view failed\n");
Ok, let's move on. Now our view is created and bound to the frame window. However, if the frame window is using toolbars, status bars or rebars, it is possible that those bars are displayed incorrectly if we now make the view visible. So let's order the frame window to do a recalculation (repositioning) of its bars.
// Do layout recalc
All that is left to do now is to show the view and make it active. The following code snippet uses the freshly created pointer to show it and order it to call its
OnInitialUpdate handler. We also mark this view as active for the frame window.
// Show the view and do an initial update
// Set this view active
If you went and ran your application now, the form wouldn't look too good, as the frame window does not get automatically resized based on the form's dimensions. Luckily, there is a function inside our view class that will order the frame window to resize its client area to accommodate space for it. Naturally, as the client area is resized, the entire window resizes as well.
// Order it to resize the parent window's client area to fit
That's it ! You can now build your application and it will give you a nifty little form instead of the boring white background.
This article covers only the basics of this type of framework exploitation.
Now, you might want to alter the formview's background to make it appear different, or you might remove some flags or settings to make it appear more smooth (the initial formview looks "sunked" into the frame window).
Remember, the the frame window in question can be any window that supports encapsulation of views, such as a
CSplitterWnd. The calling convention of this approach is a bit different, though. Perhaps I will write another article to tackle this problem.