Working with the Mouse

Working with the Mouse

In a Windows Forms application, a great deal of user interaction occurs through the mouse. As the mouse is moved and clicked by the user, events are raised and sent to your application. Although you can create an application that doesn’t use the mouse for user input, the mouse is the preferred method of input for most Windows users. The following subsections describe how you can use the mouse as an alternative to keyboard input.

Handling Mouse Movement Events

The mouse communicates with your application through events that are raised as the user interacts with the mouse. As the mouse pointer moves over a control or a form, events are raised to enable you to track the current mouse position. Events are passed only to a single form or control; normally, this is the object that’s located under the mouse pointer. Table 13-1 lists the events related to mouse movement.

Although most of the events listed in Table 13-1 accept EventHandler delegates, the event handler for the MouseMove and MouseWheel events must be an instance of the MouseEventHandler class rather than EventHandler. Instances of the MouseEventHandler delegate are created as shown here:

MouseMove += new MouseEventHandler(form_MouseMove);
private void form_MouseMove(object sender, MouseEventArgs e)
{
   
    

As shown in the preceding example, a method associated with a Mouse­EventHandler delegate accepts a MouseEventArgs reference as its second parameter, instead of a reference to an EventArgs object. The MouseEventArgs class provides additional information about the location and state of the mouse. The MouseEventArgs class exposes the following properties:

  • Button  A value from the MouseButtons enumeration that indicates which mouse button was clicked by the user

  • Clicks  The number of times the mouse button has been clicked recently

  • Delta  The number of positions, or detents, that the mouse wheel has been rotated

  • X  The x-coordinate of the mouse, in client coordinates

  • Y  The y-coordinate of the mouse, in client coordinates

So what happens if you’re handling an event that provides a simple Event­Args parameter and you need the mouse coordinates? You’re in luck, because the Form class includes the MousePosition property, which returns the current mouse position, as shown here:

private void main_Click(object sender, System.EventArgs e)
{
    Point mousePoint = PointToClient(MousePosition);
    string msg = string.Format("Mouse click at X:{0}, Y:{1}",
                                mousePoint.X,
                                mousePoint.Y);
    MessageBox.Show(msg);
}

The location returned from MousePosition is a Point structure, which consists of the xy-coordinates for a specific location on the desktop. The Point structure returned by MousePosition is relative to the entire screen rather than to the current form or control. The preceding code uses the PointToClient method to transform the location into client coordinates.

The MouseButtons enumeration, shown in Table 13-2, is used to determine the current state of the mouse buttons. Values in the MouseButtons enumeration can be combined as necessary to provide an accurate picture of the mouse state.

Table 13-2.  MouseButtons Enumeration Values 

Value

Description

None

No mouse button was pressed.

Left

The primary (usually left) mouse button was pressed.

Middle

The middle mouse button was pressed.

Right

The secondary (usually right) mouse button was pressed.

XButton1

The first XButton on a Microsoft IntelliMouse Explorer was pressed.

XButton2

The second XButton on an IntelliMouse Explorer was pressed.

The last two entries in Table 13-2 refer to extra buttons that are present on the IntelliMouse Explorer mouse. These mice have five mouse buttons; the two additional XButtons are typically used for navigation.

All of the mouse movement events can be accessed through the Forms Designer’s Properties window, except for the MouseWheel event. In most cases, controls that can be manipulated with the MouseWheel event will already provide default handling for this event. To explicitly handle MouseWheel events, you must manually add an event handler delegate to your form or control, as shown here:

nameListBox.MouseWheel += new MouseEventHandler(list_MouseWheel);
private void list_MouseWheel(object sender, MouseEventArgs e)
{
   
    

}
Handling Mouse Selection Events

The most common use of the mouse is for item selection. In most cases, you don’t need to detect mouse clicks directly. Most controls and other objects that accept user input generate Click events, as discussed in Chapter 12. Table 13-3 lists the mouse selection events.

Table 13-3.  Mouse Selection Events 

Event

Description

MouseDown

A mouse button was pressed over the control or form.

MouseUp

A mouse button was released over the control or form.

Click

The control or form was clicked.

DoubleClick

The control or form was double-clicked.

The MouseDown and MouseUp events are associated with Mouse­Event­Handler delegates, which enable them to pass a reference to a MouseEventArgs object as a parameter. The Click and DoubleClick events are associated with Event­Handler delegates, however, and don’t provide any additional information about the mouse position or state.

Although mouse events are raised asynchronously and can come at any time, there’s a guarantee with regard to the relative order of mouse events: The MouseEnter and MouseLeave mouse events bracket other mouse events, such as MouseHover, MouseMove, and any mouse selection events. A Click event occurs only after MouseDown and MouseUp events, and a DoubleClick event occurs only after a Click event.

The companion CD includes MouseEvents, an example project that demonstrates how mouse events are raised and handled. The main form from MouseEvents is shown in Figure 13-1.

Figure 13-1.
The MouseEvents main form.

As you can see, the main form includes a list box control and a label control that are used to display mouse-related events that are handled by the application. The constructor for the main form is shown here:

public MainForm()
{
    InitializeComponent();
    MouseEventHandler wheelHandler = null;
    wheelHandler = new MouseEventHandler(listBox_MouseWheel);
    eventListBox.MouseWheel += wheelHandler;
} 

The call to InitializeComponent was inserted by the Forms Designer to initialize the form and the user interface controls. After the call to InitializeComponent, an event handling delegate is created for the list box’s MouseWheel event. The implementation of the listBox_MouseWheel method that handles the wheel events from the mouse is shown below.

private void listBox_MouseWheel(object sender,
                                MouseEventArgs e)
{
    UpdateEventLabels("(ListBox)MouseWheel", e.X, e.Y, e);
}

When mouse-related events are received by the MouseEvents application, the event handler calls the UpdateEventLabels method, shown here:

private void UpdateEventLabels(string msg,
                               int x,
                               int y,
                               MouseEventArgs e)
{
    string message = string.Format("{0} X:{1}, Y:{2}",
                                    msg,
                                    x,
                                    y);
    positionLabel.Text = message;
    string eventMsg = DateTime.Now.ToShortTimeString();
    eventMsg += " " + message;
    eventListBox.Items.Insert(0, eventMsg);
    eventListBox.TopIndex = 0;

    string mouseInfo;
    if(e != null)
    {
        mouseInfo = string.Format("Clicks: {0}, Delta: {1}, " + 
                                    "Buttons: {2}",
                                    e.Clicks,
                                    e.Delta,
                                    e.Button.ToString());
    }
    else
    {
        mouseInfo = "";
    }
    mouseEventArgsLabel.Text = mouseInfo;
}

The UpdateEventLabels method accepts the following parameters:

  • msg  A string that describes the event

  • x  The mouse pointer’s x-coordinate

  • y  The mouse pointer’s y-coordinate

  • e  An optional reference to a MouseEventArgs object

The UpdateEventLabels method uses these parameters to format messages that are displayed in the dialog box. After every mouse-related event, a summary of the event is added to the list box, which is then reset so that the most recent messages are always displayed. The current position of the mouse pointer is displayed in the positionLabel label control. When a reference to a MouseEvent­Args object is passed as a parameter, additional information is displayed in the mouseEventArgsLabel label control.

For events that pass a reference to EventArgs instead of a reference to MouseEventArgs, the MousePosition property is used to specify the current mouse coordinates, as shown here:

private void listBox_MouseEnter(object sender, System.EventArgs e)
{
    Point mousePoint = PointToClient(MousePosition);
    UpdateEventLabels("(ListBox)MouseEnter",
                        mousePoint.X,
                        mousePoint.Y,
                        null);
}

Table 13-4 lists the events that are handled by the MouseEvents application and the controls that handle them.

Table 13-4. Mouse-Related Events Handled by the MouseEvents Application 

Event

Form

List Box

Click

X

DoubleClick

X

X

MouseDown

X

X

MouseEnter

X

X

MouseHover

X

X

MouseLeave

X

X

MouseMove

X

X

MouseUp

X

X

MouseWheel

X

To use the MouseEvents application, move the mouse pointer over the dialog box and child controls. Messages are generated as the mouse pointer passes over the form and the list box control. When the mouse pointer passes over child controls other than the list box, no messages appear; to see event messages for other child controls, you must add additional event handlers.

There are situations in which you need to know exactly where the mouse pointer is located, even if the mouse events would normally be sent to another form or control. In these cases, such as when you’re performing a drag-and-drop operation, you can capture the mouse pointer, which will result in all mouse events being sent to your form or control. The Capture property is used to capture the mouse pointer as well as to determine whether the mouse is currently captured, as shown here:

if(Capture == false)
{
    Capture = true;
    if(Capture == true)
    {
        // Mouse captured.
        
    

    }
} 
Providing Feedback with the Mouse Pointer

A common way to provide feedback to the user is through the mouse pointer. Because the mouse is often the primary method of user input in a Windows Forms application, altering the shape of the mouse pointer is an effective way to provide immediate feedback to the user. In most cases, the operating system and the .NET Framework will provide a mouse pointer that’s appropriate. For example, the default mouse pointer is normally set to the arrow shape over most controls and forms. The following subsections describe how you can enhance the built-in support within Windows and the .NET Framework to provide additional feedback to users.

Using Standard Mouse Pointers

Windows provides many standard mouse pointers, with each mouse pointer intended to be used in specific situations. The standard arrow mouse pointer is replaced by the hourglass when an application is busy or unable to accept mouse input. In most applications, you’ll want the mouse pointer to change to the I-beam when it’s moved over a control that accepts text input.

The mouse pointer is changed through the Cursor property, which is exposed by the Control class and all of its subclasses, including the Form class, as shown here:

Cursor = Cursors.WaitCursor;

The Cursor property must be set to an instance of a Cursor object. The Cursors class includes static properties that return the predefined mouse pointers described in the following tables. Table 13-5 lists the general-purpose mouse pointer values.

Table 13-5.  General-Purpose Mouse Pointer Values 

Value

Description

AppStarting

The mouse pointer displayed as an application is being launched

Arrow

The default mouse pointer displayed over most controls

Cross

The crosshair mouse pointer, which is commonly used when the mouse pointer is used to select small areas, such as in bitmap editors

Default

The default mouse pointer, usually the arrow mouse pointer

The mouse pointers listed in Table 13-6 are frequently used to indicate that specific actions can be taken with the mouse. These mouse pointers are context-sensitive and are typically displayed only while the mouse pointer is over a specific object that supports the action suggested by the mouse pointer.

Table 13-6.  Action-Related Mouse Pointer Values 

Value

Description

Hand

The mouse pointer typically used to indicate a hyperlink in Web pages

Help

The mouse pointer typically used when invoking help on a specific user interface object

HSplit

The mouse pointer used to indicate a horizontal splitter

IBeam

The mouse pointer used to indicate a control that displays text or accepts text as input, such as a text box

No

The mouse pointer shape used when the mouse pointer is over an area that’s invalid in the current context; often used in drag-and-drop operations to indicate that a target isn’t a valid drop target

UpArrow

The mouse pointer used to mark an insertion point

VSplit

The mouse pointer used to indicate a vertical splitter

WaitCursor

The mouse pointer used to indicate that the application or system is busy and no input is allowed; typically shaped like an hourglass

Each of the mouse pointers described in Table 13-7 is related to mouse wheel operations. Mice that include a wheel typically allow you to change the behavior of the mouse such that the mouse pointer is locked in place; any movement of the mouse causes the window to be scrolled.

Table 13-7.  Mouse Wheel–Related Pointer Values 

Value

Description

NoMove2D

The mouse pointer shape displayed when the mouse pointer can’t be moved but the window can be scrolled vertically and horizontally

NoMoveHoriz

The mouse pointer shape displayed when the mouse pointer can’t be moved but the window can be scrolled horizontally

NoMoveVert

The mouse pointer shape displayed when the mouse pointer can’t be moved but the window can be scrolled vertically

PanEast

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window to the right

PanNE

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window up and to the right

PanNorth

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window up

PanNW

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window up and to the left

PanSE

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window down and to the right

PanSouth

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window down

PanSW

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window down and to the left

PanWest

The mouse pointer shape displayed when the mouse pointer can’t be moved and the mouse is used to scroll the window to the left

There are also standard mouse pointers that are displayed for sizing operations. For example, if the mouse pointer is moved over the edge of a sizable window, the mouse pointer is changed to indicate to the user that the window can be resized. Table 13-8 provides a list of the standard mouse pointers displayed for sizing operations.

The descriptions of mouse pointers in the preceding tables refer to the default mouse pointers provided with Windows. Although the specific shapes can vary according to the user’s current desktop theme, any of the specified mouse pointers will be displayed consistently by all applications on the user’s computer.

The Cursor property can also be used to retrieve the mouse pointer, which enables you to cache the current mouse pointer and restore it at a later time, as shown here:

Cursor oldCursor = Cursor;

    

Cursor = oldCursor;
Using Custom Mouse Pointers

You can also create custom mouse pointers for your application using the Cursor class, as shown here:

smileyCursor = new Cursor("..\\..\\smiley.cur");
Cursor = smileyCursor;

In this code, the Cursor constructor takes the file path to the mouse pointer file and uses it to create a custom mouse pointer object, smileyCursor. The smiley­Cursor object is then assigned to the form’s Cursor property. Mouse pointer objects use an operating system handle. When the mouse pointer is no longer used, it should be disposed of to prevent resource leaks, as shown here:

smileyCursor.Dispose();

Although a custom mouse pointer that’s currently used by a form will be cleaned up when the form is closed, some situations will result in resource leaks or will cause external resources to be consumed in greater numbers than necessary and released more slowly than desired. For example, if you simply create a new instance of your custom mouse pointer every time you need it, you’ll probably create too many underlying resource handles. Consider this code, which creates a local version of the mouse pointer and assigns it to the form:

private void SetSmileyCursor();
{
    Cursor _smileyCursor = new Cursor("..\\..\\smiley.cur");
    Cursor = _smileyCursor;
}

Later, if a standard mouse pointer such as Cursors.Wait is assigned to the form in response to another event, you’ll have a resource leak unless you first call Dispose on the current custom mouse pointer, like this:

Cursor oldCustomCursor = Cursor;
oldCustomCursor.Dispose();
Cursor = Cursors.Wait;

But there’s a problem with this code. How do you know whether the previous mouse pointer really is a custom mouse pointer? If you can’t be sure, the best approach is to create a single instance of all custom mouse pointers when your application or form starts, as shown here:

public class MainForm: System.Windows.Forms.Form
{
    Cursor _handCursor;
    Cursor _embeddedCursor;
    Cursor _smileyCursor;
    
    

    public MainForm()
    {
        
    

        _handCursor = new Cursor(externalHand);
        _embeddedCursor = new Cursor(this.GetType(), "Smiley2.cur");
        _smileyCursor = new Cursor("..\\..\\smiley.cur");
    }
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
            _handCursor.Dispose();
            _embeddedCursor.Dispose();
            _smileyCursor.Dispose();
        }
        base.Dispose( disposing );
    }
    
    

