| Tenouk C & C++ | MFC Home | AppWizard 2 | Event Handling, Mapping Modes, and a Scrolling View 2 | Download | Site Index |


 

 

 

 

 

Module 3:

Basic Event Handling, Mapping Modes, and a Scrolling View 1

 

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:

  1. Basic Event Handling, Mapping Modes, and a Scrolling View

  2. Getting User Input: Message Map Functions

  3. The Message Map

  4. Saving the View's State - Class Data Members

  5. Initializing a View Class Data Member

  6. Invalid Rectangle Theory

  7. The Window's Client Area

  8. CRect, CPoint, and CSize Arithmetic Classes

  9. Is a Point Inside a Rectangle?

  10. The CRect LPCRECT Operator

  11. Is a Point Inside an Ellipse?

  12. The MYMFC1 Example

  13. Using ClassWizard with MYMFC1

 

 

Basic Event Handling, Mapping Modes, and a Scrolling View

 

In the previous Module, you saw how the MFC Library application framework called the view class's virtual OnDraw() function. If you look at the MSDN documentation for the CView class and its base class, CWnd, you'll see several hundred member functions. Functions whose names begin with On - such as OnKeyDown() and OnLButtonUp() - are member functions that the application framework calls in response to various Windows "events" such as keystrokes and mouse clicks.

Most of these application framework-called functions, such as OnKeyDown(), aren't virtual functions and thus require more programming steps. This Module explains how to use the Visual C++ ClassWizard to set up the message map structure necessary for connecting the application framework to your functions' code.

The first two examples use an ordinary CView class. In MYMFC1, you'll learn about the interaction between user-driven events and the OnDraw() function. In MYMFC2, you'll see the effects of different Windows mapping modes. More often than not, you'll want a scrolling view. The last example, MYMFC3, uses CScrollView in place of the CView base class. This allows the MFC library application framework to insert scroll bars and connect them to the view.

 

Getting User Input: Message Map Functions

 

Your mymfc application from previous Module did not accept user input other than the standard Microsoft Windows resizing and window close commands. The window contained menus and a toolbar, but these were not "connected" to the view code. The menus and the toolbar won't be discussed until the time comes, because they depend on the frame class, but plenty of other Windows input sources will keep you busy until then. Before you can process any Windows event, even a mouse click, however, you must learn how to use the MFC message map system.

 

The Message Map

 

When the user presses the left mouse button in a view window, Windows sends a message - specifically WM_LBUTTONDOWN - to that window. If your program needs to take action in response to WM_LBUTTONDOWN, your view class must have a member function that looks like this:

 

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)

{

    // event processing code here

}

 

Your class header file must also have the corresponding prototype:

 

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

 

The afx_msg notation is a "no-op" that alerts you that this is a prototype for a message map function. Next, your code file needs a message map macro that connects your OnLButtonDown() function to the application framework:

 

BEGIN_MESSAGE_MAP(CMyView, CView)

    ON_WM_LBUTTONDOWN() // entry specifically for OnLButtonDown

    // other message map entries

END_MESSAGE_MAP()

 

Finally, your class header file needs the statement:

 

DECLARE_MESSAGE_MAP()

 

How do you know which function goes with which Windows message? The MFC library online documentation includes a table that lists all standard Windows messages and corresponding member function prototypes. You can manually code the message-handling functions - indeed, that is still necessary for certain messages. Fortunately, Visual C++ provides a tool, ClassWizard, that automates the coding of most message map functions.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Saving the View's State - Class Data Members

 

If your program accepts user input, you'll want the user to have some visual feedback. The view's OnDraw() function draws an image based on the view's current "state," and user actions can alter that state. In a full-blown MFC application, the document object holds the state of the application, but you're not to that point yet. For now, you'll use two view class data members, m_rectEllipse and m_nColor. The first is an object of class CRect, which holds the current bounding rectangle of an ellipse, and the second is an integer that holds the current ellipse color value. By convention, MFC library non-static class data member names begin with m_.

