Premiere Issue

Controlling the cursor

by Kent Reisdorph

Windows lets you control the mouse cursor's appearance. When used properly, cursors give your application a polished look while providing visual feedback for users. (Of course, as with all GUI features, you shouldn't overdo it.) Although VCL makes cursor changes a breeze, you need to be aware of certain aspects, which we'll discuss in this article.

VCL cursor architecture

The VCL cursor architecture is relatively simple, but you can easily get lost if you're new to C++Builder and VCL. Let's examine how VCL manages and implements cursors.

The Cursor property

All VCL components derived from TControl have a Cursor property. This property controls the cursor that Windows displays when the user passes the mouse pointer over a component. (Remember, forms are components, too.)

By default, the Cursor property is set to crDefault. The crDefault style tells Windows to display the default cursor for that type of window (or component, if you prefer). For instance, if you have an Edit component on a form, then Windows will, by default, change the cursor to the I-beam shape when you pass it over that component. (This change happens only at runtime, not at design time.) You don't have to do anything to get the default cursor for your components, since the Cursor property always defaults to crDefault.

Besides changing cursors for an individual component, you can change the Cursor property for the Screen object. In VCL, the Screen object represents your application's client area. If you want a cursor change to take place regardless of the component that the cursor is over, then you'll need to modify the Cursor property of the Screen object, rather than the Cursor property of the components or of the form. Let's see why this is so.

Cursors operate on at least three levels as far as VCL is concerned. If you change the Cursor property for an individual component, then the cursor changes only when it's over that component. If you change the Cursor property for the form, then the cursor will change when it's over the form but not when it's over any of the components on the form. However, if you change the Cursor property for the Screen object, then the cursor will change when it's over the form and when it's over all components on the form. Whether you change the cursor for an individual component, for the form, or for the entire application depends on your needs for the present circumstance.

The Cursors property

In addition to the Cursor property, we need to mention the Cursors property, a member of the TScreen class. Cursors is an array property that contains a list of the cursors currently available to your application. (At first you may find it somewhat confusing to have both a Cursor property and a Cursors property, but after you've been around the block a time or two, you'll get the hang of it.)

An out-of-the-box C++Builder application includes many cursors. Table A lists them and briefly describes each one.

Table A: VCL cursors
Cursor Description
crDefaultDefault cursor for the component
crNoneNo cursor; cursor disappears when over the component
crArrowStandard arrow
crCrossCrosshair
crIBeamText-editing I-beam
crSizeSame as crArrow
crSizeNESWSizing cursor oriented diagonally from northeast to southwest
crSizeNSSizing cursor oriented vertically
crSizeNWSESizing cursor oriented diagonally from northwest to southeast
crSizeWESizing cursor oriented horizontally
crUpArrowArrow pointing up
crHourGlassHourglass
crDragArrow with a blank page in the lower-right corner
crNoDropDiagonal slash through a white circle
crHSplitBlack double-vertical bar with arrows pointing right and left
crVSplitBlack double-horizontal bar with arrows pointing up and down
crMultiDragArrow with three blank pages in the lower-right corner
crSQLWaitSQL beneath a wait cursor
crNoDiagonal slash through a black circle
crAppStartArrow combined with an hourglass
crHelpArrow next to a black question mark

VCL provides some of the cursors listed in Table A; the rest are built into Windows. It isn't necessary to know which are which.

If you don't want to use one of the built-in cursors, you can add your own custom cursors to the Cursors array. Before we discuss changing cursors at runtime, let's take a look at installing custom cursors.

Installing custom cursors

Although C++Builder provides the most commonly used cursors, you still may require custom cursors for your applications. Creating and implementing custom cursors isn't terribly complicated, but you need to know which hoops to jump through and in what order to jump. Adding a custom cursor requires that you take the following steps:
  1. Create the cursor with a resource editor.
  2. Bind the cursor resource to your EXE file.
  3. Add the new cursor to the Cursors array at runtime.

Let's examine each of these actions in more detail.

Create the cursor

The first thing you must do is to create the cursor itself--you can use any resource editor for this job. The Image Editor utility that comes with C++Builder works pretty well for creating simple resources such as cursors; if you're a longtime Borland user, you might prefer to use Resource Workshop. Either way, it's a trivial task. (Note that if you use Image Editor to create your cursor, you need to start with a new resource project and add a cursor resource to the project. When you save the project, the Image Editor will compile the project into a binary resource file that the linker will need in order to bind the cursor resource to the executable file.)

I'm not going to describe the exact steps required to create a cursor--check resource editor documentation for details. The important thing is that when you're done, you'll end up with either a compiled resource file (RES) or a resource script file (RC).

Bind the cursor to your EXE file