As shown in this code, when the main form is created, the custom mouse pointers are created. Later, when Dispose is called on the form, Dispose is called for each custom mouse pointer.

Using Microsoft Win32 Mouse Pointer Resource Handles

Three other constructors for the Cursor class enable you to create custom mouse pointers from a variety of sources. The Cursor constructor used in the following code accepts an IntPtr parameter, which is typically a Win32 resource handle obtained through a set of services called P/Invoke that allows managed code to work with unmanaged code:

private void SetExternalCursor(IntPtr handle)
{
    Cursor externalCursor = new Cursor(handle);
    Cursor = externalCursor;
}

The following code is similar to the code in the CursorSwap sample application, included on the companion CD. It includes a P/Invoke declaration that enables loading a mouse pointer through the Win32 LoadCursor API call and a method that uses the external resource handle to create a mouse pointer.

// Platform Invoke method to load 
// a standard mouse pointer using Win32
[DllImport("user32.dll", EntryPoint="LoadCursor")]
private static extern IntPtr InteropLoadCursor(int instance,
                                               int resource);

// From the Platform SDK winuser.h header file
const int IDC_HAND = 32649;

private void externalRadioButton_CheckedChanged(object sender, 
                                                System.EventArgs e)
{
    IntPtr externalHand = InteropLoadCursor(0, IDC_HAND);
    Cursor handCursor = new Cursor(externalHand);
    Cursor = handCursor;
}

Although this code is concise, it tends to hold external resources longer than necessary. As mentioned, when you’re explicitly creating a resource externally using code such as this, you should aggressively use the Dispose pattern if possible. Otherwise, you run the risk of leaking resources until enough garbage collection passes have occurred to free the external resource handles. The source code for the CursorSwap example provided on the companion CD allocates the external mouse pointer object and resources when the application starts and employs the Dispose pattern to clean up when the application closes, as shown here:

public class MainForm: System.Windows.Forms.Form
{
    Cursor _handCursor;
    
    

    public MainForm()
    {
        _handCursor = new Cursor(externalHand);
        
    

    }
    
    

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
            _handCursor.Dispose();
        }
        base.Dispose( disposing );
    }
    
    

    private void externalRadioButton_CheckedChanged(object sender, 
                                                    System.EventArgs e)
    {
        Cursor = _handCursor;
    }
    
    

}
Avoiding External Mouse Pointer Files

Creating a custom mouse pointer using the mouse pointer’s file name is a simple, straightforward approach; however, it’s often undesirable to distribute multiple files with your application. It’s much more convenient to embed resources such as mouse pointers, icons, and bitmaps into your assembly and then load the resources directly from the assembly when required.

To embed a mouse pointer resource into your assembly, you must change the mouse pointer’s BuildAction property. The default value for this property is Content, which results in a mouse pointer file that’s used as and considered part of the project’s output. The mouse pointer isn’t compiled or stored as a resource, however. To embed the resource into your assembly, select the Cursor file in Solution Explorer, and change the BuildAction property to Embedded Resource.

One of the overloaded versions of the Cursor constructor works with mouse pointer resources embedded in assemblies. The constructor accepts two parameters:

  • A type name in the assembly that’s used to determine the namespace for the mouse pointer resource

  • The name of the mouse pointer resource

The following code demonstrates how to create a mouse pointer from an embedded resource:

Cursor embeddedCursor = new Cursor(this.GetType(), "Smiley2.cur"); 

When a mouse pointer is embedded in the assembly during compilation, the mouse pointer’s name is prefixed with the project’s default namespace. Initially, the default prefix will be the project’s name. The project’s namespace can be viewed or set in the project’s Property Pages dialog box. For the CursorSwap example, the default namespace was changed to MSPress.CSharpCoreRef.Cursor­Swap.

In the preceding code, the type of the current object is part of the project’s default namespace, so this.GetType is passed for the constructor’s first parameter and the mouse pointer resource is named Smiley2.cur. This is similar to code used in the CursorSwap project except that the code in the CursorSwap project is structured so that custom mouse pointers are properly disposed of when Dispose is called for the application’s main form.



Part III: Programming Windows Forms