You'll make a message-mapped member function toggle the ellipse color (the view's state) between gray and white and the toggle is activated by the event of pressing the left mouse button. The initial values of m_rectEllipse and m_nColor are set in the view's constructor, and the color is changed in the OnLButtonDown() member function. Why not use a global variable for the view's state? Because if you did, you'd be in trouble if your application had multiple views. Besides, encapsulating data in objects is a big part of what object-oriented programming is all about.

 

Initializing a View Class Data Member

 

The most efficient place to initialize a class data member is in the constructor, like this:

 

CMyView::CMyView() : m_rectEllipse(0, 0, 200, 200) {...}

 

You could initialize m_nColor with the same syntax. Because we're using a built-in type (integer), the generated code is the same if you use an assignment statement in the constructor body.

 

Invalid Rectangle Theory

 

The OnLButtonDown() function could toggle the value of m_nColor all day, but if that's all it did, the OnDraw() function wouldn't get called (unless, for example, the user resized the view window). The OnLButtonDown() function must call the InvalidateRect() function (a member function that the view class inherits from CWnd). InvalidateRect() triggers a Windows WM_PAINT message, which is mapped in the CView class to call to the virtual OnDraw() function. If necessary, OnDraw() can access the "invalid rectangle" parameter that was passed to InvalidateRect().

There are two ways to optimize painting in Windows. First of all, you must be aware that Windows updates only those pixels that are inside the invalid rectangle. Thus, the smaller you make the invalid rectangle (in the OnLButtonDown() handler, for instance), the quicker it can be repainted. Second, it's a waste of time to execute drawing instructions outside the invalid rectangle. Your OnDraw() function could call the CDC member function GetClipBox() to determine the invalid rectangle, and then it could avoid drawing objects outside it. Remember that OnDraw() is being called not only in response to your InvalidateRect() call but also when the user resizes or exposes the window. Thus, OnDraw() is responsible for all drawing in a window, and it has to adapt to whatever invalid rectangle it gets.

 

The Window's Client Area

 

A window has a rectangular client area that excludes the border, caption bar, menu bar, and any docking toolbars. The CWnd member function GetClientRect() supplies you with the client-area dimensions. Normally, you're not allowed to draw outside the client area, and most mouse messages are received only when the mouse cursor is in the client area. Various child windows, including the toolbar window, the view windows, and the status bar windows, occupy the main frame window's client area, as shown in Figure 1.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

----------------------------------------------------------------------------

Window client area.

 

Figure 1: Window client area.

 

CRect, CPoint, and CSize Arithmetic

 

The CRect, CPoint, and CSize classes are derived from the Windows RECT, POINT, and SIZE structures, and thus they inherit public integer data members as follows:

 

Class

Data member

CRect

left, top, right, bottom

CPoint

x, y

CSize

cx, cy

 

Table 1.

 

If you look in the Microsoft Foundation Class Reference, you will see that these three classes have a number of overloaded operators. You can, among other things, do the following:

The CRect class has member functions that relate to the CSize and CPoint classes. For example, the TopLeft() member function returns a CPoint object, and the Size() member function returns a CSize object. From this, you can begin to see that a CSize object is the "difference between two CPoint objects" and that you can "bias" a CRect object by a CPoint object.

 

Is a Point Inside a Rectangle?

 

The CRect class has a member function PtInRect that tests a point to see whether it falls inside a rectangle. The second OnLButtonDown() parameter (point) is an object of class CPoint that represents the cursor location in the client area of the window. If you want to know whether that point is inside the m_rectEllipse rectangle, you can use PtInRect() in this way:

 

if (m_rectEllipse.PtInRect(point))

{

    // point is inside rectangle

}

 

As you'll soon see, however, this simple logic applies only if you're working in device coordinates (which you are at this stage).

 

The CRect LPCRECT Operator

 

