Home

MFC Document/View Architecture:
Form Views

 

Introduction to Forms

A form is an object used to host, hold, or carry other controls. By itself, a form means nothing. The controls positioned on it give it usefulness.

In the Microsoft Foundation Classes library, a form is a type of dialog box without borders. The rest of the functionality is mostly the same as a dialog box. While a dialog box is usually closed using either the system Close button or a user-created button positioned on it, such as OK or Cancel, because a form is part of a bigger application, it should use another means to be closed. In fact, the application to which the form belongs should define when and how the form would be closed.

A Form From AppWizard

There are three main techniques used to create a form. If you use AppWizard to create an SDI or an MDI application, you can specify that the class would be based on the CFormView class. When you do this, the wizard would add a form to your application and make it the (main) view.

Practical Learning: Creating a Form-Based Application From AppWizard

  1. Start Microsoft Visual Studio or Visual C++ and display the New dialog box to start a new application
  2. From the (Visual C++) Projects (tab), select MFC AppWizard (exe) or MFC Application
  3. In the (Project) Name, type Four Corner Carry Out
     
  4. Click OK
  5. Set the Application Type as Single Document
  6. Change the files as follows: 
     
    Class Name New Class Name Header File Implementation File Base Class
    CFourCornerCarryOutView CExoView ExoView.h ExoView.cpp CFormView
    CFourCornerCarryOutApp CExoApp No Change No Change No Change
    CFourCornerCarryOutDoc CExoDoc ExoDoc.h ExoDoc.cpp No Change
  7. Click Finish and click OK
  8. Notice that you are presented with a borderless form
    Click the TODO line and press Delete to remove the only existing line
    Optionally design the form as follows:
     
    Four Corner Carry Out - Form Design
  9. Press Ctrl + F5 to test the form
     
    Four Corner Carry Out - New Order

A Database Form

Another technique used to have a form is by creating a database application. This process automatically creates a form without necessarily getting it ready for database objects. To use this technique, start an application from AppWizard and specify that you need database support. The wizard would consequently create a form.

A New Form

The third alternative to getting a form is by creating one that you didn't get from AppWizard. Once again this depends on the current state of your application. If you started an application already, you may simply want to add a form. You can also decide to change the type of view that your application is using. You can also work from scratch, with no view at all, and then decide that your application would be based on a form as the main view. Either way, it is not particularly difficult to add or change the view.

As mentioned already, an MFC form is just a type of dialog box that doesn't have borders. Therefore, to add a form to an application, display the Add Resource dialog box and expand the Dialog node. Then click IDD_FORMVIEW and click OK. After adding the form, you must associate a class with it. You can either use an existing class or create your own. The class should be derived from CFormView.

If you add a form to an application, when the user starts the application, a message box ask what type of document should be displayed. The user can then select from the list and click OK.

Practical Learning: Adding a Form to an Application

  1. Display the New dialog box to start a new application
  2. Create it using MFC AppWizard (exe) or MFC Application
  3. Set the name of the project to Associates
  4. Create it as a Single Document
  5. Base the class on CListView
  6. Click Finish. If using MSVC6, click OK twice
  7. To add a form to the application, on the main menu, click Insert -> New Form...
  8. Set the Name of the class to CProducts and make sure it is based on CFormView
     
  9. Click OK
  10. Test the application and then close it
  11. To display a friendlier list, access the String Table
  12. Double-click IDR_PRODUCTS_TMPL and change its Caption as follows:
     
    Products\n\nBusiness Products\nProduc Files (*.pro)\n.pro\nProducts.Document\nProduc Document
  13. Double-click IDR_MAINFRAME and change its Caption as follows:
     
    Associates\n\nCustomers Contract\n\n\nAssociates.Document\nAssoci Document
  14. Test the application and select one of the documents
     
  15. Close it and return to MSVC

A Multi-Form Application

So far, we have discussed how you can either create a form-based application or how you can add a form to an application. In the above projects, we were dealing with only one form. In some cases, you may need to create an application that uses various forms. Once again, you have various choices:

  • The first solution that comes in mind of course would consist of creating an MDI. One situation you may encounter, which is fine if that's what you want, is that each child window would be based on the same design because each would use the same form
  • Another solution is to create different dialog boxes and use each as a form. In this case, you would not be really implementing forms
  • Another solution is to create various "mini-applications" and display each when needed. Although this last solution is possible, it could be a nightmare.
  • Another solution is to create various forms and display each as needed. This can be a valuable alternative as different forms can display in the same frame

