CS-360
Fall, 2000
Class 14
R. Eckert

MFC WINDOWS PROGRAMMING (APP/WINDOW APPROACH)

The Microsoft Foundation Class (MFC) Library--

The Windows MFC Library consists of a hierarchy of C++ classes designed to 
facilitate Windows programming. To see a context sensitive map (hot-linked 
diagram) of the entire MFC class hierarchy, search the online help for 
"hierarchy chart".)

Using the MFC library classes offers an alternative to using Win32 API 
functions. The following diagram illustrates how a Visual C++ Windows 
application can use either the Win32 API or MFC (or both) to write a program 
that tells the computer hardware what has to be done:


There are about 200 MFC classes (versus 2000+ API functions).
They provide a framework upon which to build Windows applications.

They encapsulate most of the Win32 API in a set of logically organized
classes. Some of the characteristics of MFC are--

1. They offer the convenience of REUSABLE CODE, so that:

   -Many of the tasks common to all Windows applications are provided by
    MFC.

   -Our programs can inherit and modify this functionality as needed.

   -Therefore we don't need to recreate these tasks.

   -In other words, MFC handles many clerical details.

2. They produce smaller executables:

   -MFC programs are typically 1/3 the size of their API counterparts.

3. They can lead to faster program development--

   -But there is a steep learning curve, especially for those who are not
    comfortable with the object-oriented programming paradigm.

4. MFC Programs must be written in C++ and require the use of classes, so
   the programmer needs a good grasp of:

   -How classes are declared, instantiated, and used
   -Encapsulation
   -Inheritance
   -Polymorphism--virtual functions

THE MFC CLASS HIERARCHY (See online help on "Hierarchy Chart")--

At the top: CObject ("Mother of all classes"): provides run-time support
        features like serialization (disk reading/writing); all its
        functionality is inherited by any classes derived from it.