If you read the Microsoft Foundation Class Reference carefully, you will notice that CWnd::InvalidateRect takes an LPCRECT parameter (a pointer to a RECT structure), not a CRect parameter. A CRect parameter is allowed because the CRect class defines an overloaded operator, LPCRECT that returns the address of a CRect object, which is equivalent to the address of a RECT object. Thus, the compiler converts CRect arguments to LPCRECT arguments when necessary. You call functions as though they had CRect reference parameters. The view member function code:

 

CRect rectClient;

GetClientRect(rectClient);

 

Retrieves the client rectangle coordinates and stores them in rectClient.

 

Is a Point Inside an Ellipse?

 

The MYMFC1 code determines whether the mouse hit is inside the rectangle. If you want to make a better test, you can find out whether the hit is inside the ellipse. To do this, you must construct an object of class CRgn that corresponds to the ellipse and then use the PtInRegion() function instead of PtInRect(). Here's the code:

 

CRgn rgn;

rgn.CreateEllipticRgnIndirect(m_rectEllipse);

if (rgn.PtInRegion(point)) {

// point is inside ellipse

}

 

Note that the CreateEllipticRgnIndirect() function is another function that takes an LPCRECT parameter. It builds a special region structure within Windows that represents an elliptical region inside a window. That structure is then attached to the C++ CRgn object in your program. The same type of structure can also represent a polygon.

 

The MYMFC1 Example

 

In the MYMFC1 example, an ellipse (which happens to be a circle) changes color when the user presses the left mouse button while the mouse cursor is inside the rectangle that bounds the ellipse. You'll use the view class data members to hold the view's state, and you'll use the InvalidateRect() function to cause the view to be redrawn. In the previous Module example, drawing in the window depended on only one function, OnDraw(). The MYMFC1 example requires three customized functions (including the constructor) and two data members. The complete CMymfc1View header and source code files are listed in Listing 1. The steps for creating the program are shown after the program listings. All changes to the original AppWizard and OnLButtonDown() ClassWizard output are in orange color.

 

MYMFC1VIEW.H

// mymfc1View.h: interface of the CMymfc1View class

//

///////////////////////////////////////////////////////////////////////

 

#if !defined(AFX_MYMFC1VIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2__INCLUDED_)

#define AFX_MYMFC1VIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2__INCLUDED_

 

#if _MFC_VER > 1000

#pragma once

#endif // _MFC_VER > 1000

class CMymfc1View : public CView

{

protected: // create from serialization only

    CMymfc1View();

    DECLARE_DYNCREATE(CMymfc1View)

 

// Attributes

public:

    CMymfc1Doc* GetDocument();

 

// Operations

public:

 

// Overrides

    // ClassWizard generated virtual function overrides

    //{{AFX_VIRTUAL(CMymfc1View)

    public:

    virtual void OnDraw(CDC* pDC);  // overridden to draw this view

    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

    protected:

    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

    virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

    //}}AFX_VIRTUAL

 

// Implementation

public:

    virtual ~CMymfc1View();

#ifdef _DEBUG

    virtual void AssertValid() const;

    virtual void Dump(CDumpContext& dc) const;

#endif

 

protected:

 

// Generated message map functions

protected:

    //{{AFX_MSG(CMymfc1View)

    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()

private:

    int m_nColor;

    CRect m_rectEllipse;

};

#ifndef _DEBUG  // debug version in mymfc1View.cpp

inline CMymfc1Doc* CMymfc1View::GetDocument()

   { return (CMymfc1Doc*)m_pDocument; }

#endif

 

///////////////////////////////////////////////////////////////////////

 

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations

//  immediately before the previous line.

 

#endif // !defined(AFX_MYMFC1VIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2__INCLUDED_)

 

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

MYMFC1VIEW.CPP

 

// mymfc1View.cpp : implementation of the CMymfc1View class

//

 

#include "stdafx.h"

#include "mymfc1.h"

 

#include "mymfc1Doc.h"

#include "mymfc1View.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE

__;

#endif

 

