I do not want to use the onPaint
event to draw on the TPaintBox
. I want to click on a button and then the drawing happens. how?
This is my code right now:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Math,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Objects;
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
btnPaintSmile: TButton;
procedure PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
procedure btnPaintSmileClick(Sender: TObject);
private
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.btnPaintSmileClick(Sender: TObject);
begin
// do the smile painting here on TPaintBox
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
DrawRect, faceRect, eyeRectL, eyeRectR: TRectF;
s, radius, lineW, eyeR, eyeOffsetX, eyeOffsetY: Single;
c: TPointF;
FaceColor, EyeColor, LineColor: TAlphaColor;
WithOutline: Boolean;
begin
DrawRect := PaintBox1.LocalRect;
DrawRect.Inflate(-8, -8);
if (Canvas = nil) or (DrawRect.Width <= 0) or (DrawRect.Height <= 0) then
Exit;
// Settings
FaceColor := $FFFFCC4D; // Emoji yellow
EyeColor := $FF000000; // Black
LineColor := $FF7A5200; // Warm brown outline
WithOutline := True;
// Scale & center
s := Min(DrawRect.Width, DrawRect.Height);
c := PointF(
DrawRect.Left + s * 0.5 + (DrawRect.Width - s) * 0.5,
DrawRect.Top + s * 0.5 + (DrawRect.Height - s) * 0.5
);
radius := 0.5 * s;
// Face
faceRect := RectF(
c.X - radius, c.Y - radius,
c.X + radius, c.Y + radius
);
Canvas.Fill.Kind := TBrushKind.Solid;
Canvas.Fill.Color := FaceColor;
Canvas.FillEllipse(faceRect, 1);
// Optional outline
if WithOutline then
begin
lineW := Max(1.0, s * 0.04);
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.Stroke.Color := LineColor;
Canvas.Stroke.Thickness := lineW;
Canvas.Stroke.Cap := TStrokeCap.Round;
Canvas.Stroke.Join := TStrokeJoin.Round;
Canvas.DrawEllipse(faceRect, 1);
end;
// Eyes only (no mouth)
eyeR := s * 0.08;
eyeOffsetX := s * 0.20;
eyeOffsetY := -s * 0.10;
eyeRectL := RectF(
c.X - eyeOffsetX - eyeR, c.Y + eyeOffsetY - eyeR,
c.X - eyeOffsetX + eyeR, c.Y + eyeOffsetY + eyeR
);
eyeRectR := RectF(
c.X + eyeOffsetX - eyeR, c.Y + eyeOffsetY - eyeR,
c.X + eyeOffsetX + eyeR, c.Y + eyeOffsetY + eyeR
);
Canvas.Fill.Color := EyeColor;
Canvas.FillEllipse(eyeRectL, 1);
Canvas.FillEllipse(eyeRectR, 1);
// Mouth (smile)
var mouthW, mouthH, mouthOffsetY, mouthLineW: Single;
var mouthRect: TRectF;
mouthW := s * 0.5;
mouthH := s * 0.28;
mouthOffsetY := s * 0.18;
mouthRect := RectF(
c.X - mouthW * 0.5, c.Y + mouthOffsetY - mouthH * 0.5,
c.X + mouthW * 0.5, c.Y + mouthOffsetY + mouthH * 0.5
);
mouthLineW := Max(1.0, s * 0.04);
Canvas.Stroke.Kind := TBrushKind.Solid;
Canvas.Stroke.Color := EyeColor;
Canvas.Stroke.Thickness := mouthLineW;
Canvas.Stroke.Cap := TStrokeCap.Round;
Canvas.Stroke.Join := TStrokeJoin.Round;
var points: TArray<TPointF>;
var i, segments: Integer;
var startAngleDeg, sweepDeg, angleRad, cx, cy, rx, ry: Single;
startAngleDeg := 200.0;
sweepDeg := 140.0;
segments := 24;
SetLength(points, segments + 1);
cx := (mouthRect.Left + mouthRect.Right) * 0.5;
cy := (mouthRect.Top + mouthRect.Bottom) * 0.5;
rx := mouthRect.Width * 0.5;
ry := mouthRect.Height * 0.5;
for i := 0 to segments do
begin
angleRad := DegToRad(startAngleDeg + sweepDeg * (i / segments));
points[i] := PointF(cx + Cos(angleRad) * rx, cy - Sin(angleRad) * ry);
end;
for i := 0 to segments - 1 do
Canvas.DrawLine(points[i], points[i + 1], 1);
end;
end.
When I press on my btnPaintSmile
button then I want the drawing to happen. Is this possible?
I have btnPaintSmileClick
event on button. that must activate and let the drawing begin on the TPaintBox
.
I do not want to use the onPaint event to draw on the TPaintBox.
Sorry, but you must. That is simply how TPaintBox
is designed to be used. Every time it needs to be refreshed onscreen, its OnPaint
event is triggered. So, you must do ALL of your drawing on the Canvas
from within that event.
I want to click on a button and then the drawing happens. how?
You need to keep track of what you want to draw, and then (re-)draw it every time the OnPaint
event is triggered.
When I press on my btnPaintSmile button then I want the drawing to happen. Is this possible?
Yes, there are ways to do that, for example:
define a Boolean flag as a member of the Form. If the flag is enabled, have the OnPaint
handler perform the drawing. If the flag is disabled, do not perform the drawing. You can then toggle that flag when needed, and call the TPaintBox
's InvalidateRect()
or Repaint()
method to trigger an immediate repaint so the new drawing logic takes effect.
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
btnPaintSmile: TButton;
procedure PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
procedure btnPaintSmileClick(Sender: TObject);
private
DoDrawSmiley: Boolean;
procedure DrawSmiley(Canvas: TCanvas);
end;
...
procedure TForm1.btnPaintSmileClick(Sender: TObject);
begin
if not DoDrawSmiley then
begin
DoDrawSmiley := True;
PaintBox1.Repaint;
end;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
if DoDrawSmiley then
DrawSmiley(Canvas);
end;
procedure TForm1.DrawSmiley(Canvas: TCanvas);
begin
// draw on Canvas as needed...
end;
create a separate TBitmap
, and have the OnPaint
event simply draw that image as-is. You can then update that TBitmap
when needed, and trigger a repaint to draw the latest image.
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
btnPaintSmile: TButton;
procedure FormDestroy(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
procedure btnPaintSmileClick(Sender: TObject);
private
Smiley: TBitmap;
procedure DrawSmiley;
end;
...
procedure TForm1.FormDestroy(Sender: TObject);
begin
Smiley.Free;
end;
procedure TForm1.btnPaintSmileClick(Sender: TObject);
begin
DrawSmiley;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
if Smiley <> nil then
Canvas.DrawBitmap(Smiley, Smiley.BoundsF, PaintBox1.LocalRect, 1.0);
end;
procedure TForm1.DrawSmiley;
begin
if Smiley = nil then
Smiley := TBitmap.Create(PaintBox1.Width, PaintBox1.Height)
else
Smiley.Resize(PaintBox1.Width, PaintBox1.Height);
// draw on Smiley as needed...
PaintBox1.Repaint;
end;