Creating an MFC Application Without the AppWizard


Click here to change the theme.

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?

  1. Create the stdafx.h header file with the following:
    #pragma once
    
    #define VC_EXTRALEAN
    #include <afxwin.h>
    #include <afxext.h>
  2. 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;