///////////////////////////////////////////////////////////////////////

// CMymfc1View

 

IMPLEMENT_DYNCREATE(CMymfc1View, CView)

 

BEGIN_MESSAGE_MAP(CMymfc1View, CView)

    //{{AFX_MSG_MAP(CMymfc1View)

    ON_WM_LBUTTONDOWN()

    //}}AFX_MSG_MAP

    // Standard printing commands

    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////

// CMymfc1View construction/destruction

 

CMymfc1View::CMymfc1View() : m_rectEllipse(0, 0, 200, 200)

{

    m_nColor = GRAY_BRUSH;

}

 

CMymfc1View::~CMymfc1View()

{

}

 

BOOL CMymfc1View::PreCreateWindow(CREATESTRUCT& cs)

{

    // TODO: Modify the Window class or styles here by modifying

    // the CREATESTRUCT cs

 

    return CView::PreCreateWindow(cs);

}

 

///////////////////////////////////////////////////////////////////////

// CMymfc1View drawing

 

void CMymfc1View::OnDraw(CDC* pDC)

{

    pDC->SelectStockObject(m_nColor);

    pDC->Ellipse(m_rectEllipse);

}

 

///////////////////////////////////////////////////////////////////////

// CMymfc1View printing

 

BOOL CMymfc1View::OnPreparePrinting(CPrintInfo* pInfo)

{

    // default preparation

    return DoPreparePrinting(pInfo);

}

 

void CMymfc1View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

    // TODO: add extra initialization before printing

}

 

void CMymfc1View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

    // TODO: add cleanup after printing

}

 

///////////////////////////////////////////////////////////////////////

// CMymfc1View diagnostics

 

#ifdef _DEBUG

void CMymfc1View::AssertValid() const

{

    CView::AssertValid();

}

 

void CMymfc1View::Dump(CDumpContext& dc) const

{

    CView::Dump(dc);

}

 

CMymfc1Doc* CMymfc1View::GetDocument() // non-debug version is inline

{

    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMymfc1Doc)));

    return (CMymfc1Doc*)m_pDocument;

}

#endif //_DEBUG

 

///////////////////////////////////////////////////////////////////////

// CMymfc1View message handlers

 

void CMymfc1View::OnLButtonDown(UINT nFlags, CPoint point)

{

    if (m_rectEllipse.PtInRect(point)) {

        if (m_nColor == GRAY_BRUSH) {

            m_nColor = WHITE_BRUSH;

        }

        else {

            m_nColor = GRAY_BRUSH;

        }

        InvalidateRect(m_rectEllipse);

    }

}

 

 

Listing 1.

 

Using ClassWizard with MYMFC1

 

Look at the following mymfc1View.h source code, the declaration part:

 

//{{AFX_MSG(CMymfc1View)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

//}}AFX_MSG

 

Now look at the following mymfc1View.cpp source code, the implementation part:

 

//{{AFX_MSG_MAP(CMymfc1View)

ON_WM_LBUTTONDOWN()

//}}AFX_MSG_MAP

 

AppWizard generated the funny-looking comment lines for the benefit of ClassWizard. ClassWizard adds message handler prototypes between the AFX_MSG brackets and message map entries between the AFX_MSG_MAP brackets. In addition, ClassWizard generates a skeleton OnLButtonDown() member function in mymfc1View.cpp, complete with the correct parameter declarations and return type. You run AppWizard to generate the application only once, but you can run ClassWizard as many times as necessary, and you can edit the code at any time. You're safe as long as you don't alter what's inside the AFX_MSG and AFX_MSG_MAP brackets.

 

 

Continue on next module....part 2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN MFC 9.0 class library online documentation - latest version.

  3. Porting & Migrating your older programs.

  4. MSDN Library

  5. DCOM at MSDN.

  6. COM+ at MSDN.

  7. COM at MSDN.

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | AppWizard 2 | Event Handling, Mapping Modes, and a Scrolling View 2 | Download | Site Index |