Unit 3
Unit 3
Unit 3
0 Introduction
Visual C++ provides a complete Windows programming environment for C and C++. Using the keyboard and the mouse you can visually design, write code and accomplish many of your programming tasks. Visual C++ also contains all the supporting tools and libraries necessary to write MS Windows and DOS applications.
3.1 Objectives The Framework classes / MS- Foundation class Library VC++ Components Menus, Listboxes, combo boxes, checkboxes,
optionbuttons ,slider,textboxes The menu is otherwise known as Resource Event handling and processing Message dispatch system Mode and Modeless dialogs ActiveX controls Document view architecture and serialization MDI Multiple Document Interface Splitter windows Coordination between controls Subclassing
3.2 Content
3.2.1 Frame work classes (MFC) MICROSOFT FOUNDATION CLASS LIBRARY: welcome AppWizard has created this welcome application for you. This application not only demonstrates the basics of using the Microsoft Foundation classes but is also a starting point for writing your application. This file contains a summary of what you will find in each of the files that make up your welcome application. welcome.dsp This file (the project file) contains information at the project level and is used to build a single project or subproject. Other users can share the project (.dsp) file, but they should export the makefiles locally.
Page 86
Visual Programming
welcome.h This is the main header file for the application. It includes o.ther project specific headers (including Resource.h) and declares the CWelcomeApp application class. welcome.cpp This is the main application source file that contains the application class CWelcomeApp. welcome.rc This is a listing of all of the Microsoft Windows resources that the program uses. It includes the icons, bitmaps, and cursors that are stored in the RES subdirectory. This file can be directly edited in Microsoft Visual C++, res\welcome.ico . This is an icon file, which is used as the application's icon. This icon is included by the main resource file welcome.rc. res\welcome.rc2 This file contains resources that are not edited by Microsoft Visual C++, You should place all resources not editable by the resource editor in this file. welcome.clw This file contains information used by ClassWizard to edit existing classes or add new classes. ClassWizard also uses this file to store information needed to create and edit message maps and dialog data maps and to create prototype member functions. For the main frame window: MainFrm.h, MainFrm.cpp These files contain the frame class CMainFrame, which is derived from CrameWnd and controls all SDI frame features. res\Toolbar.bmp This bitmap file is used to create tiled images for the toolbar. The initial toolbar and status bar are constructed in the CMainFrame class. Edit this toolbar bitmap using the resource editor, and update the IDR MAINFRAME TOOLBAR array in welcome.rc to add toolbar buttons. AppWizard creates one document type and one view: welcomeDoc.h, welcomeDoc.cpp - the document
Page 87
Visual Programming
These files contain your CWelcomeDoc class. Edit these files to add your special document data and to implement file saving and loading (via CWelcomeDoc::Serialize). welcomeView.h, welcomeView.cpp - the view of the document These files contain your CWelcomeView class. CWelcomeView objects are used to view CWelcomeDoc objects. Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) fiie named welcome.pth and a precompiled types file named StdAfx.obj. Resource.h This is the standard header file, which defines new resource IDs. Microsoft Visual C++ reads and updates this file. Other notes: AppWizard uses "TODO:" to indicate parts of the source code you should add to or customize. If your application uses MFC in a shared DLL, and your application is in a language other than the operating system's current language, you will need to copy the corresponding localized resources MFC42XXX.DLL from the Microsoft Visual C++ CD-ROM onto the system or system32 directory, and rename it to be MFCLOC.DLL. ("XXX" stands for the language abbreviation. For example, MFC42DEU.DLL contains resources translated to German.) If you dont do this, some of the UI elements of your application will remain in the language of the operating system. 3.2.2 VC++ components Microsoft Developer Studio is the development environment in which the elements of Visual C++ run. It consists of an integrated set of tools that run under Windows 95 or Windows NT. Developer Studio gives you the tools to complete, test and refine your application all in one place. It includes a text editor, project build facilities, a source code browse window and Books online. You can control the operation of all the tools from a single application. Because these tools run under Windows, they use a variety of familiar methods in their operation. You can then size and position the controls as required for your application. Developer Studio also includes toolbars so you can quickly invoke commands by clicking a button. To help you choose the correct button, each one displays a descriptive label if the mouse pointer rests in it. If the default toolbars are not to your liking, you can customize them or create you own toolbars with the toolbar buttons of your choice. The Microsoft Developer Studio is an integrated source editor, compiler and debugger. It is a windows hosted application that behaves according to the Microsoft Page 88
Visual Programming
Windows Application User Interface Guidelines. It uses the multiple-document interface, which means that more than one source file can be open at a time. The Microsoft Developer Studio main application menu encompasses the entire functionality of the editor, compiler and debugger. The toolbar in it provides shortcuts to commonly used features. The status bar provides messages and information, including compiler and linker errors, process status and so on. Designing and Creating a Visual C++ Program Our first Visual C++ example will be simple, window that displays the text Welcome to Visual C++. To start this program. 1. Open Visual C++ and Click the New item in the File Menu, opening the New dialog box. 2. Now select the MFC App Wizard(exe) entry in the New dialog box. 3. Give the new program the name welcome in the project name box.
Starting a new program with the MFC AppWizard 4. Click OK to start the Visual C++ AppWizard.
Page 89
Visual Programming
The MFC AppWizard lets us create Visual C++ program 5. The AppWizard will write a great deal of the code in our program of us. 6. Step 1, first of six steps in the AppWizard. 7. Accept all the defaults in the AppWizard except one By default, the AppWizard creates multi-windowed programs, and well change that so that it creates a single-windowed program. 8. Click the option marked Single Document in the AppWizard, and click the button marked Next. This moves us to step 2 of the AppWizard . 9. When the AppWizard asks what database support we want in our program; well leave the None option selected. 10. Keep pressing Next until you reach step 6 in the AppWizard 11. Here, the AppWizard indicates what classes it will create for us in the new program: (CWelcomeApp, CmainFrame, CwelcomeDoc, and CwelcomeView) . 12. Click the AppWizard Finish button now. This opens the New Project information box. 13. Click Ok button in the New Project Information box now create the project. This creates the following files in the directory youve specified: welcome.clw welcome .dsw welcome.h welcome.cpp StdAfx.h StdAfx.h MainFrm.h MainFrm.cpp welcomeDoc.h welcomeDoc.cpp welcomeView.h welcomeView.cpp Resource.h ClassWizard file Main workspace file Application header file Application code file Standard application framework header Standard application framework code Main window header Main window code Document header Document code View header View code Resource constants file
Page 90
Visual Programming
welcome.rc welcome.ncb welcome.dsp res 14. And that's it! The AppWizard has also created a file named ReadMe.txt, which explains more about some of the files the AppWizard has written for us: The Parts of a Visual C++ Program There are four major parts of a Visual C++ AppWizard program: the application object, the main window object, the document object, and the view object. The Application Object The application object, supported in welcome.h and welcome.cpp (the .h file holds the definitions of constants and the declarations of variables and methods in this class), is what Windows actually runs first. When this object is started, it places the main window on the screen. The Main Window Object The main window object displays the program itself, and this is where we find the menu bar, the title bar of the window, and the tool bar. The main window object is responsible for everything that surrounds the area where the action - the drawing, text, and other graphics - goes on in our window. That area is called the client area in a window; for example, the text that you edit in a word processor appears in the word processor's client area. The view object is responsible for handling the client area itself. The View Object The view object handles the client area-this is where we'll format and display the data in our program, such as the text we're editing if we're creating a word processing program. In fact, the view object is really a window itself that appears on top of the client area. The data we display in the view object is stored in the document object. The Document Object In the document object, we store the data for our program. You may wonder why we don't store it in the view object-the reason is that we may have a great deal of data, more than will fit into our client area at once. Visual C++ makes it easier for us by allowing us to store all that data in the document object, then handle the display of the data that will fit into the client area in the view object. Resource file Layout and dependencies file Project file Resource directory
Page 91
Visual Programming
The four parts of our Visual C++ program look like this: CWelcomeApp Our Windows Program
Displaying Our Welcome Message So far we've had an overview of our program as AppWizard has written it; now let's modify it so that it prints out the "Welcome to Visual C++" message. To do that, we will add some code to the OnDraw( ) method of the CWeIcomeViewclass ( class that handles the display of our data). The program calls the OnDraw() method when it wants to display the program's client area. Currently, that method looks like this (in the file welcomeView.cpp): void CWelcomeView::OnDraw(CDC* pDC) { CWelcomeDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here } We will add the code to display our message in this method. 1. To open this method for editing, click the ClassView tab in Visual C++. 2. Find the entry for the CWelcomeView class 3. Click the small + (plus sign) next to that entry to open it. This displays the methods in this class as shown in Figure below
Page 92
Visual Programming
4. Find the entry for the OnDraw() method, and double-click it. This opens OnDraw ( ) in the text editor as shown in Figure below 5. To customize our program, add these two lines of code: void CWelcomeView::OnDraw(CDC* pDC) { CString welcome_string = "Welcome to Visual C++"; CWelcomeDoc* pDoc ~ GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(0, 0, welcome_string); } 6. Now our program is ready. Run it by choosing the Build welcome.exe item in the Build menu, followed by Execute welcome.exe in the same menu.
Editing the OnDraw() method Menus Menus are the popdown windows that display a set of options, or menu items, that the user can select from. They provide a convenient way to present the user with a list of options and then hide the list away again until it's needed
Page 93
Visual Programming
Our First Menu Example We will add a new menu item to a program's File menu, and this menu item will be "Print Welcome":
Untitled menus File Edit View Help New Open Save As Print Print Preview Print Welcome Print Setup Exit
When the user selects this new menu item, we'll display the text "Welcome to menus!" in the client area of our window.Create this program now using AppWizard, making it an SDI program. Give it the name "menus" in the Visual C++ project name entry field. Using the Menu Editor The Visual C++ menu editor is one of the most useful Visual C++ tools and it lets us design menus easily. To open the menu editor, click the Resources tab in the Visual C++ viewer window because a menu is considered a resource in a Windows program (the specifiication of Windows resources are held in the project's . rc file). This opens the menus program's resources folder. Next, find the folder marked Menus and open it. Then double-click the entry in that folder, IDR MAINFRAME, opening the menu editor
Page 94
Visual Programming
The Visual C++ Menu Editor Adding a New Menu Item Now we're ready to add our new menu item, Print Welcome. To do that, click the File menu in the menu editor to open that menu, as shown in Figure 4.4. lf. This new menu item will go between the existing Print Preview and Print Setup menu items, so click the Print Setup menu item and press the Insert key to insert a new, blank menu item, as also shown in Figure 4.4. A multi-dotted box surrounds the new menu item. Double-click this new menu item now, opening the Menu Item Properties box. Place the caption Print Welcome in the Caption box of that dialog box, and close the box. This automatically gives the ID ID_FILE_PRINTWELCOME to our new menu item.
Visual Programming
Now, close the menu editor. The next step is to connect that menu item to our code. Connecting Menu Items to Code We'll use ClassWizard to connect our new menu item to an event handler that will be called when the user clicks the item. Open ClassWizard now, as shown in Figure 4.6. You'll find that our new menu item's ID, ID_FILE_PRINTWELCOME, is listed in the Object IDs box in ClassWizard. We'll connect an event-handling method to that new menu item now. Click ID_FILE_PRINWNELCOME in the Object IDs list in ClassWizard, then click the item's Command entry in the Messages box (the Cornrnand entry corresponds to the case in which the menu item is clicked). This makes ClassWizard suggest a name for the event-handler of OnFilePrintwelcome( ) - click OK to accept that name. When you do, this new method appears in the ClassWizard Member functions box Double-click the On Fi1ePrintwelcome( )wentry in the ClassWizard Member functions box now, opening that new method: void CMenusView::OnFilePrintwelcome() { // TODO: Add your command handler code here }
Page 96
Visual Programming
This is the method that will be called when the user clicks the new Print Welcome menu item, and we'll place the code here that will respond to that click. In particular, we want to print out the string "Welcome to menus!" when the user clicks our new menu item, so we set aside storage for that string in the document: // menusDoc.h : interface of the CMenusDoc class
. .
We also initialize that object to the empty string in the document's constructor: CMenusDoc::CMenusDoc() { StringData = ""; } Now, in the view object, we get a pointer, pDoc, to the document in OnFilePrintwelcome(): void CMenusView::OnFilePrintwelcome() { CMenusDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
.
} Now we have to place the string "Welcome to menus." in the StringData object and display that string. We do that by invalidating the view so the code we place in OnDraw() will display the string for us: void CMenusView::OnFilePrintwelcome() { CMenusDoc* pDoc =GetDocument(); ASSERT_VALID(pDoc); . pDoc->StringData = "Welcome to menus!" ; Invalidate(); } Finally, we add the code to OnDraw( ) to display the text in the Stri ngData object:
Page 97
Visual Programming
void CMenusView::OnDraw(CDC* pDC) { CMenusDoc* pDoc a GetDocument(); ASSERT_VALID(pDoc); pDc->TextOut(0, 0, pDoc->StrringData); } The Code for this program appears in menusDoc.h and menusDoc.cpp and ,menusView.h and menusView.cpp. menusDoc.h and menusDoc.cpp // menusDoc.h : interface of the CMenusDoc class // #if !defined(AFX MENUSDOC H~F9OO3AAB_925D_11D0 8860_444553540000_INCLUDED_) #define AFX MENUSDOC H F90O3AAB 925D~11D0 8860 444553540000 INCLUDED_ #if MSC_VER >= 1000 #pragma once #endif // MSC VER >= 1000 class CMenusDoc : public CDocument { protected: // create from serialization only CMenusDoc(); DECLARE DYNCREATE(CMenusDoc) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX VIRTUAL(CMenusOoc) public: virtual BOOL OnNewDocuinent(); virtual void Serialize(CArchive& ar); //}}AFX VIRTUAL // Implementation public: virtual ~CMenusDoc(); CString StringData; #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif Page 98
Visual Programming
protected: // Generated message map functions protected: //{{AFX MSG(CMenusDoc) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX MSG DECLARE_MESSAGE_MAP() }; // menusDoc.cpp : implementation of the CMenusDoc class // #include "stdafx.h" #include "menus.h" #include "menusDoc.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE __; #endif // CMenusDoc IMPLEMENT DYNCREATE(CMenusDoc, CDocument) BEGIN_MESSAGE_MAP(CMenusDoc, CDocument) //{{AFX MSG MAP(CMenusDoc) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code //}}AFX MSG MAP END_MESSAGE_MAP() // CMenusDoc construction/destruction CMenusDoc::CMenusDoc() { // TODO: add one-time construction code here StringData = ""; } CMenusDoc::~CMenusDoc() { } BOOL CMenusDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) return TRUE; }
Page 99
Visual Programming
// CMenusDoc serialization void CMenusDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } // CMenusDoc diagnostics #ifdef _DEBUG void CMenusDoc::AssertValid() const { CDocument::AssertValid(); } void CMenusDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif // DEBUG // CMenusDoc commands menusView.h and menusView.cpp // menusView.h : interface of the CMenusView class // #if !defined(AFX MENUSVIEW H_F90O3AAD 925D 11D0 8860 444553540000_INCLUDED_ ) #define AFX MENUSVIEW H~F9OO3AAD 925D 11D0 8860 444553540000 INCLUDED_ #if MSC VER >= 1000 #pragma once #endif // MSC VER >= 1000 class CMenusView : public CView { protected: // create from serialization only CMenusView(); DECLARE_DYNCREATE(CMenusView) // Attributes public: CMenusDoc* GetDocument();
Page 100
Visual Programming
// Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX VIRTUAL(CMenusView) 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* pIn,fo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFX VIRTUAL // Implementation public: virtual ~CMenusView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX MSG(CMenusView) afx_msg void OnFilePrintwelcome(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG // debug version in menusView.cpp inline CMenusDoc* CMenusView::GetDocument() { return (CMenusDoc*)m_pDocument; } #endif //{{A_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations #endif !/ !defined(AFX_MENUSVIEW_H_F9OO3AAD_925D 11DO_8860_444553540000_INCLUDED_) //menusView.cpp : implementation of the CMenusView class #include "stdafx.h" #include "menus.h" #include "menusDoc.h" #include "menusView.h"
Page 101
Visual Programming
#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE_; #endif // CMenusView IMPLEMENT_DYNCREATE(CMenusView, CView) BEGIN_MESSAGE_MAP(CMenusView, CView) /l{{AFX_MS_MAP(CMenusView) ON_COMMAND(ID FILE PRINTWELtOME, OnFilePrintwelcome) //}}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() // CMenusView construction/destruction CMenusView::CMenusView() { // TODO: add construction code here } CMenusView::~CMenusView() { } BOOL CMenusView::PreCreateWindow(CREATESTRUCT& cs) ( // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return CView::PreCreateWindow(cs); // CMenusView drawing void CMenusView::OnDraw(CDC* pDC) { CMenusDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(0, 0, pDoc->StringData); // TODO: add draw code for native data here } // CMenusView printing
Page 102
Visual Programming
BOOL CMenusView::OnPreparePrinting(CPrintInfo* pInfo) { // default preparation return DoPreparePrinting(pInfo); } void CMenusView::OnBeginPrinting(COC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: add extra initialization before printing } void CMenusView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: add cleanup after printing } // CMenusView diagnostics #ifdef _DEBUG void CMenusView::AssertValid() const { CView::AssertValid(); } void CMenusView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CMenusDoc* CMenusView::GetDocument() // non-debug version is inline { { ASSERT(m~pDocument->IsKindOf(RUNTIME CLASS(CMenusDoc))); return (CMenusDoc*)m~pDocument; } #endif // DEBUG // CMenusView message handlers void CMenusView::OnFilePrintwelcome() { CMenusDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData = "Welcome to menus!"; Page 103
Visual Programming
Invalidate(); // TODO: Add your command handler code here } Creating the Full Menu Well see how to create whole new menus, make menu items inactive by "graying" them out (that is, making them appear in gray so there is no response when the user selects them), and check menu items (placing a check in front of their names in the mQnu). We'll also add submenus, shortcut keys (keys you can press to select a menu item when that menu is open), status bar prompts (the status bar appears at the bottom of our program's main window), toolbar buttons, and tool tips (a tool tip is a small yellow window that displays a prompt when you place the mouse cursor over a control like a button). Finally, we'll see how to create accelerator keys, which are control keys you can press to select a menu item even when the menu is not open. In our new example program, fullmenus, we'll add a new menu to our program, naming that menu Demo. This new menu will have a grayed out menu item, a checked menu item, and an item that will display a submenu. When the user selects the submenu item, a new submenu will pop open, giving the user a new menu to select from. Create the program now, making it an SDI AppWizard program. Now open the new program's menu resource, IDR_MAINFRAME, in the menu editor now
Page 104
Visual Programming
Our first task is to add the new Demo menu. That menu will go between the File and Edit menus, so just highlight (by clicking) the Edit menu now in the menu editor and press the Insert key. This adds a new menu to our menu bar; doubleclick the new menu and give it the name Demo in the Menu Item Properties box that opens. Next, we will add the new menu items we want in the Demo menu. The menu editor has placed one blank item in the Demo menu already-click that item now and give it the name Grayed, The menu editor gives it the ID ID-DEMO-GRAYED automatically.
Adding the Grayed menu item This will be the item we gray out, to make it inactive. In the same way, add two more menu items, Checked and Submenus. Just give these new menu items the names Checked and Submenus, and the menu editor will give them new IDs automatically.
Adding Shortcut Keys The D in our Demo menu is underlined, which means that when the user presses Alt+D, the Demo menu opens. In this way, the user can use the keyboard to reach our menu. In the Demo menu, the Submenu item's shortcut key is S. If the user presses Alt+S when the Demo menu is open, the submenu item will be activated, showing its submenus. Adding shortcut keys is very easy-we just place an ampersand (&) in front of the letter in the menu's name or menu item's name that we want to make into the shortcut. For example, to make the G in Grayed the shortcut key, we set that item's caption to &Grayed . Adding Status Bar Prompts When the user highlights a menu item, we can give the user more information about that item in the status bar, the bar at the bottom of our programs window. To do that, we simply place the text we want to appear in the status bar in the Prompt box of the Menu Item Properties box. Place the text This item is grayed out in the Prompt box of the Menus Items Properties box. This a status bar prompts.
Visual Programming
To add submenus to the Submenus item: Double-click that item now in the menu editor to open the Menu Item Properties box. Click the Pop-up item in that box. This adds a new submenu with one blank item to the Submenu menu item. When we click that item in the menu editor, the new submenu opens. 1. We can add two new items to that submenu now: Sub Item 1 and Sub Item 2.We add these items in the usual way, by double clicking a blank menu item and filling in the new item's name in the Menu Item Properties box. 2. And that's all it takes to set up our submenus. Adding Accelerator Keys An accelerator key is a control key that the user can press at any time (even when the menu is not open), and doing so is the same as clicking the menu item. In this case, pressing Ctrl+F5 is the same as clicking the Sub Item 2 menu item.
To add an accelerator key to a menu item: 1. 2. Go to the viewer window, and open the Accelerator folder, Double-click the IDR-MAINFRAME entry in the Accelerator folder now, opening the accelerator editor. Double-click the last blank entry in the accelerator editor, opening the Accel Properties box Select the ID for the Sub Item 2, ID-DEMO-SUBMENUS_SUBITEM2. With the list open, you can type the first few characters of the ID to make this selection appear. To connect Ctrl+F5 to this ID, click the Ctrl box in the Modifiers box In addition, select VK-F5 in the Key combo box (VK stands for virtual key). Now close the Accel Properties box. This adds Ctrl+F5 as an accelerator key to the Sub Item 2 menu item for us.
3.
4.
5. 6.
7.
Page 106
Visual Programming
8. To indicate that we have a new accelerator key for Sub Item 2, change that item's caption to "Sub Item 2\tCtrl+F5" in the menu editor.
Adding Tools to the Toolbar We can connect any of our menu items to buttons in the toolbar. For example, let's connect Sub Item 1 to a new button. We will start by designing the new button for our toolbar. Do that by opening the Toolbar folder in the viewer window now, and clicking the IDR_MAINFRAME entry there. Creating a new toolbar button We'll create a simple button here, showing just a box. We use the drawing tool in the toolbar editor's toolbox-the small pencil in the toolbox. Click the blank new button at left in the toolbar, and use this drawing tool to draw a small box in the new button. Next, double-click the new button in the toolbar, opening the Toolbar Button Properties box.. Select ID_DEMO_SUBMENUS_SUBITEM1 to connect that menu item to the toolbar button. In addition, give this button the prompt "Sub menu 1\nSub menu 11, in the prompt box. Doing so means that 'Sub menu 1" will appear in the status bar when the user places the mouse cursor over the new button in the toolbar. In addition, the text following the '\n" will appear as a tool tip for the button. In this case, that means our tool tip will be "Sub menu 1. For example as in the following figure ID is ID_EDIT_COPY and prompt is Copy the selection and put it on the Clipboard\nCopy. Connecting a toolbar button to a menu lD Graying Out Menu Items Our next step will be graying out a menu item. Open ClassWizard. and find the ID_DEMO_GRAYED item in the Object IDs box, as shown in Figure4.5.15. Now connect an event handler to this ID-but click the UPDATE_COMMAND_UI item (UI stands for User Interface) in the Messages box, not the COMMAND item. This creates the new method OnUpdateDemoGrayed(): void CFullmenusview::OnUpdateDemoGrayed(CCmdUI* pCmdUI) { //TODO: Add your command update UI handler code here }
Page 107
Visual Programming
This method is called when the program is about to display our new menu item, and we can make the menu item gray. In this method, we are passed a parameter, pCmdUI, that is a pointer to the CCmdUI object-the methods of this class
Method
Does This
ContinueRouting Tells the command-routing mechanism to continue routing the current message down the chain of handlers Enable Enables or disables the user-interface item for this command SetCheck SetRadio SetText Sets the check state of the user-interface item for this command Like the SetCheck member function, but operates on radio groups Sets the text for the user-interface item for this command
The CCmdUl Class Methods In this method, we want to gray out the menu item, so we call the CCmdUI object's Enable() method, passing that method a value of false to make the item grayed out:
void CFullmenusView::OnUpdateDemoGrayed(CCmdUI* pCmdUI) { pCmdUI->Enable(false); } I In this way we can gray out menu items, making then inaccessible to the user. Just place the code in that items update event handler, which is how the program checks to see how it should display a menu item before that item is actually shown on the screen. Checking Menu Items Now lets connect a method named OnUpdateDemoChecked() to the UPDATE_COMMAND_UI message of the Checked menu item: void CFullmenmusView::OnUpdateDemoChecked(CCmdUI*pCmdUI) { //TODO: Add your command update UI handler code here } To check the Checked menu item, we first enable it with Enable(), and then call the CCMdUI objects SetCheck() method with an argument of 1 (an argument of 0 removes a check mark): void CFullmenusView::OnUpdateDemoChecked(CCmdUI* pCmdUI) Page 108
Visual Programming
{ pCmdUI->Enable(true); pCmdUI->SetCheck(1); } Now the checked menu item will appear with a check mark in front of it. Adding Code to the Submenu Item Adding code to the submenus is just as easy as adding code to normal menu items. To add code to Sub Item 1, just find ID_SUBMENUS_SUBITEM1 in ClassWizard and add an event handler to it. void CFullmenusView::OnDemoSubmenusSubitem1() { } Here, we'll just place a new string, Stringdata, in the document, and place the text 'Sub menu item 1 clicked.' in the view when the user clicks this menu item: void CFullmenusView::OnDemoSubmenusSubiteml() { CFullmenusDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData = "Sub menu item 1 clicked.'; Invalidate(); } We do the same for Sub Item 2, displaying the string "Sub menu item 2 clicked.": void CFullmenusView::OnDemoSubmenusSubitem2() { CFullmenusDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData - "Sub menu item 2 clicked.'; Invalidate(); } Remember to add code to the OnDraw() method to display the text, as you did for the "menus" demonstration program. Dialog Box Dialog boxes are those windows with lots of buttons and list boxes and other items that use to pass some information to a program. The strong advantages dialog boxes have over other windows in Visual C++ is that it is far easier to place controls (buttons, text boxes, checkboxes and so on are called controls) in a dialog box than in other windows. In fact, Visual C++ has a special tool the dialog editor, especially for use in creating dialog boxes. Creating Our First Dialog Box
Page 109
Visual Programming
Our first dialog box example, named dialogs, will show us all the aspects of working with dialog boxes. We will set up a menu item in our Flie menu, Show Dialog Untitled dialogs File Edit View Help New Open Save As Show Dialog.. Print Print Preview Print Welcome Print Setup When the user clicks this menu item, we place a dialog box on the screen with an OK button, a Cancel button, a button labeled Click Me, and a text box. Text boxes are those controls that can take or accept and display text. When the user clicks the OK button, the dialog box will be dismisses from the screen. However, the whole point of dialog boxes is usually to pass information back to the program, so in this case, well pass the text from the text box back to the main program, which will display the text in its client area. Create a new SDI program named dialogs, and add a new menu item using the menu editor to the File menu: "Show Dialog...." Using ClassWizard, add an eventhandling method to this new menu item, On Fi1eShowdialog (): void CDialogsView::OnFileShowdialog() { //TODO: Add your command handler code here } When the user clicks this menu item, we want to place our new dialog box on the screen. Creating a Dialog Box To create a new dialog box, select the Resource item in the Visual C++ Insert menu now. This opens the Insert Resource box. Select the Dialog entry and click the New button. Clicking the New button opens the dialog box editor as shown in Fig. 4.12. Like menus, dialog boxes 'are considered resources in Windows programs. The resource ID given to our new dialog box is IDD-DIALOG1 as shown in Fig. 4.12. You can also see two buttons in the dialog box already: OK and Cancel. These are the two usual buttons in a dialog box-OK accepts the actions the user has made, and Cancel rejects them. If the user clicks the OK button, the DoModal() method returns the value
Page 110
Visual Programming
IDOK, and if the user clicks the Cancel button, the DoModal() method returns IDCANCEL. Creating a new dialog box The dialog box editor Adding Controls to a Dialog Box To add a control to the dialog box, you just drag a new control from the toolbox onto the dialog box under design. In our case, we will drag a button and a text box, also called an edit box, onto the dialog box. Labeling Controls To give a control a new caption, you just select it by clicking it with the mouse and typing the new caption. For our button, this opens the Push Button Properties box. Give the button the caption Click Me. The control ID, the dialog editor gives to the button will be IDC_BUTTON1 and that for text box will be IDC_EDIT1. Creating a Dialog Box Class Start the ClassWizard now, and click the Add Class Button, and the New item in the pop-up menu that appears. This opens the New Class box. Type the name Dlg for the new class in the Name box new. Next, select CDialog as the new dialog boxs base class in the Base class box. CDialog is the MFC base class for dialog boxed. Click the OK button the New Class box now. Clicking OK opens ClassWizard again. Make sure our new class Dlg is selected in the Class name box. Now we can connect Visual C++ code to our button. Connecting Methods to Dialog Box Conrtrols When the user clicks the Click Me button, we want to display the text to dialog boxes." in the text box. To do that, we'll first need some way determining when the user clicks that button. We can connect an event-handling method to that button now, using ClassWizard. To connect an event-handler to the button, select IDC-BUTTONI now in ClassWizard and double-click the BN_CLICKED entry in the Messages box. This is a special message that buttons send (the BN prefix stands for button) when they are clicked; the other button message is BN_DOUBLECLICKED, sent when a button is double-clicked. When you double-click the BN_CLICKED entry, ClassWizard suggests the name OnButtonl() for the new event handler. Click OK to a new event-handler OnButtonl() We open that new event-handler now:
Page 111
Visual Programming
void Dlg::OnButtonl() { // TODO : Add your control notification handler code her } This is the method that the program will call when the user clicks the Click Me button the dialog box. Connecting Variables to Dialog Box Controls We use Class Wizard to connect a member variable to a control in a dialog box. Start ClassWizard now and click the Member Variables tab. Make sure our dialog box class Dlg is selelcted in the Class name box. Now select the text box control, IDC_EDIT1 and click the Add Variable button. This opens the Add Member Variable box. Here we will be able to give a name to the text in the text box. Give the new member variable the name m_text in the Member variable name box. , and make sure Value shows in the Category box and CString in the Variable type box. This connects a CStri ng variable named m_text to the text in the text box. Now click the OK button in the Add Member Variable box to close it. Now that weve set up our new member variable to refer to the text in the text box, we can place text in that text box this way on ObButton1(). void DLg::OnButton1() { m_text = Welcome to dialog boxes; . . . } Simply assigning the m-text variable to our new text doesn't make that text appear in the text box. That variable is connected to the IDC_EDIT1 control in a special method the ClassWizard has added to our Dlg dialog box class: void Dlg::DoDataExchange(CDataExchange*,pDX) CDialog::DoDataExchange(pDX); //((AFX-DATA-MAP(Dlg) DDX-Text(pDX, IDC_EDIT1, m-text); //)IAFX_DATA-MAP We still have to do a little bit of work to make sure the text box is updated with the new data, and we do that by calling the UpdateData() method: void Dlg::OnButtonl() I m_text = "Welcome to dialog boxes."; UpdateData(false); Page 112
Visual Programming
Calling this method with a value of false updates the text box from the value in m-text. Calling this method with a value of true updates m-text from the text in the text box: UpdateData(false) means: UpdateData(true) means: Overriding the OK Button You can add code to the OK button just like any other button. Using ClassWizard, connect a method named OnOK() to the OK button, whose ID is IDOK: void Dlg::OnOK() { //TODO:Add extra validation here CDialog::OnOK(); } Notice the call to the CDialog class OnOK() method. Calling this method closes the dialog box and returns the value IDOK. We want to make sure the m_text variable holds the text from the text box, so we just call UpdateData() with a value of true here: void Dlg::OnOK() { UpdateData(true); CDialog::OnOK(); } Now even if the user edits the text in the text box, we'll be able to display exactly the same text that was in the text box when they clicked the OK button. Displaying a Dialog Box We want to display our new dialog box on the screen when the user clicks the "Show Dialog..." item in our program's File menu, and we've connected that menu choice to the view class's method, which is OnFi1eShowdialog(): void CDialogsView::OnFileShowdialog() { //TODO: Add your command handler code here } We will create a new object of our dialog box's class, Dlg, here, and display it with that object's DoModal() method. To let the view class know about the members of the Dlg class, we have to include the Dlg class's header file, Dlg.h, in the view class: IDC_EDIT1 m_text m_text IDC_EDIT1
Page 113
Visual Programming
// dialogsview.cpp : implementation of the CDialogsView class #include "stdafx.hll #include "dialogs.h" #include "dialogsDoc.h" #include "dialogsView.h" #include "Dlg.h"
. . .
We first create a new object of that class named dlg: void CDialogsView::OnFileShowdialog() { Dlg dlg;
. .
} Next, we display the dialog box on the screen. We do that with the DoModal() method. This method returns an integer value, which we store as the integer result: void CDialogsView::OnFileShowdialog() { Dlg dlg; int result = dlg.DoModal(); . . } At this point, the dialog box appears on the screen. The user can then click the Click Me button if they want to, placing our message in the text variable m_text. If they click the OK button, we can display the text from the text box, m_text, in our program's client area. First, we check to make sure the user did indeed click the OK button: void CDialogsView::OnFileShowdialog() You can return your own value from a dialog box by passing that value to the EndDialog() method. If the user did click OK, we want to get the string from the text box, m_text. We'll store that string in our document, so we get a pointer to the document now: void CDialogsView-:OnFileShowdialog() { Dlg dlg; int result = dlg.DoModal();
Page 114
Visual Programming
if(result == IDOK){ CDialogsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); . . } } We can set up a new CString object named Stringdata in the document (initializing it in the constructor): //dialogsdoc.h : interface of the CDialogsDoc class . . . class CDialogsDoc : public CDocument { protected: // create from serialization only CDialogsDoc(); DECLARE_DYNCREATE(CDialogsDoc) // Attributes
publ i c: CString StringData; . . . And we place the text in the dialog box's m_text variable in the Stringdata object: void CDialogsView::OnFileShowdialogo { I Dlg dlg; int result=dlg.DoModal(); if(result == IDOK){ CDialogsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData = dlg.m_text; . . . } }
Page 115
Visual Programming
Getting data from a dialog box is as simple as that. Even though the dialog box is closed, its object still exists, and the data in that object is still intact. Now we've retrieved the m_text string from the dialog box. The next task is to display that text, and we'll do that in the OnDraw() method, so we force a call to that method now with Invalidate(): void CDialogsView::OnFileShowdialogo { Dlg dlg; int result = dlg.DoModal(); if(result IDOK){ CDialogsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData _ dlg.m_text; Invalidate(); } } And in the view's OnDraw() method, we draw the text from the dialog box: void CDialogsView::OnDraw(CDC* pDC) { CDialogDoc*pDoc = GetDocument(); ASSERT_VALID(pDoc); pDC->TExtOut(0, 0, pDoc->StringData); } Now the program is complete. Working with Check Boxes Check boxes are those little boxes that are either empty inside or display a small check mark. When you click a check box, it changes its state to either checked or unchecked. You use check boxes to let the user select one or more options from a number of options, such as the contents of a sandwich (e.g., tomato, lettuce, ham, turkey, and so on). We use a dialog box as our main window so we can then use the dialog editor to create and position our check boxes in that window. When the user clicks one of the three check boxes, we indicate which check box they have clicked in a text box. Create this program now naming it checks, and making it a dialog based program in Appwizard. After you have created the program, open the dialog box for our main window, ID = IDD_CHECKS_DIALOG, in the dialog editor. Start by deleting the TODO : Place dialog controls here label. Now we will add the controls. Adding Check Boxes to a Program
Page 116
Visual Programming
Just drag check box controls over to the dialog box from the dialog editors toolbox. The dialog editor gives the check boxes the captions Check1, Check2 and so on. Align these controls in the Dialog Editor. Connecting Check Boxes to Code We connect event-handling methods to the check boxes with ClassWizard, OpenClassWizard. You can find the three check boxes, IDC-CHECK1, IDC-CHECK2, and IDC-CHECK3. Add event handlers to these controls by clicking those ID values one by one and double-clicking the BN-CLICKED message in the Messages box. This creates the event handlers OnCheckl(), OnCheck2, and OnCheck3(). ClassWizard can be used to connect a member variable to the text in the text box, which we will name m-text. Now open OnCheckl(): void CChecksDIg::OnCheckl() { //TODO: Add your control notification handler code here } This is the method that is called when the user clicks the first check box. Clicking the check box automatically changes its state from checked to unchecked or from unchecked to checked. You can determine the current state of the check box at any time by connecting a member variable to the check box control and using the GetCheck() method. Check boxes are derived from the CButton class, so you can use the CButton methods, with check boxes. When the user clicks check box 1, we will simply display the string "Check 1 clicked" this way in the text box: void CChecksDIg::OnCheckl() { m_text = "Check 1 clicked"; UpdateData(false); } And we display a similar string for check box 2 and check box 3: void CChecksDlg::OnCheck2() { m_text = "Check 2 clicked"; UpdateData(false); } void CChecksDlg::OnCheck3() { m_text = "Check 3 clicked"; UpdateData(false); } That is all we need; run the program now . Page 117
Visual Programming
Working with Radio Buttons Radio buttons, on the other hand, only let the user select one option, such as the current day of the week (for example, Monday, Tuesday, Wednesday, and so on). Radio buttons (also called option buttons) are small circular buttons that display a black dot in the middle when clicked and are empty otherwise. Like clicking a check box, clicking a radio button causes it to change the state to checked or unchecked. Unlike check boxes, however, radio buttons are coordinated into groups, and function together. Only one radio button in a group of radio buttons can be checked at a time. When one radio button is clicked, it displays a dot in its middle, and all the other radio buttons are cleared, so only the clicked radio button displays a dot. There are two ways to group radio buttons together in Visual C++: either with a group box control or by placing them in the same window that is, all the radio buttons in the same window function together if there is no group box. In our radio button example, named "radios," we'll have three radio buttons arranged vertically. When the user clicks one of the radio buttons, well indicate which button was clicked with a message in a text box. Create this program using AppWizard, naming it radios. Open the dialog box for our main window now, ID = IDD_RADIOS_DIALOG.. We place a text box in the main window and we also add three radio buttons, Radio1, Radio2, Radio3 to the main window. Align the three radio buttons vertically and horizontally. Now open the ClassWizard and find the three radio buttons, IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, and connect the event handlers to each of these new controls, OnRadio1(), OnRadio2(), OnRadio3(). Finally connect a new member variable m_text to the text in the text box.
Page 118
Visual Programming
Connecting Radio Buttons to Code Now open OnRadiol(): void CRadiosDIg::OnRadiol() { //TODO:Add your control notification handler code here } This is the event handler that is called when the user clicks radio button 1. Here, we just report the user's action in the text box with the message "Radio 1 clicked." this way: void CRadiosDlg::OnRadiol() { m_text = "Radio 1 clicked."; UpdateData(false); }
Page 119
Visual Programming
Coordinating Radio Buttons Note that we make no other provisions here for de-selecting the other radio buttons when the user clicks this radio button. That's because the three radio buttons already function as a group automatically-they are all in the same window, so they are already coordinated. When the user clicks one of our radio buttons, the program de-selects the other automatically, because they all share the same window. Add code for the second and third radio buttons as well: void CRadiosDlg::OnRadio2() { m_text = 'Radio 2 clicked."; UpdateData(false); } void CRadiosDIg::OnRadio3() I{ m_text = "Radio 3 clicked."; UpdateData(false); } Now the radios program is ready to go . Run it and click a button. Working with List Boxes List Boxes are controls that present the user with a list of items displayed in a box. The user can click an item to highlight it and double click that item to select it. In our example, we'll see how to fill a list box with a dozen items, and the list box will present these items as a list to the user. Since we'll add a dozen items to the list box, the list box won't be able to display all the items at once, so it will display a scroll bar control on the right. When the user double-clicks a list box item, we will display which item theyve selected in a text box. Call this example lists and create it now with AppWizard, making this a dialogbased application. Drag and place a new list box to the dialog window. Next all the text box and two labels.
Page 120
Visual Programming
Designing our lists program Using Labels to Display Text Drag a new label to the top of the list box, and then simply type the text: Doubleclick an item:; when you do, that text appears in the label. Add another label on top of the text box in which we will indicate the user's selection, and give that label the text, You chose
Giving Our List Box a Member Object To add a dozen items to our list box, we have to be able to refer to that list box from our code, which means giving it a name. We do that in ClassWizard, by connecting this new list box, which the dialog editor has given the ID IDC_LIST1, to a new object of the list box class, CListbox. Start ClassWizard now, and select the Member Variables tab. Select the IDC-LISTl item in the Control lDs box, and click the Add Variable button. This opens the Add Member Variable box. In this case, we will connect the member variable, m_list, to the list box, IDC_LIST1. Type the name m_list in the Member Variable Name box.
Page 121
Visual Programming
Make sure you select Control in the Category box, so that we add a member variable for the whole control, not just its value. ClassWizard will give the new member variable the type CListBox. Now click the OK button to close the Add Member Variable box. This adds the member variable m_list of class CListBox, to our program. Now we can refer to list box in code using m_list. Now we can initializing data in the list box. Initializing the Data in a List Box To initialize data in a dialog box, we add code to the OnInitDialog() method. BOOL CListsDlg::OnInitDialog() { CDialog::OnInitDialog(); . . . } We'll place the items we want to display in the list box in that control, so when the dialog box appears on the screen, the list box will already hold our items. We'll add 12 items to the list box, naming them item 01, item 02, and so on. To do that, we use m_list's AddString() method: BOOL CListsDIg::OnInitDialog() { CDialog::OnInitDialog(); m_list.AddString("Item01"); m_list.AddString("Item02"); m_list.AddString("Item03"); m_list.AddString("Item04"); m_list.AddString("Item05"); m_list.AddString("Item06"); m_list.AddString("Item07"); m_list.AddString("Item08"); m_list.AddString("Item09"); m_list.AddString("Item10"); m_list.AddString("Item 11"); m_list.AddString("Item 12"); // Add "About..." menu item to system menu. . . }
Page 122
Visual Programming
The items in a list are numbered automatically when you place them in the list, and you refer to them by an index value. This index value is 0 for the first item in the list box, 1 for the next, and so on. When we ask the list box in code which item the user has selected, it passes back that item's index . Handling List Box Double-Clicks When the user double-clicks our list box, we want to be able to display the selection they've made in our text box, so connect a member variable to the text in the text box now, named m_text. This is the variable we'll fill with the string the user has double-clicked in the list box. The next step is determining when the user double-clicks the list box, and we'll do that with ClassWizard's help. Open ClassWizard now. Select the list box, IDC-LIST1, and double-click the LBN-DBLCLICK message in the Messages box now. Class Wizard then suggests the name OnDblclkListl() for this new event-handler. Accept that name by clicking OK. ClassWizard now creates the new method, OnDblclkListl(): void CListsDlg::OnDblclkListl() I{ //TODO: Add your control notification handler code here } This is the new method that the program will call when the user double-clicks an item in the list box. Determining Which Item is Selected in a List Box We will use the CListbox method GetCursel () to get the list box's current selection. This returns the index of the item the user double-clicked. To get the actual name of that item as we've placed it into the list box. Use CListbox method GetText(), which fills a string object that you pass to it with the item's text.
void CListsDIg::OnDblclkListl() { m_list.GetText(m_list.GetCurSel(), m_text); . } This places the items text into m_Text. We call UpdateDate() to update m_text on the screen. void CListsDIg::OnDblclkListl() { m_list.GetText(m_list.GetCurSel(), m_text); Page 123
Visual Programming
UpdateData(false); } Run the program and double click an item. When you do, the program reports which item youve chosen in the text box. Working with Combo Boxes Combo boxes are also very popular Windows controls. These controls are a combination of a text box, drop-down list box, and a button the user can press to open the list box. We'll include a combo box and a text box in which to display the user's selections. When the user clicks the downward arrow in the combo box, a list of items will appear. When the user selects one of these items, we'll indicate which one they chose in the text box. Let's start this program; call it "combos", and make it a dialog-based AppWizard program. Initializing a Combo Box We'll initialize our combo box in OnInitDialog(): BOOL CCombosDlg::OnInitDialog() { CDialog::OnInitDialog();
. .
} Here we can initialize our combo box just as we initialized our list box. Use ClassWizard to connect a member variable to the combo box control and name variable m_combo, making it a variable of class CComboBox. You can also connect a member variable to the value of a combo box. The value property for a combo box holds the text in the combo's text box. Use the AddString() method to add the strings we want ("Item 01" to "Item 12") to the combo box:
BOOL CCombosDIg::OnInitDialog() { CDialog::OnInitDialog(); m_combo.AddString("Item 0l); m_combo.AddString('Item 02); m_combo.AddString('Item 03); m_combo.AddString('Item 04); m_combo.AddString('Item,05); m_combo.AddString('Item 06); m_combo.AddString('Item 07); m combo.AddString('Item 08); m_combo.AddString(Item 09);
Page 124
Visual Programming
m_combo.AddString(Item 10"); m combo.AddString("Item ll'); m_combo.AddString("Item 12); // Add About..." menu item to system menu.
}
In addition, we select the first item in the combo box's list ("Item 01") so that that item's text will appear in the combo box's text box when the dialog window first appears BOOL CCombosDIg::OnInitDialog() { CDialog::OnInitDialog(); m_combo.AddString("Item 0l); m_combo.AddString('Item 02); m_combo.AddString('Item 03); m_combo.AddString('Item 04); m_combo.AddString('Item,05); m_combo.AddString('Item 06); m_combo.AddString('Item 07); m combo.AddString('Item 08); m_combo.AddString(Item 09); m_combo.AddString(Item 10"); m combo.AddString("Item ll'); m_combo.AddString("Item 12); m_combo.SetCurSel(0); // Add About..." menu item to system menu.
. .
Determining the Selections Made When the user selects an item from that list box, the combo box will send our program a CBN_SELCHANGE message. (CBN stands for combo box notification). Using ClassWizard, connect an event-handling method to that message now. Classwizard will suggest the name OnSelchangeCombol,() for the new method. Accept that name and open the new method now: void CCombosDlg::OnSelchangeCombol() { // TODO: Add your control notification handler code here Page 125
Visual Programming
} When this method is called, the user has made a new selection, and we want to report that selection in the text box, using the text box's m_text variable. We do using the GetCursel() method and then calling UpdateData() to update the text in the text box: void CCombosDlg::OnSelchangeCombol() { m_combo.GetLBText(m_combo.GetCurSel(), m_text); UpdateData(false); } Run the program now. When you select a new item, the program indicates which item you chose by displaying its text in the text in the text box. Adding Scroll Power with Sliders Sliders are relatively new controls, but they are also popular among programmers. They present the user with a small sliding box, called a thumb, that the user can move along a groove, much like the controls on a stereo. These controls are useful when you want to get a number from the user, such as when you want to set a color value. Sliders present the user with a small, scrollable box (the thumb) that moves along a groove. When the user moves the sliders thumb with the mouse, we can report the thumbs new position on a scale of 1 to 100 (1 = extreme left, 100 = extreme right). 1. We start by creating a dialog-based AppWizard program named "sliders". 2. Next, we add the controls we want to the slider, including a text box, two labels, and a new slider control. The sliders IDvalue is IDC-SLIDER1. 3. Now we're ready to use ClassWizard to add a new member variable of class CSlider to our program. Slider at design time Initializing a Slider Control When you create a new slider control, you should set its range. The range represents the allowable values the slider can take, from the extreme left position of the thumb to the extreme right position. In our example, we'll let the slider return values from 1 to 100 using the Cslider, SetRangeMin(), and SetRangeMax() methods. CDialog::OnInitDialog(); { m_slider.SetRangeMin(l, false); m_slider.SetRangeMax(100, false);
Page 126
Visual Programming
// Add 'About..." menu item to system menu. . . } In addition, we can display the initial value of the slider (that initial value is 1) in the text box by attaching a member variable to the text in the text box and setting it to "l" this way: BOOL CSlidersDlg::OnInitDialog() { CDialog-:OnInitDialogo; m_slider.SetRangeMin(l,false); m_slider.SetRangeMax(100, false); m_text = "1"; UpdateData(false); // Add 'About..." menu item to system menu. . . } Now our slider control is set up. Handling Slider Events When the user moves the slider's thumb, it passes us a WM-HSCROLL message (WM_VSCROLL for vertical sliders). Using ClassWizard, connect WM-HSCROLL to the OnHScrol1() method (for vertical sliders, you would use OnVScrol1()). Be sure that the object ID for CslidersDlg is selected in the ClassWizard when you add the handler. The OnHSCroll handler will be handling the message for the dialog itself: void CSlidersDIg::OnHScroll(UINT nSBCode, UINT npos, CScrollBar* pScrollBar) { // TODO: Add your message handler code here and/or call default } In this method, we are passed a scroll bar code, the new position of the scrollable control, and a pointer to the scrollable control that sent the message. Well intercept the SB_THUMSPOSITION message, sent to us when the user moves the slider's thumb. void CSlidersDlg::OniHScroll(UINT nSBCode, UINT npos, CScrollBar* pScrollBar) { if(nSBCode == SB_THUMBPOSITION) { . .
Page 127
Visual Programming
} else{ CDialog::OnHScroll(nSBCode, npos, pScrollBar); } } If the message we got was indeed SB_THUMBPOSIT1ON, the user has moved the slider's thumb, and we want to indicate the new slider position in our text box. Displaying Numbers in Text Boxes We can use the CString classs Format() method here. This method takes a format string, just as you would use for the normal C string-handling functions. That means that we can format our nPos parameter as a long integer and place it in out text box this way: void CS1idersDlg::OnHScroll(UINT nSBCode,, UINT npos, CScrollBar* pscrollbar) { if (nSBCode = = SB_THUMBPOSITION){ m_text.Format(%ld,nPos); UpdateData(false); } else{ CDialog::OnHScroll(nSBCode, npos, pScrollBar); } } Now run the program. When you move the sliders thumb, the program reports the new location of the slider. Date and Time Classes If you've ever tried to manipulate time values returned from a computer, you'll be pleased to learn about MFC's CTime and CTimeSpan classes, which represent absolute times and elapsed times, respectively. The use of these classes is pretty straightforward, so there's no sample program for this section. However, the following sections will get you started with these handy classes. Before you start working with the time classes look over the tables below, which lists the member functions of the CTime class and the member functions of the CTimeSpan class. Member Functions of the CTime Class Description Constructs a string representing the time object's time. Constructs a string representing the time object's GMT (or UTC) time. This is the Page 128
Visual Programming
Greenwich mean time. GetCurrentTime() GetDay() GetDayOfWeek() GetGmtTm() Creates a CTime object for the current time. Gets the time object's day as an integer. Gets the time object's day of the week, starting with 1 for Sunday. Gets a time object's second, minute, hour, day, month, year, day of the week, and day of the year as a tm structure. Gets the time object's hour as an integer. Gets a time object's local time, returning the second, minute, hour, day, month, year, day of the week, and day of the year in a tm structure. Gets the time object's minutes as an integer. Gets the time object's month as an integer. Gets the time object's second as an integer. Gets the time object's time as a time't value. Gets the time object's year as an integer. Member Functions of the CTimeSpan Class Description Constructs a string representing the timespan object's time. Gets the time-span object's days. Gets the time-span object's hours for the current day. Gets the time-span object's minutes for the current hour. Gets the time-span object's seconds for the current minute. Gets the time-span objects total hours. Gets the time-span object's total minutes. Gets the time-span object's total seconds.
GetHour() GetLocalTm()
GetMinute() GetMonth() GetSecond() GetTime() GetYear() Function Format() GetDays() GetHours() GetMinutes() GetSeconds() GetTotalHours() GetTotalMinutes() GetTotalSeconds()
Page 129
Visual Programming
Using a CTime Object Creating a CTime object for the current time is a simple matter of calling the GetCurrentTime() function, like this: CTime time = CTime::GetCurrentTime();
Notice that, because GetCurrentTime() is a static member function of the CTime class, you can call it without actually creating a CTime object. You do, however, have to include the class's name as part of the function call, as shown in the preceding code. As you can see, the function returns a CTime object. This object represents the current time. If you wanted to display this time, you could call upon the Format() member function, like this: CString str = time.Format("DATE: %A, %B %d, %Y"); The Format() function takes as its single argument a format string that tells the function how to create the string representing the time. The previous example creates a string that looks something like this: DATE: Saturday, April 20, 2001 The format string used with Format() is not unlike the format string that you use with functions like the old DOS favorite printf() or the Windows conversion function wsprintf(). That is, you specify the string's format by including literal characters along with control characters. The literal characters, such as the "DATE" and the commas in the previous string example, are added to the string exactly as you type them, whereas the format codes are replaced with the appropriate values. For example, the %A in the previous code example will be replaced by the name of the day and the %B will be replaced by the name of the month. Although the format-string concept is the same as that used with printf(), the Format() function has its own set of format codes, which are listed in following table. Format Codes for the Format() Function Code %a %A %b %B %c %d Description Day name, abbreviated (such as Sat for Saturday). Day name, no abbreviation. Month name, abbreviated (such as Mar for March). Month name, no abbreviation. Localized date and time (for the U.S., that would be something like 03/17/01 12:15:34). Day of the month as a number (0131).
Page 130
Visual Programming
%H %I %j %m %M %p %S %U %w %W %x %X %y %Y %z %Z %% Hour in the 24-hour format (0023). Hour in the normal 12-hour format (0112). Day of the year as a number (001366). Month as a number (0112). Minute as a number (0059). Localized a.m./p.m. indicator for 12-hour clock. Second as a number (0059). Week of the year as a number (0051, considering Sunday to be the first day of the week). Day of the week as a number (06, with Sunday being 0). Week of the year as a number (0051, considering Monday to be the first day of the week). Localized date representation. Localized time representation. Year without the century prefix as a number (0099). Year with the century prefix as a decimal number (such as 2000). Name of time zone, abbreviated. Name of time zone, no abbreviation. Percent sign.
Other CTime member functions like GetMinute(), GetYear(), and GetMonth() are obvious in their usage. However, you may like an example of using a function like GetLocalTm(): struct tm* timeStruct; timeStruct = time.GetLocalTm(); The first line of the previous code declares a pointer to a tm structure. (The tm structure is defined by Visual C++.) The second line sets the pointer to the tm structure created by the call to GetLocalTm(). This function call essentially retrieves all of the time information at once, organized in the tm structure, which is defined in the header file TIME.H, as shown in the following listing. Listing LST.TXT-The tm Structure struct tm { int tm_sec;
Page 131
Visual Programming
int tm_min; int tm_hour; int tm_mday; /* minutes after the hour - [0,59] */ /* hours since midnight - [0,23] */ /* day of the month - [1,31] */
int tm_mon; /* months since January - [0,11] */ int tm_year; /* years since 1900 */ int tm_wday; int tm_yday; /* days since Sunday - [0,6] */ /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */ }; Using a CTimeSpan Object A CTimeSpan object is nothing more complex than the difference between two times. You can use CTime objects in conjunction with CTimeSpan objects to easily determine the amount of time that's elapsed between two absolute times. To do this, first create a CTime object for the current time. Then, when the time you're measuring has elapsed, create a second CTime object for the current time. Subtracting the old time object from the new one gives you a CTimeSpan object representing the amount of time that has elapsed. The example in the following listing shows how this process works. Listing LST.TXT-Calculating a Time Span
CTime startTime = CTime::GetCurrentTime(); //. //. Time elapses... //. CTime endTime = CTime::GetCurrentTime(); CTimeSpan timeSpan = endTime - startTime; Mouse Events The mouse can generate quite a number of events, from WM-LBUTTONDOWN, when the user presses the left button on the mouse, to WM-MOUSEMOVE, when the user moves the mouse. Let the user select a location in our client area simply by clicking it. Then, when they type text, that text will appear at the location they clicked. In this way, they can decide where they want the text to appear in our window. A caret is one of those blinking vertical lines you see that indicates where text will appear when you type it. Well start with a program that simply lets us add a caret to a window and then move the caret as we type. Page 132
Visual Programming
Adding a Caret to a Window Our first example shows how to use carets. In particular, we'll place a blinking caret in our client area at the extreme upper left. Then, as the user types text, we'll display that text, starting at upper left, and move the caret to the end of the text, indicating where the next character will go: Use AppWizard to create a new single-window (SDI) program named "carets. "' We begin by setting up storage for our text string's data, Stringdata, in the program's document header file: class CCaretsDoc : public CDocument I{ protected: // create from serialization only CCaretsDoc(); ECLAR_DYNCREATE(CCaretsDoc) . . . / Implementation public: virtual ~CCaretsDoc(); CString StringData; . . } We reset that string object to an empty string, , in the document's constructor: CCaretsDoc::CCaretsDoc() { StringData // TODO: add one-time construction code here } In addition, use ClassWizard now to connect the OnChar(.) method to the WM_CHAR Windows message in the view. We add code to record the keys the user types this way: void CCaretsView::OnChar(UINT nchar, UINT nRepCnt, UINT nflags) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData += nchar; Invalidate();
Page 133
Visual Programming
CView::OnChar(nChar, nRepCnt, nflags); } Now we are ready to create and use a new caret in the view. A caret is usually made the same height as the current characters, and 1/8th the width of an average character. To determine the height and width of characters, we'll use the CDC method GetTextMetrics(). (CDC is the device context class). Measuring the Text Sizes with Textmetrics Let's create a new caret for our window now in the OnDraw() method. We'll set up a boolean variable named CaretCreated in the view object to keep track of whether or not we've already created the caret: // caretsview.h : interface of the CCaretsView class . . class CCaretsView : public CView { protected: // create from serialization only CCaretsView(); DECLARE_ DYNCREATE(CCaretsView) boolean CaretCreated; . . } After setting CaretCreated to false in the view's constructor, we check to see if we've already created the caret in a previous call to OnDraw(): void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . . } } If we haven't created the caret, it's time to do so now. We'll need to decide on a size for the caret, and we'll get that size from a TEXTMETRIC structure by calling GetTextMetrics (). The GetTextMetrics () method fills a structure of the TEXTMETRIC type. We fill a TEXTMETRIC structure named textmetric way in our program now: Page 134
Visual Programming
void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); . } } Now we're ready to create our new caret. We'll make the caret the same height as our text, using textmetric.tmheight, and 1/8th the width of an average character-textmetric. tmAveCharWidth/8. We call CreateSolidcaret() to actually create the caret. void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); . } } This creates the new caret and installs it in our view. Setting the Carets Position We'll store the caret's position in a new CPoint object named CaretPosition. T'he CPoint class has two data members, x and y, which will hold the position of the caret: // caretsview.h . . . : interface of the CCaretsView class
class CCaretsView : public CView { protected: // create from serialization only CCaretsView();
Page 135
Visual Programming
DECLARE_DYNCREATE(CCaretsView) CPoint CaretPosition; boolean CaretCreated;
.
Initially, we set the caret's position to (0, 0) in OnDraw() this way: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc =GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x = CaretPosition.y = 0; }
.
} Then we set the caret's position with SetCaretPos(),show thecaret on the screen with ShowCaret(), and set the CaretCreated boolean flag to true: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); I if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x - CaretPosition.y - 0; SetCaretPos(CaretPosition); ShowCaret(); CaretCreated = true; }
. .
} At is point, the caret appears on the screen. We havecreated our own blinking, functional caret. The next step is to move the caret as the user types text; the caret should always indicate where typed text will appear. We first display the text that the user has typed:
Page 136
Visual Programming
void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x = CaretPosition.y = 0; SetCaretPos(CaretPosition); ShowCaret(); CaretCreated = true; } pDC->TextOut(O, 0, pDoc->StringData); . } Now we can place the caret at the end of the displayed text string. First, we have to determine just where the end of the text string is, which we do by filling a CSize object named "size" using GetTextExtent(): void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(0, 0, pDoc->StringData); CSize size - pDC->GetTextExtent(pDoc->StringData); . . } To display the caret at the end of the text string, we first hide it using HideCaret(): void CCaretsView::OnDraw(CDC* PDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); . . pDC->TextOut(0 0, pDoc->StringData);
Page 137
Visual Programming
CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); . . } If you don't hide the caret before moving it, you risk leaving an image of it on the screen in the old location. Next, we set the x data member of the CaretPosition point to the end of the text string on the screen: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); . . pDC->TextOut(0, 0, pDoc->StringData); CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); CaretPosition.x = size.cx; . . Finally, we move the caret to it s new location and show it again: void CCaretsView::OnDraw(CDC* PDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc);
. .
pDC->TextOut(0 0, pDoc->St@ingData); CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); CaretPosition.x = size.cx; SetCaretPos(CaretPosition); ShowCaret(); And we're ready to run the program Showing and Hiding a Caret When we Lose or Gain the Focus When our program loses the focus, we get a WM_KILLFOCUS message, and when our program gains the focus, we get a WM_SETFOCUS message. Using ClassWizard, connect an event-handler method to the WM_KILLFOCUS message now. ClassWizard will call this method OnKi1lFocus():
Page 138
Visual Programming
void CCaretsV!ew::OnKillFocus(CWnd* pNewWnd) { CView::OnKillFocus(pNewWnd); // TODO: Add your message handler code here } Here, we've lost the focus, so we hide the caret this way: void CCaretsView::OnKillFocus(CWnd* pNewWnd) { CView::OnKillFocus(pNewWnd); HideCaret(); // TODO: Add your message handler code here } Similarly, add the OnSetFocus() method to the WM_SETFOCUS message now, and place this code in that method to show the caret when we (re)gain the focus: void CCaretsView::OnSetFocus(CWnd* pOldWnd) { CView::OnSetFocus(pOldWnd); ShowCaret(); // TODO: Add your message handler code here } That completes the program; now when we gain the focus, our caret will appear, and when we lose the focus, it will disappear. Using the Mouse In the next example, well let the user click the mouse somewhere in our client area. When they do, well display a caret at that location. They can type the text they want starting at that location. When they click in another place in the client area, well clear the characters in the text string and let them type a new text string at the new location. Using the ClassWizard Mouse Methods Now well create a new SDI program named mouser. When the user types we store the keystrokes in a CString object named StringData in the document. We also add OnKillFocus() and OnSetFocus(), placing the HideCaret() and ShowCaret() calls in those methods. Because the user has clicked a place on our client area, they want the text to appeat starting at that clicked location. This means that we should use ClassWizard to handle the left mouse button down Windows messages, WM_LBUTTONDOWN, in our code; specifically to a method the ClassWizard will name OnButtonDown(). Start ClassWizard now. Make sure our view class, CMouserView, is selected in the Class name box, and find the WM_LBUTTONDOWN message in the Message box. Double-click that message now, creating the new method
Page 139
Visual Programming
OnButtonDown(). Double-click the OnButtonDown() methods entry in that box now to open the code for that method: void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO: Add your message handler code here and/or call default CView::OnLButtonDown(nFlags, point); } Besides OnLButtonDown(), you can use methods like OnLButtonUp(), OnRButtonDown() for the right mouse button, OnLButtonDblClk() for double-clicks and so on. We are passed two parameters in OnLButtonDown(): nFl ags and poi nt. The n Fl ag s parameter indicates the state of various keys on the keyboard, and can take these values: MK_CONTROL MK_ LBUTTON MK_MBUTTON MK_RBUTTON MK_SHIFT Control key was down Left mouse button down Middle mouse button down Right mouse button down Shift key was down
The point parameter, an object of the CPoint class, holds the mouse's present location. Now that the mouse has gone down, the first order of business is to store its location, and we'll store that location in the variables x and y as (x, y), which we get from the x and y members of the point object: void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO:Add your message handler code here and/or call default x = point.x; y = point.y; . } We also put aside space for the x and y values in the view's header file, mouserview.h: // mouserview.h : interface of the CMouserView class . protected: // create from serialization only CMouserView(); DECLARE_ DYNCREATE(CMouserView) CPoint CaretPosition; boolean CaretCreated; int x, y; . .
Page 140
Visual Programming
Now that the user has moved the mouse to a new position, we will also empty the text string, using the CString class Empty() method: void CMouserView::OnLButtonDown(UINT nflagsi CPoint point) { // TODO: Add your message handler code here and/or call default x = point.x; y = point.y; CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData.Empty(); . } Finally, we invalidate the view so we will display the cursor in the view at its new location (using code we will add to the OnDraw() method): void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO:Add your message handler code here and/or call default x = point.x; y = point.y; CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData.Empty() Invalidate(); CView::OnLButtonDown(nFlags, point); } That will finish recording the location of the mouse. Drawing Text at the New Mouse Location T'he text drawing is handled in OnDraw(). Start by creating a caret if you don't already have one. void CMouserView::OnDraw(CDC* pDC) { CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc);
Page 141
Visual Programming
if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x =CaretPosition.y = 0; SetCaretPos(CaretPosition); ShowCaret(); CaretCreated = true; } . } Now we display the string of text, Stringdata. Because the user clicked the mouse at the location we've recorded as (x, y), we display the text starting at that point: void CMouserView::OnDraw(CDC* pDC) { CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . . . } pDC->TextOut(x, y, pDoc->StringData); . } We can place the caret at the end of the text string on the screen. First, we find out where the end of the string is and hide the caret: void CMouserView::OnDraw(CDC* PDC) { CMouserDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . . } pDC->TextOut(x, y, pDoc->StringData); Csize size = pDC->GetTextExtent(pdoc->StringData); HideCaret();
Page 142
Visual Programming
. . } Then we move the caret to the end of the text string and show it again: void CMouserView::OnDraw(CDC* PDC) { CMouserDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . . } pDC->TextOut(x, y, pDoc->StringData); Csize size = pDC->GetTextExtent(pdoc->StringData); HideCaret(); CaretPosition.x = x + sice.cx; CaretPositon.y = y; SetCaretPos(CaretPosition); ShowCaret(); } And that finishes off our code for this example. Run the mouser and click any place in the client window and type some text. The text appears where the user clicked and the blinking caret indicates where the next character will appear. The Mouse The mouse is a pointing device with one or more buttons. Despite much experimentation with other alternative input devices such as touch screens and light pens, the mouse reigns supreme. Together with variations such as trackballs, which are common on laptop computers, the mouse is the only alternative input device to achieve a massivevirtually universalpenetration in the PC market. Mouse Basics Windows 98 can support a one-button, two-button, or three-button mouse, or it can use a joystick or light pen to mimic a mouse. In the early days, Windows applications avoided the use of the second or third buttons in deference to users who had a one-button mouse. However, the two-button mouse has become the de facto standard, so the traditional reticence to use the second button is no longer justified. Indeed, the second button is now the standard for invoking a "context menu," which is a menu that appears
Page 143
Visual Programming
in a window outside the normal menu bar, or for special dragging operations. You can determine if a mouse is present by using the GetSystemMetrics function: fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ; The value of fMouse will be TRUE (nonzero) if a mouse is installed and 0 if a mouse is not installed. However, in Windows 98 this function always returns TRUE whether a mouse is attached or not. In Microsoft Windows NT, it works correctly. To determine the number of buttons on the installed mouse, use cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ; This function should also return 0 if a mouse is not installed. However, under Windows 98 the function returns 2 if a mouse is not installed. When the Windows user moves the mouse, Windows moves a small bitmapped picture on the display. This is called the "mouse cursor." The mouse cursor has a singlepixel "hot spot" that points to a precise location on the display. Windows supports several predefined mouse cursors that programs can use. The most common is the slanted arrow named IDC_ARROW. The hot spot is the tip of the arrow. The IDC_CROSS cursor has a hot spot in the center of a crosshair pattern. The IDC_WAIT cursor is an hourglass generally used by programs to indicate they are busy. Programmers can also design their own cursors. The default cursor for a particular window is specified when defining the window class structure, for instance: wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; The following terms describe the actions you take with mouse buttons:
Clicking Pressing and releasing a mouse button. Double-clicking Pressing and releasing a mouse button twice in quick succession. Dragging Moving the mouse while holding down a button.
On a three-button mouse, the buttons are called the left button, the middle button, and the right button. Mouse-related identifiers defined in the Windows header files use the abbreviations LBUTTON, MBUTTON, and RBUTTON. A two-button mouse has only a left button and a right button. The single button on a one-button mouse is a left button. Mouse Double-Clicks A mouse double-click is two clicks in quick succession. To qualify as a doubleclick, the two clicks must occur in close physical proximity of one another and within a specific interval of time called the "double-click speed." You can change that time interval in the Control Panel.
Page 144
Visual Programming
If you want your window procedure to receive double-click mouse messages, you must include the identifier CS_DBLCLKS when initializing the style field in the window class structure before calling RegisterClass: wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ; If you do not include CS_DBLCLKS in the window style and the user clicks the left mouse button twice in quick succession, your window procedure receives these messages: WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP The window procedure might also receive other messages between these button messages. If you include CS_DBLCLKS in your window class style, the window procedure receives these messages for a double-click: WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP The WM_LBUTTONDBLCLK message simply replaces the second WM_LBUTTONDOWN message. Double-click messages are much easier to process if the first click of a double-click performs the same action as a single click. The second click (the WM_LBUTTONDBLCLK message) then does something in addition to the first click. For example, look at how the mouse works with the file lists in Windows Explorer. A single click selects the file. Windows Explorer highlights the file with a reverse-video bar. A double-click performs two actions: the first click selects the file, just as a single click does; the second click directs Windows Explorer to open the file. Mousehandling logic could get more complex if the first click of a double-click did not perform the same action as a single click. The Timer The Microsoft Windows timer is an input device that periodically notifies an application when a specified interval of time has elapsed. Your program tells Windows the interval, in effect saying, for example, "Give me a nudge every 10 seconds." Windows then sends your program recurrent WM_TIMER messages to signal the intervals. At first, the Windows timer might seem a less important input device than the keyboard and mouse, and certainly it is for many applications. But the timer is more useful than you may think. Some uses of the Windows timer are:
Page 145
Visual Programming
Multitasking Although Windows 98 is a preemptive multitasking environment, sometimes it is more efficient for a program to return control to Windows as quickly as possible after processing a message. If a program must do a large amount of processing, it can divide the job into smaller pieces and process each piece upon receipt of a WM_TIMER message. Maintaining an updated status report A program can use the timer to display "real-time" updates of continuously changing information, such as a display of system resources or the progress of a certain task. Implementing an "autosave" feature The timer can prompt a Windows program to save a user's work to disk whenever a specified period of time has elapsed. Terminating "demo" versions of programs Some demonstration versions of programs are designed to terminate, say, 30 minutes after they begin. The timer can signal such applications when the time is up. Pacing movement Graphical objects in a game or successive displays in a computer-assisted instruction program might need to proceed at a set rate. Using the timer eliminates the inconsistencies that might result from variations in microprocessor speed. Multimedia Programs that play CD audio, sound, or music often let the audio data play in the background. A program can use the timer to periodically determine how much of the audio has played and to coordinate on-screen visual information.
Timer Basics You can allocate a timer for your Windows program by calling the SetTimer function. SetTimer includes an unsigned integer argument specifying a time-out interval that can range (in theory) from 1 msec (millisecond) to 4,294,967,295 msec, which is nearly 50 days. The value indicates the rate at which Windows sends your program WM_TIMER messages. For instance, an interval of 1000 msec causes Windows to send your program a WM_TIMER message every second. When your program is done using the timer, it calls the KillTimer function to stop the timer messages. The KillTimer call purges the message queue of any pending WM_TIMER messages. Your program will never receive a stray WM_TIMER message following a KillTimer call. Three methods to use the Timer If you need a timer for the entire duration of your program, call SetTimer from the WinMain function or while processing the WM_CREATE message, and KillTimer on exiting WinMain or in response to a WM_DESTROY message. You can use a timer in one of three ways, depending on the arguments to the SetTimer call.
Page 146
Visual Programming
Method One This method, the easiest, causes Windows to send WM_TIMER messages to the normal window procedure of the application. The SetTimer call looks like this: SetTimer (hwnd, 1, uiMsecInterval, NULL) ; The first argument is a handle to the window whose window procedure will receive the WM_TIMER messages. The second argument is the timer ID, which should be a nonzero number which is set it to 1 in this example. The third argument is a 32-bit unsigned integer that specifies an interval in milliseconds. A value of 60,000 will deliver a WM_TIMER message once a minute. You can stop the WM_TIMER messages at any time (even while processing a WM_TIMER message) by calling KillTimer (hwnd, 1); The second argument is the same timer ID used in the SetTimer call. It's considered good form to kill any active timers in response to a WM_DESTROY message before your program terminates. When your window procedure receives a WM_TIMER message, wParam is equal to the timer IDand lParam is 0. If you need to set more than one timer, use a different timer ID for each. The value of wParam will differentiate the WM_TIMER message passed to your window procedure. To make your program more readable, you may want to use #define statements for the different timer IDs: #define TIMER_SEC 1 #define TIMER_MIN 2 You can then set the two timers with two SetTimer calls: SetTimer (hwnd, TIMER_SEC, 1000, NULL) ; SetTimer (hwnd, TIMER_MIN, 60000, NULL) ; The WM_TIMER logic might look something like this: case WM_TIMER: switch (wParam) { case TIMER_SEC: [once-per-second processing] break ; case TIMER_MIN: [once-per-minute processing]
Page 147
Visual Programming
break ; } return 0 ; If you want to set an existing timer to a different elapsed time, you can simply call SetTimer again with a different time value. Following is a simple program that uses the timer. This program sets a timer for 1-second intervals. When it receives a WM_TIMER message, it alternates coloring the client area blue and red and it beeps by calling the function MessageBeep. The program sets the timer while processing the WM_CREATE message in the window procedure. During the WM_TIMER message, the program calls MessageBeep, inverts the value of bFlipFlop, and invalidates the window to generate a WM_PAINT message. During the WM_PAINT message, the program obtains a RECT structure for the size of the window by calling GetClientRect and colors the window by calling FillRect. /*------------------------------------------------------------Timer Demo Method One Using the Timer --------------------- ----------------------------------------*/ #include <windows.h> #define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Beeper1") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass))
Page 148
Visual Programming
{ MessageBox (NULL, TEXT ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Beeper1 Timer Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static BOOL fFlipFlop = FALSE ; HBRUSH hBrush ; HDC hdc ; PAINTSTRUCT ps ; RECT rc ; switch (message) { case WM_CREATE: SetTimer (hwnd, ID_TIMER, 1000, NULL) ; return 0 ; case WM_TIMER : MessageBeep (-1) ; fFlipFlop = !fFlipFlop ; InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rc) ;
Page 149
Visual Programming
hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ; FillRect (hdc, &rc, hBrush) ; EndPaint (hwnd, &ps) ; DeleteObject (hBrush) ; return 0 ; case WM_DESTROY : KillTimer (hwnd, ID_TIMER) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } Method Two The first method for setting the timer causes WM_TIMER messages to be sent to the normal window procedure. With this second method, you can direct Windows to send the timer messages to another function within your program. The function that receives these timer messages is termed a "call-back" function. This is a function in your program that is called from Windows. You tell Windows the address of this function, and Windows later calls the function. Like a window procedure, a call-back function must be defined as CALLBACK because it is called by Windows from outside the code space of the program. The parameters to the call-back function and the value returned from the call-back function depend on the purpose of the function. In the case of the call-back function associated with the timer, the parameters are actually the same as the parameters to a window procedure although they are defined differently. However, the timer call-back function does not return a value to Windows. Name the call-back function TimerProc. This function will process only WM_TIMER messages: VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime) { [process WM_TIMER messages] } The hwnd parameter to TimerProc is the handle to the window specified when you call SetTimer. Windows will send only WM_TIMER messages to TimerProc, so the message parameter will always equal WM_TIMER. The iTimerID value is the timer ID,
Page 150
Visual Programming
and dwTimer is a value compatible with the return value from the GetTickCount function. This is the number of milliseconds that has elapsed since Windows was started. When you use a call-back function to process WM_TIMER messages, the fourth argument to SetTimer is instead the address of the call-back function, like : SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ; The program that follows is functionally the same as the program in method one, except that Windows sends the timer messages to TimerProc rather than to WndProc. Notice that TimerProc is declared at the top of the program along with WndProc. /*------------------------------------------------------------Timer Demo Method Two Using the Timer --------------------- ----------------------------------------*/ #include <windows.h> #define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; VOID CALLBACK TimerProc (HWND, UINT, UINT, DWORD ) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Beeper2" ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("Program requires Windows NT!"),
Page 151
Visual Programming
szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, "Beeper2 Timer Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: SetTimer (hwnd, ID_TIMER, 1000, TimerProc) ; return 0 ; case WM_DESTROY: KillTimer (hwnd, ID_TIMER) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime) { static BOOL fFlipFlop = FALSE ; HBRUSH hBrush ; HDC hdc ; RECT rc ; MessageBeep (-1) ; fFlipFlop = !fFlipFlop ;
Page 152
Visual Programming
GetClientRect (hwnd, &rc) ; hdc = GetDC (hwnd) ; hBrush = CreateSolidBrush (fFlipFlop ? RGB(255,0,0) : RGB(0,0,255)) ; FillRect (hdc, &rc, hBrush) ; ReleaseDC (hwnd, hdc) ; DeleteObject (hBrush) ; } Method Three The third method of setting the timer is similar to the second method, except that the hwnd parameter to SetTimer is set to NULL and the second parameter is ignored. Instead, the function returns a timer ID: iTimerID = SetTimer (NULL, 0, wMsecInterval, TimerProc) ; The iTimerID returned from SetTimer will be 0 in the rare event that no timer is available. The first parameter to KillTimer (usually the window handle) must also be NULL. The timer ID must be the value returned from SetTimer: KillTimer (NULL, iTimerID) ; The hwnd parameter passed to the TimerProc timer function will also be NULL. This method for setting a timer is rarely used. It might come in handy if you do a lot of SetTimer calls at different times in your program and don't want to keep track of which timer IDs you've already used. Child Window Controls Some programs divide the client area into several smaller logical areas. These smaller areas are nothing but the child windows of the client area. The child windows divide the entire client area into several smaller rectangular regions. Each child window has its own window handle, window procedure and client area. Each child window procedure receives mouse messages that apply only to its own window. Normally, child windows help you to structure and modularize your programs. The child window processes mouse and keyboard messages and notifies the parent window when the child window's state has changed. In this way, the child window becomes a high-level input device for the parent window. It encapsulates a specific functionality with regard to its graphical appearance on the screen, its response to user input, and its method of notifying another window when an important input event has occurred. Child window controls are used most often in dialog boxes. The position and size of the child window controls are defined in a dialog box template contained in the program's resource script. However, you can also use predefined child window controls Page 153
Visual Programming
on the surface of a normal window's client area. You create each child window with a CreateWindow call and adjust the position and size of the child windows with calls to MoveWindow. The parent window procedure sends messages to the child window controls, and the child window controls send messages back to the parent window procedure. Child window controls are predefined windows that take the form of scroll bars, buttons, edit boxes, check boxes, list boxes, combo boxes and text strings. The following program creates 10 child window button controls, one for each of the 10 standard styles of buttons. #include <windows.h> struct { int iStyle ; TCHAR * szText ; } button[] = { BS_PUSHBUTTON, TEXT ("PUSHBUTTON"), BS_DEFPUSHBUTTON, TEXT ("DEFPUSHBUTTON"), BS_CHECKBOX, TEXT ("CHECKBOX"), BS_AUTOCHECKBOX, TEXT ("AUTOCHECKBOX"), BS_RADIOBUTTON, TEXT ("RADIOBUTTON"), BS_3STATE, TEXT ("3STATE"), BS_AUTO3STATE, TEXT ("AUTO3STATE"), BS_GROUPBOX, TEXT ("GROUPBOX"), BS_AUTORADIOBUTTON, TEXT ("AUTORADIO"), BS_OWNERDRAW, TEXT ("OWNERDRAW") }; #define NUM (sizeof button / sizeof button[0]) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("BtnLook") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ;
Page 154
Visual Programming
wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Button Look"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hwndButton[NUM] ; static RECT rect ; static TCHAR szTop[] = TEXT ("message wParam lParam"), szUnd[] = TEXT ("_______ ______ ______"), szFormat[] = TEXT ("%-16s%04X-%04X %04X-%04X"), szBuffer[50] ; static int cxChar, cyChar ; HDC hdc ; PAINTSTRUCT ps ; int i;
Page 155
Visual Programming
switch (message) { case WM_CREATE : cxChar = LOWORD (GetDialogBaseUnits ()) ; cyChar = HIWORD (GetDialogBaseUnits ()) ; for (i = 0 ; i < NUM ; i++) hwndButton[i] = CreateWindow ( TEXT("button"), button[i].szText, WS_CHILD | WS_VISIBLE | button[i].iStyle, cxChar, cyChar * (1 + 2 * i), 20 * cxChar, 7 * cyChar / 4, hwnd, (HMENU) i, ((LPCREATESTRUCT) lParam)->hInstance, NULL) ; return 0 ; case WM_SIZE : rect.left = 24 * cxChar ; rect.top = 2 * cyChar ; rect.right = LOWORD (lParam) ; rect.bottom = HIWORD (lParam) ; return 0 ; case WM_PAINT : InvalidateRect (hwnd, &rect, TRUE) ; hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; SetBkMode (hdc, TRANSPARENT) ; TextOut (hdc, 24 * cxChar, cyChar, szTop, lstrlen (szTop)) ; TextOut (hdc, 24 * cxChar, cyChar, szUnd, lstrlen (szUnd)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DRAWITEM : case WM_COMMAND : ScrollWindow (hwnd, 0, -cyChar, &rect, &rect) ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; TextOut (hdc, 24 * cxChar, cyChar * (rect.bottom / cyChar - 1), szBuffer,
Page 156
Visual Programming
wsprintf (szBuffer, szFormat, message == WM_DRAWITEM ? TEXT ("WM_DRAWITEM") : TEXT ("WM_COMMAND"), HIWORD (wParam), LOWORD (wParam), HIWORD (lParam), LOWORD (lParam))) ; ReleaseDC (hwnd, hdc) ; ValidateRect (hwnd, &rect) ; break ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } As you click on each button, the button sends a WM_COMMAND message to the parent window procedure Creating Child Windows Child windows are created during the WM_CREATE message processing in WndProc. The CreateWindow call uses the following parameters: Class name Window text Window style x position y position Width Height Parent window Child window ID Instance handle Extra parameters TEXT ("button") button[i].szText WS_CHILD WS_VISIBLE button[i].iStyle cxChar cyChar * (1 + 2 * i) 20 * xChar 7 * yChar / 4 hwnd (HMENU) i ((LPCREATESTRUCT) lParam) -> hInstance NULL
Page 157
Visual Programming
The class name parameter is the predefined name. The window style uses WS_CHILD, WS_VISIBLE, and any one of the button styles (BS_PUSHBUTTON, BS_DEFPUSHBUTTON, and so forth). The window text parameter is text that will be displayed with each button. The x position and y position parameters indicate the placement of the upper left corner of the child window relative to the upper left corner of the parent window's client area. The width and height parameters specify the width and height of each child window. The function named GetDialogBaseUnits is used to obtain the width and height of the characters in the default font. This is the function that dialog boxes use to obtain text dimensions. The function returns a 32-bit value comprising a width in the low word and a height in the high word. The child window ID parameter should be unique for each child window. This ID helps your window procedure identify the child window when processing WM_COMMAND messages from it. During a WM_CREATE message lParam is actually a pointer to a structure of type CREATESTRUCT ("creation structure") that has a member hInstance. So lParam is case into a pointer to a CREATESTRUCT structure and get a hInstance out. At the program's termination, Windows destroys these child windows when the parent window is destroyed.
How Child Window corresponds with its Parent Window The child window control sends a WM_COMMAND message to its parent window. The values of wParam and lParam and their meaning in WM_COMMAND message are shown below: LOWORD (wParam) HIWORD (wParam) lParam Child window ID Notification code Child window handle
The child window ID is the value passed to CreateWindow when the child window is created. The child window handle is the value that Windows returns from the CreateWindow call. The notification code indicates what the message means. The possible values of button notification codes are defined in the Windows header files: Button Notification Code Identifier BN_CLICKED BN_PAINT BN_HILITE or BN_PUSHED Value 0 1 2
Page 158
Visual Programming
BN_UNHILITE or BN_UNPUSHED BN_DISABLE BN_DOUBLECLICKED or BN_DBLCLK BN_SETFOCUS BN_KILLFOCUS 3 4 5 6 7
The notification codes 1 through 4 are for an obsolete button style called BS_USERBUTTON. The notification codes 6 and 7 are sent only if the button style includes the flag BS_NOTIFY. The notification code 5 is sent only for BS_RADIOBUTTON, BS_AUTORADIOBUTTON, and BS_OWNERDRAW buttons, or for other buttons if the button style includes BS_NOTIFY. When you click a button with the mouse in the child window, a dashed line surrounds the text of the button. This indicates that the button has the input focus. All keyboard input now goes to the child window button control rather than to the main window. However, when the button control has the input focus, it ignores all keystrokes except the Spacebar, which now has the same effect as a mouse click.
How the Parent Window corresponds with its Child A window procedure can also send messages to the child window control. These messages include many of the window messages beginning with the prefix WM. In addition, eight button-specific messages are defined in WINUSER.H; each begins with the letters BM, which stand for "button message." These button messages are shown in the following table: Button Message BM_GETCHECK BM_SETCHECK BM_GETSTATE BM_SETSTATE BM_SETSTYLE BM_CLICK BM_GETIMAGE BM_SETIMAGE Value 0x00F0 0x00F1 0x00F2 0x00F3 0x00F4 0x00F5 0x00F6 0x00F7
Page 159
Visual Programming
The BM_GETCHECK and BM_SETCHECK messages are sent by a parent window to a child window control to get and set the check mark of check boxes and radio buttons. The BM_GETSTATE and BM_SETSTATE messages refer to the normal, or pushed, state of a window when you click it with the mouse or press it with the Spacebar. The BM_SETSTYLE message lets you change the button style after the button is created. Push Buttons A push button is a rectangle enclosing text specified in the window text parameter of the CreateWindow call. The rectangle takes up the full height and width of the dimensions given in the CreateWindow or MoveWindow call. The text is centered within the rectangle. Push-button controls are used mostly to trigger an immediate action without retaining any type of on/off indication. The two types of push-button controls have window styles called BS_PUSHBUTTON and BS_DEFPUSHBUTTON. The BS in the controls stand for button style. The "DEF" in BS_DEFPUSHBUTTON stands for "default." When used to design dialog boxes, BS_PUSHBUTTON controls and BS_DEFPUSHBUTTON controls function differently from one another. When used as child window controls, however, the two types of push buttons function the same way, although BS_DEFPUSHBUTTON has a heavier outline. A push button looks best when its height is 7/4 times the height of a text character. The push button's width must accommodate at least the width of the text, plus two additional characters. When the mouse cursor is inside the push button, pressing the mouse button causes the button to repaint itself using 3D-style shading to appear as if it's been depressed. Releasing the mouse button restores the original appearance and sends a WM_COMMAND message to the parent window with the notification code BN_CLICKED. When a push button has the input focus, a dashed line surrounds the text and pressing and releasing the Spacebar has the same effect as pressing and releasing the mouse button. You can simulate a push-button flash by sending the window a BM_SETSTATE message. This causes the button to be depressed: SendMessage (hwndButton, BM_SETSTATE, 1, 0) ; The following call causes the button to return to normal: SendMessage (hwndButton, BM_SETSTATE, 0, 0) ; The hwndButton window handle is the value returned from the CreateWindow call. You can also send a BM_GETSTATE message to a push button. The child window control returns the current state of the button: TRUE if the button is depressed and FALSE if it isn't depressed. Because push buttons do not retain any on/off information, the BM_SETCHECK and BM_GETCHECK messages are not used. Check Boxes
Page 160
Visual Programming
A check box is a square box with text; the text usually appears to the right of the check box. If you include the BS_LEFTTEXT style when creating the button, the text appears to the left. Check boxes are usually incorporated in an application to allow a user to select options. The check box commonly functions as a toggle switch: clicking the box once causes a check mark to appear; clicking again toggles the check mark off. The two most common styles for a check box are BS_CHECKBOX and BS_AUTOCHECKBOX. When you use the BS_CHECKBOX style, you must set the check mark yourself by sending the control a BM_SETCHECK message. The wParam parameter is set to 1 to create a check mark and to 0 to remove it. You can obtain the current check state of the box by sending the control a BM_GETCHECK message. You might use code like this to toggle the X mark when processing a WM_COMMAND message from the control: SendMessage ((HWND) lParam, BM_SETCHECK, (WPARAM) !SendMessage ((HWND) lParam, BM_GETCHECK, 0, 0), 0) ; Notice the ! operator in front of the second SendMessage call. The lParam value is the child window handle that is passed to your window procedure in the WM_COMMAND message. When you later need to know the state of the button, send it another BM_GETCHECK message. Or you can retain the current check state in a static variable in your window procedure. You can also initialize a BS_CHECKBOX check box with a check mark by sending it a BM_SETCHECK message: SendMessage (hwndButton, BM_SETCHECK, 1, 0) ; For the BS_AUTOCHECKBOX style, the button control itself toggles the check mark on and off. Your window procedure can ignore WM_COMMAND messages. When you need the current state of the button, send the control a BM_GETCHECK message: iCheck = (int) SendMessage (hwndButton, BM_GETCHECK, 0, 0) ; The value of iCheck is TRUE or nonzero if the button is checked and FALSE or 0 if not. The other two check box styles are BS_3STATE and BS_AUTO3STATE. As their names indicate, these styles can display a third state as wella gray color within the check boxwhich occurs when you send the control a WM_SETCHECK message with wParam equal to 2. The gray color indicates to the user that the selection is indeterminate or irrelevant. The check box is aligned with the rectangle's left edge and is centered within the top and bottom dimensions of the rectangle that were specified during the CreateWindow call. Clicking anywhere within the rectangle causes a WM_COMMAND message to be sent to the parent. The minimum height for a check box is one character height. The minimum width is the number of characters in the text, plus two. Radio Buttons
Page 161
Visual Programming
A radio button is named after the row of buttons that were once quite common on car radios. Each button on a car radio is set for a different radio station, and only one button can be pressed at a time. In dialog boxes, groups of radio buttons are conventionally used to indicate mutually exclusive options. Unlike check boxes, radio buttons do not work as togglesthat is, when you click a radio button a second time, its state remains unchanged. The radio button looks very much like a check box except that it contains a little circle rather than a box. A heavy dot within the circle indicates that the radio button has been checked. The radio button has the window style BS_RADIOBUTTON or BS_AUTORADIOBUTTON, but the latter is used only in dialog boxes. When you receive a WM_COMMAND message from a radio button, you should display its check by sending it a BM_SETCHECK message with wParam equal to 1: SendMessage (hwndButton, BM_SETCHECK, 1, 0) ; For all other radio buttons in the same group, you can turn off the checks by sending them BM_SETCHECK messages with wParam equal to 0: SendMessage (hwndButton, BM_SETCHECK, 0, 0) ; Group Boxes The group box, which has the BS_GROUPBOX style, is an oddity in the button class. It neither processes mouse or keyboard input nor sends WM_COMMAND messages to its parent. The group box is a rectangular outline with its window text at the top. Group boxes are often used to enclose other button controls. Changing the Button Text You can change the text in a button (or in any other window) by calling SetWindowText: SetWindowText (hwnd, pszString) ; where hwnd is a handle to the window whose text is being changed and pszString is a pointer to a null-terminated string. For a normal window, this text is the text of the caption bar. For a button control, it's the text displayed with the button. You can also obtain the current text of a window: iLength = GetWindowText (hwnd, pszBuffer, iMaxLength) ;
Page 162
Visual Programming
The iMaxLength parameter specifies the maximum number of characters to copy into the buffer pointed to by pszBuffer. The function returns the string length copied. You can prepare your program for a particular text length by first calling iLength = GetWindowTextLength (hwnd) ; Visible and Enabled Buttons To receive mouse and keyboard input, a child window must be both visible (displayed) and enabled. When a child window is visible but not enabled, Windows displays the text in gray rather than black. If you don't include WS_VISIBLE in the window class when creating the child window, the child window will not be displayed until you make a call to ShowWindow: ShowWindow (hwndChild, SW_SHOWNORMAL) ; But if you include WS_VISIBLE in the window class, you don't need to call ShowWindow. However, you can hide the child window by this call to ShowWindow: ShowWindow (hwndChild, SW_HIDE) ; You can determine if a child window is visible by a call to IsWindowVisible (hwndChild) ; You can also enable and disable a child window. By default, a window is enabled. You can disable it by calling EnableWindow (hwndChild, FALSE) ; For button controls, this call has the effect of graying the button text string. The button no longer responds to mouse or keyboard input. This is the best method for indicating that a button option is currently unavailable. You can reenable a child window by calling EnableWindow (hwndChild, TRUE) ; You can determine whether a child window is enabled by calling IsWindowEnabled (hwndChild) ; The Static Class You create static child window controls by using "static" as the window class in the CreateWindow function. These are fairly benign child windows. They do not accept
Page 163
Visual Programming
mouse or keyboard input, and they do not send WM_COMMAND messages back to the parent window. When you move or click the mouse over a static child window, the child window traps the WM_NCHITTEST message and returns a value of HTTRANSPARENT to Windows. This causes Windows to send the same WM_NCHITTEST message to the underlying window, which is usually the parent. The parent usually passes the message to DefWindowProc, where it is converted to a client-area mouse message. The first six static window styles simply draw a rectangle or a frame in the client area of the child window. The "RECT" static styles (left column below) are filled-in rectangles; the three "FRAME" styles (right column) are rectangular outlines that are not filled in. SS_BLACKRECT SS_GRAYRECT SS_WHITERECT SS_BLACKFRAME SS_GRAYFRAME SS_WHITEFRAME
"BLACK," "GRAY," and "WHITE" do not mean the colors are black, gray, and white. Rather, the colors are based on system colors, as shown here: Static Control BLACK GRAY WHITE System Color COLOR_3DDKSHADOW COLOR_BTNSHADOW COLOR_BTNHIGHLIGHT
The window text field of the CreateWindow call is ignored for these styles. The upper left corner of the rectangle begins at the x position and y position coordinates relative to the parent window. You can also use the SS_ETCHEDHORZ, SS_ETCHEDVERT, or SS_ETCHEDFRAME styles to create a shadowed-looking frame with the white and gray colors. The static class also includes three text styles: SS_LEFT, SS_RIGHT, and SS_CENTER. These create left-justified, right-justified, and centered text. The text is given in the window text parameter of the CreateWindow call, and it can be changed later using SetWindowText. When the window procedure for static controls displays this text, it uses the DrawText function with DT_WORDBREAK, DT_NOCLIP, and DT_EXPANDTABS parameters. The text is wordwrapped within the rectangle of the child window.
Page 164
Visual Programming
The background of these three text-style child windows is normally COLOR_BTNFACE, and the text itself is COLOR_WINDOWTEXT. You can intercept WM_CTLCOLORSTATIC messages to change the text color by calling SetTextColor and the background color by calling SetBkColor and by returning the handle to the background brush. This will be demonstrated in the COLORS1 program shortly. Finally, the static class also includes the window styles SS_ICON and SS_USERITEM. However, these styles have no meaning when they are used as child window controls. The Scroll Bar Class Scroll bar controls are child windows that can appear anywhere in the client area of the parent window. You create child window scroll bar controls by using the predefined window class "scrollbar" and one of the two scroll bar styles SBS_VERT and SBS_HORZ. Unlike the button controls (and the edit and list box controls to be discussed later), scroll bar controls do not send WM_COMMAND messages to the parent window. Instead, they send WM_VSCROLL and WM_HSCROLL messages, just like window scroll bars. When processing the scroll bar messages, you can differentiate between window scroll bars and scroll bar controls by the lParam parameter. It will be 0 for window scroll bars and the scroll bar window handle for scroll bar controls. The high and low words of the wParam parameter have the same meaning for window scroll bars and scroll bar controls. Although window scroll bars have a fixed width, Windows uses the full rectangle dimensions given in the CreateWindow call (or later in the MoveWindow call) to size the scroll bar controls. You can make long, thin scroll bar controls or short, pudgy scroll bar controls. If you want to create scroll bar controls that have the same dimensions as window scroll bars, you can use GetSystemMetrics to obtain the height of a horizontal scroll bar: GetSystemMetrics (SM_CYHSCROLL) ; or the width of a vertical scroll bar: GetSystemMetrics (SM_CXVSCROLL) ; The scroll bar window style identifiers SBS_LEFTALIGN, SBS_RIGHTALIGN, SBS_TOP ALIGN, and SBS_BOTTOMALIGN are documented to give standard dimensions to scroll bars. However, these styles work only for scroll bars in dialog boxes. You can set the range and position of a scroll bar control with the same calls used for window scroll bars: SetScrollRange (hwndScroll, SB_CTL, iMin, iMax, bRedraw) ; SetScrollPos (hwndScroll, SB_CTL, iPos, bRedraw) ;
Page 165
Visual Programming
SetScrollInfo (hwndScroll, SB_CTL, &si, bRedraw) ; The difference is that window scroll bars use a handle to the main window as the first parameter and SB_VERT or SB_HORZ as the second parameter. The Automatic Keyboard Interface Scroll bar controls can also process keystrokes, but only if they have the input focus. The following table shows how keyboard cursor keys translate into scroll bar messages: Cursor Key Home End Page Up Page Down Left or Up Right or Down Scroll Bar Message wParam Value SB_TOP SB_BOTTOM SB_PAGEUP SB_PAGEDOWN SB_LINEUP SB_LINEDOWN
In fact, the SB_TOP and SB_BOTTOM scroll bar messages can be generated only by using the keyboard. If you want a scroll bar control to obtain the input focus when the scroll bar is clicked with the mouse, you must include the WS_TABSTOP identifier in the window class parameter of the CreateWindow call. When a scroll bar has the input focus, a blinking gray block is displayed on the scroll bar thumb. The Edit Class The edit class is in some ways the simplest predefined window class and in other ways the most complex. When you create a child window using the class name "edit," you define a rectangle based on the x position, y position, width, and height parameters of the CreateWindow call. This rectangle contains editable text. When the child window control has the input focus, you can type text, move the cursor, select portions of text using either the mouse or the Shift key and a cursor key, delete selected text to the clipboard by pressing Ctrl-X, copy text by pressing Ctrl-C, and insert text from the clipboard by pressing Ctrl-V. One of the simplest uses of edit controls is for single-line entry fields. But edit controls are not limited to single lines. The Edit Class Styles You create an edit control using "edit" as the window class in the CreateWindow call. The window style is WS_CHILD, plus several options. The text in edit controls can
Page 166
Visual Programming
be left-justified, right-justified, or centered. You specify this formatting with the window styles ES_LEFT, ES_RIGHT, and ES_CENTER. By default, an edit control has a single line. You can create a multiline edit control with the window style ES_MULTILINE. For a single-line edit control, you can normally enter text only to the end of the edit control rectangle. To create an edit control that automatically scrolls horizontally, you use the style ES_AUTOHSCROLL. For a multiline edit control, text wordwraps unless you use the ES_AUTOHSCROLL style, in which case you must press the Enter key to start a new line. You can also include vertical scrolling in a multiline edit control by using the style ES_AUTOVSCROLL. When you include these scrolling styles in multiline edit controls, you might also want to add scroll bars to the edit control which can be done by using the same window style identifiers as for nonchild windows: WS_HSCROLL and WS_VSCROLL. By default, an edit control does not have a border. You can add one by using the style WS_BORDER. When you select text in an edit control, Windows displays it in reverse video. When the edit control loses the input focus, however, the selected text is no longer highlighted. If you want the selection to be highlighted even when the edit control does not have the input focus, you can use the style ES_NOHIDESEL. The size of the edit control is usually set to the size of the main window.For a single-line edit control, the height of the control must accommodate the height of a character. If the edit control has a border (as most do), use 1.5 times the height of a character (including external leading). Edit Control Notification Edit controls send WM_COMMAND messages to the parent window procedure. The meanings of the wParam and lParam variables are: LOWORD (wParam) HIWORD (wParam) lParam The notification codes are shown below: EN_SETFOCUS EN_KILLFOCUS EN_CHANGE Edit control has gained the input focus. Edit control has lost the input focus. Edit control's contents will change. Child window ID Notification code Child window handle
Page 167
Visual Programming
EN_UPDATE EN_ERRSPACE EN_MAXTEXT EN_HSCROLL EN_VSCROLL Using the Edit Controls If you want to insert text into an edit field, you can do so by using SetWindowText. Getting text out of an edit control involves GetWindowTextLength and GetWindowText. Messages to an Edit Control Edit Control messages let you cut, copy, or clear the current selection. A user selects the text to be acted upon by using the mouse or the Shift key and a cursor key, thereby highlighting the selected text in the edit control: SendMessage (hwndEdit, WM_CUT, 0, 0) ; SendMessage (hwndEdit, WM_COPY, 0, 0) ; SendMessage (hwndEdit, WM_CLEAR, 0, 0) ; WM_CUT removes the current selection from the edit control and sends it to the clipboard. WM_COPY copies the selection to the clipboard but leaves it intact in the edit control. WM_CLEAR deletes the selection from the edit control without passing it to the clipboard. You can also insert clipboard text into the edit control at the cursor position: SendMessage (hwndEdit, WM_PASTE, 0, 0) ; You can obtain the starting and ending positions of the current selection: SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iStart, (LPARAM) &iEnd) ; Edit control's contents have changed. Edit control has run out of space. Edit control has run out of space on insertion. Edit control's horizontal scroll bar has been clicked. Edit control's vertical scroll bar has been clicked.
The ending position is actually the position of the last selected character plus 1. You can select text: SendMessage (hwndEdit, EM_SETSEL, iStart, iEnd) ; You can also replace a current selection with other text: SendMessage (hwndEdit, EM_REPLACESEL, 0, (LPARAM) szString) ;
Page 168
Visual Programming
For multiline edit controls, you can obtain the number of lines: iCount = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0) ; For any particular line, you can obtain an offset from the beginning of the edit buffer text: iOffset = SendMessage (hwndEdit, EM_LINEINDEX, iLine, 0) ; Lines are numbered starting at 0. An iLine value of -1 returns the offset of the line containing the cursor. You obtain the length of the line from iLength = SendMessage (hwndEdit, EM_LINELENGTH, iLine, 0) ; and copy the line itself into a buffer using iLength = SendMessage (hwndEdit, EM_GETLINE, iLine, (LPARAM) szBuffer) ; The Listbox Class A list box is a collection of text strings displayed as a scrollable columnar list within a rectangle. A program can add or remove strings in the list by sending messages to the list box window procedure. The list box control sends WM_COMMAND messages to its parent window when an item in the list is selected. The parent window can then determine which item has been selected. A list box can be either single selection or multiple selection. The latter allows the user to select more than one item from the list box. When a list box has the input focus, it displays a dashed line surrounding an item in the list box. This cursor does not indicate the selected item in the list box. The selected item is indicated by highlighting, which displays the item in reverse video. In a single-selection list box, the user can select the item that the cursor is positioned on by pressing the Spacebar. The arrow keys move both the cursor and the current selection and can scroll the contents of the list box. The Page Up and Page Down keys also scroll the list box by moving the cursor but not the selection. Pressing a letter key moves the cursor and the selection to the first (or next) item that begins with that letter. An item can also be selected by clicking or double-clicking the mouse on the item. In a multiple-selection list box, the Spacebar toggles the selection state of the item where the cursor is positioned. (If the item is already selected, it is deselected.) The arrow keys deselect all previously selected items and move the cursor and selection, just as in single-selection list boxes. However, the Ctrl key and the arrow keys can move the cursor without moving the selection. The Shift key and arrow keys can extend a selection.
Page 169
Visual Programming
Clicking or double-clicking an item in a multiple-selection list box deselects all previously selected items and selects the clicked item. However, clicking an item while pressing the Shift key toggles the selection state of the item without changing the selection state of any other item. List Box Styles You create a list box child window control with CreateWindow using "listbox" as the window class and WS_CHILD as the window style. However, this default list box style does not send WM_COMMAND messages to its parent, meaning that a program would have to interrogate the list box (via messages to the list box controls) regarding the selection of items within the list box. Therefore, list box controls almost always include the list box style identifier LBS_NOTIFY, which allows the parent window to receive WM_COMMAND messages from the list box. If you want the list box control to sort the items in the list box, you can also use LBS_SORT, another common style. By default, list boxes are single selection. Multiple-selection list boxes are relatively rare. If you want to create one, you use the style LBS_MULTIPLESEL. Normally, a list box updates itself when a new item is added to the scroll box list. You can prevent this by including the style LBS_NOREDRAW. By default, the list box window procedure displays only the list of items without any border around it. You can add a border with the window style identifier WS_BORDER. And to add a vertical scroll bar for scrolling through the list with the mouse, you use the window style identifier WS_VSCROLL. The Windows header files define a list box style called LBS_STANDARD that includes the most commonly used styles. It is defined as (LBS_NOTIFY LBS_SORT WS_VSCROLL WS_BORDER) You can also use the WS_SIZEBOX and WS_CAPTION identifiers, but these will allow the user to resize the list box and to move it around its parent's client area. The width of a list box should accommodate the width of the longest string plus the width of the scroll bar. You can get the width of the vertical scroll bar using GetSystemMetrics (SM_CXVSCROLL) ; You can calculate the height of the list box by multiplying the height of a character by the number of items you want to appear in view. Putting Strings in the List Box You can put text strings in a List Box by sending messages to the list box window procedure using the SendMessage call. The text strings are generally referenced by an index number that starts at 0 for the topmost item. the SendMessage call can return LB_ERRSPACE (defined as -2) if the window procedure runs out of available memory
Page 170
Visual Programming
space to store the contents of the list box. SendMessage returns LB_ERR (-1) if an error occurs for other reasons and LB_OKAY (0) if the operation is successful. You can test SendMessage for a nonzero value to detect either of the two errors. If you use the LBS_SORT style (or if you are placing strings in the list box in the order that you want them to appear), the easiest way to fill up a list box is with the LB_ADDSTRING message: SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) szString) ; If you do not use LBS_SORT, you can insert strings into your list box by specifying an index value with LB_INSERTSTRING: SendMessage (hwndList, LB_INSERTSTRING, iIndex, (LPARAM) szString) ; You can delete a string from the list box by specifying the index value with the LB_DELETESTRING message: SendMessage (hwndList, LB_DELETESTRING, iIndex, 0) ; You can clear out the list box by using LB_RESETCONTENT: SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ; The list box window procedure updates the display when an item is added to or deleted from the list box. If you have a number of strings to add or delete, you may want to temporarily inhibit this action by turning off the control's redraw flag: SendMessage (hwndList, WM_SETREDRAW, FALSE, 0) ; After you've finished, you can turn the redraw flag back on: SendMessage (hwndList, WM_SETREDRAW, TRUE, 0) ; A list box created with the LBS_NOREDRAW style begins with the redraw flag turned off. Selecting and Extracting Entries To find out how many items are in the list box you can use: iCount = SendMessage (hwndList, LB_GETCOUNT, 0, 0) ; For a single-selection list to highlight a default selection, you can use SendMessage (hwndList, LB_SETCURSEL, iIndex, 0) ;
Page 171
Visual Programming
Setting iParam to -1 in this call deselects all items. You can also select an item based on its initial characters: iIndex = SendMessage (hwndList, LB_SELECTSTRING, iIndex, (LPARAM) szSearchString) ; The iIndex given as the iParam parameter to the SendMessage call is the index following which the search begins for an item with initial characters that match szSearchString. An iIndex value of -1 starts the search from the top. SendMessage returns the index of the selected item, or LB_ERR if no initial characters match szSearchString. When you get a WM_COMMAND message from the list box (or at any other time), you can determine the index of the current selection using LB_GETCURSEL: iIndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0) ; The iIndex value returned from the call is LB_ERR if no item is selected. You can determine the length of any string in the list box: iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0) ; and copy the item into the text buffer: iLength = SendMessage (hwndList, LB_GETTEXT, iIndex, (LPARAM) szBuffer) ; In both cases, the iLength value returned from the call is the length of the string. The szBuffer array must be large enough for the length of the string and a terminating NULL. For a multiple-selection list box, you cannot use LB_SETCURSEL, LB_GETCURSEL, or LB_SELECTSTRING. Instead, you use LB_SETSEL to set the selection state of a particular item without affecting other items that might also be selected: SendMessage (hwndList, LB_SETSEL, wParam, iIndex) ; The wParam parameter is nonzero to select and highlight the item and 0 to deselect it. If the lParam parameter is -1, all items are either selected or deselected. You can also determine the selection state of a particular item using iSelect = SendMessage (hwndList, LB_GETSEL, iIndex, 0) ; where iSelect is set to nonzero if the item indexed by iIndex is selected and 0 if it is not. Receiving Messages from List Boxes
Page 172
Visual Programming
When a user clicks on a list box with the mouse, the list box receives the input focus. A parent window can give the input focus to a list box control by using SetFocus (hwndList); When a list box has the input focus, the cursor movement keys, letter keys, and Spacebar can also be used to select items from the list box. A list box control sends WM_COMMAND messages to its parent. The meanings of the wParam and lParam variables are: LOWORD (wParam) HIWORD (wParam) LParam Child window ID Notification code Child window handle
The notification codes and their values are as follows: LBN_ERRSPACE LBN_SELCHANGE LBN_DBLCLK LBN_SELCANCEL LBN_SETFOCUS LBN_KILLFOCUS -2 1 2 3 4 5
The list box control sends the parent window LBN_SELCHANGE and LBN_DBLCLK codes only if the list box window style includes LBS_NOTIFY. The LBN_ERRSPACE code indicates that the list box control has run out of space. The LBN_SELCHANGE code indicates that the current selection has changed; these messages occur as the user moves the highlight through the list box, toggles the selection state with the Spacebar, or clicks an item with the mouse. The LBN_DBLCLK code indicates that a list box item has been double-clicked with the mouse. The notification code values for LBN_SELCHANGE and LBN_DBLCLK refer to the number of mouse clicks. 3.2.3 Resources
Most Microsoft Windows programs include a customized icon that Windows displays in the upper left corner of the title bar of the application window. Windows also
Page 173
Visual Programming
displays the program's icon when the program is listed in the Start menu, shown in the taskbar at the bottom of the screen, listed in the Windows Explorer, or shown as a shortcut on the desktop. Some programsmost notably graphical drawing tools such as Windows Paintuse customized mouse cursors to represent different operations of the program. Many Windows programs use menus and dialog boxes. Along with scroll bars, menus and dialog boxes are the bread and butter of the Windows user interface. Icons, cursors, menus, and dialog boxes are all related. They are all types of Windows "resources." Resources are data and they are often stored in a program's .EXE file, but they do not reside in the executable program's data area. In other words, the resources are not immediately addressable by variables in the program's code. Instead, Windows provides functions that explicitly or implicitly load a program's resources into memory so that they can be used. Icons One of the benefits of using resources is that many components of a program can be bound into the program's .EXE file. Without the concept of resources, a binary file such as an icon image would probably have to reside in a separate file that the .EXE would read into memory to use. Or the icon would have to be defined in the program as an array of bytes (which might make it tough to visualize the actual icon image). As a resource, the icon is stored in a separate editable file on the developer's computer but is bound into the .EXE file during the build process. Using Icons in Programs Although Windows uses icons in several ways to denote a program, many Windows programs specify an icon only when defining the window class with the WNDCLASS structure and RegisterClass. Windows will choose the best image size in the icon file whenever it needs to display the icon image. There is an enhanced version of RegisterClass named RegisterClassEx that uses a structure named WNDCLASSEX. WNDCLASSEX has two additional fields: cbSize and hIconSm. The cbSize field indicates the size of the WNDCLASSEX structure, and hIconSm is supposed to be set to the icon handle of the small icon. Thus, in the WNDCLASSEX structure you set two icon handles associated with two icon filesone for a standard icon and one for the small icon. To dynamically change the program's icon while the program is running, you can do so using SetClassLong. For example, if you have a second icon file associated with the identifier IDI_ALTICON, you can switch to that icon using the statement SetClassLong (hwnd, GCL_HICON, LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ALTICON))) ;
Page 174
Visual Programming
If you don't want to save the handle to your program's icon but instead use the DrawIcon function to display it someplace, you can obtain the handle by using GetClassLong. For example: DrawIcon (hdc, x, y, GetClassLong (hwnd, GCL_HICON)) ; Since LoadIcon is said to be "obsolete" and LoadImage is recommended instead. LoadImage is certainly more flexible, but it hasn't replaced the simplicity of LoadIcon just yet. LoadIcon is one of the few functions that obtain a handle but do not require the handle to be destroyed. There actually is a DestroyIcon function, but it is used in conjunction with the CreateIcon, CreateIconIndirect, and CreateIconFromResource functions. These functions allow your program to dynamically create an icon image algorithmically. Using Customized Cursors Using customized mouse cursors in your program is similar to using customized icons, except that most programmers seem to find the cursors that Windows supplies to be quite adequate. Customized cursors are generally monochrome with a dimension of 32 by 32 pixels. You create a cursor in the Developer Studio by selecting Resource from the Insert menu, and picking Cursor . You can set a customized cursor in your class definition with a statement such as wndclass.hCursor = LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR)); or, if the cursor is defined with a text name, wndclass.hCursor = LoadCursor (hInstance, szCursor) ; Whenever the mouse is positioned over a window created based on this class, the customized cursor associated with IDC_CURSOR or szCursor will be displayed. If you use child windows, you may want the cursor to appear differently, depending on the child window below the cursor. If your program defines the window class for these child windows, you can use different cursors for each class by appropriately setting the hCursor field in each window class. And if you use predefined child window controls, you can alter the hCursor field of the window class by using SetClassLong (hwndChild, GCL_HCURSOR, LoadCursor (hInstance, TEXT ("childcursor")) ; If you separate your client area into smaller logical areas without using child windows, you can use SetCursor to change the mouse cursor: SetCursor (hCursor);
Page 175
Visual Programming
You should call SetCursor during processing of the WM_MOUSEMOVE message. Otherwise, Windows uses the cursor specified in the window class to redraw the cursor when it is moved. The documentation indicates that SetCursor is fast if the cursor doesn't have to be changed. Character String Resources Character string resources are primarily for easing the translation of your program to other languages. If you use character string resources rather than putting strings directly into your source code, all the text that your program uses will be in one filethe resource script. If the text in this resource script is translated into another language, all you need to do to create a foreign-language version of your program is relink the program. This method is much safer than messing around with your source code. You create a string table by selecting Resource from the Insert menu and then selecting String Table. The strings will be shown in a list at the right of the screen. Select a string by double-clicking it. For each string, you specify an identifier and the string itself. In the resource script, the strings show up in a multiline statement that looks something like this: STRINGTABLE DISCARDABLE BEGIN IDS_STRING1, "character string 1" IDS_STRING2, "character string 2" [other string definitions] END The resource script can have multiple string tables, but each ID must uniquely identify only a single string. Each string can be only one line long with a maximum of 4097 characters. Use \t and \n for tabs and ends of lines. These control characters are recognized by the DrawText and MessageBox functions. Your program can use the LoadString call to copy a string resource into a buffer in the program's data segment: LoadString (hInstance, id, szBuffer, iMaxLength) ; The id argument refers to the ID number that precedes each string in the resource script; szBuffer is a pointer to a character array that receives the character string; and iMaxLength is the maximum number of characters to transfer into the szBuffer. The function returns the number of characters in the string. The string ID numbers that precede each string are generally macro identifiers defined in a header file. Many Windows programmers use the prefix IDS_ to denote an ID number for a string. Sometimes a filename or other information must be embedded in the string when the string is displayed.
Page 176
Visual Programming
All resource text, including the text in the string table, is stored in the .RES compiled resource file and in the final .EXE file in Unicode format. The LoadStringW function loads the Unicode text directly. The LoadStringA function performs a conversion of the text from Unicode to the local code page. Custom Resources Windows also defines a "custom resource," also called the "user-defined resource". The custom resource is convenient for attaching miscellaneous data to your .EXE file and obtaining access to that data within the program. The data can be in absolutely any format you want. The Windows functions that a program uses to access the custom resource cause Windows to load the data into memory and return a pointer to it. You can do whatever you want with that data. You'll probably find this to be a more convenient way to store and access miscellaneous private data than storing it in external files and accessing it with file input functions. For instance, suppose you have a file called BINDATA.BIN that contains a bunch of data that your program needs for display purposes. This file can be in any format you choose. You can create a custom resource in Developer Studio by selecting Resource from the Insert menu and pressing the Custom button. Type in a type name by which the resource is to be known: for example, BINTYPE. Developer Studio will then make up a resource name (in this case, IDR_BINTYPE1) and display a window that lets you enter binary data. The resource script will then contain a statement like this: IDR_BINTYPE1 BINTYPE BINDATA.BIN Where BINTYPE is the resource type and BINDATA.BIN is the filename.You can use text names rather than numeric identifiers for the resource name. When you compile and link the program, the entire BINDATA.BIN file will be bound into a .EXE file. During program initialization (for example, while processing the WM_CREATE message), you can obtain a handle to this resource: hResource = LoadResource (hInstance, FindResource (hInstance, TEXT ("BINTYPE"), MAKEINTRESOURCE (IDR_BINTYPE1))) ; The variable hResource is defined with type HGLOBAL, which is a handle to a memory block. When you need access to the text, call LockResource: pData = LockResource (hResource) ; LockResource loads the resource into memory (if it has not already been loaded) and returns a pointer to it. When you're finished with the resource, you can free it from memory:
Page 177
Visual Programming
FreeResource (hResource) ; The resource will also be freed when your program terminates, even if you don't call FreeResource. Menus A menu for a Windows program tells the user what operations an application is capable of performing. A menu is probably the most important part of the consistent user interface that Windows programs offer, and adding a menu to your program is a relatively easy part of Windows programming. You define the menu in Developer Studio. Each selectable menu item is given a unique ID number. You specify the name of the menu in the window class structure. When the user chooses a menu item, Windows sends your program a WM_COMMAND message containing that ID. Menu Concepts A window's menu bar is displayed immediately below the caption bar. This menu bar is sometimes called a program's "main menu" or the "top-level menu." Items listed in the top-level menu usually invoke drop-down menus, which are also called "popup menus" or "submenus." You can also define multiple nestings of popups: that is, an item on a popup menu can invoke another popup menu. Sometimes items in popup menus invoke a dialog box for more information. Most parent windows have, to the far left of the caption bar, a display of the program's small icon. This icon invokes the system menu, which is really another popup menu. Menu items in popups can be "checked," which means that Windows draws a small check mark to the left of the menu text. The use of check marks lets the user choose different program options from the menu. These options can be mutually exclusive, but they don't have to be. Top-level menu items cannot be checked. Menu items in the top-level menu or in popup menus can be "enabled," "disabled," or "grayed." The words "active" and "inactive" are sometimes used synonymously with "enabled" and "disabled." Menu items flagged as enabled or disabled look the same to the user, but a grayed menu item is displayed in gray text. Menu Structure When you create or change menus in a program, it is important to note that the top-level menu and each popup menu are separate menus. The top-level menu has a menu handle, each popup menu within a top-level menu has its own menu handle, and the system menu (which is also a popup) has a menu handle. Each item in a menu is defined by three characteristics. The first characteristic is what appears in the menu. This is either a text string or a bitmap. The second characteristic is either an ID number that Windows sends to your program in a
Page 178
Visual Programming
WM_COMMAND message or the handle to a popup menu that Windows displays when the user chooses that menu item. The third characteristic describes the attribute of the menu item, including whether the item is disabled, grayed, or checked. Defining the Menu To use Developer Studio to add a menu to your program's resource script, select Resource from the Insert menu and pick Menu. You can then interactively define your menu. Each item in the menu has an associated Menu Item Properties dialog box that indicates the item's text string. If the Pop-up box is checked, the item invokes a popup menu and no ID is associated with the item. If the Pop-up box is not checked, the item generates a WM_COMMAND message with a specified ID. These two types of menu items will appear in the resource script as POPUP and MENUITEM statements, respectively. When you type the text for an item in a menu, you can type an ampersand (&) to indicate that the following character is to be underlined when Windows displays the menu. Such an underlined character is the character Windows searches for when you select a menu item using the Alt key. If you don't include an ampersand in the text, no underline will appear, and Windows will instead use the first letter of the menu item's text for Alt-key searches. If you select the Grayed option in the Menu Items Properties dialog box, the menu item is inactive, its text is grayed, and the item does not generate a WM_COMMAND message. If you select the Inactive option, the menu item is inactive and does not generate a WM_COMMAND message but its text is displayed normally. The Checked option places a check mark next to a menu item. The Separator option causes a horizontal separator bar to be drawn on popup menus. For items in popup menus, you can use the columnar tab character \t in the character string. Text following the \t is placed in a new column spaced far enough to the right to accommodate the longest text string in the first column of the popup. A \a in the character string right-justifies the text that follows it. The ID values you specify are the numbers that Windows sends to the window procedure in menu messages. The ID values should be unique within a menu. Referencing Menu in Programs Most Windows applications have only one menu in the resource script. You can give the menu a text name that is the same as the name of the program. Programmers often use the name of the program as the name of the menu so that the same character string can be used for the window class, the name of the program's icon, and the name of the menu. The program then makes reference to this menu in the definition of the window class: wndclass.lpszMenuName = szAppName ;
Page 179
Visual Programming
Although specifying the menu in the window class is the most common way to reference a menu resource, that's not the only way to do it. A Windows application can load a menu resource into memory with the LoadMenu function. LoadMenu returns a handle to the menu. If you use a name for the menu in the resource script, the statement looks like this: hMenu = LoadMenu (hInstance, TEXT ("MyMenu")) ; If you use a number, the LoadMenu call takes this form: hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (ID_MENU)) ; You can then specify this menu handle as the ninth parameter to CreateWindow: hwnd = CreateWindow (TEXT ("MyClass"), TEXT ("Window Caption"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL) ; In this case, the menu specified in the CreateWindow call overrides any menu specified in the window class. You can think of the menu in the window class as being a default menu for the windows based on the window class if the ninth parameter to CreateWindow is NULL. Therefore, you can use different menus for several windows based on the same window class. You can also have a NULL menu name in the window class and a NULL menu handle in the CreateWindow call and assign a menu to a window after the window has been created: SetMenu (hwnd, hMenu) ; This form lets you dynamically change a window's menu. Any menu that is attached to a window is destroyed when the window is destroyed. Any menus not attached to a window should be explicitly destroyed by calls to DestroyMenu before the program terminates. Menus and Messages Windows usually sends a window procedure several different messages when the user selects a menu item. In most cases, your program can ignore many of these messages and simply pass them to DefWindowProc. One such message is WM_INITMENU with the following parameters: wParam: lParam: Handle to main menu 0
Page 180
Visual Programming
The value of wParam is the handle to your main menu even if the user is selecting an item from the system menu. Windows programs generally ignore the WM_INITMENU message. A program also receives WM_MENUSELECT messages. A program can receive many WM_MENUSELECT messages as the user moves the cursor or mouse among the menu items. This is helpful for implementing a status bar that contains a full text description of the menu option. The parameters that accompany WM_MENUSELECT are as follows: LOWORD (wParam): HIWORD (wParam): lParam: Selected item: Menu ID or popup menu index Selection flags Handle to menu containing selected item
WM_MENUSELECT is a menu-tracking message. The value of wParam tells you what item of the menu is currently selected (highlighted). The "selection flags" in the high word of wParam can be a combination of the following: MF_GRAYED, MF_DISABLED, MF_ CHECKED, MF_BITMAP, MF_POPUP, MF_HELP, MF_SYSMENU, and MF_MOUSESELECT. You may want to use WM_MENUSELECT if you need to change something in the client area of your window based on the movement of the highlight among the menu items. Most programs pass this message to DefWindowProc. When Windows is ready to display a popup menu, it sends the window procedure a WM_INITMENUPOPUP message with the following parameters: wParam: LOWORD (lParam): HIWORD (lParam): Popup menu handle Popup index 1 for system menu, 0 otherwise
This message is important if you need to enable or disable items in a popup menu before it is displayed. The most important menu message is WM_COMMAND. This message indicates that the user has chosen an enabled menu item from your window's menu. If you happen to use the same ID codes for menus and child window controls, you can differentiate between them by examining the value of lParam, which will be 0 for a menu item. Menus Controls
Page 181
Visual Programming
LOWORD (wParam): HIWORD (wParam): lParam: Menu ID 0 0 Control ID Notification code Child window handle
The WM_SYSCOMMAND message is similar to the WM_COMMAND message except that WM_SYSCOMMAND signals that the user has chosen an enabled menu item from the system menu: wParam: lParam: Menu ID 0
However, if the WM_SYSCOMMAND message is the result of a mouse click, LOWORD (lParam) and HIWORD (lParam) will contain the x and y screen coordinates of the mouse cursor's location. For WM_SYSCOMMAND, the menu ID indicates which item on the system menu has been chosen. If you add menu items to the system menu, the low word of wParam will be the menu ID that you define. It is important that you pass normal WM_SYSCOMMAND messages to DefWindowProc. If you do not, you'll effectively disable the normal system menu commands. The message WM_MENUCHAR, isn't really a menu message at all. Windows sends this message to your window procedure in one of two circumstances: if the user presses Alt and a character key that does not correspond to a menu item, or, when a popup is displayed, if the user presses a character key that does not correspond to an item in the popup. The parameters that accompany the WM_MENUCHAR message are as follows: LOWORD (wParam): HIWORD (wParam): LParam: The selection code is:
0 No popup is displayed. MF_POPUP Popup is displayed. MF_SYSMENU System menu popup is displayed.
Windows programs usually pass this message to DefWindowProc, which normally returns a 0 to Windows, which causes Windows to beep. New Menu Commands
Page 182
Visual Programming
The five new menu commands are:
AppendMenu Adds a new item to the end of a menu. DeleteMenu Deletes an existing item from a menu and destroys the item. InsertMenu Inserts a new item into a menu. ModifyMenu Changes an existing menu item. RemoveMenu Removes an existing item from a menu.
The difference between DeleteMenu and RemoveMenu is important if the item is a popup menu. DeleteMenu destroys the popup menu but RemoveMenu does not. Adding Menus in Programs Defining a menu in a program's resource script is usually the easiest way to add a menu in your window. But you also can create a menu entirely within your program by using two functions called CreateMenu and AppendMenu. After you finish defining the menu, you can pass the menu handle to CreateWindow or use SetMenu to set the window's menu. Here's how it's done. CreateMenu simply returns a handle to a new menu: hMenu = CreateMenu () ; The menu is initially empty. AppendMenu inserts items into the menu. You must obtain a different menu handle for the top-level menu item and for each popup. The popups are constructed separately; the popup menu handles are then inserted into the toplevel menu. The following code creates a menu in this fashion. hMenu = CreateMenu () ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_NEW, "&New") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_OPEN, "&Open...") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE, "&Save") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE_AS, "Save &As...") ; AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_EXIT, "E&xit") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&File") ; hMenuPopup = CreateMenu () ;
Page 183
Visual Programming
AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_UNDO, "&Undo") ; AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_CUT, "Cu&t") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_COPY, "&Copy") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_PASTE, "&Paste") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_CLEAR, "De&lete") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Edit") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING MF_CHECKED, IDM_BKGND_WHITE, "&White") ; AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_LTGRAY, "&Light Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_GRAY, "&Gray") ; AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_DKGRAY, "&Dark Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_BLACK, "&Black") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Background") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_TIMER_START, "&Start") ; AppendMenu (hMenuPopup, MF_STRING MF_GRAYED, IDM_TIMER_STOP, "S&top") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Timer") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_HELP_HELP, "&Help") ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_ABOUT, "&About MenuDemo...") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Help") ; Other Menu Commands When you change a top-level menu item, the change is not shown until Windows redraws the menu bar. You can force this redrawing by calling DrawMenuBar (hwnd) ; Page 184
Visual Programming
Notice that the argument to DrawMenuBar is a handle to the window rather than a handle to the menu. You can obtain the handle to a popup menu using hMenuPopup = GetSubMenu (hMenu, iPosition) ; where iPosition is the index (starting at 0) of the popup within the top-level menu indicated by hMenu. You can then use the popup menu handle with other functions (such as AppendMenu). You can obtain the current number of items in a top-level or popup menu by using iCount = GetMenuItemCount (hMenu) ; You can obtain the menu ID for an item in a popup menu from id = GetMenuItemID (hMenuPopup, iPosition) ; where iPosition is the position (starting at 0) of the item within the popup. You can check or uncheck an item in a popup menu using CheckMenuItem (hMenu, id, iCheck) ; hMenu is the handle to the top-level menu, id is the menu ID, and the value of iCheck can either be MF_CHECKED or MF_UNCHECKED. If hMenu is a handle to a popup menu, the id parameter can be a positional index rather than a menu ID. The EnableMenuItem function works similarly to CheckMenuItem, except that the third argument is MF_ENABLED, MF_DISABLED, or MF_GRAYED. If you use EnableMenuItem on a top-level menu item that has a popup, you must also use the MF_BYPOSITION identifier in the third parameter because the menu item has no menu ID. HiliteMenuItem is similar to CheckMenuItem and EnableMenuItem but uses MF_HILITE and MF_UNHILITE. This highlighting is the reverse video that Windows uses when you move among menu items. You do not normally need to use HiliteMenuItem. if you no longer need a menu in your program, you can destroy it: DestroyMenu (hMenu) ; This function invalidates the menu handle 3.2.4 Event Handling Handling Slider Events
Page 185
Visual Programming
When the user moves the slider's thumb, it passes us a WM-HSCROLL message (WM_VSCROLL for vertical sliders). Using ClassWizard, connect WM-HSCROLL to the OnHScrol1() method (for vertical sliders, you would use OnVScrol1()). Be sure that the object ID for CslidersDlg is selected in the ClassWizard when you add the handler. The OnHSCroll handler will be handling the message for the dialog itself: void CSlidersDIg::OnHScroll(UINT nSBCode, UINT npos, CScrollBar* pScrollBar) { // TODO: Add your message handler code here and/or call default } In this method, we are passed a scroll bar code, the new position of the scrollable control, and a pointer to the scrollable control that sent the message. Well intercept the SB_THUMSPOSITION message, sent to us when the user moves the slider's thumb. void CSlidersDlg::OniHScroll(UINT nSBCode, UINT npos, CScrollBar* pScrollBar) { if(nSBCode == SB_THUMBPOSITION) { . } else{ CDialog::OnHScroll(nSBCode, npos, pScrollBar); } } If the message we got was indeed SB_THUMBPOSIT1ON, the user has moved the slider's thumb, and we want to indicate the new slider position in our text box. Displaying Numbers in Text Boxes We can use the CString classs Format() method here. This method takes a format string, just as you would use for the normal C string-handling functions. That means that we can format our nPos parameter as a long integer and place it in out text box this way: void CS1idersDlg::OnHScroll(UINT nSBCode,, UINT npos, CScrollBar* pscrollbar) { if (nSBCode = = SB_THUMBPOSITION){ m_text.Format(%ld,nPos); UpdateData(false); } else{ CDialog::OnHScroll(nSBCode, npos, pScrollBar); } }
Page 186
Visual Programming
Now run the program. When you move the sliders thumb, the program reports the new location of the slider. Date and Time Classes If you've ever tried to manipulate time values returned from a computer, you'll be pleased to learn about MFC's CTime and CTimeSpan classes, which represent absolute times and elapsed times, respectively. The use of these classes is pretty straightforward, so there's no sample program for this section. However, the following sections will get you started with these handy classes. Before you start working with the time classes look over the tables below, which lists the member functions of the CTime class and the member functions of the CTimeSpan class. Member Functions of the CTime Class Description Constructs a string representing the time object's time. Constructs a string representing the time object's GMT (or UTC) time. This is the Greenwich mean time. Creates a CTime object for the current time. Gets the time object's day as an integer. Gets the time object's day of the week, starting with 1 for Sunday. Gets a time object's second, minute, hour, day, month, year, day of the week, and day of the year as a tm structure. Gets the time object's hour as an integer. Gets a time object's local time, returning the second, minute, hour, day, month, year, day of the week, and day of the year in a tm structure. Gets the time object's minutes as an integer. Gets the time object's month as an integer. Gets the time object's second as an integer. Gets the time object's time as a time't value. Gets the time object's year as an integer. Member Functions of the CTimeSpan Class Page 187
GetHour() GetLocalTm()
Visual Programming
Function Format() GetDays() GetHours() GetMinutes() GetSeconds() GetTotalHours() GetTotalMinutes() GetTotalSeconds() Using a CTime Object Creating a CTime object for the current time is a simple matter of calling the GetCurrentTime() function, like this: CTime time = CTime::GetCurrentTime(); Description Constructs a string representing the timespan object's time. Gets the time-span object's days. Gets the time-span object's hours for the current day. Gets the time-span object's minutes for the current hour. Gets the time-span object's seconds for the current minute. Gets the time-span objects total hours. Gets the time-span object's total minutes. Gets the time-span object's total seconds.
Notice that, because GetCurrentTime() is a static member function of the CTime class, you can call it without actually creating a CTime object. You do, however, have to include the class's name as part of the function call, as shown in the preceding code. As you can see, the function returns a CTime object. This object represents the current time. If you wanted to display this time, you could call upon the Format() member function, like this: CString str = time.Format("DATE: %A, %B %d, %Y"); The Format() function takes as its single argument a format string that tells the function how to create the string representing the time. The previous example creates a string that looks something like this: DATE: Saturday, April 20, 2001 The format string used with Format() is not unlike the format string that you use with functions like the old DOS favorite printf() or the Windows conversion function wsprintf(). That is, you specify the string's format by including literal characters along with control characters. The literal characters, such as the "DATE" and the commas in the previous string example, are added to the string exactly as you type them, whereas the format codes are replaced with the appropriate values. For example, the %A in the
Page 188
Visual Programming
previous code example will be replaced by the name of the day and the %B will be replaced by the name of the month. Although the format-string concept is the same as that used with printf(), the Format() function has its own set of format codes, which are listed in following table. Format Codes for the Format() Function Code %a %A %b %B %c %d %H %I %j %m %M %p %S %U %w %W %x %X %y %Y %z %Z %% Description Day name, abbreviated (such as Sat for Saturday). Day name, no abbreviation. Month name, abbreviated (such as Mar for March). Month name, no abbreviation. Localized date and time (for the U.S., that would be something like 03/17/01 12:15:34). Day of the month as a number (0131). Hour in the 24-hour format (0023). Hour in the normal 12-hour format (0112). Day of the year as a number (001366). Month as a number (0112). Minute as a number (0059). Localized a.m./p.m. indicator for 12-hour clock. Second as a number (0059). Week of the year as a number (0051, considering Sunday to be the first day of the week). Day of the week as a number (06, with Sunday being 0). Week of the year as a number (0051, considering Monday to be the first day of the week). Localized date representation. Localized time representation. Year without the century prefix as a number (0099). Year with the century prefix as a decimal number (such as 2000). Name of time zone, abbreviated. Name of time zone, no abbreviation. Percent sign.
Page 189
Visual Programming
Other CTime member functions like GetMinute(), GetYear(), and GetMonth() are obvious in their usage. However, you may like an example of using a function like GetLocalTm(): struct tm* timeStruct; timeStruct = time.GetLocalTm(); The first line of the previous code declares a pointer to a tm structure. (The tm structure is defined by Visual C++.) The second line sets the pointer to the tm structure created by the call to GetLocalTm(). This function call essentially retrieves all of the time information at once, organized in the tm structure, which is defined in the header file TIME.H, as shown in the following listing. Listing LST.TXT-The tm Structure struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; /* seconds after the minute - [0,59] */ /* minutes after the hour - [0,59] */ /* hours since midnight - [0,23] */ /* day of the month - [1,31] */ /* months since January - [0,11] */ /* years since 1900 */ /* days since Sunday - [0,6] */ /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */ }; Using a CTimeSpan Object A CTimeSpan object is nothing more complex than the difference between two times. You can use CTime objects in conjunction with CTimeSpan objects to easily determine the amount of time that's elapsed between two absolute times. To do this, first create a CTime object for the current time. Then, when the time you're measuring has elapsed, create a second CTime object for the current time. Subtracting the old time
Page 190
Visual Programming
object from the new one gives you a CTimeSpan object representing the amount of time that has elapsed. The example in the following listing shows how this process works. Listing LST.TXT-Calculating a Time Span CTime startTime = CTime::GetCurrentTime(); //. Time elapses... CTime endTime = CTime::GetCurrentTime(); CTimeSpan timeSpan = endTime - startTime;
Mouse Events The mouse can generate quite a number of events, from WM-LBUTTONDOWN, when the user presses the left button on the mouse, to WM-MOUSEMOVE, when the user moves the mouse. Let the user select a location in our client area simply by clicking it. Then, when they type text, that text will appear at the location they clicked. In this way, they can decide where they want the text to appear in our window. A caret is one of those blinking vertical lines you see that indicates where text will appear when you type it. Well start with a program that simply lets us add a caret to a window and then move the caret as we type. Adding a Caret to a Window Our first example shows how to use carets. In particular, we'll place a blinking caret in our client area at the extreme upper left. Then, as the user types text, we'll display that text, starting at upper left, and move the caret to the end of the text, indicating where the next character will go: Use AppWizard to create a new single-window (SDI) program named "carets. "' We begin by setting up storage for our text string's data, Stringdata, in the program's document header file: class CCaretsDoc : public CDocument I{ protected: // create from serialization only CCaretsDoc(); ECLAR_DYNCREATE(CCaretsDoc) . . . / Implementation public: virtual ~CCaretsDoc(); CString StringData;
Page 191
Visual Programming
. . } We reset that string object to an empty string, , in the document's constructor: CCaretsDoc::CCaretsDoc() { StringData // TODO: add one-time construction code here } In addition, use ClassWizard now to connect the OnChar(.) method to the WM_CHAR Windows message in the view. We add code to record the keys the user types this way: void CCaretsView::OnChar(UINT nchar, UINT nRepCnt, UINT nflags) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData += nchar; Invalidate(); CView::OnChar(nChar, nRepCnt, nflags); } Now we are ready to create and use a new caret in the view. A caret is usually made the same height as the current characters, and 1/8th the width of an average character. To determine the height and width of characters, we'll use the CDC method GetTextMetrics(). (CDC is the device context class). Measuring the Text Sizes with Textmetrics Let's create a new caret for our window now in the OnDraw() method. We'll set up a boolean variable named CaretCreated in the view object to keep track of whether or not we've already created the caret: // caretsview.h : interface of the CCaretsView class . . class CCaretsView : public CView { protected: // create from serialization only CCaretsView(); DECLARE_ DYNCREATE(CCaretsView) boolean CaretCreated; . .
Page 192
Visual Programming
} After setting CaretCreated to false in the view's constructor, we check to see if we've already created the caret in a previous call to OnDraw(): void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . } } If we haven't created the caret, it's time to do so now. We'll need to decide on a size for the caret, and we'll get that size from a TEXTMETRIC structure by calling GetTextMetrics (). The GetTextMetrics () method fills a structure of the TEXTMETRIC type. We fill a TEXTMETRIC structure named textmetric way in our program now: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); . . } } Now we're ready to create our new caret. We'll make the caret the same height as our text, using textmetric.tmheight, and 1/8th the width of an average character-textmetric. tmAveCharWidth/8. We call CreateSolidcaret() to actually create the caret. void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric);
Page 193
Visual Programming
CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); . } } This creates the new caret and installs it in our view. Setting the Carets Position We'll store the caret's position in a new CPoint object named CaretPosition. T'he CPoint class has two data members, x and y, which will hold the position of the caret:
// caretsview.h . . .
class CCaretsView : public CView { protected: // create from serialization only CCaretsView(); DECLARE_DYNCREATE(CCaretsView) CPoint CaretPosition; boolean CaretCreated; . . Initially, we set the caret's position to (0, 0) in OnDraw() this way: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc =GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x = CaretPosition.y = 0; } . . }
Page 194
Visual Programming
Then we set the caret's position with SetCaretPos(),show thecaret on the screen with ShowCaret(), and set the CaretCreated boolean flag to true: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); I if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x - CaretPosition.y - 0; SetCaretPos(CaretPosition); ShowCaret(); CaretCreated = true; } . . } At is point, the caret appears on the screen. We havecreated our own blinking, functional caret. The next step is to move the caret as the user types text; the caret should always indicate where typed text will appear. We first display the text that the user has typed: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x = CaretPosition.y = 0; SetCaretPos(CaretPosition); ShowCaret(); CaretCreated = true; } pDC->TextOut(O, 0, pDoc->StringData); . Page 195
Visual Programming
} Now we can place the caret at the end of the displayed text string. First, we have to determine just where the end of the text string is, which we do by filling a CSize object named "size" using GetTextExtent(): void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); pDC->TextOut(0, 0, pDoc->StringData); CSize size - pDC->GetTextExtent(pDoc->StringData); . . . } To display the caret at the end of the text string, we first hide it using HideCaret(): void CCaretsView::OnDraw(CDC* PDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); . . pDC->TextOut(0 0, pDoc->StringData); CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); . . } If you don't hide the caret before moving it, you risk leaving an image of it on the screen in the old location. Next, we set the x data member of the CaretPosition point to the end of the text string on the screen: void CCaretsView::OnDraw(CDC* pDC) { CCaretsDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); . . pDC->TextOut(0, 0, pDoc->StringData);
Page 196
Visual Programming
CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); CaretPosition.x = size.cx; . . Finally, we move the caret to it s new location and show it again: void CCaretsView::OnDraw(CDC* PDC) { CCaretsDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); . pDC->TextOut(0 0, pDoc->St@ingData); CSize size = pDC->GetTextExtent(pDoc->StringData); HideCaret(); CaretPosition.x = size.cx; SetCaretPos(CaretPosition); ShowCaret(); And we're ready to run the program Showing and Hiding a Caret When we Lose or Gain the Focus When our program loses the focus, we get a WM_KILLFOCUS message, and when our program gains the focus, we get a WM_SETFOCUS message. Using ClassWizard, connect an event-handler method to the WM_KILLFOCUS message now. ClassWizard will call this method OnKi1lFocus(): void CCaretsV!ew::OnKillFocus(CWnd* pNewWnd) { CView::OnKillFocus(pNewWnd); // TODO: Add your message handler code here } Here, we've lost the focus, so we hide the caret this way: void CCaretsView::OnKillFocus(CWnd* pNewWnd) { CView::OnKillFocus(pNewWnd); HideCaret(); // TODO: Add your message handler code here }
Page 197
Visual Programming
Similarly, add the OnSetFocus() method to the WM_SETFOCUS message now, and place this code in that method to show the caret when we (re)gain the focus: void CCaretsView::OnSetFocus(CWnd* pOldWnd) { CView::OnSetFocus(pOldWnd); ShowCaret(); // TODO: Add your message handler code here } That completes the program; now when we gain the focus, our caret will appear, and when we lose the focus, it will disappear.
Using the Mouse In the next example, well let the user click the mouse somewhere in our client area. When they do, well display a caret at that location. They can type the text they want starting at that location. When they click in another place in the client area, well clear the characters in the text string and let them type a new text string at the new location. Using the ClassWizard Mouse Methods Now well create a new SDI program named mouser. When the user types we store the keystrokes in a CString object named StringData in the document. We also add OnKillFocus() and OnSetFocus(), placing the HideCaret() and ShowCaret() calls in those methods. Because the user has clicked a place on our client area, they want the text to appeat starting at that clicked location. This means that we should use ClassWizard to handle the left mouse button down Windows messages, WM_LBUTTONDOWN, in our code; specifically to a method the ClassWizard will name OnButtonDown(). Start ClassWizard now. Make sure our view class, CMouserView, is selected in the Class name box, and find the WM_LBUTTONDOWN message in the Message box. Double-click that message now, creating the new method OnButtonDown(). Double-click the OnButtonDown() methods entry in that box now to open the code for that method: void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO: Add your message handler code here and/or call default CView::OnLButtonDown(nFlags, point); }
Page 198
Visual Programming
Besides OnLButtonDown(), you can use methods like OnLButtonUp(), OnRButtonDown() for the right mouse button, OnLButtonDblClk() for double-clicks and so on. We are passed two parameters in OnLButtonDown(): nFl ags and poi nt. The n Fl ag s parameter indicates the state of various keys on the keyboard, and can take these values: MK_CONTROL Control key was down MK_ LBUTTON Left mouse button down MK_MBUTTON Middle mouse button down MK_RBUTTON Right mouse button down MK_SHIFT Shift key was down The point parameter, an object of the CPoint class, holds the mouse's present location. Now that the mouse has gone down, the first order of business is to store its location, and we'll store that location in the variables x and y as (x, y), which we get from the x and y members of the point object: void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO:Add your message handler code here and/or call default x = point.x; y = point.y; } We also put aside space for the x and y values in the view's header file, mouserview.h: // mouserview.h : interface of the CMouserView class . . protected: // create from serialization only CMouserView(); DECLARE_ DYNCREATE(CMouserView) CPoint CaretPosition; boolean CaretCreated; int x, y; . . Now that the user has moved the mouse to a new position, we will also empty the text string, using the CString class Empty() method: void CMouserView::OnLButtonDown(UINT nflagsi CPoint point) { // TODO: Add your message handler code here and/or call default x = point.x; y = point.y; CMouserDoc* pdoc GetDocument();
Page 199
Visual Programming
ASSERT_VALID(pDoc); pDoc->StringData.Empty(); . . } Finally, we invalidate the view so we will display the cursor in the view at its new location (using code we will add to the OnDraw() method): void CMouserView::OnLButtonDown(UINT nflags, CPoint point) { // TODO:Add your message handler code here and/or call default x = point.x; y = point.y; CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); pDoc->StringData.Empty() Invalidate(); CView::OnLButtonDown(nFlags, point); } That will finish recording the location of the mouse. Drawing Text at the New Mouse Location T'he text drawing is handled in OnDraw(). Start by creating a caret if you don't already have one.
void CMouserView::OnDraw(CDC* pDC) { CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ TEXTMETRIC textmetric; pDC->GetTextMetrics(&textmetric); CreateSolidCaret(textmetric.tmAveCharWidth/8, textmetric.tmHeight); CaretPosition.x =CaretPosition.y = 0; SetCaretPos(CaretPosition); Page 200
Visual Programming
ShowCaret(); CaretCreated = true; } . .} Now we display the string of text, Stringdata. Because the user clicked the mouse at the location we've recorded as (x, y), we display the text starting at that point: void CMouserView::OnDraw(CDC* pDC) { CMouserDoc* pdoc GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . } pDC->TextOut(x, y, pDoc->StringData); . } We can place the caret at the end of the text string on the screen. First, we find out where the end of the string is and hide the caret: void CMouserView::OnDraw(CDC* PDC) { CMouserDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if(!CaretCreated){ . . } pDC->TextOut(x, y, pDoc->StringData); Csize size = pDC->GetTextExtent(pdoc->StringData); HideCaret(); . .} Then we move the caret to the end of the text string and show it again: void CMouserView::OnDraw(CDC* PDC) { CMouserDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc);
Page 201
Visual Programming
if(!CaretCreated){ . . } pDC->TextOut(x, y, pDoc->StringData); Csize size = pDC->GetTextExtent(pdoc->StringData); HideCaret(); CaretPosition.x = x + sice.cx; CaretPositon.y = y; SetCaretPos(CaretPosition); ShowCaret(); } And that finishes off our code for this example. Run the mouser and click any place in the client window and type some text. The text appears where the user clicked and the blinking caret indicates where the next character will appear. 3.2.5 Message Dispatch system Client-Area Mouse Messages A window procedure receives mouse messages whenever the mouse passes over the window or is clicked within the window, even if the window is not active or does not have the input focus. Windows defines 21 messages for the mouse. However, 11 of these messages do not relate to the client area. These are called "nonclient-area messages," and Windows applications usually ignore them. When the mouse is moved over the client area of a window, the window procedure receives the message WM_MOUSEMOVE. When a mouse button is pressed or released within the client area of a window, the window procedure receives the messages in this table: Button Pressed Left WM_LBUTTONDOWN Released WM_LBUTTONUP Pressed (Second Click) WM_LBUTTONDBLCLK
Your window procedure receives MBUTTON messages only for a three-button mouse and RBUTTON messages only for a two-button mouse. The window procedure
Page 202
Visual Programming
receives DBLCLK (double-click) messages only if the window class has been defined to receive them. For all these messages, the value of lParam contains the position of the mouse. The low word is the x-coordinate, and the high word is the y-coordinate relative to the upper left corner of the client area of the window. You can extract these values using the LOWORD and HIWORD macros: x = LOWORD (lParam) ; y = HIWORD (lParam) ; The value of wParam indicates the state of the mouse buttons and the Shift and Ctrl keys. You can test wParam using these bit masks defined in the WINUSER.H header file. The MK prefix stands for "mouse key." MK_LBUTTON Left button is down MK_MBUTTON Middle button is down MK_RBUTTON Right button is down MK_SHIFT Shift key is down MK_CONTROL Ctrl key is down For example, if you receive a WM_LBUTTONDOWN message, and if the value wparam & MK_SHIFT is TRUE (nonzero), you know that the Shift key was down when the left button was pressed. As you move the mouse over the client area of a window, Windows does not generate a WM_MOUSEMOVE message for every possible pixel position of the mouse. The number of WM_MOUSEMOVE messages your program receives depends on the mouse hardware and on the speed at which your window procedure can process the mouse movement messages. In other words, Windows does not fill up a message queue with unprocessed WM_MOUSEMOVE messages. If you click the left mouse button in the client area of an inactive window, Windows changes the active window to the window that is being clicked and then passes the WM_LBUTTONDOWN message to the window procedure. When your window procedure gets a WM_LBUTTONDOWN message, your program can safely assume the window is active. However, your window procedure can receive a WM_LBUTTONUP message without first receiving a WM_LBUTTONDOWN message. This can happen if the mouse button is pressed in one window, moved to your window, and released. Similarly, the window procedure can receive a WM_LBUTTONDOWN without a corresponding WM_LBUTTONUP message if the mouse button is released while positioned over another window.
Page 203
Visual Programming
There are two exceptions to these rules:
A window procedure can "capture the mouse" and continue to receive mouse messages even when the mouse is outside the window's client area.
If a system modal message box or a system modal dialog box is on the display, no other program can receive mouse messages. System modal message boxes and dialog boxes prohibit switching to another window while the box is active. An example of a system modal message box is the one that appears when you shut down your Windows session NonClient Area Mouse Messages If the mouse is outside a window's client area but within the window, Windows sends the window procedure a "nonclient-area" mouse message. The nonclient area of a window includes the title bar, the menu, and the window scroll bars. You do not usually need to process nonclient-area mouse messages. Instead, you simply pass them on to DefWindowProc so that Windows can perform system functions. In this respect, the nonclient-area mouse messages are similar to the system keyboard messages WM_SYSKEYDOWN, WM_SYSKEYUP, and WM_SYSCHAR. The nonclient-area mouse messages parallel almost exactly the client-area mouse messages. The message identifiers include the letters "NC" to indicate "nonclient." If the mouse is moved within a nonclient area of a window, the window procedure receives the message WM_NCMOUSEMOVE. The mouse buttons generate these messages:
Button Pressed
Left Middle Right
WM_NCLBUTTONDOWN WM_NCMBUTTONDOWN WM_NCRBUTTONDOWN
Released
WM_NCLBUTTONUP WM_NCMBUTTONUP WM_NCRBUTTONUP
The wParam and lParam parameters for nonclient-area mouse messages are somewhat different from those for client-area mouse messages. The wParam parameter indicates the nonclient area where the mouse was moved or clicked. It is set to one of the identifiers beginning with HT (standing for "hit-test") that are defined in the WINUSER.H. The lParam parameter contains an x-coordinate in the low word and a ycoordinate in the high word. However, these are screen coordinates, not client-area coordinates as they are for client-area mouse messages. For screen coordinates, the upper-left corner of the display area has x and y values of 0. Values of x increase as you move to the right, and values of y increase as you move down the screen. Page 204
Visual Programming
You can convert screen coordinates to client-area coordinates and vice versa with these two Windows functions: ScreenToClient (hwnd, &pt) ; ClientToScreen (hwnd, &pt) ; where pt is a POINT structure. These two functions convert the values stored in the structure without preserving the old values. If a screen-coordinate point is above or to the left of the window's client area, the x or y value of the client-area coordinates could be negative. The Hit-Test Message The mouse message WM_NCHITTEST, which stands for "nonclient hit test" precedes all other client-area and nonclient-area mouse messages. The lParam parameter contains the x and y screen coordinates of the mouse position. The wParam parameter is not used. Windows applications generally pass this message to DefWindowProc. Windows then uses the WM_NCHITTEST message to generate all other mouse messages based on the position of the mouse. For nonclient-area mouse messages, the value returned from DefWindowProc when processing WM_NCHITTEST becomes the wParam parameter in the mouse message. This value can be any of the wParam values that accompany the nonclient-area mouse messages plus the following: HTCLIENT HTNOWHERE HTTRANSPARENT HTERROR Client area Not on any window A window covered by another window Causes DefWindowProc to produce a beep
If DefWindowProc returns HTCLIENT after it processes a WM_NCHITTEST message, Windows converts the screen coordinates to client-area coordinates and generates a client-area mouse message. If you include the lines case WM_NCHITTEST: return (LRESULT) HTNOWHERE ; in your window procedure, you will effectively disable all client-area and nonclient-area mouse messages to your window. The mouse buttons will simply not work while the mouse is anywhere within your window, including the system menu icon, the sizing buttons, and the close button. Hit-Testing Program The program first determines exactly which file or directory the user is pointing at with the mouse. This is called "hit-testing." Just as DefWindowProc must do some hit-
Page 205
Visual Programming
testing when processing WM_NCHITTEST messages, a window procedure often must do hit-testing of its own within the client area. In general, hit-testing involves calculations using the x and y coordinates passed to your window procedure in the lParam parameter of the mouse message. The program given below demonstrates some simple hit-testing. The program divides the client area into a 5-by-5 array of 25 rectangles. If you click the mouse on one of the rectangles, the rectangle is filled with an X. If you click there again, the X is removed. #include <windows.h> #define DIVISIONS 5 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Hit Testing") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Checker1 Mouse Hit-Test Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
Page 206
Visual Programming
NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAMlParam) { static BOOL fState[DIVISIONS][DIVISIONS] ; static int cxBlock, cyBlock ; HDC hdc ; int x, y ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_SIZE : cxBlock = LOWORD (lParam) / DIVISIONS ; cyBlock = HIWORD (lParam) / DIVISIONS ; return 0 ; case WM_LBUTTONDOWN : x = LOWORD (lParam) / cxBlock ; y = HIWORD (lParam) / cyBlock ; if (x < DIVISIONS && y < DIVISIONS) { fState [x][y] ^= 1 ; rect.left = x * cxBlock ; rect.top = y * cyBlock ; rect.right = (x + 1) * cxBlock ; rect.bottom = (y + 1) * cyBlock ; InvalidateRect (hwnd, &rect, FALSE) ; } else MessageBeep (0) ;
Page 207
Visual Programming
return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (x = 0 ; x < DIVISIONS ; x++) for (y = 0 ; y < DIVISIONS ; y++) { Rectangle (hdc, x * cxBlock, y * cyBlock, (x + 1) * cxBlock, (y + 1) * cyBlock) ; if (fState [x][y]) { MoveToEx (hdc, x * cxBlock, y * cyBlock, NULL) ; LineTo (hdc, (x+1) * cxBlock, (y+1) * cyBlock) ; MoveToEx (hdc, x * cxBlock, (y+1) * cyBlock, NULL) ; LineTo (hdc, (x+1) * cxBlock, y * cyBlock) ; } } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } If the width or height of the client area is not evenly divisible by five, a small strip of client area at the left or bottom will not be covered by a rectangle. For error processing, the program responds to a mouse click in this area by calling MessageBeep. When the program receives a WM_PAINT message, it repaints the entire client area by drawing rectangles using the GDI Rectangle function. If the fState value is set, the program draws two lines using the MoveToEx and LineTo functions. During WM_PAINT processing, the program does not check whether each rectangular area lies within the invalid rectangle, but it could. One method for checking validity involves building a RECT structure for each rectangular block within the loop and checking whether that rectangle intersects the invalid rectangle (available as ps.rcPaint) by using the function IntersectRect. 3.2.6 Mode and Modeless Dialogs Dialog Boxes
Page 208
Visual Programming
Dialog boxes are most often used for obtaining additional input from the user beyond what can be easily managed through a menu. The programmer indicates that a menu item invokes a dialog box by adding an ellipsis (...) to the menu item. A dialog box generally takes the form of a popup window containing various child window controls. The size and placement of these controls are specified in a "dialog box template" in the program's resource script file. Although a programmer can define a dialog box template "manually," these days dialog boxes are usually interactively designed in the Visual C++ Developer Studio. Developer Studio then generates the dialog template. When a program invokes a dialog box based on a template, Microsoft Windows 98 is responsible for creating the dialog box popup window and the child window controls, and for providing a window procedure to process dialog box messages, including all keyboard and mouse input. The code within Windows that does all this is sometimes referred to as the "dialog box manager." Many of the messages that are processed by that dialog box window procedure located within Windows are also passed to a function within your own program, called a "dialog box procedure" or "dialog procedure." The dialog procedure is similar to a normal window procedure, but with some important differences. Generally, you will not be doing much within the dialog procedure beyond initializing the child window controls when the dialog box is created, processing messages from the child window controls, and ending the dialog box. Dialog procedures generally do not process WM_PAINT messages, nor do they directly process keyboard and mouse input. Modal Dialog Boxes Dialog boxes are either "modal" or "modeless." The modal dialog box is the most common. When your program displays a modal dialog box, the user cannot switch between the dialog box and another window in your program. The user must explicitly end the dialog box, usually by clicking a push button marked either OK or Cancel. The user can, however, switch to another program while the dialog box is still displayed. Some dialog boxes (called "system modal") do not allow even this. System modal dialog boxes must be ended before the user can do anything else in Windows. The Dialog Procedure The dialog box procedure handles messages to the dialog box. Although it looks very much like a window procedure, it is not a true window procedure. The window procedure for the dialog box is within Windows. That window procedure calls your dialog box procedure with many of the messages that it receives. The parameters to this function are the same as those for a normal window procedure. The dialog box procedure must be defined as a CALLBACK function. BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,
Page 209
Visual Programming
WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : case IDCANCEL : EndDialog (hDlg, 0) ; return TRUE ; } break ; } return FALSE ; } The differences between this function and a window procedure are:
A window procedure returns an LRESULT; a dialog box procedure returns a BOOL, which is defined in the Windows header files as an int. A window procedure calls DefWindowProc if it does not process a particular message; a dialog box procedure returns TRUE (nonzero) if it processes a message and FALSE (0) if it does not. A dialog box procedure does not need to process WM_PAINT or WM_DESTROY messages. A dialog box procedure will not receive a WM_CREATE message; instead, the dialog box procedure performs initialization during the special WM_INITDIALOG message.
The WM_INITDIALOG message is the first message the dialog box procedure receives. This message is sent only to dialog box procedures. If the dialog box procedure returns TRUE, Windows sets the input focus to the first child window control in the dialog box that has a WS_TABSTOP style. In this dialog box, the first child window control that has a WS_TABSTOP style is the push button. Alternatively, during the processing of WM_INITDIALOG, the dialog box procedure can use SetFocus to set the focus to one of the child window controls in the dialog box and then return FALSE. The only other message this dialog box processes is WM_COMMAND. This is the message the push-button control sends to its parent window either when the button is clicked with the mouse or when the Spacebar is pressed while the button has the input focus. The ID of the control is in the low word of wParam. For this message, the dialog
Page 210
Visual Programming
box procedure calls EndDialog, which tells Windows to destroy the dialog box. For all other messages, the dialog box procedure returns FALSE to tell the dialog box window procedure within Windows that our dialog box procedure did not process the message. Invoking the Dialog Box A dialog box can be invoked using the DialogBox function. DialogBox (hInstance, szTemplate, hwndParent, DialogProc) ; This function requires the instance handle (saved during WM_CREATE), the name of the dialog box (as defined in the resource script), the parent of the dialog box (which is the program's main window), and the address of the dialog procedure. If you use a numeric identifier rather than a name for the dialog box template, you can convert it to a string using the MAKEINTRESOURCE macro. You can end this dialog box by clicking the OK button with the mouse, by pressing the Spacebar, or by pressing Enter. For any dialog box that contains a default push button, Windows sends a WM_COMMAND message to the dialog box, with the low word of wParam equal to the ID of the default push button when Enter or the Spacebar is pressed. That ID is IDOK. You can also end the dialog box by pressing Escape. In that case Windows sends a WM_COMMAND message with an ID equal to IDCANCEL. The DialogBox function you call to display the dialog box will not return control to WndProc until the dialog box is ended. The value returned from DialogBox is the second parameter to the EndDialog function called within the dialog box procedure. WndProc can then return control to Windows. Even when the dialog box is displayed, however, WndProc can continue to receive messages. In fact, you can send messages to WndProc from within the dialog box procedure. Modeless Dialog Boxes Modeless dialog boxes allow the user to switch between the dialog box and the window that created it as well as between the dialog box and other programs. The modeless dialog box is thus more akin to the regular popup windows that your program might create. Modeless dialog boxes are preferred when the user would find it convenient to keep the dialog box displayed for a while. For instance, word processors often use modeless dialog boxes for the text Find and Change dialogs. If the Find dialog box were modal, the user would have to choose Find from the menu, enter the string to be found, end the dialog box to return to the document, and then repeat the entire process to search for another occurrence of the same string. Allowing the user to switch between the document and the dialog box is much more convenient.
Page 211
Visual Programming
As you've seen, modal dialog boxes are created using DialogBox. The function returns a value only after the dialog box is destroyed. It returns the value specified in the second parameter of the EndDialog call that was used within the dialog box procedure to terminate the dialog box. Modeless dialog boxes are created using CreateDialog. This function takes the same parameters as DialogBox: hDlgModeless = CreateDialog (hInstance, szTemplate, hwndParent, DialogProc) ; The difference is that the CreateDialog function returns immediately with the window handle of the dialog box. Normally, you store this window handle in a global variable. Differences Between Modal and Modeless Dialog Boxes Working with modeless dialog boxes is similar to working with modal dialog boxes, but there are several important differences. First, modeless dialog boxes usually include a caption bar and a system menu box. These are actually the default options when you create a dialog box in Developer Studio. The STYLE statement in the dialog box template for a modeless dialog box will look something like this: STYLE WS_POPUP WS_CAPTION WS_SYSMENU WS_VISIBLE he caption bar and system menu allow the user to move the modeless dialog box to another area of the display using either the mouse or the keyboard. You don't normally provide a caption bar and system menu with a modal dialog box, because the user can't do anything in the underlying window anyway. The second big difference is that if you neither include WS_VISIBLE in CreateDialog call nor call ShowWindow, the modeless dialog box will not be displayed. hDlgModeless = CreateDialog ( . . . ) ; ShowWindow (hDlgModeless, SW_SHOW) ; The third difference is that unlike messages to modal dialog boxes and message boxes, messages to modeless dialog boxes come through your program's message queue. The message queue must be altered to pass these messages to the dialog box window procedure. Here's how you do it: When you use CreateDialog to create a modeless dialog box, you should save the dialog box handle returned from the call in a global variable (for instance, hDlgModeless). Change your message loop to look like while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == 0 !IsDialogMessage (hDlgModeless, &msg))
Page 212
Visual Programming
{ TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } If the message is intended for the modeless dialog box, then IsDialogMessage sends it to the dialog box window procedure and returns TRUE (nonzero); otherwise, it returns FALSE (0). The TranslateMessage and DispatchMessage functions should be called only if hDlgModeless is 0 or if the message is not for the dialog box. If you use keyboard accelerators for your program's window, the message loop looks like this: while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgModeless == 0 !IsDialogMessage (hDlgModeless, &msg)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } } Because global variables are initialized to 0, hDlgModeless will be 0 until the dialog box is created, thus ensuring that IsDialogMessage is not called with an invalid window handle. The hDlgModeless variable can also be used by other parts of the program as a test of the existence of the modeless dialog box. For example, other windows in the program can send messages to the dialog box while hDlgModeless is not equal to 0. The final difference: Use DestroyWindow rather than EndDialog to end a modeless dialog box. When you call DestroyWindow, set the hDlgModeless global variable to NULL. The user customarily terminates a modeless dialog box by choosing Close from the system menu. Although the Close option is enabled, the dialog box window procedure within Windows does not process the WM_CLOSE message. You must do this yourself in the dialog box procedure: case WM_CLOSE : DestroyWindow (hDlg) ; hDlgModeless = NULL ; break ;
Page 213
Visual Programming
Note the difference between these two window handles: the hDlg parameter to DestroyWindow is the parameter passed to the dialog box procedure; hDlgModeless is the global variable returned from CreateDialog that you test within the message loop. 3.2.7 Importing VBX Controls ActiveX controls are specially designed to be embedded in programs and especially in Web pages that browsers can display. The Boxer ActiveX Control This example will present the user with a small rectangle, divided into four sections; when the user clicks one of those sections, we will shade that section in black. When the user clicks another section, we will switch the shading to that section. Start visual C++ now to create the boxer program. This time in the New Dialog box, select the entry marked MFC ActiveX ControlWizard. There are only two steps in the MFC ActiveX ControlWizard. Accept all the defaults by pressing the Finish button and allow the ControlWizard to create out new ActiveX control. The code for this new control appears in files BoxerCtl.h and BoxerCtl.cpp. The new control is based on the COleControl class and the code in BoxerCtl.cpp resembles the code we would find in the standard view class. Drawing the ActiveX Control The ActiveX controls OnDraw() method in BoxerCtl.cpp looks like this: void CBoxerCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const Crect& rcInvalid) { // TODO : Replace the following code with your own drawing code. pdc-> FillRect(rcBounds) CBrush::FromHAndle((HBRUSH)GetStockObject(WHITE_BRUSH))); pdc->Ellipse(rcBounds); } We are passed a rectangle in which to draw the control, rcBounds and the code already in place simply colors in that rectangle white and draws a demo ellipse. Well do our own drawing, so remove the code pdc-> Ellipse(rcBounds); Our job is to start dividing the control up into four rectangles, naming those rectangles box1 to box4 and declaring them in BoxerCtl.h class CBoxerCtl : public COleControl { // implementation protected: `CBoxerCtrl(); Page 214
Visual Programming
CRect box1; CRect box2; CRect box3; CRect box4; Now well use those four rectangles to divide the control up in OnDraw(), placing the four boxes at upper left, upper right, lower left and lower right. void CBoxerCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const Crect& rcInvalid) { pdc->FillRect(rcBounds, CBrush::FromHGandle((HBRUSH)GetStockObject(WHITE_BRUSH))); box1 = CRect(rcBounds.left, rcBounds.top. rcBounds.right/2 rcBounds.bottom/2); box2 = CRect(rcBounds.left, rcBounds.bottom/2. rcBounds.right/2, rcBounds.bottom); box3 = CRect(rcBounds.right/2, rcBounds.top. rcBounds.right, rcBounds.bottom/2); box4 = CRect(rcBounds.right/2, rcBounds.bottom/2, rcBounds.right, rcBounds.bottom); . } Then we draw the four rectantgles:
void CBoxerCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const Crect& rcInvalid) { pdc->FillRect(rcBounds, CBrush::FromHGandle((HBRUSH)GetStockObject(WHITE_BRUSH))); box1 = CRect(rcBounds.left, rcBounds.top. rcBounds.right/2 rcBounds.bottom/2); box2 = CRect(rcBounds.left, rcBounds.bottom/2. rcBounds.right/2, rcBounds.bottom); box3 = CRect(rcBounds.right/2, rcBounds.top. rcBounds.right, rcBounds.bottom/2); box4 = CRect(rcBounds.right/2, rcBounds.bottom/2, rcBounds.right, rcBounds.bottom); pdc->Rectangle(&box1); pdc->Rectangle(&box2); pdc->Rectangle(&box3); pdc->Rectangle(&box4); } Now the four rectangles appear on the screen. Then next task is to enable the mouse events so we can shade in the rectangle that the user clicks. Adding Event Handler We can use ClassWizard to connect the WM_LBUTTONDOWN message to our control so that we can handle mouse clicks. ClassWizard adds an OnLButtonDown() method to our ActiveX control this way:
Page 215
Visual Programming
void CBoxerCtrl::OnButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default COleControl::OnButtonDown(nFlags, point); } Here we can record which of our four rectangle the user clicked, and thus which rectangle to fill with color by setting up four new Boolean flags, fill1 to fill4 in the CBoxerCtl header: class CBoxerCtl : public COleControl { . . //Implementation protected: `CBoxerCtrl(); CRect box1; CRect box2; CRect box3; CRect box4; boolean fill1; boolean fill2; boolean fill3; boolean fill4; . . |} And we set those flags to false in the controls constructor: CBoxerCtrl::CBoxerCtrl() { InitializeIIdx(&IID_DBoxer, &IID_BoxerEvents); Fill1 = fill2 = fill3 = fill4 = false; } Then in OnLButtonDown(), we can set the Boolean fill flags using the handy CRect method PTInRect(), which returns true if the point you pass to it is in a certain rectangle, such as our box1 to box4 objects: void CBoxerCtrl:OnButtonDown(UNIT nFlags, CPoint pont) { fill1 = box1.PtInRect(point); fill2 = box2.PtInRect(point); Page 216
Visual Programming
fill3 = box3.PtInRect(point); fill4 = box4.PtInRect(point); Invalidate(); COleControl::OnButtonDown(nFlags, point); } After setting the Boolean flags to indicate which rectangle the user clicked, we call Invalidate() to redraw the view. This calls OnDraw() and there all we need to do is to check the four Boolean fill flags and fill the corresponding rectangle using the CDC method FillSolidRect(); void CBoxerCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const Crect& rcInvalid) { pdc->FillRect(rcBounds, CBrush::FromHGandle((HBRUSH)GetStockObject(WHITE_BRUSH))); box1 = CRect(rcBounds.left, rcBounds.top. rcBounds.right/2 rcBounds.bottom/2); box2 = CRect(rcBounds.left, rcBounds.bottom/2. rcBounds.right/2, rcBounds.bottom); box3 = CRect(rcBounds.right/2, rcBounds.top. rcBounds.right, rcBounds.bottom/2); box4 = CRect(rcBounds.right/2, rcBounds.bottom/2, rcBounds.right, rcBounds.bottom); pdc->Rectangle(&box1); pdc->Rectangle(&box2); pdc->Rectangle(&box3); pdc->Rectangle(&box4); if(fill1) pdc->FillSolidRect(&box1, RBG(0, 0, 0)); if(fill2) pdc->FillSolidRect(&box2, RBG(0, 0, 0)); if(fill3) pdc->FillSolidRect(&box3, RBG(0, 0, 0)); if(fill4) pdc->FillSolidRect(&box4, RBG(0, 0, 0)); } Now our code is complete and our program is ready to test.
Testing an ActiveX Control To test boxer, start by selecting the Build Boxer.ocx menu item in the Build menu to create Boxer.ocx and register that control with Windows. Next, select the ActiveX Control Test Container item in the Tools menu. Now select the Insert New Control item in the test containers Edit menu and double-click the Boxer control in the Insert Control box that appears. This inserts ActiveX control in the test container. You can click one of
Page 217
Visual Programming
the boxes in the control now, shading it. Clicking another rectangle shades that rectangle instead. Using ActiveX Control in VC++ program Using AppWizard create a new dialog-based program named Boxerapp. Well be able to insert a control of the Boxer type in this program. To do so, select Project-Add To Project Components and Controls, opening the Components and Controls Gallery. Now double-click the Registered ActiveX Controls entry, displaying the available ActiveX Controls. Select the Boxer control entry and click the Insert button. A box up pops up asking if you want to insert this control. Click OK. Next, the Confirm Class box appears, indicating that a new class will be created for this control, CBoxer. Click Ok, then close the Components and Controls Gallery. This inserts the Boxer control into the dialog editors toolbox where it has an icon displaying the letters OCX. As with any other control, we are now free to draw the Boxer control to the dialog window in the dialog editor. This installs our new Boxer control. Run the program. The Boxer control appears in our Visual C++ program. You can click the ActiveX control as you like, shading the clicked rectangle. 3.2.8 Document View Architecture The Document/View architecture uses serialization to save or open documents. When a document is saved or loaded, the MFC framework in cooperation with the application's document class creates a CArchive object and serializes the document to or from storage. The CDocument member functions required to perform serialization in a Document/View application are mapped onto the New, Open, Save, and Save As commands available from the File menu. These member functions take care of creating or opening a document, tracking the modification status of a document, and serializing it to storage. When documents are loaded, a CArchive object is created for reading, and the archive is deserialized into the document. When documents are saved, a CArchive object is created for writing, and the document is written to the archive. At other times, the CDocument class tracks the current modification status of the document's data. If the document has been updated, the user is prompted to save the document before closing it. The Document/View support for serialization greatly simplifies the work required to save and load documents in a Windows program. For a typical program that uses persistent objects, you must supply only a few lines of source code to receive basic support for serialization in a Document/View program. The Customers project has about a page of Document/View source code; most of it is for handling input and output required for the example.
Page 218
Visual Programming
The routines used by CArchive for reading and writing to storage are highly optimized and have excellent performance, even when you're serializing many small data objects. In most cases, it is difficult to match both the performance and ease of use that you get from using the built-in serialization support offered for Document/View applications. How Are Document/View Applications Serialized? A class derived from CDocument contains data stored in a Document/View application. This class also is responsible for controlling the serialization of all data contained by the document class. This includes tracking modifications to the document so that the program can display a warning before the user closes an unsaved document. There are five phases in a document's life cycle:
Creating a new document Modifying the document Storing, or serializing, the document Closing the document Loading, or deserializing, the document
Creating a New Document An MDI application creates a new CDocument class for every open document, whereas an SDI program reuses a single document. Both SDI and MDI applications call the OnNewDocument function to initialize a document object. The default version of OnNewDocument calls the DeleteContents function to reset any data contained by the document. ClassWizard can be used to add a DeleteContents function to your document class. Most applications can just add code to DeleteContents instead of overriding OnNewDocument. Storing a Document When the user saves a document by selecting File | Save, the CWinApp::OnFileSave function is called. This function is almost never overridden; it's a good idea to leave it alone because it calls the CDocument::OnOpenDocument function to serialize the document's data. The default version of OnOpenDocument creates a CArchive object and passes it to the document's Serialize member function. Usually, you serialize the data contained in the document in the same way that other member data was serialized earlier this chapter. After the document's data has been serialized, the dirty bit is cleared, marking the document as unmodified. The steps involved in storing a document are shown in the following figure.
Page 219
Visual Programming
The major functions called when you store a document. The default version of OnOpenDocument is sufficient for most applications. However, if your application stores data in a different way--for example, in several smaller files or in a database--you should override OnOpenDocument. When the user selects Save As from the File menu, a Common File dialog box collects filename information. After the user selects a filename, the program calls the same CDocument functions, and the serialization process works as described previously. Closing a Document when the user closes a document, the MFC Document/View framework calls the document object's OnCloseDocument member function, as shown in the following figure. The default version of this function checks the document to make sure that calling the IsModified function loses no unsaved changes. If the user did not modify the document object, DeleteContents is called to free the data stored by the document, and all views for the document are closed.
The major functions called when you close a document. If the user made changes to the document, the program displays a message box that asks the user whether the document's unsaved changes should be saved. If the user elects to save the document, the Serialize function is called. Calling DeleteContents and closing all views for the document then close the document.
Page 220
Visual Programming
Loading a Document When you're loading a document, the MFC framework calls the document object's OnOpenDocument function. The default version of this function calls the DeleteContents member function and then calls Serialize to load, or deserialize, the archive. The default version of OnOpenDocument, shown in the following figure, is sufficient for almost any application. Modifying the Document Class The document class used in the Customers project has one new data member, a CArray object that stores a collection of CUser pointers representing a customer list. The document class also has two member functions used to access the array of CUser pointers. Add declarations for m_setOfUsers and two member functions to the CCustomersDoc class, as shown in the following listing.
The major functions called when you open a document. TYPE: Listing. Adding a CArray member variable to the CCustomersDoc class. // Attributes public: int GetCount() const; CUser* GetUser( int nUser ) const; protected: CArray<CUser*, CUser*&> m_setOfUsers; You should make two other changes to the CustomersDoc.h header file. First, because the CArray template m_setOfUsers is declared in terms of CUser pointers, you must add an #include statement for the Users.h file. Second, you use a version of the SerializeElements helper function so you need a declaration of that global function. Add the source code provided in the following listing to the top of CustomersDoc.h.
TYPE: Listing. Changes to the CustomersDoc.h header file. #include "Users.h" void AFXAPI SerializeElements( CArchive& ar,
Page 221
Visual Programming
CUser** pUser, int nCount ); Because the CCustomerDoc class contains a CArray member variable, the template collection declarations must be included in the project. Add an #include statement to the bottom of the StdAfx.h file: #include "afxtempl.h" Creating a Dialog Box The dialog box used to enter data for the Customers example is similar to dialog boxes you created for previous examples. Create a dialog box that contains two edit controls, as shown in the following figure.
The dialog box used in the Customers sample project. Give the new dialog box a resource ID of IDD_USER_DLG. The two edit controls are used to add user names and email addresses to a document contained by the CCustomerDoc class. Use the values from the following table for the two edit controls. Table. Edit controls contained in the IDD_USER_DLG dialog box. Edit Control Resource ID Name IDC_EDIT_NAME Address IDC_EDIT_ADDR
Using ClassWizard, add a class named CUsersDlg to handle the new dialog box. Add two CString variables to the class using the values from the following table. Table. New CString member variables for the CUsersDlg class. Resource ID Name Category Variable Type CString CString
Adding a Menu Item Use the values from the following table to add a menu item and message-handling function to the CCustomersDoc class. Add the new menu item, labeled New User..., to the Edit menu in the IDR_CUSTOMTYPE menu resource. To reduce the amount of
Page 222
Visual Programming
source code required for this example, handle the menu item directly with the document class. However, the dialog box can also be handled by a view class or CMainFrame.
Table. New member functions for the CCustomersDoc class. Menu ID Caption Event Function Name ID_EDIT_USER Add User... COMMAND OnEditUser The following listing contains the complete source code for the OnEditUser function, which handles the message sent when the user selects the new menu item. If the user clicks OK, the contents of the dialog box are used to create a new CUser object, and a pointer to the new object is added to the m_setOfUsers collection. The SetModifiedFlag function is called to mark the document as changed. Add the source code provided in the listing to the CCustomersDoc::OnEditUser member function. TYPE: Listing. Adding a new CUser object to the document class. void CCustomersDoc::OnEditUser() { CUsersDlg dlg; if( dlg.DoModal() == IDOK ) { CUser* pUser = new CUser( dlg.m_szName, dlg.m_szAddr ); m_setOfUsers.Add( pUser ); UpdateAllViews( NULL ); SetModifiedFlag(); } } Add the source code provided in the following listing to the CustomersDoc.cpp source file. These functions provide access to the data contained by the document. The view class, CCustomerView, calls the two CCustomersDoc member functions provided in listing when updating the view window. TYPE: Listing. Document class member functions used for data access. int CCustomersDoc::GetCount() const { return m_setOfUsers.GetSize(); } CUser* CCustomersDoc::GetUser( int nUser ) const { CUser* pUser = 0; if( nUser < m_setOfUsers.GetSize() ) pUser = m_setOfUsers.GetAt( nUser ); return pUser; Page 223
Visual Programming
} Every document needs a Serialize member function. The CCustomersDoc class has only one data member so its Serialize function deals only with m_setOfUsers, as shown in the following listing. Add this source code to the CCustomersDoc::Serialize member function. TYPE: Listing. Serializing the contents of the document class. void CCustomersDoc::Serialize(CArchive& ar) { m_setOfUsers.Serialize( ar ); } As discussed earlier in this chapter, the CArray class uses the SerializeElements helper function when the collection is serialized. Add the SerializeElements function that was provided earlier in the CustomersDoc.cpp source file. Add two #include statements to the CustomersDoc.cpp file so that the CCustomersDoc class can have access to declarations of classes used by CCustomersDoc. Add the source code from the following listing near the top of the CustomersDoc.cpp file, just after the other #include statements. TYPE: Listing. Include statements used by the CCustomersDoc class. #include "Users.h" #include "UsersDlg.h" Modifying the View The view class, CCustomersView, displays the current contents of the document. When the document is updated, the view is repainted and displays the updated contents. You must update two functions in the CCustomersView class: OnDraw and OnUpdate. AppWizard creates a skeleton version of the CCustomersView::OnDraw function. Add the source code from the following listing to OnDraw so that the current document contents are displayed in the view. Because this isn't a scrolling view, a limited number of items from the document can be displayed. TYPE: Listing. Using OnDraw to display the current document's contents. void CCustomersView::OnDraw(CDC* pDC) { CCustomersDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // Calculate the space required for a single // line of text, including the inter-line area. TEXTMETRIC tm; pDC->GetTextMetrics( &tm ); int nLineHeight = tm.tmHeight + tm.tmExternalLeading; CPoint ptText( 0, 0 ); for( int nIndex = 0; nIndex < pDoc->GetCount(); nIndex++ ) { CString szOut; CUser* pUser = pDoc->GetUser( nIndex );
Page 224
Visual Programming
szOut.Format( "User = %s, email = %s", pUser->GetName(), pUser->GetAddr() ); pDC->TextOut( ptText.x, ptText.y, szOut ); ptText.y += nLineHeight; } } As with most documents, the CCustomersDoc class calls UpdateAllViews when it is updated. The MFC framework then calls the OnUpdate function for each view connected to the document. Use ClassWizard to add a message-handling function for CCustomersView::OnUpdate and add the source code from the following listing to it. The OnUpdate function invalidates the view; as a result, the view is redrawn with the updated contents. TYPE: Listing. Invalidating the view during OnUpdate. void CCustomersView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint) { InvalidateRect( NULL ); } Add an #include statement to the CustomersView.cpp file so that the view can use the CUser class. Add the include statement beneath the other include statements in CustomersView.cpp. #include "Users.h" Compile and run the Customers project. Add names to the document by selecting Add User from the Edit menu. The following figure shows an example of the Customers project running with a few email addresses.
The Customers example with some email addresses. Serialize the contents of the document by saving it to a file, and close the document. You can reload the document by opening the file
Page 225
Visual Programming
3.2.9 Serialization What Is Serialization? Serialization is the process of storing the state of an object for the purpose of loading it at another time. The property of an object to be stored and loaded is persistence, which is also defined as the capability of an object to remember its state between executions. Serialization is the way in which classes derived from CDocument store and retrieve data from an archive, which is usually a file. The following figure shows the interaction between a serialized object and an archive.
Serializing an object to and from an archive. When an object is serialized, information about the type of object is written to the storage along with information and data about the object. When an object is deserialized, the same process happens in reverse, and the object is loaded and created from the input stream. Why Use Serialization? The goal behind serialization is to make the storage of complex objects as simple and reliable as the storage of the basic data types available in C++. You can store a basic type, such as an int, in a file in the following way: int nFoo = 5; fileStream << nFoo; If a file contains an int value, it can be read from the stream in the following way: fileStream >> nFoo; A persistent object can be serialized and deserialized using a similar syntax, no matter how complicated the object's internal structure. The alternative is to create
Page 226
Visual Programming
routines that understand how every object is implemented and handle the process of storing and retrieving data from files. Using serialization to store objects is much more flexible than writing specialized functions that store data in a fixed format. Objects that are persistent are capable of storing themselves, instead of relying on an external function to read and write them to disk. This makes a persistent object much easier to reuse because the object is more selfcontained. Persistent objects also help you easily write programs that are saved to storage. An object that is serialized might be made up of many smaller objects that are also serialized. Because individual objects are often stored in a collection, serializing the collection also serializes all objects contained in the collection. A Document/View Serialization Example Using AppWizard, create an MDI project named Customers. This project uses serialization to store a very simple list of customer names and email addresses, using a persistent class named CUser. This project will serve as the basis for examples and source code used in the remainder of this chapter. Serializing the Customers Project You can use the insertion operator, or <<, to output a value to the screen. This operator is actually the C++ left-shift operator, but it is overloaded so that whenever an output object and variable are separated by a <<, as in the following code line, the variable is written to the output object: file_object << data In a similar way, whenever input is performed and the objects are separated by a >>, as in the following code line, a new value for the variable is retrieved from the input object: file_object >> data In C++, unlike some other languages, input and output are controlled by the interaction between file and variable objects. The exact process used for input and output is controlled by the way in which the classes implement the >> and << operators. For the topics in this chapter, you create a persistent class named CUser, along with the helper functions required to serialize a collection of CUser objects. Each CUser object contains a customer name and email address.
Page 227
Visual Programming
The MFC Classes Used for Serialization You use two MFC classes to serialize objects:
CArchive is almost always a file and is the object that other persistent objects are serialized to or from. CObject defines all the interfaces used to serialize objects to or from a CArchive object.
Objects are serialized in one of two ways. As a rule of thumb, if an object is derived from CObject, that object's Serialize member function is called in the following way: myObject.Serialize( ar ); If the object isn't derived from CObject--such as a CRect object--you should use the inser-tion operator in the following way: ar << rcWnd; This insertion operator is overloaded in the same way it is for cout, cin, and cerr, which were used earlier for console mode input and output. Using the CObject Class You must use the CObject class for all classes that use the MFC class library's built-in support for serialization. The CObject class contains virtual functions that are used during serialization. In addition, the CArchive class is declared as a "friend" class for CObject, providing it access to private and protected member variables. The most commonly used virtual function in CObject is Serialize, which is called to serialize or deserialize the object from a CArchive object. This function is declared as virtual so that any persistent object can be called through a pointer to CObject in the following way: CObject* pObj = GetNextObject(); pObj->Serialize( ar ); As discussed later in the section "Using the Serialization Macros," when you're deriving a persistent class from CObject, you must use two macros to help implement the serialization functions. The CArchive Class The CArchive class is used to model a generic storage object. In most cases, a CArchive object is attached to a disk file. In some cases, however, the object might be
Page 228
Visual Programming
connected to an object that only seems to be a file, like a memory location or another type of storage. When a CArchive object is created, it is defined as used for either input or output but never both. You can use the IsStoring and IsLoading functions to determine whether a CArchive object is used for input or output, as shown in the following listing. TYPE: Listing. Using the CArchive::IsStoring function to determine the serialization direction. CMyObject:Serialize( CArchive& ar ) { if( ar.IsStoring() ) // Write object state to ar else // Read object state from ar } Using the Insertion and Extraction Operators The MFC class library overloads the insertion and extraction operators for many commonly used classes and basic types. You often use the insertion operator, <<, to serialize--or store--data to the CArchive object. You use the extraction operator, >>, to deserialize--or load--data from a CArchive object. These operators are defined for all basic C++ types, as well as a few commonly used classes not derived from CObject, such as the CString, CRect, and CTime classes. The insertion and extraction operators return a reference to a CArchive object, enabling them to be chained together in the following way: archive << m_nFoo << m_rcClient << m_szName; When used with classes that are derived from CObject, the insertion and extraction operators allocate the memory storage required to contain an object and then call the object's Serialize member function. If you don't need to allocate storage, you should call the Serialize member function directly. As a rule of thumb, if you know the type of the object when it is deserialized, call the Serialize function directly. In addition, you must always call Serialize exclusively. If you use Serialize to load or store an object, you must not use the insertion and extraction operators at any other time with that object. Using the Serialization Macros There are two macros that you must use when creating a persistent class based on CObject. Use the DECLARE_SERIAL macro in the class declaration file and the IMPLEMENT_SERIAL macro in the class implementation file.
Page 229
Visual Programming
Declaring a Persistent Class The DECLARE_SERIAL macro takes a single parameter: the name of the class to be serialized. An example of a class that can be serialized is provided in the following listing. Save this source code in the Customers project directory in a file named Users.h. TYPE: Listing. The CUser class declaration. #ifndef CUSER #define CUSER class CUser : public CObject { DECLARE_SERIAL(CUser); public: // Constructors CUser(); CUser( const CString& szName, const CString& szAddr ); // Attributes void Set( const CString& szName, const CString& szAddr ); CString GetName() const; CString GetAddr() const; // Operations virtual void Serialize( CArchive& ar ); // Implementation private: // The user's name CString m_szName; // The user's e-mail addresss CString m_szAddr; }; #endif CUSER Defining a Persistent Class The IMPLEMENT_SERIAL macro takes three parameters and is usually placed before any member functions are defined for a persistent class. The parameters for IMPLEMENT_SERIAL are the following:
The class to be serialized The immediate base class of the class being serialized The schema, or version number
The schema number is a version number for the class layout used when you're serializing and deserializing objects. If the schema number of the data being loaded doesn't match the schema number of the object reading the file, the program throws an exception. The schema number should be incremented when changes are made that affect serialization, such as adding a class member or changing the serialization order.
Page 230
Visual Programming
The member functions for the CUser class, including the IMPLEMENT_SERIAL macro, are provided in the following listing. Save this source code in the Customers project directory as Users.cpp. TYPE: Listing. The CUser member functions. #include "stdafx.h" #include "Users.h" IMPLEMENT_SERIAL( CUser, CObject, 1 ); CUser::CUser() { } CUser::CUser( const CString& szName, const CString& szAddr ) { Set( szName, szAddr ); } void CUser::Set( const CString& szName, const CString& szAddr ) { m_szName = szName; m_szAddr = szAddr; } CString CUser::GetName() const { return m_szName; } CString CUser::GetAddr() const { return m_szAddr; } Overriding the Serialize Function Every persistent class must implement a Serialize member function, which is called in order to serialize or deserialize an object. The single parameter for Serialize is the CArchive object used to load or store the object. The version of Serialize used by the CUser class is shown in the listing below; add this function to the Users.cpp source file. TYPE: Listing. The CUser::Serialize member function. void CUser::Serialize( CArchive& ar ) { if( ar.IsLoading() ) { ar >> m_szName >> m_szAddr; } else { ar << m_szName << m_szAddr; } } Page 231
Visual Programming
Creating a Serialized Collection You can serialize most MFC collection classes, enabling large amounts of information to be stored and retrieved easily. For example, you can serialize a CArray collection by calling its Serialize member function. As with the other MFC templatebased collection classes, you cannot use the insertion and extraction operators with CArray. By default, the template-based collection classes perform a bitwise write when serializing a collection and a bitwise read when deserializing an archive. This means that the data stored in the collection is literally written, bit by bit, to the archive. Bitwise serialization is a problem when you use collections to store pointers to objects. For example, the Customers project uses the CArray class to store a collection of CUser objects. The declaration of the CArray member is as follows: CArray<CUser*, CUser*&> m_setOfUsers; Because the m_setOfUsers collection stores CUser pointers, storing the collection using a bitwise write will only store the current addresses of the contained objects. This information becomes useless when the archive is deserialized. Most of the time, you must implement a helper function to assist in serializing a template-based collection. Helper functions don't belong to a class; they are global functions that are overloaded based on the function signature. The helper function used when serializing a template is SerializeElements. The below figure shows how you call the SerializeElements function to help serialize items stored in a collection.
The SerializeElements helper function. A version of SerializeElements used with collections of CUser objects is provided in the following listing.
Page 232
Visual Programming
TYPE: Listing. The SerializeElements function. void AFXAPI SerializeElements( CArchive& ar, CUser** pUser, int nCount ) { for( int i = 0; i < nCount; i++, pUser++ ) { if( ar.IsStoring() ) { (*pUser)->Serialize(ar); } else { CUser* pNewUser = new CUser; pNewUser->Serialize(ar); *pUser = pNewUser; } } } The SerializeObjects function has three parameters:
A pointer to a CArchive object, as with Serialize. The address of an object stored in the collection. In this example, pointers to CUser are stored in a CArray, so the parameter is a pointer to a CUser pointer. The number of elements to be serialized.
In this example, when you're serializing objects to the archive, each CUser object is individually written to the archive. When you're deserializing objects, a new CUser object is created, and that object is deserialized from the archive. The collection stores a pointer to the new object. 3.2.10 Mutiple Document Interface (MDI) MDI stands for Multiple Document Interface. If an application is a MDI type application it means it can have more than one document open at a time Most of your better word processors are MDI applications. Lower end stuff, like Windows WordPad or Notepad, aren't smart enough to have more than one document open at a time. One thing that traditional web applications don't do well is MDI (Multiple Doucment Interface). Yes, there are frameworks that make it easier, but it simply is not reliable across multiple platforms and browsers. MDI just happens to be *another* one of those things that Flex allows you to do very easily and do well within a web based application. I'm not just talking about using the PopUpManager class to maintain multiple non-modal dialogs within the context of the application. I'm talking full-blown "windowed" Page 233
Visual Programming
applications that run within the context of the Flex swf. Take a look... This is a prototype MDI application interface that allows for multiple window creation, minimizing/maximizing windows, window drag/drop, window allignment (tile and cascade). Don't forget this is just a prototype :-) ... I have every intention of making this better by adding animation/transitions to it, and by making it a bit more robust. On this, you can even double click the headers to toggle between minimized/restore state. Sorry, this is closed source, but here's basically how I did it... There is a canvas that is used to contain the "windows". I created a base class that creates the window framework. The base class can be extended to contain pretty much any type of flex component that is derived from the mx.core.UIComponent class. I created a MDIManager class that contains functions for adding/removing windows from the canvas, and it manages alignment/reorganization of the windows. When you generate your source code with AppWizard, you get an application including a toolbar, a status bar, ToolTips, menus, and even an About dialog box. But the application really doesnt do anything useful. In order to create an application that does more than look pretty on your desktop, you need to modify the code that AppWizard generates. Probably the most important set of modifications are those related to the document the information the user can save from your application and restore later and to the view the way that information is presented to the user. MFCs document/view architecture separates an applications data from the way the user actually views and manipulates that data. Simply, the document object is responsible for storing, loading, and saving the data, whereas the view object enables the user to see the data onscreen and to edit that data in a way that is appropriate to the application. SDI and MDI applications created with AppWizard are document/view applications. That means that AppWizard generates a class for you derived from CDocument, and delegates certain tasks to this new document class. It also creates a view class derived from CView and delegates other tasks to your new view class. Choose File, New, and select the Projects tab. Fill in the project name as Hena and fill in an appropriate directory for the project files. Make sure that MFC AppWizard (exe) is selected. Click OK. Move through the AppWizard dialog boxes, changing the settings to match those in the following table, and then click Next to continue: Select Multiple documents and Deselect all check boxes except Printing and Print Preview. After you click Finish on the last step, the New project information box summarizes your work. Click OK to create the project. Expand the Hena classes in ClassView, and you see that six classes have been created: CAboutDlg CHenaApp CHenaDoc Page 234
Visual Programming
CHenaView CChildFrame CMainframe
CHenaDoc represents a document; it holds the applications document data. You add storage for the document by adding data members to the CHenaDoc class. To see how this works, look at the following listing, which shows the header file AppWizard creates for the CApp1Doc class. HenaDOC.H - The Header File for the CHenaDoc Class // HenaDoc.h : interface of the CHenaDoc class // ///////////////////////////////////////////////////////////////////////////// #if !defined (AFX_HENADOC _H__ EDF8D560_FEA6_11D2_90B1_0020182E366E__INCLUDED_) #define AFX_HENADOC_H__EDF8D560_FEA6_11D2_90B1_0020182E366E__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CHenaDoc : public CDocument { protected: // create from serialization only CHenaDoc(); DECLARE_DYNCREATE(CHenaDoc) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CHenaDoc) public: virtual BOOL OnNewDocument();
Page 235
Visual Programming
virtual void Serialize(CArchive& ar); //}}AFX_VIRTUAL // Implementation public: virtual ~CHenaDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CHenaDoc) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG DECLARE_MESSAGE_MAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AF X_HENAD 11D2_90B1_0020182E366E__INCLUDED_) OC_H__EDF8D560 _FEA6_
Near the top of the listing, you can see the class declarations Attributes section, which is followed by the public keyword. This is where you declare the data members that will hold your applications data. In the program that you create a little later in this chapter, the application must store an array of CPoint objects as the applications data. That array is declared as a member of the document class like this: // Attributes public: CPoint points[100];
Page 236
Visual Programming
CPoint is an MFC class that encapsulates the information relevant to a point on the screen, most importantly the x and y coordinates of the point. Notice also in the classs header file that the CApp1Doc class includes two virtual member functions called: OnNewDocument() Serialize(). MFC calls the OnNewDocument() function whenever the user selects the File, New command. You can use this function to perform whatever initialization must be performed on your documents data. In an SDI application, which has only a single document open at any time, the open document is closed and a new blank document is loaded into the same object; in an MDI application, which can have multiple documents open, a blank document is opened in addition to the documents that are already open. The Serialize() member function is where the document class loads and saves its data. View Class As mentioned previously, the view class displays the data stored in the document object and enables the user to modify this data. The view object keeps a pointer to the document object, which it uses to access the documents member variables in order to display or modify them. The following listing is the header file for CHenView, as generated by AppWizard. Listing HENAVIEW.HThe Header File for the CHenaView Class // HenaView.h : interface of the CHenaView class ///////////////////////////////////////////////////////////////////////////// #if !defined( AFX_ HENAVIEW _H__EDF8D562 _FEA6_11D2_90B1_0020182E366E__INCLUDED_) #define AFX_HENAVIEW_H__EDF8D562_FEA6_11D2_90B1_0020182E366E__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CHenaView : public CView { protected: // create from serialization only CHenaView(); DECLARE_DYNCREATE(CHenaView)
Page 237
Visual Programming
// Attributes public: CHenaDoc* GetDocument(); // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CHenaView) public: virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: //}}AFX_VIRTUAL // Implementation public: virtual ~CHenaView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CHenaView) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG DECLARE_MESSAGE_MAP() };
Page 238
Visual Programming
#ifndef _DEBUG // debug version in HenaView.cpp inline CHenaDoc* CHenaView::GetDocument() { return (CHenaDoc*)m_pDocument; } #endif ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX _HENAVIEW_ H__EDF8D562_FE A6_11D2_90B1_0020182 E366E__INCLUDED_) Near the top of the listing, you can see the classs public attributes, where it declares the GetDocument() function as returning a pointer to a CHenaDoc object. Anywhere in the view class that you need to access the documents data, you can call GetDocument() to obtain a pointer to the document. For example, to add a CPoint object to the aforementioned array of CPoint objects stored as the documents data, you might use the following line: GetDocument()->m_points[x] = point; You also can do this a little differently, of course, by storing the pointer returned by GetDocument() in a local pointer variable and then using that pointer variable to access the documents data, like this: pDoc = GetDocument(); pDoc->m_points[x] = point; The second version is more convenient when you need to use the document pointer in several places in the function, or if using the less clear GetDocument() >variable version makes the code hard to understand. Notice that the view class, like the document class, overrides a number of virtual functions from its base class. OnDraw() is the virtaul function, which is the most important of these virtual functions, is where you paint your windows display. As for the other functions, MFC calls PreCreateWindow() before the window element is created and attached to the MFC window class, giving you a chance to modify the windows attributes such as size and position. OnPreparePrinting() is used to modify the Print dialog box before it displays for the user, the OnBeginPrinting() function gives you a chance to create GDI objects like pens and brushes that you need to handle the print job, and OnEndPrinting() is where you can destroy any objects you might have created in OnBeginPrinting().
Page 239
Visual Programming
Creating the Application - HENA Now that youve had an introduction to documents and views, a little hands-on experience should help you better understand how these classes work. In the steps that follow, you build the Squares application, which demonstrates the manipulation of documents and views. When you first run this application, it will draw an empty window. Wherever you click in the window, a small square will be drawn. You can resize the window, or minimize and restore it, and the squares will be redrawn at all the coordinates where you clicked, because squares keeps an array of coordinate points in the document and uses that array in the view. Use AppWizard to create the basic files for the Squares program, selecting the Single Document Interface and deselect the Print and Print Preview Options.
Click the OK button to create the project files. Name the project as Hena and set the project path to the directory into which you want to store the projects files. Leave the other options set to their defaults.
Now that you have a starter application, its time to add code to the document and view classes in order to create an application that actually does something. This application will draw many Squares in the view and save the coordinates of the Squares in the document. Follow these steps to add the code that modifies the document class to handle the applications data, which is an array of CPoint objects that determine where rectangles should be drawn in the view window: 1. Click the ClassView tab to display the ClassView in the project workspace window at the left of the screen. Expand the Hena classes by clicking the + sign before them. 2. Right-click the CHenaDoc class and choose Add Member Variable from the shortcut menu that appears. 3. Fill in the Add Member Variable dialog box. For Variable Type, enter CPoint. For Variable Name, enter m_points[100]. Make sure the Public radio button is selected. Click OK.
4. Again, right-click the CHenaDoc class and choose Add Member Variable. For Variable Type, enter UINT. For Variable Name, enter m_pointIndex. Make sure the Public radio button is selected. Click OK.
Page 240
Visual Programming
5. Click the + next to CHenaDoc in ClassView to see the member variables and functions. The two member variables you added are now listed. The m_points[] array holds the locations of rectangles displayed in the view window. The m_pointIndex data member holds the index of the next empty element of the array. UINT m_pointIndex; CPoint m_points[100]; Now you need to get these variables initialized to appropriate values and then use them to draw the view. MFC applications that use the document/view paradigm initialize document data in a function called OnNewDocument(), which is called automatically when the application first runs and whenever the user chooses File, New. The list of member variables and functions of CHenaDoc should still be displayed in ClassView. Double-click OnNewDocument() in that list to edit the code. Modify the OnNewDocument() as per the following listing. Listing HenaDoc.CPP CHenaDoc::OnNewDocument() BOOL CHenaDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; m_pointIndex = 0; // TODO: add reinitialization code here // (SDI documents will reuse this document) return TRUE; } There is no need to initialize the array of points because the index into the array will be used to ensure no code tries to use an uninitialized element of the array. At this point your modifications to the document class are complete. View Class : It will use the document data to draw Squares onscreen. OnDraw() function of your view class does the drawing. Expand the CHenaView class in ClassView and double click OnDraw(). Remove the comments left by AppWizard and add code to draw a rectangle at each point in the array as per the listing below. Listing HenaView.CPPCHenaView::OnDraw()
Page 241
Visual Programming
void CHenaView::OnDraw(CDC* pDC) { CHenaDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); UINT pointIndex = pDoc->m_pointIndex; for (UINT i=0; i<pointIndex; ++i) { UINT x = pDoc->m_points[i].x; UINT y = pDoc->m_points[i].y; pDC->Rectangle(x, y, x+20, y+20); } } Your modifications to the starter application generated by AppWizard are almost complete. You have added member variables to the document, initialized those variables in the documents OnNewDocument() function, and used those variables in the views OnDraw() function. All that remains is to enable the user to add points to the array. You catch the mouse message with ClassWizard and then add code to the message handler. Follow these steps: 1. 2. Choose View, ClassWizard. Make sure that CHenaView is selected in the Class Name and Object IDs boxes. Then, double-click WM_LBUTTONDOWN in the Messages box to add the OnLButtonDown() message-response function to the class. Whenever the application receives a WM_LBUTTONDOWN message, it will call OnLButtonDown(). Click the Edit Code button to jump to the OnLButtonDown() function in your code. Then, add the code shown in the listing below to the function.
3.
Listing HenaView.cpp CHenaView::OnLButtonDown() void CHenaView::OnLButtonDown(UINT nFlags, CPoint point) { if (pDoc->m_pointIndex == 100) return; //store the click location pDoc->m_points[pDoc->m_pointIndex] = point; pDoc->m_pointIndex++; pDoc->SetModifiedFlag(); Page 242
Visual Programming
Invalidate(); CView::OnLButtonDown(nFlags, point); } The new OnLButtonDown() adds a point to the documents point array each time the user clicks the left mouse button over the view window. It increments m_pointIndex so that the next click goes into the point on the array after this one. The call to SetModifiedFlag() marks this document as modified, or dirty. MFC automatically prompts the user to save any dirty files on exit. Any code you write that changes any document variables should call SetModifiedFlag(). Finally, the call to Invalidate() causes MFC to call the OnDraw() function, where the windows display is redrawn with the new data. Invalidate() takes a single parameter that determines if the background is erased before calling OnDraw(). On rare occasions you may choose to call Invalidate(FALSE) so that OnDraw() draws over whatever was already onscreen. Finally, a call to the base class OnLButtonDown() takes care of the rest of the work involved in handling a mouse click. Youve now finished the complete application. Click the toolbars Build button, or choose Build, Build from the menu bar, to compile and link the application. After you have the Squares application compiled and linked, run it by choosing Build, Execute. When you do, you see the applications main window. Place your mouse pointer over the windows client area and click. A rectangle appears. Go ahead and keep clicking. You can place up to 100 Squares in the window Other View Classes The view classes generated by AppWizard in this chapters sample applications have been derived from MFCs CView class. There are cases, however, when it is to your advantage to derive your view class from one of the other MFC view classes derived from CView. These additional classes provide your view window with special capabilities such as scrolling and text editing. Class CView CCtrlView Description The base view class from which the specialized view classes are derived A base class from which view classes that implement 32bit Windows common controls (such as the ListView, TreeView, and RichEdit controls) are derived Same as CRecordView, except used with the OLE DB database classes A view class that provides basic text-editing features Page 243
CDaoRecordView CEditView
Visual Programming
CFormView CHtmlView CListView COleDBRecordView CRecordView CRichEditView CScrollView CTreeView A view class that implements a form-like window using a dialog box resource A view class that can display HTML, with all the capabilities of Microsoft Internet Explorer A view class that displays a ListView control in its window Same as CRecordView, except used with the DAO database classes A view class that can display database records along with controls for navigating the database A view class that provides more sophisticated textediting capabilities by using the RichEdit control A view class that provides scrolling capabilities A view class that displays a TreeView control in its window.
To use one of these classes, substitute the desired class for the CView class in the applications project. When using AppWizard to generate your project, you can specify the view class you want in the wizards Step 6 of 6 dialog box. When you have the desired class installed as the projects view class, you can use the specific classs member functions to control the view window. A CEditView object, on the other hand, gives you all the features of a Windows edit control in your view window. Using this class, you can handle various editing and printing tasks, including find-and-replace. You can retrieve or set the current printer font by calling the GetPrinterFont() or SetPrinterFont() member function or get the currently selected text by calling GetSelectedText(). Moreover, the FindText() member function locates a given text string, and OnReplaceAll() replaces all occurrences of a given text string with another string. The CRichEditView class adds many features to an edit view, including paragraph formatting (such as centered, right-aligned, and bulleted text), character attributes (including underlined, bold, and italic), and the capability to set margins, fonts, and paper size. The CRichEditView class features a rich set of methods you can use to control your applications view object. Document templates, views and frame windows Because youve been working with AppWizard-generated applications in this chapter, youve taken for granted a lot of what goes on in the background of an MFC document/view program. That is, much of the code that enables the frame window, the
Page 244
Visual Programming
document, and the view window to work together is automatically generated by AppWizard and manipulated by MFC. For example, if you look at the InitInstance() method of the Squares applications CHenaApp class, you see the lines shown in the listing below. Listing Hena.CPP - Initializing an Applications Document CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CHenaDoc), RUNTIME_CLASS(CMainFrame), RUNTIME_CLASS(CHenaView)); AddDocTemplate(pDocTemplate); In the above listing, you discover one secret that makes the document/view system work. In that code, the program creates a document-template object. These document templates have nothing to do with C++ templates. A document template is an older concept, named before C++ templates were implemented by Microsoft, that pulls together the following objects: A resource ID identifying a menu resource - IDR_MAINFRAME in this case A document class CHenaDoc in this case A frame window class - always CMainFrame A view class CHenaView in this case Notice that you are not passing an object or a pointer to an object. You are passing the name of the class to a macro called RUNTIME_CLASS. It enables the framework to create instances of a class at runtime, which the application object must be able to do in a program that uses the document/view architecture. In order for this macro to work, the classes that will be created dynamically must be declared and implemented as such. To do this, the class must have the DECLARE_DYNCREATE macro in its declaration (in the header file) and the IMPLEMENT_DYNCREATE macro in its implementation. AppWizard takes care of this for you. For example, if you look at the header file for the Square applications CMainFrame class, you see the following line near the top of the classs declaration: DECLARE_DYNCREATE(CMainFrame) As you can see, the DECLARE_DYNCREATE macro requires the classs name as its single argument. Now, if you look near the top of CMainFrames implementation file (MAINFRM.CPP), you see this line: IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) // main SDI frame window
Page 245
Visual Programming
The IMPLEMENT_DYNCREATE macro requires as arguments the name of the class and the name of the base class. If you explore the applications source code further, you find that the document and view classes also contain the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros. If you havent heard of frame windows before, you should know that they contain all the windows involved in the applicationsthis means control bars as well as views. They also route messages and commands to views and documents. The last line of the above listing calls AddDocTemplate() to pass the object on to the application object, CHenaApp, which keeps a list of documents. AddDocTemplate() adds this document to this list and uses the document template to create the document object, the frame, and the view window. Because this is a Single Document Interface, a single document template (CSingleDocTemplate) is created. Multiple Document Interface applications use one CMultiDocTemplate object for each kind of document they support. For example, a spreadsheet program might have two kinds of documents: tables and graphs. Each would have its own view and its own set of menus. Two instances of CMultiDocTemplate would be created in InitInstance(), each pulling together the menu, document, and view that belong together. If youve ever seen the menus in a program change as you switched from one view or document to another, you know how you can achieve the same effect: Simply associate them with different menu resource IDs as you build the document templates 3.2.11 Splitter windows To generate a splliter window first select "Splitter window templates" from the tools menu. The following dialog will be displayed -
The image buttons indicate the type of template that will be generated when the button is clicked. Hovering the mouse cursor over a button also pops up a tooltip with a brief description. The default action is to test a selected template. Other options are to copy the template code to the clipboard for pasting in other source code, and to edit the code using FormPad. To compile the splitter window as a child window ensure that the box 'as
Page 246
Visual Programming
Child-Window' is checked. If you want to save the source code to a file you can do so from FormPad. 3.2.12 Co-ordination between controls Single Document interface (SDI) application-Program that deals with one document at a time All programs to date have been SDI programs Multiple Document Interface (MDI) application-Program organized to handle multiple documents simultaneously Multiple open documents can be of same or different types Example of an MDI application: Microsoft Word View--A rendering of a document; a physical representation of the data Provides mechanism for displaying data stored in a document Defines how data is to be displayed in a window Defines how the user can interact with it Frame Window--window in which a view of a document is displayed A document can have multiple views associated with it (different ways of looking at the same data) But a view has only one document associated with it
MFC Template class object-Handles coordination between documents, views, and frame windows In general: Application object creates a template: which coordinates the display of a document's data
Page 247
Visual Programming
in a view inside a frame window
3.2.13 Sub classing Subclass Text & Combo Boxes to Display PopUp Menus Right clicking a textbox pops up the standard context menu with the Cut, Copy and Paste options. Often you need to display your own menu. You can by calling the PopupMenu command in the textbox's MouseUp event. However, the standard context menu still appears. You can remove it with sub classing. Subclassing lets you process the messages Window's sends to your control allowing you to customize their behavior by reacting to or ignoring events that Visual Basic does not normally give you control over. Be forewarned, however, that subclassing removes much of the stability and robustness built into Visual Basic.
Page 248
3.5 Summary
AppWizard is used to create initial Windows applications. Class Wizard is a utility for managing C++ classes developed using Application Wizard. The four major parts of a Visual C++ AppWizard program are the application object, the main window object, the document object, and the view object. Menus are the popdown windows that display a set of options, or menu items, that the user can select from. List Boxes are controls that present the user with a list of items displayed in a box. The user can click an item to highlight it and double click that item to select it.
Page 249
Visual Programming
Combo boxes are a combination of a text box, drop-down list box, and a button the user can press to open the list box. ActiveX controls are specially designed to be embedded in programs and especially in Web pages that browsers can display. The mouse is a pointing device with one or more buttons. The Microsoft Windows timer is an input device that periodically notifies an application when a specified interval of time has elapsed. A push button is a rectangle enclosing text specified in the window text parameter of the CreateWindow call. The text is centered within the rectangle. Dialog boxes are used for obtaining additional input from the user.
3.8 Assignments
1. Write a C++ program with dialogs ? 2. Explain how to import VBX controls ?
Page 250
Visual Programming
6. Schildt, C++ : The Complete Reference, thord Edition, TMH, 2000
3.11 Keywords.
MFC - Microsoft Foundation Class Library SDI Single Document Interface MDI Multiple Document Interface CBN Combobox Notification Persistence The property of an object to be stored and loaded
Page 251