Teach Yourself Visual C++® 5 in 24 Hours

Previous chapterNext chapterContents


- Hour 14 -
Icons and Cursors

Icons and cursors are two commonly used GDI objects. In this hour, you will learn how to

There are several examples in this hour; you will add icons to pushbutton controls, clip cursors to a specific rectangle, and change the cursor to an hourglass to indicate that a long process is in progress.

What Is an Icon?

New Term: An icon is a small bitmap that represents another object in a Windows program.

Icons are used to represent minimized child windows in an MDI application. Icons also are widely used by Windows itself. When a program is minimized, its icon is displayed in the Windows 95 taskbar. When you're using the Explorer, the icon representing an application associated with each file is displayed next to the file's name. Windows displays the program's icon in the upper-left corner of the main window title bar.

The Windows 95 Explorer uses the icon resources associated with your program when determining which icons to display. If large and small icons are available, the Explorer uses the icon resources from your application's EXE file. However, if you provide only a large icon, the Explorer synthesizes a small icon, which usually results in a small, distorted icon.


Just a Minute: Icons also are used in dialog boxes. For example, the message dialog boxes discussed in Hour 4, "Dialog Boxes and C++ Classes," use icons to indicate the type of message conveyed. It's also common practice to include an application's icon in the About dialog box.

There are several different types of icon resources. In Hour 18, "List View Controls," you will use large and small icons in a list view control. Four different types of icons are available:

Icons are similar to bitmaps, which are covered in Hour 15, "Using Bitmaps." However, the code required to use an icon is so simple that there's not even an MFC class named CIcon dedicated to making using icons easier. Instead, you manipulate an HICON, or handle to an icon, directly.


Time Saver: An image list is ideal for collecting icon images. Any image stored in an image list can be converted into an icon using the ExtractIcon member function. Image lists are covered in Hour 17, "Using Image Lists and Bitmaps."

Creating Icons Using the Image Editor


Just a Minute: Because icons are resources, you add them to a program's resource file just as you do bitmaps, menus, and dialog boxes. You create new icons using the resource editor.

When creating a new project, AppWizard creates a set of default icons for your project automatically. You can use the Developer Studio image editor to edit or create new icons for your project.

To open the image editor, open the ResourceView in the project workspace and then open the Icon folder. Double-click any icon resource contained in the folder to open the editor. In an MDI application created by AppWizard, two icon resources will be defined for a new project:

You can change these icon resources to represent the application with which you are working.

The color palette is displayed whenever you are editing an image resource. The color palette consists of several colored boxes. To change the color of the current drawing tool, click the color you want. There are two special color icons in the color palette:

You can find the transparent and reverse video colors on the upper-right corner of the color palette. Switch to the transparent color by clicking the small video display icon with a green screen, and switch to the reverse video color by clicking the small video display with the red screen.

Inserting a New Icon Resource

To insert a new icon resource into an existing project, right-click the Icon folder in the resource view, and select Insert Icon from the pop-up menu; this opens the image editor with a blank icon, ready for editing. You can change attributes for icon resources, as with all resources, by double-clicking the edge of the icon resource or by pressing Alt+Enter on the keyboard.

Loading an Icon

After you have added an icon to a project, loading and displaying it requires three lines of code. To load an icon and prepare it for display, use the LoadIcon function:

HICON hIcon = AfxGetApp()->LoadIcon( IDI_LOGO );

Because LoadIcon is a CWinApp member function, a pointer to the application's CWinApp object must be fetched using the AfxGetApp function.

After the icon has been loaded, display it by calling DrawIcon:

pDC->DrawIcon( 0,0, hIcon );

The DrawIcon function is a member of the CDC class. The coordinates and icon handle must be passed as parameters.

After using LoadIcon, release the icon resource by calling the DestroyIcon function:

DestroyIcon( hIcon );


CAUTION: If you forget to call DestroyIcon, the memory allocated for the icon isn't released.

Changing a Program's Icon

The icon used for a program is created by AppWizard when you initially create the project. To change this icon, open the image editor by double-clicking the application's icon in the resource view Icon folder.

After opening the icon, use the image editor tools to modify the icon as you want. Every application written for Windows can have its icon displayed in large and small formats. If you edit one of the icon formats, make sure you make corresponding changes in all formats supported by the icon. To display and edit all the available formats, click the drop-down combo box above the image editor, which displays all the supported formats for the icon. Selecting a new format loads that version of the icon into the image editor.

Every child window type also has a unique icon. You can edit that icon just as you do the program's main icon. As discussed earlier this hour, the child window icon is named with a shared resource identifier in the form IDR_MYAPPTYPE, where the application is named MyApp.

Retrieving Icons from Image Lists

When an image is stored in an image list, the image list often draws the item directly, using the MFC CImageList::Draw member function. However, you also can have the image list create an icon based on an individual image. The CImageList::ExtractIcon member function is used to create such an icon:

HICON hIcon = m_imageList.ExtractIcon( 2 );

Using this member function is useful when several icons must be stored together.

Displaying an Icon on a Button

Another useful way to use an icon is to display it in a button. Beginning with Windows 95, it's possible to display an icon in a button almost as easily as displaying a text string. Use the CButton member function SetIcon to set a button's icon. The icon must be loaded before it is used and destroyed after the button is destroyed.

Adding New Icon Resources

For this example, add two buttons to the DCTest About dialog box. These two "stop-light" buttons work just like the traditional OK and Cancel buttons. Figure 14.1 shows the IDI_RED icon; although you can't tell from the figure, this icon consists of a red circle surrounded by the transparent color. Also, create a similar icon named IDI_GREEN, using a green circle surrounded by the transparent color.

Figure 14.1.
The new icons used in the DCTest example.

Use the two icons created earlier to label buttons in the DCTest About dialog box. Modify the IDD_ABOUT dialog box by adding an extra button to it, as shown in Figure 14.2.

Figure 14.2.
The About dialog box used in the Icon example.

Use the values from Table 14.1 to set the attributes for the two buttons in the About dialog box. Use ClassWizard to add the two buttons to the CAboutDlg class as CButton variables.

Table 14.1. Values used for button controls in the DCTest About dialog box.

ID Variable Name Control Type Attributes
IDOK m_btnOkay CButton Visible, Tabstop, Icon, Default
IDCANCEL m_btnCancel CButton Visible, Tabstop, Icon

Buttons that have icon labels instead of text must have the Icon attribute set. Review each button's Properties dialog box under the Styles tab and make sure the Icon option is checked.

Changes to the CAboutDlg Class

Add two new variables to the CAboutDlg class. These variables are used to store the handles to icons displayed on the dialog box buttons. Add the source code from Listing 14.1 to the Implementation section of the CAboutDlg project. Also, add a declaration for a destructor for the CAboutDlg class, just after the constructor declaration.

TYPE: Listing 14.1. Additions to the CAboutDlg class declaration.

// Implementation

public:

    "CAboutDlg();

protected:

    HICON   m_hIconOkay;

    HICON   m_hIconCancel;

The icons are added to the dialog box's buttons when the dialog box receives the WM_INITDIALOG message. Using ClassWizard, add a message-handling function for WM_INITDIALOG to the CAboutDlg class. Use the default name provided by ClassWizard, OnInitDialog. Edit the OnInitDialog member function so it looks like the code provided in Listing 14.2.

TYPE: Listing 14.2. The AboutDlg::OnInitDialog member function.

BOOL CAboutDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

    CWinApp* pApp = AfxGetApp();

    if( pApp != 0 )

    {

        m_hIconOkay = pApp->LoadIcon( IDI_GREEN );

        m_hIconCancel = pApp->LoadIcon( IDI_RED );

        ASSERT(m_hIconOkay);

        ASSERT(m_hIconCancel);

        m_btnOkay.SetIcon( m_hIconOkay );

        m_btnCancel.SetIcon( m_hIconCancel );

    }

    return TRUE;

}

The source code in Listing 14.2 loads the two stop-light icons created earlier. After the icons are loaded, the icon handles are passed to the SetIcon function for each of the buttons contained in the dialog box.


Just a Minute: When an icon is drawn on a button, the icon is clipped if necessary. The icon isn't scaled to fit inside the button; it is displayed "actual size." This might mean that you must experiment with the relative sizes of the icon and the button.

As the dialog box is destroyed, the icons previously loaded using LoadIcon must be destroyed. Use the source code from Listing 14.3 to create the CAboutDlg class destructor.

TYPE: Listing 14.3. Using the CAboutDlg class destructor to destroy the previously loaded icons.

CAboutDlg::"CAboutDlg()

{

    DestroyIcon( m_hIconOkay );

    DestroyIcon( m_hIconCancel );

}

Compile and run the DCTest example. Figure 14.3 shows the DCTest About box with icons placed in the pushbutton controls.

Figure 14.3.
The DCTest dialog box after adding icons to the pushbutton controls.

What Is a Cursor?

A cursor is the little bitmap that moves around the screen providing feedback about the current mouse position. The cursor also provides other types of feedback:

The most commonly used cursors are supplied by Windows. The hourglass, I-beam, and arrow cursors are three of the more popular standard cursors. In addition, each program can define cursors that you add to the application just as you do other resources.


Just a Minute: The cursor is an important part of the feedback supplied to a user of a Windows program. Changing the style of cursor is an easy way to alert the user that a change of some type has occurred. Many times, changing the cursor is the only type of feedback required.

Using Cursors in Windows Programs

Most window classes have a cursor assigned to the class. In almost all cases, it's the standard arrow cursor. This means that for most default behavior, you don't have to do anything to use a cursor; Windows provides it free of charge. However, there are some situations in which you must take control over the cursor yourself. For the examples in this hour, you create an SDI project named Cursor.

Creating a Cursor Resource

You create a cursor image using the Developer Studio image editor, much like icons were created earlier this hour. Figure 14.4 shows the cursor used in later examples ready for editing in the image editor.

Create the cursor shown in Figure 14.4 and name it IDC_BANG. To create a cursor resource, right-click in the resource view window and choose Insert... from the pop-up menu; then select Cursor from the Resource Type dialog box. The editing tools you use to create a cursor are the same ones you used to create icons earlier in this hour. The standard Windows naming convention is for cursors to have names beginning with IDC_.

Adding a Hotspot to a Cursor

New Term: A hotspot is the actual point that determines the current cursor position.

Every cursor has a hotspot. The hotspot for the arrow cursor is located at the very tip of the arrow. The default hotspot for a cursor is the upper-left corner of the cursor. The cursor-image editor enables you to move the hotspot to a position that is reasonable for the cursor image.

Figure 14.4.
The IDC_BANG cursor inside the Developer Studio image editor.

For example, the IDC_BANG cursor you created in the previous section will not work properly if a new hotspot isn't defined. Because the current hotspot is part of the background, this cursor won't work as well for operations in which the mouse clicks must be accurate. One solution, as shown in Figure 14.5, is to modify the cursor to add a well-defined hotspot to the cursor--in this case a bull's-eye, or target, in the upper-left corner of the cursor bitmap.

Figure 14.5.
The new version of IDC_BANG, with a hotspot and a bull's-eye.

The hotspot control is a button located above the edited image. Click the hotspot button and then click the new hotspot pixel. For IDC_BANG, create a new hotspot in the center of the bull's-eye.

Changing a Cursor

Changing the current mouse cursor is probably the most common cursor-related activity in Windows programming. The operating system sends a WM_SETCURSOR message to a window as the mouse cursor passes over it. You can use this message to change the cursor, or you can let Windows choose the cursor that was defined for the window when it was registered.

To change the current cursor for a window, you handle the WM_SETCURSOR message. Using ClassWizard, add a message-handling function for WM_SETCURSOR to the CAboutDlg class. Listing 14.4 contains source code for OnSetCursor that changes the cursor to IDC_BANG.

TYPE: Listing 14.4. Changing the cursor during WM_SETCURSOR.

BOOL CAboutDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest,

                            UINT message)

{

    // Load and set the new cursor. Return TRUE to stop

    // further processing of this message.

    CWinApp* pApp = AfxGetApp();

    HICON hIconBang = pApp->LoadCursor( IDC_BANG );

    SetCursor( hIconBang );

    return TRUE;

}

Conditionally Changing a Cursor

Changing a cursor conditionally is often convenient, based on the cursor's location. Listing 14.5 is a new version of OnSetCursor that restores the arrow cursor when the cursor is over the dialog box's OK button.

TYPE: Listing 14.5. Conditionally changing the cursor during WM_SETCURSOR.

BOOL CAboutDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest,

                            UINT message)

{

    BOOL    bReturn;

    CRect   rcBtn;

    CPoint  ptCursor;

    //

    // Calculate the current cursor position, and change the

    // cursor if we're not over the OK button.

    //

    CWnd*   pBtn = GetDlgItem( IDOK );

    pBtn->GetWindowRect( rcBtn );

    GetCursorPos( &ptCursor );

    if( rcBtn.PtInRect( ptCursor ) == FALSE )

    {

        // Load and set the new cursor. Return TRUE to stop

        // further processing of this message.

        CWinApp* pApp = AfxGetApp();

        HICON hIconBang = pApp->LoadCursor( IDC_BANG );

        SetCursor( hIconBang );

        bReturn = TRUE;

    }

    else

    {

        // We're over the OK button, use the default cursor.

        bReturn = CDialog::OnSetCursor(pWnd, nHitTest, message);

    }

    return bReturn;

}

The two key lines in Listing 14.5 retrieve the current mouse cursor position as a CPoint object. The CPoint object is tested to see whether it is inside the boundary of the OK pushbutton:

GetCursorPos( &ptCursor );

if( rcBtn.PtInRect( ptCursor ) == FALSE )

{

    // cursor not over rectangle

}

Using the Standard Cursors

Windows provides 19 standard cursors for use in your programs. These cursors often are used by Windows. For example, the IDC_APPSTARTING cursor is displayed when an application is launched by Windows. Table 14.2 lists the names and descriptions of the 19 standard cursors.

Table 14.2. The standard cursors provided by Windows.

Cursor Name Description
IDC_ARROW Arrow cursor
IDC_IBEAM I-beam cursor
IDC_WAIT Hourglass cursor
IDC_CROSS Crosshair cursor
IDC_UPARROW Up-arrow cursor
IDC_SIZENWSE Sizing cursor, points northwest and southeast
IDC_SIZENESW Sizing cursor, points northeast and southwest
IDC_SIZEWE Sizing cursor, points west and east
IDC_SIZENS Sizing cursor, points north and south
IDC_SIZEALL Sizing cursor, points north, south, east, and west
IDC_NO "No" cursor (circle with a slash through it)
IDC_APPSTARTING Application-starting cursor
IDC_HELP Help cursor
IDI_APPLICATION Application icon
IDI_HAND Stop sign icon
IDI_QUESTION Question mark icon
IDI_EXCLAMATION Exclamation point icon
IDI_ASTERISK Asterisk or information icon
IDI_WINLOGO Windows logo icon

Using these cursors is similar to using stock objects. Listing 14.6 uses the IDC_UPARROW cursor in response to WM_SETCURSOR.

TYPE: Listing 14.6. Using a standard Windows cursor.

BOOL CAboutDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest,

                            UINT message)

{

    // Load and set the new cursor. Return TRUE to stop

    // further processing of this message.

    CWinApp* pApp = AfxGetApp();

    HICON hIcon = pApp->LoadStandardCursor( IDC_UPARROW );

    SetCursor( hIcon );

    return TRUE;

}

A cursor set in response to the WM_SETCURSOR message will interfere with the remaining examples in the hour. After you are finished with this example, remove the OnSetCursor function using ClassWizard.

Changing the Cursor to the Hourglass


Just a Minute: When a large amount of processing is performed, ignoring input from the user is common. It's considered good manners for a Windows program to change the cursor to an hourglass when user input won't be acknowledged.

A common place to ignore user input is during long initialization routines. It's common to display a user interface but disregard user input until the initialization is complete. It can take several seconds before an application is ready for input, particularly in applications that work with large amounts of data that must be initialized. In these cases, you should use the BeginWaitCursor and EndWaitCursor functions.

To demonstrate how these functions are used, add a message-handling function to the CAboutDlg class using ClassWizard. Add a message-handling function for WM_TIMER, and accept the default function name provided by ClassWizard. Listing 14.7 contains the source code for the OnInitDialog and OnTimer functions.

TYPE: Listing 14.7. Modifying OnInitDialog and OnTimer to use the hourglass cursor.

BOOL CAboutDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

    CWinApp* pApp = AfxGetApp();

    if( pApp != 0 )

    {

        m_hIconOkay = pApp->LoadIcon( IDI_GREEN );

        m_hIconCancel = pApp->LoadIcon( IDI_RED );

        ASSERT(m_hIconOkay);

        ASSERT(m_hIconCancel);

        m_btnOkay.SetIcon( m_hIconOkay );

        m_btnCancel.SetIcon( m_hIconCancel );

    }

    SetCapture();

    BeginWaitCursor();

    SetTimer( 1, 15000, NULL );

    return TRUE;

}



