Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are we responsible for drawing the caption when using Windows API to draw a Button?

Introduction

I have been having a little fun with the Windows API and Parts and States and seeing how the controls can be painted onto a canvas.

With a lot of trial and error I managed to come up with this procedure which will draw a button onto a canvas:

type
  TButtonState = (bsDefault, bsDisabled, bsHot, bsNormal, bsPressed);

procedure DrawButton(ACanvas: TCanvas; X, Y, AWidth, AHeight: Integer;
  AFont: TFont; Caption: string; ButtonState: TButtonState);
var
  Size: TSize;
  R: TRect;
  H: HTHEME;
begin
  Size.cx := AWidth;
  Size.cy := AHeight;

  R := Rect(X, Y, X + AWidth, Y + AHeight);

  if Winapi.uxTheme.UseThemes then
  begin
    H := OpenThemeData(0, 'BUTTON');
    if H <> 0 then
    try
      ACanvas.Brush.Style := bsClear;
      if AFont <> nil then
      begin
        ACanvas.Font.Assign(AFont);
      end;

      case ButtonState of
        bsDefault:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DEFAULTED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DEFAULTED, R, nil);
        end;

        bsDisabled:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, R, nil);
          //ACanvas.Font.Color := $00838383; //todo get actual disabled font color
        end;

        bsHot:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_HOT, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_HOT, R, nil);
        end;

        bsNormal:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_NORMAL, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_NORMAL, R, nil);
        end;

        bsPressed:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_PRESSED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_PRESSED, R, nil);
        end;
      end;

      // draw the button caption
      DrawText(ACanvas.Handle, PChar(Caption), Length(Caption), R, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
    finally
      CloseThemeData(H);
    end
  end
  else
  begin
    // draw button in classic theme?
  end;
end;

If I call that procedure like so:

procedure TForm1.FormPaint(Sender: TObject);
begin
  DrawButton(Image1.Canvas, 10, 10, 75, 25, Form1.Font, 'Normal', bsNormal);
  DrawButton(Image1.Canvas, 10, 40, 75, 25, Form1.Font, 'Default', bsDefault);
  DrawButton(Image1.Canvas, 10, 70, 75, 25, Form1.Font, 'Disabled', bsDisabled);
  DrawButton(Image1.Canvas, 10, 100, 75, 25, Form1.Font, 'Hot', bsHot);
  DrawButton(Image1.Canvas, 10, 130, 75, 25, Form1.Font, 'Pressed', bsPressed);
end;

The result is how I expected it to be, to appear just like any Windows button control would:

enter image description here

Question

Whilst I was playing around with the procedure I soon realised that the button had no caption, and I could not see a way of adding a caption other than drawing it myself on top of the button. As you can see though, the "Disabled" button still shows the default font color, disabled controls usually have a different color to help show that the control is disabled. The disabled font color under a standard Windows theme is $00838383 (found using a screen color picker), but hard coded values are never a good idea as these values are usually unique to each theme.

There are a few parts to my question, when drawing a button using the Winows API do we have to manually draw the caption ourselves? If so, how do I ensure I am drawing the correct font name, style and size etc to ensure the button is the same as the system wide drawn buttons?

Bonus

When Themes are not enabled how should I draw the button in the classic Windows style?

like image 361
Craig Avatar asked Jul 07 '15 14:07

Craig


1 Answers

You should draw the text yourself. You can use theme api to draw text too. Eg:

...
 bsDisabled:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, R, nil);
          DrawThemeText(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, PChar(Caption),
              Length(Caption), DT_CENTER or DT_VCENTER or DT_SINGLELINE, 0, R);
          //ACanvas.Font.Color := $00838383; //todo get actual disabled font color
        end;
...

Since you would call the api with the appropriate state, include the call in the case branches and remove the call to DrawText.

You can use DrawFrameControl for classic style. Eg:

DrawFrameControl(ACanvas.Handle, R, DFC_BUTTON, DFCS_BUTTONPUSH);
like image 123
Sertac Akyuz Avatar answered Oct 29 '22 12:10

Sertac Akyuz