This will describe how to create an MFC application without using the
AppWizard. I do not recommend doing this always of course but I think this can
be a useful alternative to using a dialog-based application. People often use
dialog-based MFC applications when they do not want all the complications of the
"MFC Document/View Architecture" yet dialogs have limitations. So in those
situations that you want the simplicity of a dialog yet you want the
conveniences of a real window you might want to use this solution. You can use
the AppWizard to generate a SDI application and yank out and/or override parts
you do not need so it is more a matter of personal preference.
There are some MFC experts that would say you are foolish to not use the
AppWizard to generate a MFC project initially, as if MFC was designed to only be
used in a project generated by the AppWizard. I learned MFC beginning with the
first version when the AppWizard was not as powerful as it is now (as if it is really
powerful now). I know that there was definitely MFC documentation showing how to
make a simple MFC program with only about a dozen lines. It is relatively easy to create a small program that uses MFC
without using the AppWizard; the only trick is that it is practically a
necessity to have a resource file. Assuming we are not making an application
that just has a modal dialog box, then we must have menu and string table
resources. Initially our small applications will only have a plain CFrameWnd
window without any overrides or handlers or anything like that but I will show
how to add a view.
The following explains how to Create an MFC application without the AppWizard
and with ClassWizard support.
The Project
To create the project:
- Use the "File | New" menu as usual for creating projects
- In the template tree on the left, select "Visual C++" | "Win32"
- Create a "Win32 Project"
- Use whatever name you wish to for the project; I am using "WithoutWizard"
- In the "Application Settings" page select the checkbox for "Empty project"
When the project has been created, in the Project Settings, on the "General"
tab, change "Use of MFC" from saying not to use MFC to saying to Use MFC (for all configurations). You can choose to use MFC in either a static or dynamic libraries but of course we usually use dynamic libraries.
Create a "targetver.h" file with the following contents:
#pragma once
#include <SDKDDKVer.h>
Precompiled Headers
Now we create the header and implementation files for Precompiled Headers. I am using the
standard filenames stdafx.h and stdafx.cpp to ensure use of the ClassWizard,
although I am not sure how important that is. At least it allows use of
precompiled headers in a standard manner, right?
- Create the stdafx.h header file with the following:
#pragma once
#define VC_EXTRALEAN
#include <afxwin.h>
#include <afxext.h>
- Create the stdafx.cpp file with the following:
#include "stdafx.h"
Then:
- in the project's properties for "C/C++" | "Precompiled Headers" set "Precompiled Header" to "Use (/Yu)"
- in the properties for the stdafx.cpp file set "Precompiled Header" to "Create (/Yc)" for just that file
The Resource File
There are a few requirements for the resource file. The menu can have anything in it; the requirement is that the resource Id for
the menu must be IDR_MAINFRAME. The string table can have only one string
in it and that string should have a resource Id also of IDR_MAINFRAME. The
string is used by CWinApp for the application's title, such as the application's default title in message boxes.
To create a Resource file, in the Solution Explorer right-click on the "Resource Files" node and select "Add" | "Resource...". Then press "Cancel" in the "Add Resource" dialog. Double-click the new file and then the file will be shown in a "Resource View". In "Resource View" right-click the new file and select "Resource Includes". For "Read-only symbol directives" remove the #include for "winres.h" and use the following instead:
#ifndef APSTUDIO_INVOKED
#include "targetver.h"
#endif
#include "afxres.h"
#include "verrsrc.h"
For "Compile-time directives" use the following:
#define _AFX_NO_OLE_RESOURCES
#define _AFX_NO_TRACKER_RESOURCES
#define _AFX_NO_PROPERTY_RESOURCES
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE 9, 1
#include "afxres.rc" // Standard components
#endif
Click "OK". You will get a warning; click "OK" for it.
Then in the Solution Explorer, edit the "resource.h" file (there might be one or two message boxes asking to close other files) and before the following line:
// Next default values for new objects
Add:
#define IDR_MAINFRAME 128
Again in the Solution Explorer, right-click the resources (rc) file and select "Open With..."
then select "C++ Source Code Editor"; if it asks to close it then select "OK".
Find the following lines near the bottom:
#endif // APSTUDIO_INVOKED
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
Add the following lines before the line "#ifndef APSTUDIO_INVOKED":
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_MAINFRAME MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_APP_EXIT
END
END
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDR_MAINFRAME "{Caption for your main window}"
AFX_IDS_APP_TITLE "{Your application's title}"
ID_APP_EXIT "Quit the application; prompts to save documents\nExit"
END
Except specify an approriate value for IDR_MAINFRAME and AFX_IDS_APP_TITLE.
Save and close that.
Creating the Header and Implementation Files
Create the header file for your program. Assuming the project is named
"WithoutWizard", the header file would be "WithoutWizard.h". Put the
following in it:
#pragma once
class CWithoutWizardApp : public CWinApp {
public:
CWithoutWizardApp() {};
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWithoutWizardApp)
public:
//}}AFX_VIRTUAL
// Implementation
//{{AFX_MSG(CWithoutWizardApp)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
Create the main cpp file for your program. Assuming the project is named
"WithoutWizard", the cpp file would be "WithoutWizard.cpp". Put the
following in it:
#include "stdafx.h"
#include "resource.h"
#include "WithoutWizard.h"
BEGIN_MESSAGE_MAP(CWithoutWizardApp, CWinApp)
//{{AFX_MSG_MAP(CWithoutWizardApp)
//}}AFX_MSG
END_MESSAGE_MAP()
CWithoutWizardApp WithoutWizardApp;
Override InitInstance
Notice that we do not have even an InitInstance for our
application. We are ready to use the ClassWizard and create an InitInstance for the application.
Save all unsaved files in the project.
Then right-click the solution in the Solution Explorer and select "Class Wizard...". Then in the ClassWizard select your project from the Project drop-down if it is not yet selected. Add an override for InitInstance in the "Virtual Functions" tab for your
application by clicking "Add Function".
Frame Window
You can use the ClassWizard to add a class (such as CMainFrame) derived from CFrameWnd and use that class for your frame, so you can
customize the window as needed. For me, however, ClassWizard always says that CMainFrame already exists. I don't know why. So I use a slightly different name and then rename the files and the class.
Assuming you will not be using dynamic creation, change the DECLARE_DYNCREATE macro in the header to
DECLARE_DYNAMIC and make the default constructor public. Correspondingly in the implementation file change the IMPLEMENT_DYNCREATE macro to IMPLEMENT_DYNAMIC.
Finishing the Application
Use the following in the InitInstance:
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(8); // Load standard INI file options (including MRU)
CMainFrame* pFrame = new CMainFrame;
if (!pFrame)
return FALSE;
m_pMainWnd = pFrame;
if (!pFrame->LoadFrame(IDR_MAINFRAME)) {
AfxMessageBox(_T("Frame not created"));
return FALSE;
}
pFrame->ShowWindow(m_nCmdShow);
pFrame->UpdateWindow();
return CWinApp::InitInstance();
Build the project and test it. This will provide a minimal application that
can be used for many things. The application would probably be especially
useful if you only needed to use GDI with the window.
See How to: Create Project Templates. This might be a good place to create a Project Template based on the project we have built.
Making a View
Since we are using CFrameWnd::LoadFrame to create a frame window, it is quite
easy to add a view. First derive a class from a view. Then specify the derived class in an instance of CCreateContext,
then pass that to LoadFrame. So your InitInstance could be something like:
BOOL CWithoutWizardApp::InitInstance() {
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(8); // Load standard INI file options (including MRU)
CFrameWnd *pFrameWnd;
pFrameWnd = new CFrameWnd;
if(!pFrameWnd) {
AfxMessageBox(_T("Window not made"));
return FALSE;
}
CCreateContext CreateContext;
CreateContext.m_pNewViewClass = RUNTIME_CLASS(CWithoutWizardView);
CreateContext.m_pCurrentFrame = pFrameWnd;
if (!pFrameWnd->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
NULL, &CreateContext)) {
AfxMessageBox(_T("Frame Load Error"));
return FALSE;
}
m_pMainWnd = pFrameWnd;
pFrameWnd->InitialUpdateFrame(NULL, TRUE); // Will do ShowWindow
return CWinApp::InitInstance();
}
Additional Notes
I have summarized what CFrameWnd::LoadFrame
does in case that helps.
Also, I am not sure if it would work to use CWnd::Create to create a plain
window instead of using CFrameWnd::LoadFrame
and getting a frame window. I tried but could not get it to work. It is easy
enough to try but until I do let me just say that one thing that is important is
that the main window object must be allocated in the heap (with "new"). That is
because MFC will destroy the main window for you and will use "delete" for the
object. It does this for the object that m_pMainWnd points to. If m_pMainWnd
is NULL then InitInstance will exit the application, so it does not work to do
that.
Since we are operating with a stripped-down MFC we are missing a few
conveniences. One problem is: where is the view? How can the applicaton class
communicate with the view? The following is one way for the application to find
the view:
CView * CWithoutWizardApp::FindView() {
const CString ClassName("CWithoutWizardView");
CRuntimeClass* pRuntimeClass;
CWnd* pWnd=NULL;
pWnd = m_pMainWnd->GetDescendantWindow(AFX_IDW_PANE_FIRST);
if (!pWnd)
return NULL;
pRuntimeClass = pWnd->GetRuntimeClass();
if (pRuntimeClass->m_lpszClassName == ClassName)
return (CView *)pWnd;
return NULL;
}
Notice that using CRuntimeClass to check the class is not necessary and could
be removed; at least it is not necessary for a release build. So FindView can be
used as in the following:
pWithoutWizardView = (CWithoutWizardView*)FindView();
if (pWithoutWizardView) {
pWithoutWizardView->OnUpdate(NULL, 1, &MessageText);
}
To use OnUpdate in that manner, use the following in your view class:
friend class CWithoutWizardApp;