Capturing the Mouse


Drawing a rectangle - The Naive Case

normally, a window receives mouse messages only when the mouse is over the window. On occasions, a program may have the requirement of receiving messages even when the mouse moves outside the boundaries of the window. This section provides an example of such a case.

The first program of this section performs an operation that some drawing packages perform - drawing a filled rectangle. The output of the program is shown below.

In the above picture, a filled rectangle has already been drawn and another rectangle (that will replace the existing one) is in the process of being formed. The rectangular frame is drawn with foreground mix mode of mix::Not (inverted color of the current client background). This implies that as the frame is moved, it may be redrawn to erase the existing frame and then drawn in the new position. The client window procedure contains a couple of flags and some points to describe the rectangle being drawn and the frame used to size the new rectangle (as static variables). A portion of the client window procedure is shown in the code fragment below.

result __stdcall client(handle window,
                       unsigned identity,
                       parameter parameter1,
                       parameter parameter2)
{
 static bool Blocking,validBox;
 static point Start,end,BoxStart,Boxend;
 ....
}

These variables are described in the following table.

Blocking true when a frame is being used to define a new rectangle.
validBox true when a blocked rectangle is already defined.
Start The starting point of an existing rectangular block.
end The ending point of an existing rectangular block.
BoxStart The starting point of a rectangular frame being drawn.
Boxend The ending point of a rectangular frame being drawn.

Static variables are initialized to zero by default - which means Blocking and validBox are initially false. When a left button down message is received:

  1. the members Start and end are set to equal the position of the mouse,
  2. the function DrawBoxOutline is called to draw an initial frame,
  3. the cursor is set to be cursor_identity::cross and
  4. blocking is set to be true (indicating that the rectangular frame is to be drawn).

This processing is shown below.

case message::left_button_down:
 Start(0) = end(0) = low_part(parameter2);
 Start(1) = end(1) = high_part(parameter2);

 DrawBoxOutline(window,Start,end);

 set_cursor(load_cursor((handle)0,(const character*)cursor_identity::cross));

 Blocking = true;
 break;

The function DrawBoxOutline is shown below.

void DrawBoxOutline(handle window, point Start, point end)
{
 handle device_context = get_device_context(window);
 set_foreground_mix(device_context,mix::Not);
 select_object(device_context,get_standard_object(standard_brush::null));
 draw_rectangle(device_context,Start(0),Start(1),end(0),end(1));
 release_device_context(window,device_context);
}

The border of the rectangle being drawn inverts the current value of the pixels in the client window. This means that the method may be used to erase an existing rectangle as well as draw a new rectangle. The value of the brush being standard_brush::null implies that the interior of the rectangle is left untouched.

When a left button up message is received, if blocking is in effect, the coordinates of the filled rectangle are replaced, the frame is removed, the cursor is reset and Blocking is set to false and validBox is set to true. The window is then invalidated. This code is shown below.

case message::left_button_up:
 if (Blocking)
  {
   DrawBoxOutline(window,Start,end);

   BoxStart  = Start;
   Boxend(0) = low_part(parameter2);
   Boxend(1) = high_part(parameter2);

   set_cursor(load_cursor((handle)0,(const character*)cursor_identity::arrow));
 
   Blocking = false;

   validBox = true;

   invalidate_rectangle(window,(const rectangle*)0,true);
  }
 break;

The mouse move code is shown below.

case message::mouse_move:
 if (Blocking)
  {
   set_cursor(load_cursor((handle)0,(const character*)cursor_identity::cross));

   DrawBoxOutline(window,Start,end);

   end(0) = low_part(parameter2);
   end(1) = high_part(parameter2);

   DrawBoxOutline(window,Start,end);
  }
 break;

If Blocking is true, the cursor is set and the existing frame is removed via a call to the method DrawBoxOutline. The new position of the mouse is set and the frame is redrawn via a second call to DrawBoxOutline.

When a paint message is received, both the existing rectangle and the frame are drawn - as shown below.

case message::paint:
 {
  paint paint_struct;
  handle device_context = begin_paint(window,&paint_struct);

  if (validBox)
   {
    select_object(device_context,get_standard_object(standard_brush::Black));
    draw_rectangle(device_context,BoxStart(0),BoxStart(1),Boxend(0),Boxend(1));
   }

  if (Blocking)
   {
    set_foreground_mix(device_context,mix::Not);
    select_object(device_context,get_standard_object(standard_brush::null));
    draw_rectangle(device_context,Start(0),Start(1),end(0),end(1));
   }

  end_paint(window,&paint_struct);
 }
 break;

Capturing the Mouse

So what is wrong with the previously presented program? When running the program, the operator should hold the mouse button down and move the cursor outside the bounds of the client area, then release the button. Upon reentering the client area, the program is unable to detect that the mouse button was released. The window stopped receiving mouse messages when the cursor was outside the window.

To fix this deficiency, a second version of the program is presented. Apart from setting the title string, the second version differs from the previous example only in three lines. The code for message::left_button_down has the additional line

set_capture(window);

and the messages message::left_button_up and message::character have a call to the function release_capture().

When the mouse is captured, the client window receives messages even when the mouse is over the system portion of the window. Whilst the mouse is captured, the system keyboard functions are also disabled. If the mouse is captured and a mouse button is not currently down, when the mouse passes over a window other than the window that has the mouse captured, messages are directed to that window. The mouse should only be captured when a button is depressed and the capture should be released when that button is released.