Owner Draw Buttons


Owner draw buttons allow the application to control the appearance of the window whilst the button still controls the keyboard and mouse interaction. The example of this section provides two owner draw buttons. A snapshot of the application in action is shown below.

As may be observed, the buttons are centered vertically in the window and are distributed evenly along a horizontal centering line. The left most button decreases the size of the containing window by 10%; whereas, the right button increases the size of the window by 10%. Most owner draw buttons contain bitmaps; however, this application merely draws the buttons as a number of triangles (pointing inwards or outwards).

The two buttons handles are contained as static variables in the client window procedure - as shown below.

result __stdcall client(handle window_handle,
                       unsigned identity,
                       parameter parameter1,
                       parameter parameter2)
{
 static handle Smaller,Larger;
 ....
}

The client window procedure initializes these variables upon receiving the message message::create.

case message::create:
 ....
 width_of_character  = low_part(get_dialog_base_units());
 height_of_character = high_part(get_dialog_base_units());

 Smaller = create_window(Buttonwclassname,
                        style::Child | style::Visible | button_style::OwnerDraw,
                        ButtonIdentity::Smaller,
                        window_handle);

 Larger = create_window(Buttonwclassname,
                       style::Child | style::Visible | button_style::OwnerDraw,
                       ButtonIdentity::Larger,
                       window_handle);
 break;

Upon receiving a size message, the button windows are positioned and scaled as follows.

case message::size:
 width_of_client  = low_part(parameter2);
 height_of_client = high_part(parameter2);

 move_window(Smaller,
            width_of_client/2 - 3*ButtonDimensions::Width*width_of_character/2,
            height_of_client/2 - ButtonDimensions::height*height_of_character/2,
            ButtonDimensions::Width*width_of_character,
            ButtonDimensions::height*height_of_character,true);

 move_window(Larger,
            width_of_client/2 + ButtonDimensions::Width*width_of_character/2,
            height_of_client/2 - ButtonDimensions::height*height_of_character/2,
            ButtonDimensions::Width*width_of_character,
            ButtonDimensions::height*height_of_character,true);
 break;

When selected, the buttons notify the owner (being the client window) of the event using command messages. The code that handles these notifications is shown below.

case message::command:
 {
  rectangle RectangleWindow;
  get_window_rectangle(window_handle,&RectangleWindow);

  switch (parameter1) // Make the rectangle 10% smaller or larger
   {
    case ButtonIdentity::Smaller:
     RectangleWindow[0](0) += width_of_client/20;
     RectangleWindow[1](0) -= width_of_client/20;
     RectangleWindow[0](1) += height_of_client/20;
     RectangleWindow[1](1) -= height_of_client/20;
     break;

    case ButtonIdentity::Larger:
     RectangleWindow[0](0) -= width_of_client/20;
     RectangleWindow[1](0) += width_of_client/20;
     RectangleWindow[0](1) -= height_of_client/20;
     RectangleWindow[1](1) += height_of_client/20;
     break;
   }

  move_window(window_handle,
             RectangleWindow[0](0),
             RectangleWindow[0](1),
             RectangleWindow[1](0)-RectangleWindow[0](0),
             RectangleWindow[1](1)-RectangleWindow[0](1),true);
 }
 break;

The rectangle of the client window is made 10% smaller or 10% bigger as requested (based upon the identity of the selected control window). If this was all the code that was presented, the program would be completely functional, but the buttons would not be visible. The code shown below draws the buttons upon receiving the notification message::draw_item.

case message::draw_item:
 {
  draw_item* Item = (draw_item*)parameter2;

               // fill area with white and frame it black

  fill_rectangle(Item->device,
                 &Item->bounds,
                 get_standard_object(standard_brush::White));

  frame_rectangle(item->device,
                  &Item->bounds,
                  get_standard_object(standard_brush::Black));

            // draw inward and outward black triangles

  int Width  = Item->bounds[1](0) - Item->bounds[0](0);
  int height = Item->bounds[1](1) - Item->bounds[0](1) ;

  point Points[3];
  switch (Item->Control)
   {
    case ButtonIdentity::Smaller:
     Points[0](0) = 3 * Width / 8;  Points[0](1) = 1 * height / 8;
     Points[1](0) = 5 * Width / 8;  Points[1](1) = 1 * height / 8;
     Points[2](0) = 4 * Width / 8;  Points[2](1) = 3 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 7 * Width / 8;  Points[0](1) = 3 * height / 8;
     Points[1](0) = 7 * Width / 8;  Points[1](1) = 5 * height / 8;
     Points[2](0) = 5 * Width / 8;  Points[2](1) = 4 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 5 * Width / 8;  Points[0](1) = 7 * height / 8;
     Points[1](0) = 3 * Width / 8;  Points[1](1) = 7 * height / 8;
     Points[2](0) = 4 * Width / 8;  Points[2](1) = 5 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 1 * Width / 8;  Points[0](1) = 5 * height / 8;
     Points[1](0) = 1 * Width / 8;  Points[1](1) = 3 * height / 8;
     Points[2](0) = 3 * Width / 8;  Points[2](1) = 4 * height / 8;

     Triangle(Item->device,Points);
     break;

    case ButtonIdentity::Larger:
     Points[0](0) = 5 * Width / 8;  Points[0](1) = 3 * height / 8;
     Points[1](0) = 3 * Width / 8;  Points[1](1) = 3 * height / 8;
     Points[2](0) = 4 * Width / 8;  Points[2](1) = 1 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 5 * Width / 8;  Points[0](1) = 5 * height / 8;
     Points[1](0) = 5 * Width / 8;  Points[1](1) = 3 * height / 8;
     Points[2](0) = 7 * Width / 8;  Points[2](1) = 4 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 3 * Width / 8;  Points[0](1) = 5 * height / 8;
     Points[1](0) = 5 * Width / 8;  Points[1](1) = 5 * height / 8;
     Points[2](0) = 4 * Width / 8;  Points[2](1) = 7 * height / 8;

     Triangle(Item->device,Points);

     Points[0](0) = 3 * Width / 8;  Points[0](1) = 3 * height / 8;
     Points[1](0) = 3 * Width / 8;  Points[1](1) = 5 * height / 8;
     Points[2](0) = 1 * Width / 8;  Points[2](1) = 4 * height / 8;

     Triangle(Item->device,Points);
     break;
    }

            // Invert the rectangle if the button is selected

  if (Item->State & owner_draw_state::Selected)
   invert_rectangle(Item->device,&Item->bounds);

          // Draw a Focus rectangle if the button has the Focus

  if (Item->State & owner_draw_state::Focus)
   {
    Item->bounds[0](0) += width / 16;
    Item->bounds[0](1) += height / 16;
    Item->bounds[1](0) -= width / 16;
    Item->bounds[1](1) -= height / 16;

    draw_focus_rectangle(Item->device,&Item->bounds);
   }
 }
 break;

The device context for drawing is found in an object of the class draw_item. The button is filled with a white background and framed with a black border. The identity of the control is then used to determine whether the inwards or outwards pointing triangles are drawn. The function that actually draws the triangles is shown below.

void Triangle(handle device_context, point Points[])
{
 select_object(device_context,get_standard_object(standard_brush::Black));
 draw_polygon(device_context,Points,3);
 select_object(device_context,get_standard_object(standard_brush::White));
}

after the triangles are drawn, the state of the button is tested and the button is color inverted if it is selected. The state is also tested to see whether the button has the focus. When the button has the focus, a focus rectangle is drawn.