delphifiremonkeydelphi-12-athenstpaintbox

How can I activate a paint on TPaintBox from a button click in Delphi FMX?


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.


Solution

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