How to fix flickering on the right edge of TPaintBox (e.g. when resizing)

Summation:
Let's say that I have TForm and two panels. The panels are aligned with alTop and alClient. The alClient panel contains a TPaintBox, in which OnPaint drawing codes are included.

The default DoubleBuffered component value is false.

During the drawing process, flickering is obvious because the shape, the panels all draw their background.

Since the form is covered with panels, it is probably fine to intercept its WM_ERASEBKGND message. If not, you can see the flicker on the panels and the flicker on the right edge of the panels when you resize the form, because the form draws its background.

Secondly, since the alTop panel is designed for containers for some buttons, it might be fine to set the DoubleBuffered value to true so that Delphi ensures that it doesn't flicker. This will probably not bring a lot of work.

Thirdly, since the alClient panel is intended only for a container for another drawing component, this panel is most likely not involved in the final drawing. In this regard, it is probably useful to use a TPanel descendant instead of the standard TPanel. In this TPanel descendant, redefine the protected Paint procedure and do nothing inside the procedure, especially the unherited call, to avoid calling FillRect in the base class TCustomPanel.Paint. Also, intercept the WM_ERASEBKGND message and do nothing inside. This is because when TPanel.ParentBackground is False, Delphi is responsible for redrawing the background, and when it is True, ThemeService is responsible.

Finally, to draw flicker-free in TPaintBox:
(1) Using VCL's built-in drawing procedures is probably better ...
(2) Using OpenGL with OpenGL double buffer enabled.
(3) ...

=== Q: How to eliminate flicker on the right edge of TPaintBox? ===

Suppose for one TForm I have two panels on it. The top is aligned with AltTop relative to the shape and is treated as a container for buttons. The other aligns with alClient relative to the shape and is considered as a container for drawing components (for example, TPaintBox from VCL or TPaintBox32 from Graphics32). For the last panel, the WM_ERASEBKGND message was intercepted.

Now I am using an instance of TPaintBox in the following code example. In the OnPaint handler, I have two options for drawing a drawing, which I expect it to be flickering. Option 1 is drawing after filling the rectangle. Since its parent panel should not erase the background, the picture should be flickering. Option 2 is drawn on TBitmap, whose canvas is then copied back to the paintbox.

However, both options flicker, and the second option flickers especially. My main problem is choosing 1. If you resize the form, you will see that the bulk of the flicker occurs on the right edge. Why is this happening? Can someone help comment on the reason and possible solution? (Note that if I use TPaintBox32 instead of TPaintBox here, the right edge will not flicker at all.)

My secondary problem is that when using option 1, a small fraction of the flicker happens randomly in the frame. This is not very obvious, but you can still observe if you quickly resize the form. In addition, when using option 2, this kind of flicker becomes much more serious. I did not find the reason for this. Can someone help comment on a possible cause and solution?

Any suggestion appreciated!

unit uMainForm; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ExtCtrls, Dialogs; type TMainForm = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } FPnlCtrl, FPnlScene: TPanel; FPbScene: TPaintBox; OldPnlWndProc: TWndMethod; procedure PnlWndProc(var Message: TMessage); procedure OnScenePaint(Sender: TObject); public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); begin Self.Color := clYellow; Self.DoubleBuffered := False; FPnlCtrl := TPanel.Create(Self); FPnlCtrl.Parent := Self; FPnlCtrl.Align := alTop; FPnlCtrl.Color := clPurple; FPnlCtrl.ParentColor := False; FPnlCtrl.ParentBackground := False; FPnlCtrl.FullRepaint := False; FPnlCtrl.DoubleBuffered := False; FPnlScene := TPanel.Create(Self); FPnlScene.Parent := Self; FPnlScene.Align := alClient; FPnlScene.Color := clBlue; FPnlScene.ParentColor := False; FPnlScene.ParentBackground := False; FPnlScene.FullRepaint := False; FPnlScene.DoubleBuffered := False; FPbScene := TPaintBox.Create(Self); FPbScene.Parent := FPnlScene; FPbScene.Align := alClient; FPbScene.Color := clRed; FPbScene.ParentColor := False; // OldPnlWndProc := Self.FPnlScene.WindowProc; Self.FPnlScene.WindowProc := Self.PnlWndProc; FPbScene.OnPaint := Self.OnScenePaint; end; procedure TMainForm.PnlWndProc(var Message: TMessage); begin if (Message.Msg = WM_ERASEBKGND) then Message.Result := 1 else OldPnlWndProc(Message); end; procedure TMainForm.OnScenePaint(Sender: TObject); var tmpSceneBMP: TBitmap; begin // Choice 1 FPbScene.Canvas.FillRect(FPbScene.ClientRect); FPbScene.Canvas.Ellipse(50, 50, 150, 150); // Choice 2 // tmpSceneBMP := TBitmap.Create; // tmpSceneBMP.Width := FPbScene.ClientWidth; // tmpSceneBMP.Height := FPbScene.ClientHeight; // tmpSceneBMP.Canvas.Brush.Color := FPbScene.Color; // tmpSceneBMP.Canvas.FillRect(FPbScene.ClientRect); // tmpSceneBMP.Canvas.Ellipse(50, 50, 150, 150); // FPbScene.Canvas.CopyRect(FPbScene.ClientRect, tmpSceneBMP.Canvas, // FPbScene.ClientRect); end; end. 