void CAboutDlg::OnTimer(UINT nIDEvent)

{

    ReleaseCapture();

    EndWaitCursor();

    KillTimer( 1 );

}

In Listing 14.7, the OnInitDialog function simulates the beginning of a long processing period. The SetCapture and BeginWaitCursor functions are called to change the cursor to an hourglass. While changed, the cursor cannot be used to interact with any controls. A five-second timer is started, which calls the OnTimer function when the timer expires. The OnTimer function restores the cursor and kills the timer.


Time Saver: The order of the statements in OnInitDialog is important. Before calling BeginWaitCursor, the mouse must be captured using SetCapture; otherwise, the hourglass cursor immediately reverts to the arrow cursor.

Clipping a Cursor

There are times when restricting a cursor to a single window is convenient. This is usually the case when you are working with error messages, or in other situations in which you would like to force the user to make a selection. Forcing a cursor to stay with the boundaries of a single window is known as clipping the cursor.

As an example, force the cursor to stay over the Cursor project's About dialog box. Using ClassWizard, add message-handling functions for WM_DESTROY and WM_MOVE to the CAboutDlg class. Add the source code in Listing 14.8 to the CAbout::OnMove and CAbout::OnDestroy member functions.

TYPE: Listing 14.8. Source code used to form a clipping region for the cursor.

void CAboutDlg::OnMove(int x, int y)

{

    CDialog::OnMove(x, y);



    CRect   rcCursor;

    GetWindowRect( rcCursor );

    ClipCursor( &rcCursor );

}



void CAboutDlg::OnDestroy()

{

    ClipCursor( NULL );

    CDialog::OnDestroy();

}

When the MFC framework creates the About dialog box and moves it to the center of the view window, the WM_MOVE message is sent to the CAboutDlg class. Inside the OnMove function, the dialog box's screen coordinates are used to set the clipping rectangle for the cursor. When the dialog box is destroyed, the WM_DESTROY message is handled by the CAboutDlg::OnDestroy function, and the clipping rectangle is reset.


CAUTION: It is important to reset the cursor clipping region by calling ClipCursor(NULL) when the window is destroyed or when the clipping region is no longer needed. If this function isn't called, the cursor will be restricted to the requested rectangle even after the window has disappeared.

Summary

In this hour you learned how to use icons in Windows programs. You learned how to change the main program icon, as well as child icons. You used a sample program to learn the functions used to load, draw, and destroy icon resources. You also learned how to use cursors to provide feedback when programming Windows applications.

Q&A

Q What are the advantages of providing an icon for a button versus using the MFC CBitmapButton class?

A Assigning an icon to a button is much simpler than using the CBitmapButton class. It is also more efficient because Windows will handle drawing the image instead of the MFC class library.

Q The CDC::DrawIcon function will only draw an icon the size of its original image. How can I draw an icon larger than its original size?

A You can use the DrawIconEx function to draw an icon to an arbitrary size. Unlike DrawIcon, the DrawIconEx function isn't a member of the CDC class. You must pass the internal handle used by the CDC object as the first parameter in the call to DrawIconEx, as shown in the OnDraw function:
void CMyTestView::OnDraw(CDC* pDC)

{

    CRect rc;

    GetClientRect(&rc);

    HICON hIcon = AfxGetApp()->LoadIcon(IDI_TEST);

    DrawIconEx(pDC->m_hDC,

               0,

               0,

               hIcon,

               rc.Width(),

               rc.Height(),

               0,

               NULL,

               DI_NORMAL);

    DestroyIcon(hIcon);

}
The really interesting parameters passed to DrawIconEx are parameters five and six; these are the width and height of the final image.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you've learned, and begin thinking ahead to putting your knowledge into practice. The answers to the quiz are in Appendix B, "Quiz Answers."

Quiz

1. What function is used to associate an icon with a pushbutton?

2. What is the name of the area on the cursor that is used as the current mouse location?

3. What function is used to restrict a cursor to a specific rectangle?

4. What function is used to trap all mouse messages?

5. What function is used to change the current cursor?

6. What message must be handled in order to change the current cursor?

7. What is the size of an application's small icon?

8. What function is used to change to the hourglass cursor?

Exercise

1. Modify Listing 14.5 so that the hourglass cursor is shown unless the mouse is over the OK or Cancel button.


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.