The MFC library gives you some useful tools for diagnostic dumping. You enable these tools when you select the Debug target. When you select the Win32 Release target, diagnostic dumping is disabled and the diagnostic code is not linked to your program. All diagnostic output goes to the Debug view in the debugger's Output window.
To clear diagnostic output from the debugger's Output window, position the cursor in the Output window and click the right mouse button. Then choose Clear from the pop-up menu.
You've seen the TRACE macro used throughout the preceding examples in this book. TRACE statements are active whenever the constant _DEBUG is defined (when you select the Debug target and when the afxTraceEnabled variable is set to TRUE). TRACE statements work like C language printf statements, but they're completely disabled in the release version of the program. Here's a typical TRACE statement:
int nCount = 9; CString strDesc("total"); TRACE("Count = %d, Description = %s\n", nCount, strDesc);
The TRACE macro takes a variable number of parameters and is thus easy to use. If you look at the MFC source code, you won't see TRACE macros but rather TRACE0, TRACE1, TRACE2, and TRACE3 macros. These macros take 0, 1, 2, and 3 parameters, respectively, and are leftovers from the 16-bit environment, where it was necessary to conserve space in the data segment.
An alternative to the TRACE statement is more compatible with the C++ language. The MFC afxDump object accepts program variables with a syntax similar to that of cout, the C++ output stream object. You don't need complex formatting strings; instead, overloaded operators control the output format. The afxDump output goes to the same destination as the TRACE output, but the afxDump object is defined only in the Debug version of the MFC library. Here is a typical stream-oriented diagnostic statement that produces the same output as the TRACE statement above:
int nCount = 9; CString strDesc("total"); #ifdef _DEBUG afxDump << "Count = " << nCount << ", Description = " << strDesc << "\n"; #endif // _DEBUG
Although both afxDump and cout use the same insertion operator (<<), they don't share any code. The cout object is part of the Microsoft Visual C++ iostream library, and afxDump is part of the MFC library. Don't assume that any of the cout formatting capability is available through afxDump.
Classes that aren't derived from CObject, such as CString, CTime, and CRect, contain their own overloaded insertion operators for CDumpContext objects. The CDumpContext class, of which afxDump is an instance, includes the overloaded insertion operators for the native C++ data types (int, double, char*, and so on). The CDumpContext class also contains insertion
operators for CObject references and pointers, and that's where things get interesting.
The Dump Context and the CObject Class
If the CDumpContext insertion operator accepts CObject pointers and references, it must also accept pointers and references to derived classes. Consider a trivial class, CAction, that is derived from CObject, as shown here:
class CAction : public CObject { public: int m_nTime; };
What happens when the following statement executes?
#ifdef _DEBUG afxDump << action; // action is an object of class CAction #endif // _DEBUG
The virtual CObject::Dump function gets called. If you haven't overridden Dump for CAction, you don't get much except for the address of the object. If you have overridden Dump, however, you can get the internal state of your object. Here's a CAction::Dump function:
#ifdef _DEBUG void CAction::Dump(CDumpContext& dc) const { CObject::Dump(dc); // Always call base class function dc << "time = " << m_nTime << "\n"; } #endif // _DEBUG
The base class (CObject) Dump function prints a line such as this:
a CObject at $4115D4
If you have called the DECLARE_DYNAMIC macro in your CAction class definition and the IMPLEMENT_DYNAMIC macro in your CAction declaration, you will see the name of the class in your dump
a CAction at $4115D4
even if your dump statement looks like this:
#ifdef _DEBUG afxDump << (CObject&) action; #endif // _DEBUG
The two macros work together to include the MFC library runtime class code in your derived CObject class. With this code in place, your program can determine an object's class name at runtime (for the dump, for example) and it can obtain class hierarchy information.
The (DECLARE_SERIAL, IMPLEMENT_SERIAL) and (DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE) macro pairs provide the same runtime class features as those provided by the (DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC) macro pair.
With the Debug target selected, the application framework dumps all objects that are undeleted when your program exits. This dump is a useful diagnostic aid, but if you want it to be really useful, you must be sure to delete all your objects, even the ones that would normally disappear after the exit. This object cleanup is good programming discipline.
The code that adds debug information to allocated memory blocks is now in the Debug version of the CRT (C runtime) library rather than in the MFC library. If you choose to dynamically link MFC, the MSVCRTD DLL is loaded along with the necessary MFC DLLs. When you add the line
#define new DEBUG_NEWat the top of a CPP file, the CRT library lists the filename and line number at which the allocations were made. AppWizard puts this line at the top of all the CPP files it generates.
Window Subclassing for Enhanced Data-Entry ControlWhat if you want an edit control (in a dialog or a form view) that accepts only numeric characters? That's easy. You just set the Number style in the control's property sheet. If, however, you want to exclude numeric characters or change the case of alphabetic characters, you must do some programming.
The MFC library provides a convenient way to change the behavior of any standard control, including the edit control. Actually, there are several ways. You can derive your own classes from CEdit, CListBox, and so forth (with their own message handler functions) and then create control objects at runtime. Or you can register a special window class, as a Win32 programmer would do, and integrate it into the project's resource file with a text editor. Neither of these methods, however, allows you to use the dialog editor to position controls in the dialog resource.
The easy way to modify a control's behavior is to use the MFC library's window subclassing feature. You use the dialog editor to position a normal control in a dialog resource, and then you write a new C++ class that contains message handlers for the events that you want to handle yourself. Here are the steps for subclassing an edit control:
- With the dialog editor, position an edit control in your dialog resource. Assume that it has the child window ID IDC_EDIT1.
- Write a new classfor example, CNonNumericEditderived from CEdit. Map the WM_CHAR message and write a handler like this:
void CNonNumericEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (!isdigit(nChar)) { CEdit::OnChar(nChar, nRepCnt, nFlags); } }
- In your derived dialog or form view class header, declare a data member of class CNonNumericEdit in this way:
private: CNonNumericEdit m_nonNumericEdit;
- If you're working with a dialog class, add the following line to your OnInitDialog override function:
m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this);- If you're working with a form view class, add the following code to your OnInitialUpdate override function:
if (m_nonNumericEdit.m_hWnd == NULL) { m_nonNumericEdit.SubclassDlgItem(IDC_EDIT1, this); }
The CWnd::SubclassDlgItem member function ensures that all messages are routed through the application framework's message dispatch system before being sent to the control's built-in window procedure. This technique is called dynamic subclassing and is explained in more detail in Technical Note #14 in the online documentation.
The code in the preceding steps only accepts or rejects a character. If you want to change the value of a character, your handler must call CWnd::DefWindowProc, which bypasses some MFC logic that stores parameter values in thread object data members. Here's a sample handler that converts lowercase characters to uppercase:
void CUpperEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (islower(nChar)) { nChar = toupper(nChar); } DefWindowProc(WM_CHAR, (WPARAM) nChar, (LPARAM) (nRepCnt | (nFlags << 16))); }You can also use window subclassing to handle reflected messages, which were mentioned in Chapter 6. If an MFC window class doesn't map a message from one of its child controls, the framework reflects the message back to the control. Technical Note #62 in the online documentation explains the details.
If you need an edit control with a yellow background, for example, you can derive a class CYellowEdit from CEdit and use ClassWizard to map the =WM_CTLCOLOR message in CYellowEdit. (ClassWizard lists the message name with an equal sign in front to indicate that it is reflected.) The handler code, shown below, is substantially the same as the nonreflected WM_CTLCOLOR handler. (Member variable m_hYellowBrush is defined in the control class's constructor.)
HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor) { pDC->SetBkColor(RGB(255, 255, 0)); // yellow return m_hYellowBrush; }