=== Q: How to intercept a panel by repainting its background correctly? ===
(If I ask this in a separate question, just say it and I will delete it.)

The new VCL application, having inserted the sample code, attach FormCreate, run debug. Now hover over the form, you will see that the panel clearly redraws its background. However, as shown in the code example, I had to intercept this behavior by intercepting the WM_ERASEBKGND message.

Notice if I comment on these three lines,

 FPnlScene.Color := clBlue; FPnlScene.ParentColor := False; FPnlScene.ParentBackground := False; 

then the WM_ERASEBKGND message can be written. I do not mean this difference.

Can someone help comment on the reason for this behavior and how to correctly intercept the WM_ERASEBKGND message (when ParentBackground: = False)?

  unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ExtCtrls, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } FPnlScene: TPanel; FPbScene: TPaintBox; FOldPnlWndProc: TWndMethod; procedure PnlWndProc(var Message: TMessage); procedure OnSceneMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure OnScenePaint(Sender: TObject); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin Self.Color := clYellow; Self.DoubleBuffered := False; FPnlScene := TPanel.Create(Self); FPnlScene.Parent := Self; FPnlScene.Align := alClient; FPnlScene.Color := clBlue; FPnlScene.ParentColor := False; FPnlScene.ParentBackground := False; FPnlScene.FullRepaint := False; FPnlScene.DoubleBuffered := False; FPbScene := TPaintBox.Create(Self); FPbScene.Parent := FPnlScene; FPbScene.Align := alClient; FPbScene.Color := clRed; FPbScene.ParentColor := False; // FOldPnlWndProc := Self.FPnlScene.WindowProc; Self.FPnlScene.WindowProc := Self.PnlWndProc; Self.FPbScene.OnMouseMove := Self.OnSceneMouseMove; Self.FPbScene.OnPaint := Self.OnScenePaint; end; procedure TForm1.PnlWndProc(var Message: TMessage); begin if Message.Msg = WM_ERASEBKGND then begin OutputDebugStringW('WM_ERASEBKGND'); Message.Result := 1; end else FOldPnlWndProc(Message); end; procedure TForm1.OnSceneMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin FPbScene.Repaint; end; procedure TForm1.OnScenePaint(Sender: TObject); begin FPbScene.Canvas.FillRect(FPbScene.ClientRect); FPbScene.Canvas.Ellipse(50, 50, 150, 150); end; end. 
+7
source share
2 answers

The usual technique is to play with form. DoubleBuffered, which, as I see it, you are already doing in the code, so if everything was so simple, I would have thought that you would have solved it.

I think that any operation in OnPaint could also be avoided, except for stretching directly to your paintbox.Canvas, from your offscreen bitmap. Everything else in OnPaint is a potentially flickering bug. This means that no modification of TBitmap from within OnPaint. Let me say that a third time; Do not change state in paint events. Paint events should contain a bitmap-blit operation, a GDI rectangle and line calls, etc., but nothing else.

I am embarrassed to recommend to everyone that they experiment with WM_SETREDRAW, but this is one of those that people use. You can intercept events or messages of the move / resize window and enable / disable WM_SETREDRAW, but this is SO fraught with complications and problems that I do not recommend. You can also call various Win32 functions to block a window, and all of them are very dangerous and not recommended.

+4
source

For what it's worth, for me there is no flicker:

 unit uMainForm; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ExtCtrls, Dialogs; type TMainForm = class(TForm) procedure FormCreate(Sender: TObject); private FPnlCtrl, FPnlScene: TPanel; FPbScene: TPaintBox; procedure OnScenePaint(Sender: TObject); end; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); begin Self.Color := clYellow; FPnlCtrl := TPanel.Create(Self); FPnlCtrl.Parent := Self; FPnlCtrl.Align := alTop; FPnlCtrl.Color := clPurple; FPnlScene := TPanel.Create(Self); FPnlScene.Parent := Self; FPnlScene.Align := alClient; FPnlScene.Color := clBlue; FPbScene := TPaintBox.Create(Self); FPbScene.Parent := FPnlScene; FPbScene.Align := alClient; FPbScene.Color := clRed; FPbScene.OnPaint := Self.OnScenePaint; end; procedure TMainForm.OnScenePaint(Sender: TObject); begin FPbScene.Canvas.FillRect(FPbScene.ClientRect); FPbScene.Canvas.Ellipse(50, 50, 150, 150); end; end. 
+2
source

All Articles