Important derived classes--

   CFile: Support for file operations.

   CDC: Encapsulates the device context (Graphical Drawing).

   CGdiObject: Base class for various drawing objects (bitmaps, pens, etc.)

   CMenu: Encapsulates menu management.

   CCmdTarget: Encapsulates process of message passing & is the parent of:

     CWnd: Base class from which all windows are derived; most common:

        CFrameWindow: ("normal" kind of window that can contain other
        windows--the kind that we've been using).

        CView: encapsulates process of displaying data.

     CWinThread: Defines a thread of execution.

        CWinApp: Most important class dealt with in MFC apps--
           Encapsulates an MFC application.
           Controls the following aspects of Windows programs:
              Startup, initialization, execution, shutdown.
           An application should have one CWinApp object.
           When instantiated, the application begins to run.

     CDocument:
        Encapsulates the data associated with a program.

The primary task in writing the code for an MFC program is to create
classes.

Most of these will be derived from one of the MFC library classes.

MFC MEMBER FUNCTIONS--

  Most of the functions an application will call are members of an MFC
  class.

  Examples: ShowWindow()--a member of CWnd class.
            TextOut()--a member of CDC.
            LoadBitmap()--a member of CBitmap.

  Applications can also call API functions directly, but it's more
  convenient to use MFC member functions.

MFC GLOBAL FUNCTIONS--

  MFC also defines important "global" functions that are not members of any
  MFC class.

  They always begin with Afx prefix (From Application FrameworKS).

  Example: AfxMessageBox()--since message boxes are predefined windows,
  it's possible to activate one independently from the rest of an
  application.

  Afx functions are either independent of or span the MFC class hierarchy.

A MINIMAL MFC PROGRAM (APP/WINDOW APPROACH)--

The simplest MFC programs contain two classes we must derive from the
hierarchy:

   An application class derived from CWinApp--defines the application.

   A window class usually derived from CFrameWnd--defines the application's
   main window.

Every MFC program must have these two classes.

MESSAGE PROCESSING UNDER MFC--

Just as in Win32 API programs, MFC programs must handle messages from
Windows. In the API case the mechanism is a huge switch/case statement. MFC
uses something called "message maps," which are lookup tables. Each table
contains entries that consist of a message number and a pointer to a
derived class member message-processing function. We must declare the
message-processing functions and map them to the specific messages that our
application is going to respond to. The mapping is done by "message-mapping
macros." In an MFC application most windows use a window procedure supplied
by the library. The message maps enable the library window procedure to
find the class member function that corresponds to the current message.
(The figures below attempt to illustrate the difference between how API and
MFC applications respond to messages.)



SPECIFIC STEPS IN WRITING A SIMPLE MFC PROGRAM (App/Window Approach)--

A. DECLARATIONS (.h) [See PROG1.H and MSG1.H for simple examples]:

1. Declare a window class derived from CFrameWnd (e.g., CMainWin);
   Class Members:
     The constructor
     Message-processing function declarations [e.g., afx_msg void OnChar()].
       (There are none in PROG1.)
     DECLARE_MESSAGE_MAP() macro to:
       -allow windows based on this class to respond to messages;
       -declare that a message map will be used to map messages to functions;
       This should be the last class member declared.

2. Declare an application class derived from CWinApp (e.g., CApp):
     It Must override the CWinApp's virtual InitInstance() function.
       -This function is called each time a new instance of the application
        is started; i.e., when an object of this class is instantiated.

B. IMPLEMENTATION (.CPP) [See PROG1.CPP & MSG1.CPP for simple examples]:

1. Define the constructor for the class derived from CFrameWnd (CMainWin)--
     It should make a call to member function Create() to create the window.
       -This does what CreateWindow() does in API programming.

2. Define the message map for the class derived from CFrameWnd (CMainWin)--
     BEGIN_MESSAGE_MAP(owner, base)
       [list of "message macros" [e.g., ON_WM_CHAR()]
     END_MESSAGE_MAP()
        (There are none in the PROG1 example.)

3. Define the message-processing functions declared in 1 above--
        (There are none in the PROG1 example.)

4. Define the InitInstance() overriding function in the class derived from
   CWinApp (CApp) --
     Should have initialization code for each new instance of the application:
       -Create a CMainWin object --> a pointer to program's main window.
         (This is used like hWnd in API programs to refer to the window.)
       -Invoke the object's ShowWindow() member function.
       -Invoke the object's UpdateWindow() member function.
       -Must return non-zero to indicate success.
     [MFC's implementation of WinMain() calls this function.]

   At this point the nature and form of the simple window and application
   have been defined, but neither exists until an application object
   derived from CWinApp (CApp) is instantiated.

5. Create an instance of the application class (CApp):

   This causes WinMain() to execute--it's now a part of MFC [WINMAIN.CPP].
   WinMain() does the following:
    -Calls AfxWinInit(), 
       which calls AfxRegisterClass() to register the window class.
    -Calls CApp::InitInstance() [the virtual function overridden in 2 above],
       which creates the window and shows it.
    -Calls CWinApp::Run(),
        which calls CWinThread::PumpMessage()--
           which contains the GetMessage() loop.
    -After this returns (i.e., when the WM_QUIT message is received),
        AfxWinTerm() is called, which cleans up and exits.

PROG1 APPLICATION--Just creates a skeleton frame window--

SPECIFIC STEPS IN CREATING AND BUILDING AN MFC APPLICATION LIKE PROG1
"MANUALLY" USING MICROSOFT DEVELOPER STUDIO--

1. Use "File | New", choose "Win32 Application" as always, and enter a
Project Name and Location as usual.

2. Enter the text for the header file (e.g., PROG1.H--see DECLARATIONS above)
and for the .CPP source file (e.g., PROG1.CPP--see IMPLEMENTATION above).
[Or copy these sources into the project's directory and use "Project | Add To
Project | Files", entering the name of the .CPP file.]

3. Use "Project | Settings", and select the "General" Tab. From the "Microsoft
Foundation Classes:" combo box, choose "Use MFC in a Shared DLL".

4. Build the project as usual.


THE PROG1 DECLARATIONS (PROG1.H)--

PROG1.H just declares the CMainWin class derived from CFrameWnd, its
constructor CMainWin() and its message map. Note that this simple application
does not respond to any messages, so there are no message handler functions
to declare. It then declares the CApp class derived from CWinAPP and declares
that the InitInstance() virtual function base class member will be
overridden.

THE PROG1 IMPLEMENTATION (PROG1.CPP)--

1. PROG1.CPP first defines the CMainWin constructor, which simply makes a
call to the member function Create(). Whenever a CMainWin object is
instantiated, this will cause a window with the title "An MFC Application
Skeleton" to be created. Note that setting the first parameter to NULL causes
most of the characteristics of the window that is created to take on their
default frame-style values. (See online help on CFrameWnd::Create.)

2/3. PROG1.CPP next defines an empty message map using the macros
BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP(). Even though the application
doesn't respond to any messages, we need to define the message map. In almost
all MFC applications, there will be several message-mapping macros in the
message map.

4. PROG1.CPP next defines the override to the CApp's InitInstance() member
function. First a new CMainWin object is instantiated, and this gives us a
pointer to the resulting window. Note that in our CMainWin constructor (see
above) we made a call to Create(), which creates the window, so at this point
our frame window has been created. We then use the pointer to the resulting
window to invoke the ShowWindow() member function, which causes the window to
display itself. And then the UpdateWindow() member function causes a the
window to update its client area. Note that these are the same steps that
were followed in API programming in the WinMain() function after the window
was created by CreateWindow().

5. Finally a CApp object is instantiated. As described in the "IMPLEMENTATION"
section above, this causes the WinMain() [now buried in MFC] to execute,
which, in turn, causes our CApp::InitInstance() to run--so the window
appears. And, as described above, the message loop begins.

When this application is built and run, we can visualize the following sequence
of events occurring:

CApp object is created -->
    MFC's WinMain() executes-->
        Registers class (default)
        Calls our CApp::InitInstance()-->
            Our override creates a CMainWin object-->
                The CMainWin constructor calls Create() which creates the window
            Our override calls the window's ShowWindow() function which displays it
            Our override calls UpdateWindow() which paints its client area
    WinMain() continues by calling its Run() function-->
        Which calls PumpMessage()-->
            Which starts the message loop.

The MSG1 Example MFC Program (Mouse/Character Message Processing)--

This application responds to Left and Right mouse button presses by

recording the current position of the mouse cursor and setting up a string
to be displayed.

It responds to key presses by saving the character in a string which will
be displayed at the upper left hand corner of the client area.

In either case, it forces a WM_PAINT message which is responded to by
displaying the string at the correct location on the screen.

MSG1 Declarations (MSG1.H file)--

The CMainWin class must contain the message handler functions the
application is going to use to handle expose events, keyboard character
entry, and left/right mouse button presses. These are, respectively,
OnPaint(), OnChar(), OnLButtonDown(), and OnRButtonDown(), and must be
declared in the .H file prior to the DECLARE_MESSAGE_MAP() statement.

The implementation (.CPP file) must subsequently define these functions.

MSG1 Implementation (MSG1.CPP file)--

Global variables (x,y) are declared to keep track of where the text is to
be displayed in the window's client area. Their initial value is set to
(1,1). Also a string (str) is initialized to "Sample Output". This will be
what is displayed at (x,y) when the application starts (i.e., after the
first WM_PAINT message). As the user moves/clicks the mouse or enters
characters, the values of (x,y) and of the string will change.

CMainWin constructor--defines a RECT structure to specify the
position/dimensions of the window to be created. It then calls the Create()
member function using the RECT structure to make the window. Note that
here we are using more of the parameters of the Create() function to create
the kind of frame window we want.

CMainWin's message map--uses the message map macros associated with the
messages this application is going to handle; namely, WM_CHAR, WM_PAINT,
WM_LBUTTONDOWN, WM_RBUTTONDOWN. These macros are, respectively:
ON_WM_CHAR(), ON_WM_PAINT(), ON_WM_LBUTTONDOWN(), and ON_WM_RBUTTONDOWN().

Next come the definitions of the message handler functions. Notice that in
each case, Windows/MFC provides the necessary information as parameters in
the functions (See online help for each of the message handler functions):

    OnChar(ch,count,flags)--Sent in response to the user pressing a key with
    a printable character. The first parameter contains the character code
    of the key pressed. Our override of OnChar() sets x and y to 1, then
    uses wsprintf() to put the character into the global string (str).
    Finally it invalidates the client area forcing a WM_PAINT message. The
    result is that whatever string is stored in str will be displayed at
    location (1,1) of the window's client area--its upper lefthand corner.

    OnLButtonDown(flags,loc)--Sent in response to the user pressing the left
    mouse button. The second parameter contains the CPoint (x,y) location
    where the button press occurred. Our override of OnLButtonDown() just
    sets the globals x and y to the x and y fields of the location 
    specified in the loc CPoint structure and formats str to contain the 
    string "Left button is down". It then invalidates the client area 
    forcing a WM_PAINT message.

    OnRButtonDown(flage,loc)--Does the same as the previous function, but
    in response to the right mouse button being pressed.

    OnPaint()--Sent in response to any expose event (or an invalidation of
    the client area). Our override of OnPaint() creates a CPaintDC object
    (dc) connected to "this" window. The object's constructor performs a
    BeginPaint() and the destructor an EndPaint(). Since CPaintDC is
    derived from CDC, the object obtained (dc) gives us access to all the
    functionality in CDC, including the TextOut() member function. After
    creating dc, our OnPaint() handler function calls dc.TextOut(x, y,
    str, lstrlen(str)) to display the current contents of the global
    string at position (x,y) in the windows's client area.

CApp's InitInstance() is defined to create and show the window, as in
PROG1.CPP.

Finally a CApp object (App) is instantiated, which causes execution to
begin.


USING RESOURCES WITH MFC (Menus)--

When an MFC application creates a window with a menu, the message map
macro that is used to map WM_COMMAND messages to handler functions is 
ON_COMMAND(IDM_***,On***), where IDM_*** is the menu item identifier
in the resource script (.RC file) and On***() is the handler function
that you will need to write to respond to the user having selected the
menu item. For example, if you had a menu item "Alpha" with an ID of
IDM_ALPHA defined in the .RC file, the handler function (that you write)
would normally be called OnAlpha(), and would, of course, be a member
function of your CMainWin class. In this example, the message macro
would be: ON_COMMAND(IDM_ALPHA,OnAlpha).

As in API programming, we can set up a .rc menu resource script file with 
either a text editor or visually with the Developer Studio menu editor. If 
we do it visually, we'll have to edit both the resulting resource.h and the 
.rc files to get rid of the extra "garbage" inserted by Developer Studio's 
menu editor for use by its AppWizard (which we'll look at later). The 
resource.h file should only contain the #defines for the menu item IDs. The 
program's .rc file should only have the menu section and two #includes at 
the top:
                              #include <afxres.h>
                              #include "resource.h"
All the other stuff should be edited out of the two files. We should add
the .cpp source file and the .rc file to the project (as usual), and it 
should be built as in previous examples.

The MENU1 example program--

This program has a menu with items "One", "Two", and "Help". The first two
are popup menus with items "Alpha" and "Beta" under "One" and with items
"Gamma" under "Two". Upon selection of any of these menu items an
appropriate message box is displayed.

The program also responds to left mouse button presses by displaying an
"Abort/Retry/Ignore" message box. An appropriate message box is displayed
depending on which button the user selects from the message box.

MENU1.H--

Declares our CMainWin class with its the left button down handler function
[OnLButtonDown()], its menu item handler functions [OnAlpha(), OnBeta(),
OnGamma(), OnHelp()], its message map, and finally our CApp class.

MENU1.RC--

This was created with the menu editor and edited to get rid of the AppWizard
"garbage". This process also produced the resource.h file which was edited to 
get rid of its AppWizard "garbage".

MENU1.CPP--

Defines the CMainWin constructor, which calls Create() to create the
window. Notice that the "rectDefault" rectangle position and dimensions are
used as well as the "MYMENU" name of the menu defined in the menu1.rc file.

Defines the message map. As indicated above, the ON_COMMAND message macro is
different from the other message map macros we've seen. The first parameter 
is the menu item ID (as defined in resource.h and used in menu1.rc); the
second parameter is the name of the handler function that we will need to
write. Here the names of the handler functions are: OnAlpha, OnBeta, 
OnGamma, and OnHelp.

CMainWin's OnLButtonDown() handler function override--just puts up an
"abort/retry/ignore" message box and stores the return value in the
variable i. A case statement then puts up a simple "OK" message box with
the caption of "Abort", "Retry", or "Ignore", depending on the value of i.
Notice that since OnLbuttonDown() is a member function of CMainWin which is
derived from CWnd, we can use the latter's MessageBox() member function.

CMainWin's menu item processing functions [OnAlpha(), OnBeta(), OnGamma(),
and OnHelp()] all work the same way. They just use MessageBox() to display
a message box with the appropriate caption and contents.

USING MODAL DIALOG BOXES IN MFC--

Recall that a dialog box is a child window that contains one or more
child window controls. The dialog template is given in the resource (.rc)
file. Under the Win32 API, a dialog box is created with the function
DialogBox() or DialogBoxParam(). The dialog box has its own message
processing function with a switch/case statement to handle messages from
the various controls it contains. Under MFC, dialog boxes are encapsulated
by the CDialog class (derived from CWnd). So when we require a dialog
box, we should derive our own dialog box class from CDialog. The dialog
box message handling is done the same way as for the main window--namely
by using message maps. So that inside your dialog box class declarations
(.h file) you will need to declare the message handling functions and a
message map. The implementation (.cpp file) will have to define the
dialog box class message map and the handler functions.

To create a dialog box, the constructor of the class derived from CDialog
should call the CDialog constructor. The arguments are the name or ID of
the dialog box (as specified in the .rc file) and a pointer to the owner 
window. If any initialization needs to be done as the dialog box is created, 
the code that does it should be placed in CDialog's OnInitDialog() handler
function which is invoked in response to the WM_INITDIALOG message.

To activate the dialog box use CDialog's DoModal() member function. As in
the Win32 API, DoModal() does not return until the dialog box has been
closed. Until then, all messages from the dialog box controls will go to
the dialog box Message map handler functions.

CDialog's EndDialog() member function is invoked to close the dialog box
and thus cause DoModal() to return.

The DIALOG1 Example Program--

The menu has "Dialog" and "Exit" items. If the user selects "Dialog" a
modal dialog box containing the buttons "Red", "Green", "Cancel" and
"OK" is displayed. If the user chooses "Red" or "Green", a message box
with appropriate text in it appears; if either "Cancel" or "OK" is
chosen, the dialog box closes. It the user selects the "Exit" menu item,
the window is closed.

DIALOG1.RC and RESOURCE.H--These files were prepared with the Developer
Studio menu editor and the dialog box editor. As in the MENU1 example
the "garbage" was edited out afterwards. Note that the default ID's for
the dialog box "OK" and "Cancel" buttons were used. CDialog provides
default handlers for both IDOK and IDCANCEL WM_COMMAND messages.

DIALOG1.H--What is new here is the declaration of our CSampleDialog
class, derived from CDialog. The constructor simply specifies that the
CDialog base class constructor is to be used with the arguments provided
when the CSampleDialog object is created. We declare handler functions
for three of the buttons inside the dialog box: OnRed(), OnGreen(), and
OnCancel(). [The default handler in CDialog for the "OK" button will be
used, so we don't need it in the message map.]

DIALOG1.CPP--After defining the main window's message map to take care of
WM_COMMAND messages from the two menu items, we define the two handlers:
OnDialog() and OnExit(). In the first case a CSampleDialog object
(dialOb) is instantiated, and its constructor invoked passing it the name
of the dialog box and "this" to indicate that this window is the owner.
Then the resulting object's DoModal() function is called to create the
dialog box. In the case of OnExit(), we simply cause the CMainWin to send
a WM_CLOSE message by invoking SendMessage(). This, of course, goes to
the main window's message map and causes Windows to close the main window
and terminate the application.

The next section of the program sets up the dialog box message map,
specifying that OnRed(), OnGreen(), and OnCancel() are the handler
functions for messages from the buttons inside the dialog box. We then
define the three functions. In the first two cases, message boxes are
displayed; in the case of OnCancel() a call to EndDialog() is made which
causes the dialog box to close. Notice that the default handler for the
IDOK message does the same thing automatically.

DIALOG BOXES CONTAINING LIST BOXES--

As you will recall, list boxes are controls that both generate and
receive messages. An application can send a list box messages to fill it
with strings (LB_ADDSTRING), get the current selection (LB_GETCURSEL), 
get text item (LB_GETTEXT), etc. The list box sends messages in response
to the user selecting items, etc. Under the Win32 API we use
SendDlgItemMessage() to send these messages to the list box. UNDER MFC,  
messages can be sent to a list box (and other controls) in a dialog box by 
using member functions of the classes from which the list box (or other
control) is derived. For example, for a list box, the CListBox class has
member functions like AddString(), GetCurSel(), GetText(), etc. (Refer to
the online help.)

As in API programming, to send a message to a control in a dialog box, we
must identify which control us being accessed. Under API this is done by
using the function GetDlgItem(). Under MFC, the CWnd class has a similar
member function with the same name that returns a pointer to the control.
Each time we access the control we must retrieve the pointer returned by:

              CWnd::GetDlgItem(CONTROL_ID);

Since there are many possible controls, the result must be type cast to
the type of control object being obtained. In the case of a list box
control, that means using code like:

              CListBox *plistbox = (CListBox *)GetDlgItem(CONTROL_ID);

Once this pointer is retrieved, it can be used to access any of the
member functions that send messages to the control. For example:

              plistbox->AddString("some string");

would cause "some string" to be added to the list box.

To initialize a list box, the WM_INITDIALOG message handler,
OnInitDialog(), should be overridden so that it adds strings to the list
box when it is first created. That means OnInitDialog() must be declared
in the section of the .h file that declares our CDialog class. In the
implementation (.cpp), our OnInitDialog() should first call the base
class OnInitDialog() so that initialization is done correctly. Our
OnInitDialog() can then make calls to AddString() to fill the dialog box.

The DIALOG2 Example Program--

In this application, when the user selects the "Dialog" menu item, a
dialog box with a fruit selection list box appears. If the user double
clicks on an item or selects an item and then presses the "Select Fruit"
button, a message box appears with the selection and its position in the
list box. So in response to either IDD_SELFRUIT (the button) or
LBN_DBLCLK of the IDD_LB1 list box (the list box notification code
generated when the user double clicks on an item in the IDD_LB1 list box),
the OnSelect() handler specified in the dialog box message map is called.
This function makes calls to GetDlgItem() to get a pointer to the list 
box, then invokes GetCurSel() and GetText() to retrieve the item selected.