Before your application can access a custom cursor, you must bind the resource to the executable file. (You could also place the cursor in a DLL, but we'll leave that discussion for another article.) The linker actually carries out the process of binding the cursor resource to the executable file--all you have to do is tell the linker where to find the cursor resource.

To add the cursor resource to your EXE, just choose Add To Project from C++Builder's Project menu or click the Add To Project button on the ToolBar. When the Add To Project dialog box opens, locate the RES or RC file in which the cursor resides and click OK. Now, the C++Builder IDE will add either a

USERES()
or
USERC()
directive to your project source file. For example, if you had a cursor in a file called CROSS.RES, the IDE would add the following line to your project source file:
USERES("cross.res");
The next time you click the Run button, do a Make, or do a Build All, the linker will bind the cursor resource to the executable file. Now that the cursor is in the EXE, you need to put it to use.

Add the cursor to the Cursors array

The Cursors array contains a list of all the cursors available to the application. Before you can use your custom cursor, you must load it into the Cursors array. The predefined cursors occupy indexes -17 through 0 in the array--you may use any other index number when you add a custom cursor.

Add the cursor to the array using the Windows API function LoadCursor(), which looks like this:

Screen->Cursors[1]= LoadCursor(HInstance,"IC_PENCIL");
This line tells VCL to load the cursor called
IC_PENCIL
from the program's executable file and assign it to index 1 of the Cursors array. If you have several cursors, you may want to define and use constants rather than raw index numbers, as follows:
const TCursor crPencil = 1;
Screen->Cursors[crPencil]
=
LoadCursor(HInstance,
"IC_PENCIL");
Once you've loaded your new cursor into the Cursors array, you can use it. Let's look at how to do so.

Changing cursors

To change the cursor for a particular component at design time, just alter the Cursor property in the Object Inspector. Each time the mouse cursor passes over the component, the cursor will change accordingly. Set the cursor for any specialty components at design time whenever applicable.

While you can change cursors at design time, you'll most often want to change the cursor at runtime. For example, if your program starts a lengthy process, you should let the user know by changing the cursor to the hourglass shape (sometimes called the wait cursor) for the duration of the process, then back to the arrow shape when the process finishes.

To change a cursor at runtime, you set the Cursor property of the form, Screen object, or other component to one of the cursors contained in the Cursors array:

Screen->Cursor = crHourGlass;
It's that simple for the built-in types. You can use any of the constants from Table A to quickly and easily change the cursors in your applications at runtime.

If you want to change to a custom cursor that you've previously added to the Cursors array, then you specify the actual array index:

Screen->Cursor = (TCursor)1;
While the cast to TCursor isn't strictly necessary, the step avoids the compiler warning Assigning int to TCursor. The program would probably work fine anyway, but I always apply the cast to avoid the warning.

A better approach is to use a constant for your cursor, as we discussed earlier. Then you can use this line:

Screen->Cursor = crPencil;
Now you have more readable code that will compile without issuing a warning.

Cursor concerns

Earlier, I mentioned that you can change the cursor for the Screen object to achieve a global cursor change effect. However, when you change the cursor for the Screen object, you may get some unexpected results. Specifically, you'll notice that Windows uses your cursor shape while the cursor is over any part of your application except the title bar and the menu bar (the nonclient area of the application). When the cursor is over the title bar, it reverts to the arrow shape. The result is that users can minimize or close the application, move the window around onscreen, and even access the menu. This is standard Windows behavior, but it's probably not what you want to have happen.

In many circumstances, you don't want the cursor to revert to the arrow shape when it's over the nonclient areas. For example, a critical process in an application should alert the user by displaying the hourglass cursor and should then prevent the user from doing anything else in the application until the process has finished. If this type of behavior is what you're after, then you must take extra steps to get VCL to perform for you.

Essentially, you need to grab the WM_SETCURSOR message and handle it yourself. (See the article "Incorporate Custom Message-Handling in Your Applications" for details on handling messages outside of VCL.) After you set up the message map for your application, your custom message handler for the WM_SETCURSOR message should look something like this:

void __fastcall TMainForm::OnSetCursor(TWMSetCursor &Message) {
if (working) {
  // load the stock Windows hourglass cursor 
  ::SetCursor(LoadCursor(NULL, IDC_WAIT));
   Message.Result= true;
}
else
  TForm::Dispatch(&Message);
}
When you use this technique to change the cursor, it won't revert to the arrow shape when it moves over the nonclient area of the application. While this technique involves a little more work, it gives you more flexibility than you get with the pure VCL method.

Conclusion

Effective use of cursors can help to distinguish your application from the competition's. As we've explained in this article, changing cursors--even adding custom cursors--is simple once you know how to do it.

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.