Hit Testing or Correlation


Nonclient Mouse Messages

When the mouse is outside the client area of a window but still within the window, non-client mouse messages are generated. The non-client messages that may be generated are parallel to the similar client based messages - as shown in the table below.

message::nonclient_left_button_double_click
message::nonclient_middle_button_double_click
message::nonclient_right_button_double_click
message::nonclient_left_button_down
message::nonclient_middle_button_down
message::nonclient_right_button_down
message::nonclient_left_button_up
message::nonclient_middle_button_up
message::nonclient_right_button_up
message::nonclient_mouse_move
message::nonclient_hit_test

The Hit-Test Messages

The last message in the above table (being message::nonclient_hit_test) has no client equivalent. This message precedes all other mouse messages (client and non-client alike). The message message::nonclient_hit_test is usually passed to the default window procedure, which in turn uses it to generate all other mouse messages - based upon the position of the mouse. For non-client messages, the value returned by the default window procedure when processing this message becomes parameter 1 of the generated message - the hit test result. If the default window procedure determines that the position of the mouse is over the client area, it returns the value hit_test::client. In this case, the screen coordinates present in the initial message are converted to client coordinates and an appropriate message is generated and sent to the client. The functions that convert between screen and client coordinates are:

A Sample message Flow

It is a common phenomenon for a message to generate a sequence of other messages (noting that all mouse messages are sourced via the message message::nonclient_hit_test). When an operator double clicks the system menu of a window, a series of hit test messages is sent to the window and these usually flow through to the default window procedure. Upon receiving these hit-test messages, the default window procedure returns a value of hit_test::system_menu. This results in the generation of the message message::nonclient_left_button_double_click. Again, the non-client double click message flows through the default window procedure which generates the further message message::system_command with option system_command::close. Yet again this message is usually passed to the default window procedure; in which case, the message message::close is generated. An application may intercept this message; but if it doesn't, the message flows back to the default window procedure. Upon receiving message::close, the default window procedure sends a quit message to the queue.

Correlating graphics

Correlation is the means by which an application relates the position of a mouse selection with a graphics object within the application. This section provides a simple program that demonstrates how to relate a mouse selection with a portion of a graphic display. A snapshot of the sample running is shown below.

As can be seen, the client window is divided into a grid of 5x5 rectangles. Each of these rectangles is related to an integer occurring as part of a static two dimensional array (state_array) defined within the client window data, as shown below.

struct window_data
{
    int state_array[divisions][divisions];
    int width_of_block, height_of_block;
};

Apart from the two dimensional array, the variables width_of_block and height_of_block are calculated when the window is sized. When the left button is clicked, the corresponding state (in the two dimensional array) is toggled. The code that does this is shown below.

case message::left_button_down:
    {
        window_data* data = (window_data*)get_window_pointer(window_handle, 0);

        int x = low_part(parameter2) / data->width_of_block;
        int y = high_part(parameter2) / data->height_of_block;

        if (x < divisions && y < divisions)
        {
            data->state_array[x][y] ^= 1;

            irectangle rectangle_invalid(x * data->width_of_block,
                y * data->height_of_block,
                (x + 1) * data->width_of_block,
                (y + 1) * data->height_of_block);

            invalidate_rectangle(window_handle, &rectangle_invalid, false);
        }
        else
            message_beep(message_box_style::ok);
    }
    break;

Given the mouse position, x and y coordinates in terms of divisions are calculated. If the client width or height is not exactly divisible by five, a small strip of pixels may be uncovered either at the bottom or right of the client window. This eventuality is covered via testing with the if statement in the code fragment (upon which a beep is issued). If the block coordinates are within the range, the associated array element is inverted and the rectangle which it covers is invalidated. The paint routine paints the rectangle with a cross when the associated array element is non-zero - as shown in the code fragement below.

    case message::paint:
    {
        window_data* data = (window_data*)get_window_pointer(window_handle, 0);

        paint paint_structure;
        handle device_context = begin_paint(window_handle, &paint_structure);

        for (int x = 0; x < divisions; x++)
            for (int y = 0; y < divisions; y++)
            {
                draw_rectangle(device_context,
                    x * data->width_of_block,
                    y * data->height_of_block,
                    (x + 1) * data->width_of_block,
                    (y + 1) * data->height_of_block);

                if (data->state_array[x][y])
                {
                    move_to(device_context, x * data->width_of_block, y * data->height_of_block);
                    draw_line_to(device_context, (x + 1) * data->width_of_block, (y + 1) * data->height_of_block);
                    move_to(device_context, x * data->width_of_block, (y + 1) * data->height_of_block);
                    draw_line_to(device_context, (x + 1) * data->width_of_block, y * data->height_of_block);
                }
            }

        end_paint(window_handle, &paint_structure);
    }
    break;

Correlating Graphics with a Keyboard Interface Added

The program presented in the previous section works only with a mouse. A second program that performs the same as the first except that it adds keyboard processing is the topic of this section. In addition to the code presented there, the code fragment that performs the keyboard processing is shown below.

case message::key_down:
 {
  ipoint cursor_position;
  get_cursor_position(&cursor_position);
  screen_to_client(window,&cursor_position);

  int x = maximum(0,minimum((int)(divisions-1),cursor_position(0)/WidthOfBlock));
  int y = maximum(0,minimum((int)(divisions-1),cursor_position(1)/heightOfBlock));

  switch (parameter1)
   {
    case virtual_key::up:     y--;                   break;
    case virtual_key::down:   y++;                   break;
    case virtual_key::left:   x--;                   break;
    case virtual_key::right:  x++;                   break;
    case virtual_key::home:   x = y = 0;             break;
    case virtual_key::end:    x = y = divisions-1;   break;

    case virtual_key::return:
    case virtual_key::space:
     send_message(window,
                 message::left_button_down,
                 mouse_state::left_button,
                 make_integer(x*width_of_block,y*height_of_block));
     break;
   }

  x = (x + divisions) % divisions;
  y = (y + divisions) % divisions;

  cursor_position(0) = x*data->width_of_block + data->width_of_block/2;
  cursor_position(1) = y*data->>heightof_block + data->height_of_block/2;

  client_to_screen(window,&cursor_position);
  set_cursor_position(cursor_position(0),cursor_position(1));
 }
 break;

As may be observed, the key down message is used to manipulate the cursor. Firstly, the current position of the cursor is queried using the function get_cursor_position. The next statement converts the position from screen coordinates to client coordinates. The block coordinates are then manipulated to remain in the range 0 through to 4. The virtual keys are then applied to the coordinates. In the case of the return key and the space key, a left button message is generated and sent. This simulates the action already coded for the left button click. The new position of the cursor is then calculated (with wrapping) and the position of the cursor is then set at the middle of a rectangle using the function set_cursor_position.