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:
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?
When Themes are not enabled how should I draw the button in the classic Windows style?
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With