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:
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?