Delphi FMX平台PaintBox Paint方法无法更新画布问题求助
Hey there, let's tackle this FMX PaintBox problem you're facing—first, let's break down why the weird behavior happens, then fix it properly.
Why Resizing the Window Updates the PaintBox?
FMX controls automatically trigger a repaint (via Invalidate → Repaint) when their size changes. That's why adjusting your ScreenView form makes the latest frame appear: the resize forces the pbViewPaint event to run again, drawing your updated Bitmap to the canvas. Your core drawing logic works—you just haven't told the PaintBox to redraw when new images arrive.
What's Wrong with the Current Code?
There are two key issues holding you back:
Incorrect Canvas Usage in FMX Paint Event:
Your modified FMX paint event usespbView.Canvasinstead of theCanvasparameter passed to the event. In FMX, the parameterizedCanvasis the valid, prepared drawing context for the current paint cycle. UsingpbView.Canvasdirectly can lead to drawing not being rendered correctly, especially outside of the paint event.Missing Explicit Repaint Trigger:
VCL sometimes implicitly marks controls as needing repaint after certain operations, but FMX is stricter. When yourClientreceives a new image and updates theBitmap, the PaintBox has no idea it needs to redraw—so it stays stuck on the first frame. The Timer hack works because it forces a repaint, but it's inefficient and unstable.
Step-by-Step Fixes
1. Correct the Paint Event Code
Update your pbViewPaint to use the provided Canvas parameter instead of the control's direct Canvas property:
procedure TFrmScreenView.pbViewPaint(Sender: TObject; Canvas: TCanvas); begin Client.DrawBitmap(Canvas); // Use the event-provided Canvas, not pbView.Canvas end;
2. Trigger Repaint When Bitmap Updates
You need to tell the PaintBox to redraw as soon as the Bitmap is updated. The cleanest way is to add an event to your TClient class that fires when the bitmap changes, then call Repaint on the PaintBox (must be done on the main thread, since FMX controls aren't thread-safe).
First, add an event to TClient:
type TBitmapUpdatedEvent = procedure(Sender: TObject) of object; TClient = class private FOnBitmapUpdated: TBitmapUpdatedEvent; // ... your existing fields public property OnBitmapUpdated: TBitmapUpdatedEvent read FOnBitmapUpdated write FOnBitmapUpdated; // ... your existing methods end;
Then, modify the code where you update the Bitmap in TClient to trigger the event:
procedure TClient.UpdateBitmap(NewBitmap: TBitmap); begin if Assigned(Bitmap) then Bitmap.Free; Bitmap := TBitmap.Create; Bitmap.Assign(NewBitmap); // Notify the UI that the bitmap has changed if Assigned(FOnBitmapUpdated) then FOnBitmapUpdated(Self); end;
Finally, bind the event in your form and trigger the repaint safely on the main thread:
procedure TFrmScreenView.FormCreate(Sender: TObject); begin Client.OnBitmapUpdated := ClientBitmapUpdated; end; procedure TFrmScreenView.ClientBitmapUpdated(Sender: TObject); begin // Use TThread.Queue to run the repaint on the main thread TThread.Queue(nil, procedure begin pbView.Repaint; end); end;
3. Ditch the Timer
Remove the Timer you were using—this new approach triggers a repaint only when a new frame arrives, which is far more efficient and reliable than periodic forced repaints.
Why This Works
- Using the event-provided
Canvasensures your drawing uses the correct FMX rendering context, eliminating invisible or incorrect draws. - Triggering
Repaintimmediately after theBitmapupdates tells the PaintBox it needs to redraw right away, so you get real-time updates without relying on window resizing or a hacky Timer.
内容的提问来源于stack exchange,提问作者Aras