You can use various forms in the same application and display each upon request. This solution can bring tremendous results on a database because that type of application usually deal with different groups or sets of records that don't belong to the same object.

To use various forms in an application, create each form and associate it with its own class. To prepare a convenient way to display them, you can add items to a menu and let the user decide when to display a particular form.

Practical Learning: Creating a Multi-Form-Based Application

  1. Display the New (Project) dialog box to start a new application
  2. Create it using MFC AppWizard (exe) or MFC Application
  3. Set the name of the application to EmplSolutions
  4. Create it as a Single Document and accept the default view class as CView
  5. Click Finish (and OK)
  6. To create a form, display the Insert Resources (Insert -> Resource...) or the Add Resources (Project -> Add Resource...) dialog box
  7. Expand the Dialog node and click IDD_FORMVIEW
     
  8. Click New
  9. On the form, delete the TODO line and place some random controls on it
  10. Change the ID of the form to IDD_VIEW_EMPLAPP
  11. Double-click an empty area on the form. When the Adding A Dialog Box dialog comes up, click Create A New Class and click OK
  12. Set the Name of the class to CEmplAppView and set its Base Class to CFormView
     
  13. Click OK twice
  14. Display the Insert Resource dialog box again
  15. Expand the Dialog node and double-click IDD_FORMVIEW
  16. Delete the TODO line and place some random controls on it
  17. Change the ID of the form to IDD_VIEW_TIMESHEET
  18. Create A New Class for the new form
  19. Name the class CTimeSheetView and base it on CFormView
  20. Display the Insert Resource dialog box again
  21. Expand the Dialog node and double-click IDD_FORMVIEW
  22. Delete the TODO line and place some random controls on it
  23. Change the ID of the form to IDD_VIEW_RFTIMEOFF
  24. Create A New Class for the new form
  25. Name the class CTimeOffView and base it on CFormView
  26. Display the EmplSolutions.cpp source file and change the default document as follows:
     
    // EmplSolutions.cpp : Defines the class behaviors for the application.
    //
    
    #include "stdafx.h"
    #include "EmplSolutions.h"
    
    #include "MainFrm.h"
    #include "EmplSolutionsDoc.h"
    #include "EmplSolutionsView.h"
    #include "EmplAppView.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
    /////////////////////////////////////////////////////////////////////////////
    // CEmplSolutionsApp
    
    BEGIN_MESSAGE_MAP(CEmplSolutionsApp, CWinApp)
    	//{{AFX_MSG_MAP(CEmplSolutionsApp)
    	ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
    	//}}AFX_MSG_MAP
    	// Standard file based document commands
    	ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    	ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
    	// Standard print setup command
    	ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
    END_MESSAGE_MAP()
    
    /////////////////////////////////////////////////////////////////////////////
    // CEmplSolutionsApp construction
    
    CEmplSolutionsApp::CEmplSolutionsApp()
    {
    }
    
    /////////////////////////////////////////////////////////////////////////////
    // The one and only CEmplSolutionsApp object
    
    CEmplSolutionsApp theApp;
    
    /////////////////////////////////////////////////////////////////////////////
    // CEmplSolutionsApp initialization
    
    BOOL CEmplSolutionsApp::InitInstance()
    {
    	AfxEnableControlContainer();
    
    	// Standard initialization
    
    #ifdef _AFXDLL
    	Enable3dControls();			// Call this when using MFC in a shared DLL
    #else
    	Enable3dControlsStatic();	// Call this when linking to MFC statically
    #endif
    
    	// Change the registry key under which our settings are stored.
    	SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    
    	LoadStdProfileSettings();  // Load standard INI file options (including MRU)
    
    	// Register document templates
    
    	CSingleDocTemplate* pDocTemplate;
    	pDocTemplate = new CSingleDocTemplate(
    		IDR_MAINFRAME,
    		RUNTIME_CLASS(CEmplSolutionsDoc),
    		RUNTIME_CLASS(CMainFrame),       // main SDI frame window
    		RUNTIME_CLASS(CEmplAppView));
    	AddDocTemplate(pDocTemplate);
    
    	// Parse command line for standard shell commands, DDE, file open
    	CCommandLineInfo cmdInfo;
    	ParseCommandLine(cmdInfo);
    
    	// Dispatch commands specified on the command line
    	if (!ProcessShellCommand(cmdInfo))
    		return FALSE;
    	m_pMainWnd->ShowWindow(SW_SHOW);
    	m_pMainWnd->UpdateWindow();
    
    	return TRUE;
    }
    
    
    . . .
    
  27. Test the application and close it to return to MSVC
  28. Display the IDR_MAINFRAME menu and change it as follows:
     
    ID Caption Prompt
    ID_VIEW_EMPLAPP &Employment application Display the employment application\nEmployment Application
    ID_VIEW_TIMESHEET Ti&me Sheet Display Time Sheet Form\nTime Sheet
    ID_VIEW_REQTIMEOFF &Request For Time Off Display Request For Time Off form\nRequest For Time Off
  29. Access the header file of the frame and declare an unsigned integer of type UINT and named m_CurrentView
     
    class CMainFrame : public CFrameWnd
    {
    	
    protected: // create from serialization only
    	CMainFrame();
    	DECLARE_DYNCREATE(CMainFrame)
    
    // Attributes
    public:
    	UINT m_CurrentView;
    
    // Operations
    public:
    
    . . .
    
    
  30. In the constructor of the frame, initialize the above variable to the IDentifier of the first view:
     
    CMainFrame::CMainFrame()
    {
    	m_CurrentView = ID_VIEW_EMPLAPP;
    }
  31. In Class View, right-click the CMainFrame node and click Add Member Function...
  32. Set the public function type as void and its name as SelectView with an argument of type UINT named ViewID
     
  33. Implement the member function as follows:
     
    // MainFrm.cpp : implementation of the CMainFrame class
    //
    
    #include "stdafx.h"
    #include "EmplSolutions.h"
    
    #include "MainFrm.h"
    #include "EmplAppView.h"
    #include "TimeSheetView.h"
    #include "TimeOffView.h"
    
    . . .
    
    void CMainFrame::SelectView(UINT ViewID)
    {
    	// If the view the user selected is already displaying, do nothing
    	if( ViewID == m_CurrentView )
    		return;
    
    	// Get a pointer to the current view
    	CView* pCurrentView = GetActiveView();
    
    	// We are about to change the view, so we need a pointer to the runtime class
    	CRuntimeClass* pNewView;
    
    	// We will process a form
    	// First, let's change the identifier of the current view to our integer
    	::SetWindowLong(pCurrentView->m_hWnd, GWL_ID, m_CurrentView);
    
    	// Now we will identify what form the user selected
    	switch(ViewID)
    	{
    	case ID_VIEW_EMPLAPP:
    		pNewView = RUNTIME_CLASS(CEmplAppView);
    		break;
    	
    	case ID_VIEW_TIMESHEET:
    		pNewView = RUNTIME_CLASS(CTimeSheetView);
    		break;
    	
    	case ID_VIEW_REQTIMEOFF:
    		pNewView = RUNTIME_CLASS(CTimeOffView);
    		break;
    	}
    
    	// We will deal with the frame
    	CCreateContext crtContext;
    
    	// We have a new view now. So we initialize the context
    	crtContext.m_pNewViewClass = pNewView;
    	// No need to change the document. We keep the current document
    	crtContext.m_pCurrentDoc   = GetActiveDocument();
    
    	CView* pNewViewer = STATIC_DOWNCAST(CView, CreateView(&crtContext));
    
    	// Now we can create a new view and get rid of the previous one
    	if( pNewViewer != NULL )
    	{
    		pNewViewer->ShowWindow(SW_SHOW);
    		pNewViewer->OnInitialUpdate();
    		SetActiveView(pNewViewer);
    		RecalcLayout();
    		m_CurrentView = ViewID;
    		pCurrentView->DestroyWindow();
    	}
    }
  34. In Class View, right-click the CMainFrame node and click Add Member Function...
  35. Set the function type as void and its name as OnUpdateSelectViewUI with a pointer argument of type CCmdUI* named pCmdUI
     
  36. Click OK
  37. Implement the member function as follows:
     
    void CMainFrame::OnUpdateSelectViewUI(CCmdUI *pCmdUI)
    {
    	pCmdUI->SetCheck(pCmdUI->m_nID == m_CurrentView);
    }
  38. In the same source file, to use the above two member functions for the menu items used to change the view, create their command ranges as follows:
     
    /////////////////////////////////////////////////////////////////////////////
    // CMainFrame
    
    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
    
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    	//{{AFX_MSG_MAP(CMainFrame)
    	ON_WM_CREATE()
    	//}}AFX_MSG_MAP
    	ON_COMMAND_RANGE(ID_VIEW_EMPLAPP, ID_VIEW_REQTIMEOFF, SelectView)
    	ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_EMPLAPP, ID_VIEW_REQTIMEOFF, OnUpdateSelectViewUI)
    END_MESSAGE_MAP()
  39. Test the application and change different views