Windows 95 offers the programmer a new set of dialog controls that are available in Windows NT as of version 3.51. Throughout this chapter, these controls are referred to as the Win32 Common Controls. The sample program was created under Windows 95, but the details of an implementation under Windows NT are identical.
The Win32 Common Controls are supported with COMCTL32.DLL. When you use the Common Controls with Visual C++ 4, you need to include the AFXCMN.H header file. With Visual C++-created projects, Visual C++ should automatically include AFXCMN.H in the STDAFX.H file.
NOTE
Don't confuse COMCTL32.DLL, which supports Common Controls, with COMDLG32.DLL, which supports the common dialog boxes. The Common Controls are separate from the common dialog boxes.
Although you can write applications that are non-MFC-based that use the Common Controls, using MFC makes interacting with the Common Controls easier and cleaner. Microsoft has provided a sample program in the January 1996 release of the Microsoft Developer Library CD. Search for the keyword LISTVIEW and select ListView: Implements the List View Common Control in Windows 95. The program displays an application that shows the ListView Common Control without an MFC interface. You can compare the ListView example with the sample program in this chapter to see why you might want to use MFC to manage your ListView and other Common Control objects.
There are a number of Win32 Common Controls, which are documented in the next part of this chapter. In the final part of this chapter, you will develop a simple MFC application that demonstrates how to implement the ListView and TreeView controls. The other Win32 Common Controls are easy to use and should present no difficulties for the typical Visual C++ programmer.
Another source of information about the Win32 Common Controls is Windows 95 Programming Unleashed (Sams Publishing, 1995). This book covers all the controls and shows a non-MFC implementation of the RichText control.
CAUTION
Be careful, because the Win32 Common Controls will never be available for native Windows 3.x 16-bit programs. If backward compatibility is important to you, you might want to limit your usage of the Win32 Common Controls.
When you're creating new applications, you probably won't want backward compatibility with Windows 3.x, because there is a wide backward-compatibility gap between both Windows 3.x (16-bit) and the 32-bit versions of Windowsand also between Visual C++ 1.5x and Visual C++ 4.0.
Ten Win32 Common Controls are available to the programmer. One of these controlsthe Header controlis normally used by the ListView control and is seldom used by programmers directly.
Of the 10 controls, two (TreeView and ListView) could be very useful to database applications developers. These two controls let you display information in a logical manner. Of course, the other controls can also prove to be most valuable to database programmers.
In a moment, you'll take a quick look at the new Win32 Common Controls. The next part of this chapter offers a sample application that uses both the TreeView control and the ListView control to display data to the user.
This section also documents property sheets. The property sheet extensions to the Win32 interface are available to developers of 16-bit applications. Other (nonWin32 Common Control) MFC classes that provide simple toolbars and status bars also are available to developers of 16-bit applications.
All the examples in this chapter are implemented in a single source project called WIN32CC. The complete source for this project is found on the sample source CD that is supplied with this book.
The Status Bar control lets an application provide visual and verbal feedback to the user. When AppWizard creates an application that includes status bar support, the CStatusBar class is used to manage the status bar. However, CStatusBar objects can be used only with frame windows, not with dialog windows.
In the sample program, a status bar has been added to a dialog box. This status bar could show virtually anything, but it's useful for extended help, debugging, and other messages that need to be communicated to the user unobtrusively. Both CStatusBar and CStatusBarCtrl controls include the capability to assign the status window's position to either the top or bottom of the application's window.
Most applications that are created by using AppWizard and that have a CStatusBar-implemented status bar will have a default set of attributes. These attributes include the following:
AppWizard also includes strings for the following indicators:
The MFC doesn't supply a default handler for these three indicators (REC, EXT, and OVR). If your application is going to use these three handlers, it must code its own handlers for each of them.
Figure 11.1 shows the main status bar in the sample application. This status bar has had all six of its panes activated, but no handler has been written for the three unsupported panes.
Figure 11.1. The standard CStatusBar in an MFC application.
With the sample application, I've added a simple status bar to a dialog box. I therefore needed to leave space for the status bar in the dialog box template (I could have resized the dialog box to make space if I wanted to). The status bar in the dialog box was implemented by using CStatusBarCtrl and has only one pane: an output area for simple status messages. Figure 11.2 shows the implementation of CStatusBarCtrl.
Figure 11.2. A dialog box with a CStatusBarCtrl status bar.
The code to implement the CStatusBarCtrl object is rather simple, as shown in Listing 11.1. In the header file for the dialog box (or in any other window), you must define a member variable of type CStatusBarCtrl. The line that shows this definition appears in bold.
Listing 11.1. Creating a CStatusBarCtrl object.
// statusba.h : header file // ///////////////////////////////////////////////////////////////////////////// // StatusBar dialog class StatusBar : public CDialog { // Construction public: StatusBar(CWnd* pParent = NULL); // Standard constructor CStatusBarCtrl m_StatusBar; // Dialog data //{{AFX_DATA(StatusBar) enum { IDD = IDD_STATUSBAR }; // NOTE: ClassWizard will add data members here //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(StatusBar) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(StatusBar) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Listing 11.2 is the actual code that displays the status bar and places some text in the status bar's output area. Code that has been added to the AppWizard-generated application appears in bold. Most applications will place text in the status bar using a message handler.
Listing 11.2. The CStatusBarCtrl initialization code.
// statusba.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "statusba.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // StatusBar dialog StatusBar::StatusBar(CWnd* pParent /*=NULL*/) : CDialog(StatusBar::IDD, pParent) { //{{AFX_DATA_INIT(StatusBar) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void StatusBar::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(StatusBar) // NOTE: ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(StatusBar, CDialog) //{{AFX_MSG_MAP(StatusBar) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // StatusBar message handlers BOOL StatusBar::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here RECT rect; // Never really used! Default sizes used. rect.top = 0; rect.left = 0; rect.right = 100; rect.bottom = 20; m_StatusBar.Create(WS_CHILD | WS_VISIBLE | CCS_BOTTOM, rect, this, 0); m_StatusBar.SetWindowText("Hello, CStatusBarCtrl here!"); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
The CStatusBarCtrl.Create() function requires four parameters. The first parameter controls the style of the status bar. You can specify the style SBARS_SIZEGRIP to add a sizing grip to the right end of the status bar.
The rect parameter specifies the size of the Status Bar control, and the this keyword tells the status bar what the parent window is. The final parameter, 0, is the control identifier (which isn't used in the sample program).
NOTE
Microsoft documentation states that the SBARS_SIZEGRIP sizing grip isn't functional when the status bar is aligned with the top of the window. Experiments I've performed have shown that this isn't true: Regardless of the placement of the status bar, the sizing grip is always active. However, it's difficult to use the sizing grip when the status bar isn't attached to the bottom of the parent window.
The RichText control is based on the Microsoft rich text specification. Rich text could easily be used to store formatted text in a database. (All rich text documents use only the standard text character sets.)
Usually, your programs will use the CRichEditCtrl as an application's main window, probably using Visual C++ 4's AppWizard to create their applications. There are situations in which you might very well want to create an application that has a dialog with a rich text edit control that is located in a dialog box.
In the rich edit control section of the sample program, I've created the most basic implementation of a CRichEditCtrl: I create the control and fill it from a file that the user selects.
The RichText control offers a vast array of functionalities:
The example of a RichText control shown in Figure 11.3 creates a RichText control and serializes (from an existing .RTF file) some text into the control. You create this control by first providing a dummy frame control (so that you know the location and size when you create the RichText control) and then making a few modifications to the source files as created by ClassWizard.
Figure 11.3 shows the dialog box with its frame control to provide a place (both location and size) for the RichText control to be located. The attributes of this frame control aren't critical, because it will be completely covered with the RichText control when the dialog box is created.
Figure 11.3. The dialog box that will have the RichEdit control example.
After the program executes (and a file has been loaded), your RichText control might look like that shown in Figure 11.4.
Figure 11.4. The RichEdit control example in a dialog box.
Listing 11.3 shows the header file RICHTEXT.H. In this file, a CRichEditCtrl member and prototypes for some functions that will be called have been added manually (and appear in bold). Remember that callback functions must be external to C++ classes, as Listing 11.3 shows.
Listing 11.3. The RICHTEXT.H file.
// RichText.h : header file // ///////////////////////////////////////////////////////////////////////////// // CRichEdit dialog #define ID_RTFCTRL 12345 class CRichEdit : public CDialog { // Construction public: CRichEdit(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(CRichEdit) enum { IDD = IDD_RICHTEXT }; CStatic m_RichFrame; CRichEditCtrl m_RichText; //}}AFX_DATA // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CRichEdit) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL BOOL GetFileName(LPSTR szFileName, BOOL bOpen); // Implementation protected: // Generated message map functions //{{AFX_MSG(CRichEdit) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; // Callbacks must be external to the class! DWORD CALLBACK OpenCallback (DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
In the source file, RICHTEXT.CPP, functions have been added to get the filename of the .RTF file and to serialize this file into the control. The WM_INITDIALOG handler has also been implemented where the CRichEditCtrl control has been created. Listing 11.4 shows the RICHTEXT.CPP file with edits marked in bold.
Listing 11.4. The RichText control implementation file, RICHTEXT.CPP.
// RichText.cpp : implementation file // #include "stdafx.h" #include "win32cc.h" #include "RichText.h" #include "cderr.h" // For the common dialog error codes! #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CRichEdit dialog CRichEdit::CRichEdit(CWnd* pParent /*=NULL*/) : CDialog(CRichEdit::IDD, pParent) { //{{AFX_DATA_INIT(CRichEdit) //}}AFX_DATA_INIT } void CRichEdit::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CRichEdit) DDX_Control(pDX, IDC_RICHFRAME, m_RichFrame); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CRichEdit, CDialog) //{{AFX_MSG_MAP(CRichEdit) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CRichEdit message handlers BOOL CRichEdit::OnInitDialog() { CDialog::OnInitDialog(); WINDOWPLACEMENT lpwndpl; // We use the frame to locate our RTF control: m_RichFrame.GetWindowPlacement(&lpwndpl); m_RichText.Create( WS_CHILD | WS_VISIBLE | WS_BORDER | ES_NOHIDESEL | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN, lpwndpl.rcNormalPosition, this, ID_RTFCTRL); HFILE hFile; // File handle OFSTRUCT OpenFileName; // Open file strucuture EDITSTREAM es; // The EDITSTREAM structure char szFileName[512]; // Filename buffer szFileName[0] = '\0'; if(GetFileName(szFileName, TRUE)) { // Open the file, read mode: hFile = OpenFile(szFileName, &OpenFileName, OF_READ); es.dwCookie = 0; es.dwError = 0; es.pfnCallback = (EDITSTREAMCALLBACK)NULL; if (hFile) { // Set up the EDITSTREAM structure es.dwCookie = (DWORD)hFile; // Our file handle es.dwError = 0; // No errors es.pfnCallback = OpenCallback; // Use callback // Get the file using the callback: m_RichText.StreamIn(SF_RTF, es); // Close the file when done. _lclose(hFile); } } return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } BOOL CRichEdit::GetFileName(LPSTR szFileName, BOOL bOpen) { BOOL nReturn = FALSE; OPENFILENAME OpenFileName; OpenFileName.lStructSize = sizeof(OPENFILENAME); OpenFileName.hwndOwner = (HWND)m_hWnd; OpenFileName.hInstance = NULL; OpenFileName.lpstrFilter = "RTF Files (*.RTF)\0*.RTF\0"; OpenFileName.lpstrCustomFilter = (LPSTR)NULL; OpenFileName.nMaxCustFilter = 0L; OpenFileName.nFilterIndex = 1L; OpenFileName.lpstrFile = szFileName; OpenFileName.nMaxFile = 512; OpenFileName.lpstrFileTitle = NULL; OpenFileName.nMaxFileTitle = 0; OpenFileName.lpstrInitialDir = NULL; OpenFileName.lpstrTitle = (LPSTR)NULL; OpenFileName.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST; OpenFileName.nFileOffset = 0; OpenFileName.nFileExtension = 0; OpenFileName.lpstrDefExt = (LPSTR)"RTF"; OpenFileName.lCustData = 0L; OpenFileName.lpfnHook = 0L; // eliminate compiler warning OpenFileName.lpTemplateName = (LPSTR)NULL; if (bOpen) { nReturn = GetOpenFileName (&OpenFileName); } else { nReturn = GetSaveFileName(&OpenFileName); } if (!nReturn) { // Process any errors here if desired! switch(CommDlgExtendedError()) {// Generic common dialog box error handler: case CDERR_DIALOGFAILURE : AfxMessageBox("The common dialog box procedure's call to " "the DialogBox function failed. "); break; case CDERR_FINDRESFAILURE : AfxMessageBox("The common dialog box procedure failed to " "find a specified resource."); break; case CDERR_GENERALCODES : AfxMessageBox("General error codes for common dialog " "boxes."); break; case CDERR_INITIALIZATION : AfxMessageBox("The common dialog box procedure failed " "during initialization."); break; case CDERR_LOADRESFAILURE : AfxMessageBox("The common dialog box procedure failed to " "load a specified resource."); break; case CDERR_LOADSTRFAILURE : AfxMessageBox("The common dialog box procedure failed to " "load a specified string."); break; case CDERR_LOCKRESFAILURE : AfxMessageBox("The common dialog box procedure failed to " "lock a specified resource."); break; case CDERR_MEMALLOCFAILURE : AfxMessageBox("The common dialog box procedure was unable " "to allocate memory for internal "); break; case CDERR_MEMLOCKFAILURE : AfxMessageBox("The common dialog box procedure was unable " "to lock the memory associated"); break; case CDERR_NOHINSTANCE : AfxMessageBox("The ENABLETEMPLATE flag was specified in " "the Flags member of a structure"); break; case CDERR_NOHOOK : AfxMessageBox("The ENABLEHOOK flag was specified in the " "Flags member of a structure"); break; case CDERR_NOTEMPLATE : AfxMessageBox("The ENABLETEMPLATE flag was specified in " "the Flags member of a structure "); break; case CDERR_REGISTERMSGFAIL : AfxMessageBox("The RegisterWindowMessage function " "returned an error code when it was called"); break; case CDERR_STRUCTSIZE : AfxMessageBox("The lStructSize member of a structure " "for the corresponding common dialog box"); break; case CFERR_CHOOSEFONTCODES : AfxMessageBox("Error codes for the Font common dialog " "box. These errors are in the range 0"); break; case CFERR_MAXLESSTHANMIN : AfxMessageBox("The size specified in the nSizeMax member " "of the CHOOSEFONT structure is wrong"); break; case CFERR_NOFONTS : AfxMessageBox("No fonts exist."); break; case FNERR_BUFFERTOOSMALL : AfxMessageBox("The buffer for a filename is too small."); break; case FNERR_FILENAMECODES : AfxMessageBox("Error codes for the Open and Save As " "common dialog boxes."); break; case FNERR_INVALIDFILENAME : AfxMessageBox("A filename is invalid."); break; case FNERR_SUBCLASSFAILURE : AfxMessageBox("An attempt to subclass a list box failed " "because insufficient memory was available"); break; case FRERR_BUFFERLENGTHZERO : AfxMessageBox("A member in a structure for the " "corresponding common dialog box points to a null buffer"); break; case FRERR_FINDREPLACECODES : AfxMessageBox("Error codes for the Find and Replace " "common dialog boxes. "); break; case PDERR_CREATEICFAILURE : AfxMessageBox("The PrintDlg function failed when it " "attempted to create an information context"); break; case PDERR_DEFAULTDIFFERENT : AfxMessageBox("An application called the PrintDlg " "function with the DN_DEFAULTPRN flag specified"); break; case PDERR_DNDMMISMATCH : AfxMessageBox("The data in the DEVMODE and DEVNAMES " "structures describes two different priters"); break; case PDERR_GETDEVMODEFAIL : AfxMessageBox("The printer driver failed to initialize " "a DEVMODE structure. "); break; case PDERR_INITFAILURE : AfxMessageBox("The PrintDlg function failed during " "initialization"); break; case PDERR_LOADDRVFAILURE : AfxMessageBox("The PrintDlg function failed to load " "the device driver for the specified printer"); break; case PDERR_NODEFAULTPRN : AfxMessageBox("A default printer does not exist."); break; case PDERR_NODEVICES : AfxMessageBox("No printer drivers were found."); break; case PDERR_PARSEFAILURE : AfxMessageBox("The PrintDlg function failed to parse " "the strings in the [devices] section "); break; case PDERR_PRINTERCODES : AfxMessageBox("Error codes for the Print common dialog box. "); break; case PDERR_PRINTERNOTFOUND : AfxMessageBox("The [devices] section of the WIN.INI file " "did not contain an entry..."); break; case PDERR_RETDEFFAILURE : AfxMessageBox("The PD_RETURNDEFAULT flag was specified in " "the Flags member of the PRINTDLG."); break; case PDERR_SETUPFAILURE : AfxMessageBox("The PrintDlg function failed to load the " "required resources."); break; default : AfxMessageBox("default: the call must have worked!"); break; } } return(nReturn); } DWORD CALLBACK OpenCallback (DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { // Read as much data as allowed in the cb variable *pcb = _lread((HFILE)dwCookie, pbBuff, cb); if(*pcb < cb) {// If done, say: return (0); // All done! } return (unsigned long)*pcb; // Otherwise, say: There is more to read! }
Of course, this sample program doesn't do much. A full implementation of a RichText control requires a full user interface (how else can the user set how the text is to look?) and support for input, output, and printing as applicable.
A classic example of what a RichText control can really do is the Windows 95 WordPad application. WordPad was written as a shell around the RichText control and is actually a rather simple program. The source for WordPad can be found in a number of places, including the Visual C++ 4 directory \MSDEV\SAMPLES\MFC\OLE\WORDPAD.
NOTE
The WordPad sample program is an excellent example of the RichText control's implementation and of issues that must be addressed when you're creating an OLE application.
When you create an AppWizard application, you can specify that a toolbar be included. This stock toolbar is implemented with the CToolBar class and will be located in the main frame window of the application.
You can add a toolbar to any other window (including the frame window) by using the CToolBarCtrl class. A toolbar created using CToolBarCtrl will offer some additional functionality (such as more configurability) that the CToolBar class toolbars don't offer. The standard CToolBar toolbar in the sample application's main frame window is shown in Figure 11.5.
Figure 11.5. A CToolBar-created toolbar in the application's main frame window.
Figure 11.6 shows a dialog box that has a toolbar added to it. I gave this toolbar large buttons in order to show you how to implement a nonstandard-size toolbar button.
Figure 11.6. A CToolBarCtrl-created toolbar in a dialog box.
Creating a toolbar is simple. In the class header for the parent window, create a CToolBarCtrl object, as shown in Listing 11.5. Lines that were added to the original AppWizard shell appear in bold. Note that you must create an array of TBBUTTON objects to hold the toolbar button information. This array is then passed to your CToolBarCtrl object when the toolbar is created.
Listing 11.5. Creating a CToolBarCtrl object.
// toolbar.h : header file // ///////////////////////////////////////////////////////////////////////////// // Toolbar dialog class Toolbar : public CDialog { // Construction public: Toolbar(CWnd* pParent = NULL); // Standard constructor TBBUTTON m_Buttons[5]; CToolBarCtrl m_ToolBar; // Dialog data //{{AFX_DATA(Toolbar) enum { IDD = IDD_TOOLBAR }; // NOTE: ClassWizard will add data members here //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(Toolbar) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(Toolbar) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Listing 11.6 shows the actual code that displays the status bar and places some text in the status bar's output area. Lines that were added to the original AppWizard shell appear in bold. In most applications, the placing of text in the status bar's output area will be done by using a message handler.
Listing 11.6. The CStatusBarCtrl initialization code.
// toolbar.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "toolbar.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Toolbar dialog Toolbar::Toolbar(CWnd* pParent /*=NULL*/) : CDialog(Toolbar::IDD, pParent) { //{{AFX_DATA_INIT(Toolbar) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void Toolbar::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(Toolbar) // NOTE: ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(Toolbar, CDialog) //{{AFX_MSG_MAP(Toolbar) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // Toolbar message handlers BOOL Toolbar::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here RECT rect; // Never really used! Default sizes used. rect.top = 0; rect.left = 0; rect.right = 100; rect.bottom = 60; m_ToolBar.Create(WS_CHILD | WS_VISIBLE | CCS_TOP, rect, this, 0); m_Buttons[0].iBitmap = 0; m_Buttons[0].idCommand = IDOK; m_Buttons[0].fsState = TBSTATE_ENABLED; m_Buttons[0].fsStyle = TBSTYLE_BUTTON; m_Buttons[0].dwData = 0; m_Buttons[0].iString = IDS_FIRST_BUTTON; m_Buttons[1].iBitmap = 1; m_Buttons[1].idCommand = IDCANCEL; m_Buttons[1].fsState = TBSTATE_ENABLED; m_Buttons[1].fsStyle = TBSTYLE_BUTTON; m_Buttons[1].dwData = 0; m_Buttons[1].iString = IDS_SECOND_BUTTON; m_ToolBar.AddButtons(2, m_Buttons); m_ToolBar.AddBitmap(2, IDB_TOOLBAR); CSize sizeButton(55, 65); m_ToolBar.SetBitmapSize(sizeButton); CSize sizeBitmap(48, 45); m_ToolBar.SetBitmapSize(sizeBitmap); m_ToolBar.AutoSize(); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
Creating and implementing a toolbar by using CToolBarCtrl is more complex than creating and implementing a simple status bar. You must give the toolbar object information about each button, including the button's size and the bitmap size.
There are several steps to creating the toolbar. First, you must initialize your toolbar with a call to CToolBarCtrl::Create(). Then you must initialize the TBBUTTON array members as shown in Listing 11.6. After the TBBUTTON array has been initialized, it is then passed to CToolBarCtrl::AddButtons(). Finally, the bitmap for the buttons is given to the toolbar using a call to CToolBarCtrl::AddBitmap(). After the bitmap has been attached, the bitmap size and the button size are set, and a call is made to CToolBarCtrl::AutoSize() to resize the toolbar to fit the new sizes passed.
NOTE
You need to specify button and bitmap sizes only if your button bitmap isn't the same size as the default. For both CToolBar and CToolBarCtrl, the default size for a bitmap is 16x15 pixels. The default button size is 24x22 pixels. The button is larger than the bitmap to allow for three-dimensional borders on the button image.
The Up-Down control, usually called a spinbox control when combined with an edit control that displays the selected value, provides a handy way for users to enter or update a value when the normal action would be for the user to increment or decrement the value. The Up-Down control is implemented by using the CSpinButtonCtrl object.
Using the Up-Down control is simple. As with all the other Win32 Common Controls, you create a dialog box and place the Up-Down control somewhere on the dialog box. If you're creating a spinbox-type control, you also create an edit control and place the edit control next to the Up-Down control. Figure 11.7 shows the sample dialog box under development in the resource editor.
Figure 11.7. The Up-Down control in the resource editor.
Figure 11.8 shows the Up-Down control in operation. This example shows the numeric value displayed in the edit control. Notice that when you use the standard font for the edit control (which is very large in this figure so that it will show up well), the number is too small to be seen well. If you're going to create a large edit control, you might want to use a larger typeface to make the displayed number more readable.
Figure 11.8. The spin control, using an Up-Down control.
The edit control that is attached to the Up-Down control (logically, and usually physically) is referred to as the buddy control. This edit control is updated by the Up-Down control as the Up-Down control's value changes. Listing 11.7 shows the header file for the dialog box that contains the Up-Down control. This header file includes the definition of the Up-Down control, shown in bold. This code was added by using ClassWizard.
Listing 11.7. The CSpinButtonCtrl initialization code.
// updown.h : header file // ///////////////////////////////////////////////////////////////////////////// // UpDown dialog class UpDown : public CDialog { // Construction public: UpDown(CWnd* pParent = NULL); // standard constructor // Dialog data //{{AFX_DATA(UpDown) enum { IDD = IDD_UPDOWN }; CEdit m_EditWnd; CSpinButtonCtrl m_UpDown; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(UpDown) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(UpDown) virtual void OnOK(); virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
The actual code to use the UpDown class is shown in Listing 11.8. This code shows how little programming is needed to create your spinbox control.
Listing 11.8. Creating a CSpinButtonCtrl object.
// updown.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "updown.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // UpDown dialog UpDown::UpDown(CWnd* pParent /*=NULL*/) : CDialog(UpDown::IDD, pParent) { //{{AFX_DATA_INIT(UpDown) //}}AFX_DATA_INIT } void UpDown::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(UpDown) DDX_Control(pDX, IDC_EDIT1, m_EditWnd); DDX_Control(pDX, IDC_UPDOWN, m_UpDown); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(UpDown, CDialog) //{{AFX_MSG_MAP(UpDown) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // UpDown message handlers void UpDown::OnOK() { // TODO: Add extra validation here int nPosition; char szBuffer[255]; // OK, user is done. Get the Trackbar's current value: nPosition = m_UpDown.GetPos(); sprintf(szBuffer, "Trackbar position = %d", nPosition); AfxMessageBox(szBuffer); CDialog::OnOK(); } BOOL UpDown::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here m_UpDown.SetBuddy(&m_EditWnd); m_UpDown.SetRange(10, 100); m_UpDown.SetPos(10); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
As Listing 11.8 shows (in bold), you assign the buddy control and then set the range of the Up-Down control as desired. Finally, you set the initial position. There are no defaults for the Up-Down control's range or desired position. It's good programming practice to initialize these values.
NOTE
When you're creating the Up-Down control in the dialog box, make sure that if you're using an automatically updated edit control, you select the Set Buddy Integer property. If this property isn't set, the edit control won't be automatically updated.
Time I lost to this error: one hour!
The Progress Indicator control gives feedback to the user about the progress of a lengthy operation. For example, an install program that must copy and process files from disks or a CD-ROM might use the Progress Indicator control to tell the user how far the process has progressed. This is better than simply using a wait cursor because the user will know that something is happening! The Progress Indicator is implemented by using CProgressCtrl.
Using a Progress Indicator control is easy. You can set a few simple parameters and then utilize the control. First, you must define the control in a dialog box. Then, as with other controls, use ClassWizard to bind a control object (of type CProgressCtrl) to the Progress Indicator control. Figure 11.9 shows a Progress Indicator control in a simple dialog box. This indicator is being updated with a timer loop to simulate a lengthy process.
Figure 11.9. The standard Progress Indicator control in an MFC application.
The code to implement the CProgressCtrl object is rather simple. In the header file for the dialog box (or any other window in which you place a Progress Indicator control), you must define a member variable of CProgressCtrl type. In most cases, you can use ClassWizard to link the CProgressCtrl object to the dialog box's control. The CProgressCtrl definition in Listing 11.9 appears in bold.
Listing 11.9. Creating a CProgressCtrl object.
// progress.h : header file // ///////////////////////////////////////////////////////////////////////////// // Progress dialog class Progress : public CDialog { // Construction public: Progress(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(Progress) enum { IDD = IDD_PROGRESS }; CProgressCtrl m_Progress; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(Progress) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(Progress) virtual BOOL OnInitDialog(); afx_msg void OnTimer(UINT nIDEvent); virtual void OnOK(); virtual void OnCancel(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Listing 11.10 shows the code used to initialize the CProgressCtrl control and to update the control. Lines that were added to the original AppWizard shell appear in bold. In this example, the CProgressCtrl is set to have a range of 0 to 100 and an increment of 1. A timer loop is then created, which increments the Progress Indicator control's display.
Listing 11.10. Creating a CProgressCtrl object.
// progress.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "progress.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Progress dialog Progress::Progress(CWnd* pParent /*=NULL*/) : CDialog(Progress::IDD, pParent) { //{{AFX_DATA_INIT(Progress) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void Progress::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(Progress) DDX_Control(pDX, IDC_PROGRESS, m_Progress); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(Progress, CDialog) //{{AFX_MSG_MAP(Progress) ON_WM_TIMER() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // Progress message handlers BOOL Progress::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here m_Progress.SetRange(0, 100); // Default values! m_Progress.SetPos(0); // Default value! m_Progress.SetStep(1); // Default is 10 SetTimer(999, 100, NULL); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void Progress::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default // Increment our progress. (When we exceed 100%, // CProgressCtrl will reset the indicator to 0!) // If this action is not desired, you must track // the current position and call CProgressCtrl::SetPos() // to set the Progress bar's position. m_Progress.StepIt(); CDialog::OnTimer(nIDEvent); } void Progress::OnOK() { // TODO: Add extra validation here KillTimer(999); CDialog::OnOK(); } void Progress::OnCancel() { // TODO: Add extra cleanup here KillTimer(999); CDialog::OnCancel(); }
The initialization process consists of this code:
m_Progress.SetRange(0, 100); // Default values! m_Progress.SetPos(0); // Default value! m_Progress.SetStep(1); // Default is 10
The code to update the Progress Indicator control is a simple call to the member function StepIt(), which will increment the control's indicator by the step value defined when the control is initialized.
NOTE
The default range is 0 to 100, and the step value is 10. This can create a choppy-acting control. Generally, you will want to increment the control in about one-percent steps.
A Trackbar control lets the user input an inexact value within a predefined range of values. Typical uses for Trackbar controls include multimedia volume and mixer controls and other pseudo-analog applications.
A Trackbar control is easy to implement. You first define the Trackbar control in a dialog box, and then you use ClassWizard to attach a CSliderCtrl object to the Trackbar control. The sample application in Figure 11.10 shows a simple Trackbar control. This control is a minimum implementation of the Trackbar control, with index marks (called tick marks) on the trackbar.
Figure 11.10. The standard Trackbar control in an MFC application.
The code to implement a basic CSliderCtrl object is simple. In the header file for the dialog box (or in any other window), you must define a member variable of CSliderCtrl type, which is usually done by using ClassWizard. This line appears in bold.
Listing 11.11. Creating a CSliderCtrl object.
// trackbar.h : header file // ///////////////////////////////////////////////////////////////////////////// // TrackBar dialog class TrackBar : public CDialog { // Construction public: TrackBar(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(TrackBar) enum { IDD = IDD_TRACKBAR }; CSliderCtrl m_Slider; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(TrackBar) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(TrackBar) virtual BOOL OnInitDialog(); virtual void OnOK(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Listing 11.12 is the actual implementation of a CSliderCtrl object. This code implements the minimum programming needed to create a usable control. You could also modify the tick mark frequency and add numeric indicators at each end of the slider control to show minimum and maximum values.
The bold code in Listing 11.12 shows how you set both the range of the slider control and the display of tick marks that assist the user in determining just where the slider is positioned.
Listing 11.12. Creating a CSliderCtrl object.
// trackbar.cpp : implementation file // #include "stdafx.h" #include "win32cc.h" #include "trackbar.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // TrackBar dialog TrackBar::TrackBar(CWnd* pParent /*=NULL*/) : CDialog(TrackBar::IDD, pParent) { //{{AFX_DATA_INIT(TrackBar) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void TrackBar::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(TrackBar) DDX_Control(pDX, IDC_TRACKBAR, m_Slider); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(TrackBar, CDialog) //{{AFX_MSG_MAP(TrackBar) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // TrackBar message handlers BOOL TrackBar::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // Set the slider's range from 10 to 100 m_Slider.SetRange(10, 100); // Set the slider's ticks to every tenth point m_Slider.SetTicFreq(10); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void TrackBar::OnOK() { // TODO: Add extra validation here int nPosition; char szBuffer[255]; // OK, user is done. Get the Trackbar's current value: nPosition = m_Slider.GetPos(); sprintf(szBuffer, "Trackbar position = %d", nPosition); AfxMessageBox(szBuffer); CDialog::OnOK(); }
The Header control is used by the ListView control to display column headings when data is being displayed in the report view. There are bound to be other implementations for the Header control in the future, but currently there is only limited usage for the Header control as a stand-alone control in a dialog box. The Header control is automatically implemented by the ListView control.
The ListView control is one of the most complex controls implemented by the Win32 Common Controls. This control supports four different views of the data:
The sample program in Figure 11.11 shows the Report View mode of the ListView control. The Report View mode might be the most important mode that this control offers, and it's the most difficult mode to implement. Figure 11.11 also shows the ListView control displaying a set of columnar data. This data is from a simple table contained in the program; however, in database programs, the data can be retrieved from a datasource.
Figure 11.11. The ListView control in Report View mode.
The code to implement the CListCtrl object (see Listing 11.13) is usually simple. In the header file for the dialog box (or any other window), you must define a member variable of CListCtrl type. You can use ClassWizard to do this. This line appears in bold.
Listing 11.13. Creating a CListCtrl object.
// listview.h : header file // ///////////////////////////////////////////////////////////////////////////// // ListView dialog class ListView : public CDialog { // Construction public: ListView(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(ListView) enum { IDD = IDD_LISTVIEW }; CListCtrl m_ListView; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(ListView) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(ListView) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
The real work is done in the source file, where the ListView control must be initialized and have the data added. The code to do this is shown in Listing 11.14. Lines that were added to the original AppWizard shell appear in bold.
Listing 11.14. Creating a CListCtrl object.
// listview.cpp : implementation file // #include "stdafx.h" #include "win32cc.h" #include "listview.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // ListView dialog ListView::ListView(CWnd* pParent /*=NULL*/) : CDialog(ListView::IDD, pParent) { //{{AFX_DATA_INIT(ListView) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void ListView::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(ListView) DDX_Control(pDX, IDC_LISTVIEW, m_ListView); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(ListView, CDialog) //{{AFX_MSG_MAP(ListView) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // ListView message handlers BOOL ListView::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here char *szColumn[6] = {"Region", "District", "Territory", "Population", "1994 Sales", "1993 Sales"}; char *szData[10][6] = {{"New England", "Massachusetts", "Territory 1", "345,678", " 1.25", " .95"}, {"New England", "Massachusetts", "Territory 2", " 45,678", " .25", " .25"}, {"New England", "Massachusetts", "Territory 3", "245,678", " 1.75", " 1.15"}, {"New England", "Massachusetts", "Territory 4", " 2,378", "94.00", "87.00"}, {"New England", "Massachusetts", "Territory 5", " 25,678", " 1.00", " .95"}, {"New England", "New Hampshire", "Territory 1", "123,234", " 1.25", " .95"}, {"New England", "New Hampshire", "Territory 2", " 12,123", " .25", " .25"}, {"New England", "New Hampshire", "Territory 3", "100,321", " 1.75", " 1.15"}, {"New England", "New Hampshire", "Territory 4", "123,123", "94.00", "87.00"}, {"New England", "New Hampshire", "Territory 5", "321,312", " 1.00", " .95"}}; // Add the columns LV_COLUMN lvc; lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; for (int j = 0; j < 6; j++) { lvc.cx = m_ListView.GetStringWidth(szColumn[j]) + 25; lvc.pszText = szColumn[j]; if (j > 2) lvc.fmt = LVCFMT_RIGHT; lvc.iSubItem = j; m_ListView.InsertColumn(j, &lvc); } // Need to add some items to the ListView control LV_ITEM lvi; lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iSubItem = 0; for (int i = 0; i < 10; i++) { lvi.iItem = i; lvi.pszText = szData[i][0]; lvi.cchTextMax = 5; lvi.iImage = i; m_ListView.InsertItem(&lvi); for (int k = 1; k < 6; k++) { // m_ListView.SetItemText(i, k, TEXT("SubItem")); m_ListView.SetItemText(i, k, szData[i][k]); } } return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
Because of the complexity of the ListView control's initialization, take a closer look at the code presented in Listing 11.14. First, you need to create a dummy dataset. Rather than make this listing even more complex by calling an ODBC datasource, I have simply hard-coded some dummy data into the program.
char *szColumn[6] = {"Region", "District", "Territory", "Population", "1994 Sales", "1993 Sales"}; char *szData[10][6] = {{"New England", "Massachusetts", "Territory 1", "345,678", " 1.25", " .95"}, {"New England", "Massachusetts", "Territory 2", " 45,678", " .25", " .25"}, {"New England", "Massachusetts", "Territory 3", "245,678", " 1.75", " 1.15"}, {"New England", "Massachusetts", "Territory 4", " 2,378", "94.00", "87.00"}, {"New England", "Massachusetts", "Territory 5", " 25,678", " 1.00", " .95"}, {"New England", "New Hampshire", "Territory 1", "123,234", " 1.25", " .95"}, {"New England", "New Hampshire", "Territory 2", " 12,123", " .25", " .25"}, {"New England", "New Hampshire", "Territory 3", "100,321", " 1.75", " 1.15"}, {"New England", "New Hampshire", "Territory 4", "123,123", "94.00", "87.00"}, {"New England", "New Hampshire", "Territory 5", "321,312", " 1.00", " .95"}};
After you have some data to place in your ListView control, you can define the variables that are needed to fill the control. The LV_COLUMN structure is used to define the columns (whose names are stored in the szColumn[] array). You define the columns with certain attributes. The attributes that are being set are defined in the mask member variable, and in this example, the text attribute (the title for the column), the width (how wide the column will be), and the column's format are being defined.
// Add the columns LV_COLUMN lvc; lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; for (int j = 0; j < 6; j++) { lvc.cx = m_ListView.GetStringWidth(szColumn[j]) + 25; lvc.pszText = szColumn[j]; if (j > 2) lvc.fmt = LVCFMT_RIGHT; lvc.iSubItem = j; m_ListView.InsertColumn(j, &lvc); }
This fragment shows you how to use a loop to define each column. Notice how the final columns (columns four, five, and six) are right-justified to force the alignment of the columns.
Setting the width of the columns is important and can be difficult: Do you set the width based on the column's title, the column's data, or both? In this example, the width is set based on the width of the title string. The width is defined in pixels (not characters or dialog units). This could present a problem, so Microsoft has included a function called GetStringWidth(), which returns the width of a string in pixels. Knowing the string's width in pixels makes defining column widths easier.
The next step is to add items to the ListView control. You do this by first adding the main item (the first, extreme-left column) and then any following columns (which are called subitems).
// Need to add some items to the ListView control LV_ITEM lvi; lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iSubItem = 0; for (int i = 0; i < 10; i++) { lvi.iItem = i; lvi.pszText = szData[i][0]; lvi.cchTextMax = 5; lvi.iImage = i; m_ListView.InsertItem(&lvi); for (int k = 1; k < 6; k++) { m_ListView.SetItemText(i, k, szData[i][k]); } }
Notice that Windows returns the index number for the item. You can assign an arbitrary 32-bit value to an item. This value is often the address or a pointer to a structure containing information about the item.
The TreeView control lets the user select items from a hierarchical structure. Directories and files are classic examples of this type of structure, where a directory tree (within the limits of the operating system) could be of any depth.
Using a TreeView to show hierarchical data lets the programmer present a visual representation of the data. One program that I developed is used for sales territories. Sales territories are often arranged in a hierarchy of regions, districts, and territories. Territories are areas that are actually serviced by an individual sales representative. A classic example of a sales territory definition is North East Region, New Hampshire District, Territory 1.
The example for the TreeView control shows a sales territory arrangement. Figure 11.12 shows the TreeView control, where the North East Region and the New Hampshire district branches have been expanded.
Figure 11.12. The TreeView control in a dialog box.
In the sample application, I didn't want to define icons for the items in the TreeView. No icons are defined (yet!) for sales territories.
The code to implement the CTreeViewCtrl object (see Listing 11.15) can be kept simple. In the header file for the dialog box (or any other window), you must define a member variable of CTreeViewCtrl type. You can use AppWizard to define this member variable. This line appears in bold.
Listing 11.15. Creating a CTreeViewCtrl object.
// treeview.h : header file // ///////////////////////////////////////////////////////////////////////////// // TreeView dialog class TreeView : public CDialog { // Construction public: TreeView(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(TreeView) enum { IDD = IDD_TREEVIEW }; CTreeCtrl m_TreeView; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(TreeView) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(TreeView) virtual BOOL OnInitDialog(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
The code to actually fill the TreeView control (see Listing 11.16) is a bit more complex. Your application must maintain the relationships of the items in the CTreeViewCtrl object. The code that creates the CTreeViewCtrl object appears in bold.
Listing 11.16. Creating a CTreeViewCtrl object.
// treeview.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "treeview.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // TreeView dialog TreeView::TreeView(CWnd* pParent /*=NULL*/) : CDialog(TreeView::IDD, pParent) { //{{AFX_DATA_INIT(TreeView) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void TreeView::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(TreeView) DDX_Control(pDX, IDC_TREEVIEW, m_TreeView); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(TreeView, CDialog) //{{AFX_MSG_MAP(TreeView) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // TreeView message handlers BOOL TreeView::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // First the tree control: (WORKS!) int nRegion; int nDistrict; int nTerritory; HTREEITEM RegionParent; HTREEITEM DistrictParent; HTREEITEM TerritoryParent; TV_INSERTSTRUCT InsertItem; char szBuffer[255]; char *szRegion[3] = {"North East", "Mid Atlantic", "South East"}; char *szDistrict[3][5] = {{"New Hampshire", "Massachusetts", "Vermont", "Maine", "Rhode Island"}, {"New York", "New Jersey", "Delaware", "DC", "Maryland"}, {"Virginia", "North Carolina", "South Carolina", "Georgia", "Florida"}}; for (nRegion = 0; nRegion < 3; ++nRegion) { InsertItem.item.mask = TVIF_TEXT; InsertItem.item.pszText = szRegion[nRegion]; InsertItem.item.cchTextMax = 15; InsertItem.hParent = NULL; InsertItem.hInsertAfter = TVI_SORT; RegionParent = m_TreeView.InsertItem(&InsertItem); for (nDistrict = 0; nDistrict < 5; ++nDistrict) { InsertItem.item.pszText = szDistrict[nRegion][nDistrict]; InsertItem.item.cchTextMax = 20; InsertItem.hParent = RegionParent; InsertItem.hInsertAfter = TVI_SORT; DistrictParent = m_TreeView.InsertItem(&InsertItem); for (nTerritory = 0; nTerritory < 5; ++nTerritory) { sprintf(szBuffer, "Territory %d", nTerritory); InsertItem.item.pszText = szBuffer; InsertItem.item.cchTextMax = 20; InsertItem.hParent = DistrictParent; InsertItem.hInsertAfter = TVI_SORT; TerritoryParent = m_TreeView.InsertItem(&InsertItem); } } } return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
Take a closer look at the initialization of the TreeView control. First, you need to create some variables to assist you in filling your TreeView control:
//First the tree control: (WORKS!) int nRegion; int nDistrict; int nTerritory; HTREEITEM RegionParent; HTREEITEM DistrictParent; HTREEITEM TerritoryParent; TV_INSERTSTRUCT InsertItem; char szBuffer[255]; char *szRegion[3] = {"North East", "Mid Atlantic", "South East"}; char *szDistrict[3][5] = {{"New Hampshire", "Massachusetts", "Vermont", "Maine", "Rhode Island"}, {"New York", "New Jersey", "Delaware", "DC", "Maryland"}, {"Virginia", "North Carolina", "South Carolina", "Georgia", "Florida"}};
In the preceding code fragment, you create a few integers that will be used to hold indexes. You also create the data for this example, as well as an HTREEITEM structure for each level in your tree. Finally, you create a TV_INSERTSTRUCT structure to be used to interface with the CTreeViewCtrl object.
for (nRegion = 0; nRegion < 3; ++nRegion) { InsertItem.item.mask = TVIF_TEXT; InsertItem.item.pszText = szRegion[nRegion]; InsertItem.item.cchTextMax = 15; InsertItem.hParent = NULL; InsertItem.hInsertAfter = TVI_SORT; RegionParent = m_TreeView.InsertItem(&InsertItem); for (nDistrict = 0; nDistrict < 5; ++nDistrict) { InsertItem.item.pszText = szDistrict[nRegion][nDistrict]; InsertItem.item.cchTextMax = 20; InsertItem.hParent = RegionParent; InsertItem.hInsertAfter = TVI_SORT; DistrictParent = m_TreeView.InsertItem(&InsertItem); for (nTerritory = 0; nTerritory < 5; ++nTerritory) { sprintf(szBuffer, "Territory %d", nTerritory); InsertItem.item.pszText = szBuffer; InsertItem.item.cchTextMax = 20; InsertItem.hParent = DistrictParent; InsertItem.hInsertAfter = TVI_SORT; TerritoryParent = m_TreeView.InsertItem(&InsertItem); } } }
After the variables have been created, you use a loop to insert items into the CTreeViewCtrl class object. This is done in the object's hierarchies order: The regions are the first (called the root) level, then the districts, and finally the territories. That is, you add a region, then the first district, then the territories in the first district. You then add the second district, the territories in the second district, and so on.
The Tab control lets you create property sheet dialog boxes. Visual C++ uses a property page dialog box for ClassWizard, in which there are property sheets for each of the functionalities that ClassWizard offers. This control is similar to the CPropertySheet/CPropertyPage class objects that are used to create property page dialog boxes.
I had to think for a few minutes about why someone would want to use CTabCtrl (which is more complex than CPropertySheet/CPropertyPage), but I did come up with a logical usage. Sometimes you just have to look under your own nose: Take a look at the Visual C++ Project Settings dialog box, shown in Figure 11.13. A dialog box such as this could be created only with the Tab Common Control and the CTabCtrl class object.
Figure 11.13. Visual C++'s Project Settings dialog box.
The Tab control is one of the most difficult Win32 Common Controls to implement. Locating a Tab control in a dialog box is easy, as is attaching the CTabCtrl object to the control. Even creating a set of tabs is not too difficult.
The fun begins when you want to place something in your Tab control. There is no direct support to do anything specific with the Tab control. However, it was possible to put together an example of a tabbed dialog box (or a property sheet, if that's what you want to call it). First, take a look at the final product. Figure 11.14 shows the Tab control in action.
Figure 11.14. The Tab dialog control in action.
Next, take a look at the code used to produce the Tab dialog control and to make it work. Listing 11.17 shows the header file, where the main CTabCtrl object is defined. As in all the other Win32 Common Controls, you can use ClassWizard to create your control's class object. The CTabCtrl object's definition appears in bold.
Listing 11.17. Creating a CTabCtrl object.
// tab.h : header file // ///////////////////////////////////////////////////////////////////////////// // Tab dialog class Tab : public CDialog { // Construction public: Tab(CWnd* pParent = NULL); // Standard constructor // Dialog Data //{{AFX_DATA(Tab) enum { IDD = IDD_TAB }; CTabCtrl m_TabCtrl; //}}AFX_DATA // Overrides // ClassWizard-generated virtual function overrides //{{AFX_VIRTUAL(Tab) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(Tab) virtual BOOL OnInitDialog(); afx_msg void OnSelchangingTab(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnDestroy(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
After you have created your CTabCtrl object, you can begin to create your tabs. Listing 11.18 contains TAB.CPP, which has the code to manage the Tab control.
NOTE
The three dialog boxes that are placed in the tabs in the Tab control example and in the following CPropertySheet/CPropertyPage example are simple dialog boxes with the child and thin border styles set.
Listing 11.18. The CStatusBarCtrl initialization code.
// tab.cpp : implementation file // #include "stdafx.h" #include "Win32CC.h" #include "tab.h" #include "page1.h" #include "page2.h" #include "page3.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Tab dialog Tab::Tab(CWnd* pParent /*=NULL*/) : CDialog(Tab::IDD, pParent) { //{{AFX_DATA_INIT(Tab) // NOTE: ClassWizard will add member initialization here //}}AFX_DATA_INIT } void Tab::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(Tab) DDX_Control(pDX, IDC_TAB, m_TabCtrl); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(Tab, CDialog) //{{AFX_MSG_MAP(Tab) ON_NOTIFY(TCN_SELCHANGING, IDC_TAB, OnSelchangingTab) ON_NOTIFY(TCN_SELCHANGE, IDC_TAB, OnSelchangeTab) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // Tab message handlers BOOL Tab::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // For the Tab control, we will add three tabs, // called 'Tab 1', 'Tab 2,' and 'Tab 3' TC_ITEM TabItem; TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 1"; m_TabCtrl.InsertItem(0, &TabItem); TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 2"; m_TabCtrl.InsertItem(1, &TabItem); TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 3"; m_TabCtrl.InsertItem(2, &TabItem); Page1* pPage1; pPage1 = new Page1; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage1; m_TabCtrl.SetItem(0, &TabItem); VERIFY(pPage1->Create(Page1::IDD, &m_TabCtrl)); pPage1->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage1->ShowWindow(SW_SHOW); Page2* pPage2; pPage2 = new Page2; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage2; m_TabCtrl.SetItem(1, &TabItem); VERIFY(pPage2->Create(Page2::IDD, &m_TabCtrl)); pPage2->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage2->ShowWindow(SW_HIDE); Page3* pPage3; pPage3 = new Page3; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage3; m_TabCtrl.SetItem(2, &TabItem); VERIFY(pPage3->Create(Page3::IDD, &m_TabCtrl)); pPage3->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage3->ShowWindow(SW_HIDE); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void Tab::OnSelchangingTab(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here int iTab = m_TabCtrl.GetCurSel(); TC_ITEM tci; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(iTab, &tci); ASSERT(tci.lParam); CWnd* pWnd = (CWnd *)tci.lParam; pWnd->ShowWindow(SW_HIDE); *pResult = 0; } void Tab::OnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here int iTab = m_TabCtrl.GetCurSel(); TC_ITEM tci; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(iTab, &tci); ASSERT(tci.lParam); CWnd* pWnd = (CWnd *)tci.lParam; pWnd->ShowWindow(SW_SHOW); *pResult = 0; } void Tab::OnDestroy() { // TODO: Add your message handler code here int iTab = 0; TC_ITEM tci; CWnd* pWnd; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(0, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; m_TabCtrl.GetItem(1, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; m_TabCtrl.GetItem(2, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; CDialog::OnDestroy(); }
In your dialog box, you will need to create a handler for the WM_INITDIALOG message. This handler is common to most dialog box classes. The handler for your Tab control must first create the tabs (three tabs in this example) and then must create three dialog boxes that will be displayed in the Tab control. Which dialog box is displayed in the Tab control is controlled by which tab is currently active.
First, as the following code fragment shows, you create the tabs. A TC_ITEM structure is used to define the tabs, and a call to CTabCtrl::InsertItem() creates the tab.
// For the Tab control, we will add three tabs, // called 'Tab 1', 'Tab 2,' and 'Tab 3'. TC_ITEM TabItem; TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 1"; m_TabCtrl.InsertItem(0, &TabItem); TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 2"; m_TabCtrl.InsertItem(1, &TabItem); TabItem.mask = TCIF_TEXT; TabItem.pszText = "Tab 3"; m_TabCtrl.InsertItem(2, &TabItem);
After you have created your tabs, you then need to create the dialog boxes. There must be one dialog box for each tab.
Page1* pPage1; pPage1 = new Page1; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage1; m_TabCtrl.SetItem(0, &TabItem); VERIFY(pPage1->Create(Page1::IDD, &m_TabCtrl)); pPage1->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage1->ShowWindow(SW_SHOW); Page2* pPage2; pPage2 = new Page2; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage2; m_TabCtrl.SetItem(1, &TabItem); VERIFY(pPage2->Create(Page2::IDD, &m_TabCtrl)); pPage2->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage2->ShowWindow(SW_HIDE); Page3* pPage3; pPage3 = new Page3; TabItem.mask = TCIF_PARAM; TabItem.lParam = (LPARAM)pPage3; m_TabCtrl.SetItem(2, &TabItem); VERIFY(pPage3->Create(Page3::IDD, &m_TabCtrl)); pPage3->SetWindowPos(NULL, 10, 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER); pPage3->ShowWindow(SW_HIDE);
Page1, Page2, and Page3 are all simple dialog box classes. You can choose the controls in the dialog boxes that will be placed on the tabbed pages. The only requirement is that the dialog box must fit inside the Tab control; therefore, don't make the dialog boxes too large.
When the first tab is active, the first dialog box is displayed with a call to ShowWindow() using the parameter of SW_SHOW. The other two dialog boxes aren't active, so they're hidden using calls to ShowWindow() with the parameter of SW_HIDE. Note the call to m_TabCtrl.SetItem(), which saves the handle to the Tab dialog boxes by placing the handle in the TC_ITEM.lParam member.
The next thing you must program is the change from the current Tab dialog box to the new Tab dialog box when the user changes tabs. You do this in two steps. First, the current Tab dialog box is hidden. You accomplish this by using the CTabCtrl's TCN_SELCHANGING handler, which is called before the current tab changes. This handler is simple: Get the handle for the current Tab dialog box and call ShowWindow(SW_HIDE).
void Tab::OnSelchangingTab(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here int iTab = m_TabCtrl.GetCurSel(); TC_ITEM tci; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(iTab, &tci); ASSERT(tci.lParam); CWnd* pWnd = (CWnd *)tci.lParam; pWnd->ShowWindow(SW_HIDE); *pResult = 0; }
Next, the new Tab dialog box is shown. This is done with CTabCtrl's TCN_SELCHANGE handler, which is called after the current tab changes. This handler is simple: Get the handle for the current Tab dialog box and call ShowWindow(SW_SHOW).
void Tab::OnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here int iTab = m_TabCtrl.GetCurSel(); TC_ITEM tci; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(iTab, &tci); ASSERT(tci.lParam); CWnd* pWnd = (CWnd *)tci.lParam; pWnd->ShowWindow(SW_SHOW); *pResult = 0; }
That's all there is to changing the Tab dialog boxes. However, you must do a final bit of housecleaning. When the main dialog box is destroyed, you must manually destroy the Tab dialog boxes. This is accomplished in the OnDestroy handler, which you can create by using ClassWizard. This handler is called when the main dialog window is destroyed. You then can destroy the Tab dialog boxes.
void Tab::OnDestroy() { // TODO: Add your message handler code here int iTab = 0; TC_ITEM tci; CWnd* pWnd; tci.mask = TCIF_PARAM; m_TabCtrl.GetItem(0, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; m_TabCtrl.GetItem(1, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; m_TabCtrl.GetItem(2, &tci); ASSERT(tci.lParam); pWnd = (CWnd *)tci.lParam; pWnd->DestroyWindow(); delete pWnd; CDialog::OnDestroy(); }
Finally, in a real program, you would code your OK and Cancel buttons. The OK button's handler would check each of the Tab dialog boxes to see what has changed and then would pass this information back to the caller, as appropriate.
Property sheets aren't part of the Win32 Common Controls. However, property sheets are so closely related to the Tab Common Control that any discussion of the Tab Common Control would be incomplete without a mention of the Property Sheet dialog box.
Figure 11.15 shows a simple property sheet dialog box using the same set of Tab dialog boxes that you used with the Tab Common Control, as described earlier.
Figure 11.15. A CPropertySheet/CPropertyPage dialog box.
NOTE
Property sheet dialog boxes can contain only property sheets. If you need to have controls outside of the property sheets, you need to use a Tab Common Control, as described earlier.
The code to produce the property page shown in Figure 11.15 is contained in Listing 11.19.
Listing 11.19. Creating a CPropertyPage/CPropertySheet dialog box.
void CMainFrame::OnWin32commoncontrolsPropertypage() { // TODO: Add your command handler code here Page1 OurPage1; Page2 OurPage2; Page3 OurPage3; CPropertySheet PropertySheet(IDS_PROPERTY_SHEET_TITLE); PropertySheet.AddPage(&OurPage1); PropertySheet.AddPage(&OurPage2); PropertySheet.AddPage(&OurPage3); // A test to see if the user pressed cancel or OK should be // made on the call to DoModal()... PropertySheet.DoModal(); }
As you can see, it's not difficult to create property page dialog boxes in this manner. Simply create the property page dialog boxes and then add them to the property sheet. Finally, call DoModal() to create the dialog box. The text on each tab is extracted from the dialog box titles.
The Animation control lets an application display certain types of animation (AVI) files (usually called AVI clips) in a window. The AVI file must meet certain criteria in order to be used with the Animation control:
At the time this book was written, Visual C++ 4 didn't enable direct creation of animation resources. To create an Animation resource, follow these steps:
m_AnimateCtrl.Open(IDR_CLOCK);
Having your AVI clips as resources makes managing the application easier, because there won't be a separate AVI file for each animation effect. However, when AVI clips are resources, it's much more difficult to use a different AVI clip without more extensive programming. Generally, however, most applications will want to include their AVI clips as application resources.
Figure 11.18 shows a typical implementation of an Animation control in a dialog box. This example is from the sample program.
Figure 11.18. An Animation control shown in a dialog box.
When your application uses a CAnimateCtrl class object, it won't need to do much management of the Animation control. The application must open the AVI clip that will be displayed, and it may optionally start the playback of the AVI. An Animation control has a few special attributes, which are described in Table 11.1. In addition to these special attributes, the Animation control also supports the standard attributes for dialog box controls.
Attribute | Description |
Center | The actual window that is created will be centered in the window defined by either the dialog box template or the rect structure passed to the Create() function. |
Transparent | The Animation control's background will be transparent instead of being the background color that was specified by the AVI file. |
Autoplay | The AVI clip will automatically play once the Animation control has been created and is made visible. Once Autoplay has started, the AVI clip will repeat automatically until it is explicitly ended. |
After you've added the Animation control to a dialog box, you can use ClassWizard to bind a CAnimateCtrl object to the Animation control. This CAnimateCtrl object is your interface with the Animation control.
If you're using an Animation control in a dialog box, you can use ClassWizard to bind a CAnimateCtrl object to the control. If you're implementing an Animation control in a window that isn't a dialog box, you need call the CAnimateCtrl::Create() function to create the Animation control. You might find a use for an Animation control in a normal window, or perhaps as part of a toolbar.
Figure 11.19 shows the dialog box (in Visual C++'s resource editor) with the properties for the Animate control. You can easily set these properties using the resource editor for Animation controls that are in dialog boxes. When the Animation control is in a window other than a dialog box, the properties are set when the Animation control is created.
Figure 11.19. The Animation control and its properties in Visual C++'s resource editor.
In Figure 11.19 you can see that I've added two new buttons, Play and Stop. The handlers for these two buttons simply call the CAnimateCtrl::Play() and CAnimateCtrl::Stop() functions.
Listing 11.20 is the code that manages the Animate sample dialog box. The basic code was developed using ClassWizard and then was modified to add the necessary features. The code that was added manually appears in bold.
Listing 11.20. The ANIMATED.CPP file.
// animated.cpp : implementation file // #include "stdafx.h" #include "wpu32cc.h" #include "animated.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CAnimateDlg dialog CAnimateDlg::CAnimateDlg(CWnd* pParent /*=NULL*/) : CDialog(CAnimateDlg::IDD, pParent) { //{{AFX_DATA_INIT(CAnimateDlg) //}}AFX_DATA_INIT } void CAnimateDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAnimateDlg) DDX_Control(pDX, IDC_ANIMATE, m_AnimateCtrl); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAnimateDlg, CDialog) //{{AFX_MSG_MAP(CAnimateDlg) ON_BN_CLICKED(IDC_PLAY, OnPlay) ON_BN_CLICKED(IDC_STOP, OnStop) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CAnimateDlg message handlers BOOL CAnimateDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // Use the filename to open a file, an identifier // to open a resource. // m_AnimateCtrl.Open("CLOCK.AVI"); // Use AVI file m_AnimateCtrl.Open(IDR_CLOCK); // Use AVI resource return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void CAnimateDlg::OnPlay() { // TODO: Add your control notification handler code here m_AnimateCtrl.Play(0, (UINT)-1, 1); } void CAnimateDlg::OnStop() { // TODO: Add your control notification handler code here m_AnimateCtrl.Stop(); }
Listing 11.21 is the ANIMATED.H header file that accompanies the ANIMATED.CPP file shown in Listing 11.20. ANIMATED.H was modified only by ClassWizard; no manual changes were necessary.
Listing 11.21. The ANIMATED.H file.
// animated.h : header file // ///////////////////////////////////////////////////////////////////////////// // CAnimateDlg dialog class CAnimateDlg : public CDialog { // Construction public: CAnimateDlg(CWnd* pParent = NULL); // Standard constructor // Dialog data //{{AFX_DATA(CAnimateDlg) enum { IDD = IDD_ANIMATE }; CAnimateCtrl m_AnimateCtrl; //}}AFX_DATA // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAnimateDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(CAnimateDlg) virtual BOOL OnInitDialog(); afx_msg void OnPlay(); afx_msg void OnStop(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
As I mentioned earlier, you would usually bind your CAnimateCtrl object to an Animation control using ClassWizard. Once you have bound the CAnimateCtrl object, you can then initialize your Animation control in the OnInitDialog() function.
Generally, you will want to load an ANI clip (either from a resource or an ANI file). Often the animation will be started at this time. You can usually use the Autoplay attribute to start an animation at creation time.
The following code shows (in bold) the line that initializes the Animation control. I've also added the statement that would be used if you wanted to start the animation immediately when the animation window is created:
BOOL CAnimateDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // The first example of Open() will open an AVI file: // m_AnimateCtrl.Open("CLOCK.AVI"); // The second example of Open() will open an AVI resource: m_AnimateCtrl.Open(IDR_CLOCK); // Start playing the AVI clip, with frame zero, // to the end of the clip. Play it one time only: m_AnimateCtrl.Play(0, (UINT)-1, 1); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
To create a CAnimateCtrl object in a window that isn't a dialog box, you can call CAnimateCtrl::Create(). This function takes four parameters: a style (only the standard dialog box control styles are supported by an Animation control), a rect structure specifying the Animation control's location and size, a pointer to the parent window, and the control ID.
CAnimateCtrl::Create( WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ACS_AUTOPLAY, rect, this, 0);
There are many situations in which you might want to create an Animation control in a nondialog window. Using CAnimateCtrl::Create() is an easy way to do so.
Table 11.2 lists the options for the window styles of an Animation control. None of these options are mandatory.
Attribute | Description |
ACS_CENTER | The actual window created will be centered in the window defined by either the dialog box template or the rect structure passed to the Create() function. |
ACS_TRANSPARENT | The background of the Animation control will be transparent rather than the background color that was specified by the AVI file. |
ACS_AUTOPLAY | The AVI clip will automatically play once the Animation control has been created and the control is visible. Once Autoplay has started, the AVI clip will repeat automatically until explicitly ended. |
The CAnimateCtrl object lets you play, seek, and stop the animation. The Animation control doesn't have any facility to return the current frame, nor is there a method to determine the AVI file's size or number of frames.
NOTE
Once an Animation control has been stopped, you must restart it from a known point. There is no way to determine the currently stopped frame.
CAUTION
Animation controls only support AVI clips of 65,535 frames. At 20 frames per second, this limits your AVI clip to about 55 minutes in length. No full-length movies here!
The CAnimateCtrl Animate object takes no input from the user and can only provide output (the AVI clip). You can create start and stop buttons for the user, but the usefulness of such controls is questionable. For more advanced animation techniques, it would be best if you used the MCIWnd class window instead.
You can initialize the CAnimateCtrl Animation control using the CAnimateCtrl::Open() member function. This function takes either the filename for an AVI file or the resource identifier for an AVI resource. An example of how to create an AVI resource was provided earlier.
This chapter showed you how to use the Win32 Common Controls, which offer the programmer a new set of dialog box controls. Controls covered in this chapter include the following: