Getting User Input--Message Map Functions

Your EX03A application from Chapter 3 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 Part III of this book 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? Appendix A (and 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 nonstatic 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. (The toggle is activated by 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.

For Win32 Programmers

The MFC library makes it easy to attach your own state variables to a window through C++ class data members. In Win32 programming, the WNDCLASS members cbClsExtra and cbWndExtra are available for this purpose, but the code for using this mechanism is so complex that developers tend to use global variables instead.

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.

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:

CRectleft, top, right, bottom
CPointx, y
CSizecx, cy

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 EX04A 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 EX04A Example

In the EX04A 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 Chapter 3 example, drawing in the window depended on only one function, OnDraw. The EX04A example requires three customized functions (including the constructor) and two data members. The complete CEx04aView header and source code files are listed in Figure 4-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 boldface.

EX04AVIEW.H

// ex04aView.h : interface of the CEx04aView class // /////////////////////////////////////////////////////////////////////// #if !defined(AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2 __INCLUDED_) #define AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2 __INCLUDED_ #if _MFC_VER > 1000 #pragma once #endif // _MFC_VER > 1000 class CEx04aView : public CView { protected: // create from serialization only     CEx04aView();     DECLARE_DYNCREATE(CEx04aView) // Attributes public:     CEx04aDoc* GetDocument(); // Operations public: // Overrides     // ClassWizard generated virtual function overrides     //{{AFX_VIRTUAL(CEx04aView)     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 ~CEx04aView(); #ifdef _DEBUG     virtual void AssertValid() const;     virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected:     //{{AFX_MSG(CEx04aView)     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 ex04aView.cpp inline CEx04aDoc* CEx04aView::GetDocument()    { return (CEx04aDoc*)m_pDocument; } #endif /////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations //  immediately before the previous line. #endif // !defined(AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2__INCLUDED_) 

EX04AVIEW.CPP

// ex04aView.cpp : implementation of the CEx04aView class // #include "stdafx.h" #include "ex04a.h" #include "ex04aDoc.h" #include "ex04aView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE __; #endif /////////////////////////////////////////////////////////////////////// // CEx04aView IMPLEMENT_DYNCREATE(CEx04aView, CView) BEGIN_MESSAGE_MAP(CEx04aView, CView)     //{{AFX_MSG_MAP(CEx04aView)     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() /////////////////////////////////////////////////////////////////////// // CEx04aView construction/destruction CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 200, 200) {     m_nColor = GRAY_BRUSH; } CEx04aView::~CEx04aView() { } BOOL CEx04aView::PreCreateWindow(CREATESTRUCT& cs) {     // TODO: Modify the Window class or styles here by modifying     //  the CREATESTRUCT cs     return CView::PreCreateWindow(cs); } /////////////////////////////////////////////////////////////////////// // CEx04aView drawing void CEx04aView::OnDraw(CDC* pDC) {     pDC->SelectStockObject(m_nColor);     pDC->Ellipse(m_rectEllipse); } /////////////////////////////////////////////////////////////////////// // CEx04aView printing BOOL CEx04aView::OnPreparePrinting(CPrintInfo* pInfo) {     // default preparation     return DoPreparePrinting(pInfo); } void CEx04aView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) {     // TODO: add extra initialization before printing } void CEx04aView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) {     // TODO: add cleanup after printing } /////////////////////////////////////////////////////////////////////// // CEx04aView diagnostics #ifdef _DEBUG void CEx04aView::AssertValid() const {     CView::AssertValid(); } void CEx04aView::Dump(CDumpContext& dc) const {     CView::Dump(dc); } CEx04aDoc* CEx04aView::GetDocument() // non-debug version is inline {     ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx04aDoc)));     return (CEx04aDoc*)m_pDocument; } #endif //_DEBUG /////////////////////////////////////////////////////////////////////// // CEx04aView message handlers void CEx04aView::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);     } } 

Figure 4-1. The CEx04aView header and source code files.

Using ClassWizard with EX04A

Look at the following ex04aView.h source code:

//{{AFX_MSG(CEx04aView) afx_msg void OnLButtonDown(UINT nFlags, CPoint point); //}}AFX_MSG 

Now look at the following ex04aView.cpp source code:

//{{AFX_MSG_MAP(CEx04aView) 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 ex04aView.cpp, complete with the correct parameter declarations and return type.

Notice how the AppWizard_ClassWizard combination is different from a conventional code generator. You run a conventional code generator only once and then edit the resulting code. 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.

Using AppWizard and ClassWizard Together

The following steps show how you use AppWizard and ClassWizard together to create this application:

  1. Run AppWizard to create EX04A. Use AppWizard to generate an SDI project named EX04A in the \vcpp32\ex04a subdirectory. The options and the default class names are shown here.

  2. Add the m_rectEllipse and m_nColor data members to CEx04aView. With the Workspace window set to ClassView, right-click the CEx04aView class, select Add Member Variable, and then insert the following two data members:

    private:     CRect m_rectEllipse;     int m_nColor; 

    If you prefer, you could type the above code inside the class declaration in the file ex04aView.h.

  3. Use ClassWizard to add a CEx04aView class message handler. Choose ClassWizard from the View menu of Visual C++, or right-click inside a source code window and choose ClassWizard from the context menu. When the MFC ClassWizard dialog appears, be sure that the CEx04aView class is selected, as shown in the illustration below. Now click on CEx04aView at the top of the Object IDs list box, and then scroll down past the virtual functions in the Messages list box and double-click on WM_LBUTTONDOWN. The OnLButtonDown function name should appear in the Member Functions list box, and the message name should be displayed in bold in the Messages list box. Here's the ClassWizard dialog box.

    click to view at full size.

    Instead of using ClassWizard, you can map the function from the Visual C++ WizardBar (shown in Figure 1-2 in Chapter 1).

  • Edit the OnLButtonDown code in ex04aView.cpp. Click the Edit Code button. ClassWizard opens an edit window for ex04aView.cpp in Visual C++ and positions the cursor on the newly generated OnLButtonDown member function. The following boldface code (that you type in) replaces the previous code:

    void CEx04aView::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);     } } 
  • Edit the constructor and the OnDraw function in ex04aView.cpp. The following boldface code (that you type in) replaces the previous code:

    CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 200, 200) {     m_nColor = GRAY_BRUSH; } . . . void CEx04aView::OnDraw(CDC* pDC) {     pDC->SelectStockObject(m_nColor);     pDC->Ellipse(m_rectEllipse); } 

  • Build and run the EX04A program. Choose Build Ex04a.exe from the Build menu, or, on the Build toolbar, click the button shown here.

    Then choose Execute Ex04a.exe from the Build menu. The resulting program responds to presses of the left mouse button by changing the color of the circle in the view window. (Don't press the mouse's left button quickly in succession; Windows interprets this as a double click rather than two single clicks.)

    For Win32 Programmers

    A conventional Windows-based application registers a series of window classes (not the same as C++ classes) and, in the process, assigns a unique function, known as a window procedure, to each class. Each time the application calls CreateWindow to create a window, it specifies a window class as a parameter and thus links the newly created window to a window procedure function. This function, called each time Windows sends a message to the window, tests the message code that is passed as a parameter and then executes the appropriate code to handle the message.

    The MFC application framework has a single window class and window procedure function for most window types. This window procedure function looks up the window handle (passed as a parameter) in the MFC handle map to get the corresponding C++ window object pointer. The window procedure function then uses the MFC runtime class system (see Appendix B) to determine the C++ class of the window object. Next it locates the handler function in static tables created by the dispatch map functions, and finally it calls the handler function with the correct window object selected.



  • Programming Visual C++
    Advanced 3ds max 5 Modeling & Animating
    ISBN: 1572318570
    EAN: 2147483647
    Year: 1997
    Pages: 331
    Authors: Boris Kulagin
    BUY ON AMAZON

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net