如何消除TPaintBox右边缘的闪烁(例如,调整大小时) [英] How to eliminate the flicker on the right edge of TPaintBox (for example when resizing)

查看:132
本文介绍了如何消除TPaintBox右边缘的闪烁(例如,调整大小时)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

摘要:
假设我有一个TForm和两个面板.面板对齐alTop和alClient. alClient面板包含一个TPaintBox,其OnPaint包含绘图代码.

Summarization:
Say that I have a TForm and two panels. The panels are aligned alTop and alClient. The alClient panel contains a TPaintBox, whose OnPaint involve drawing codes.

组件上DoubleBuffered的默认值为false.

The default value of DoubleBuffered on the components are false.

在绘制过程中,闪烁是很明显的,因为窗体,面板都绘制了背景.

During the drawing process, flicker is obvious because the form, the panels all paint their background.

由于面板覆盖了表单,因此拦截其WM_ERASEBKGND消息可能很好.如果不是这样,则在调整表单大小时,可能会看到面板上闪烁,并且在面板的右边缘上闪烁,因为表单绘制了背景.

Because the form is covered by the panels, it is probably fine to intercept its WM_ERASEBKGND message. If not, one could see flickering on the panels, and flickering on the right edge of the panels when the form is resized, because form paints its background.

第二,因为alTop面板旨在用作某些按钮的容器,所以最好将DoubleBuffered设置为true,以使Delphi确保没有闪烁.它可能不会带来太多的性能负担.

Secondly, because the alTop panel is intended to be a container for some buttons, it is probably fine to set its DoubleBuffered to true to let Delphi ensure there is no flicker on it. It probably won't introduce much performance burden.

第三,由于alClient面板仅打算用作其他图形组件的容器,因此该面板很可能参与组成最终图形.在这方面,最好使用TPanel后代而不是标准TPanel.在此TPanel子孙中,重写受保护的过程Paint,并且在过程内不执行任何操作,尤其是不要继承调用,以避免在基类TCustomPanel.Paint中进行FillRect调用.此外,拦截WM_ERASEBKGND消息,并且在内部也不执行任何操作.这是因为当TPanel.ParentBackground为False时,Delphi负责重新绘制背景,而当它为True时,ThemeService则负责.

Thirdly, because the alClient panel is intended only to be a container for another drawing component, this panel is most likely not involved in composing the final drawing. In this respect, it's probably good to use a TPanel descendant instead of a standard TPanel. In this TPanel descendant, override the protected procedure Paint and do nothing inside the procedure, especially not the inherited call to avoid the FillRect call in the base class TCustomPanel.Paint. Furthermore, intercept the WM_ERASEBKGND message and also do nothing inside. This is because when the TPanel.ParentBackground is False, Delphi is responsible for repainting the background, and when it is True, ThemeService is responsible.

最后,要在TPaintBox中绘画而不会闪烁:
(1)使用VCL内置的绘图例程,最好是...
(2)使用OpenGL,并启用OpenGL的双缓冲区.
(3)...

Lastly, to paint without flicker in the TPaintBox:
(1) Using VCL built-in drawing routines, it is probably better that...
(2) Using OpenGL, with OpenGL's double buffer enabled.
(3) ...

===问:如何消除TPaintBox右边缘的闪烁?===

假设对于一个TForm,我有两个面板.顶部是相对于表单对齐的alTop,并被视为按钮的容器.另一个相对于表单对齐alClient,并被视为绘图组件的容器(例如VCL中的TPaintBox或Graphics32中的TPaintBox32).对于后一个面板,将截获其WM_ERASEBKGND消息.

Suppose that for one TForm, I have two panels on it. The top one is aligned alTop relative to the form and considered as a container for buttons. The other one is aligned alClient relative to the form and considered as a container for drawing components (such as TPaintBox from VCL, or TPaintBox32 from Graphics32). For the latter panel, its WM_ERASEBKGND message is intercepted.

现在,在以下示例代码中使用TPaintBox实例.在其OnPaint处理程序中,我有两种选择来绘制我希望不会闪烁的图形.选择1在填充矩形后绘制.因为其父面板不应擦除背景,所以图形应无闪烁.选择2正在绘制到TBitmap上,然后将其Canvas复制回至绘画盒.

Now, I use a TPaintBox instance in the following sample code. In its OnPaint handler, I have two choices to draw a drawing that I would expect to be flicker-free. Choice 1 is drawing after filling the rect. Because its parent panel should not erase the background, the drawing should be flicker-free. Choice 2 is drawing onto a TBitmap, whose Canvas is then copied back to the paintbox.

但是,两个选择都闪烁,而第二个选择尤其闪烁.我主要关心的是选择1.如果调整窗体的大小,您会看到闪烁的主要部分出现在右边缘.为什么会这样?有人可以帮忙评论一下原因和可能的解决方案吗? (请注意,如果我在这里使用TPaintBox32而不是TPaintBox,则右边缘完全不会闪烁.)

However, both choices are flickering, and the 2nd choice is especially flickering. My main concern is regarding choice 1. If you resize the form, you could see the main part of the flickering occurs on the right edge. Why does this happen? Could some one help to comment on the reason and possible solution? (Note, if I use TPaintBox32 instead of TPaintBox here, the right edge will not flicker at all.)

我的第二个担心是,使用选项1时,闪烁的一小部分会随机发生在画板上.如果您快速调整表单的大小,它不是很明显,但是仍然可以观察到.此外,当使用选项2时,这种闪烁会变得更加严重.我没有找到原因.有人可以帮忙评论一下可能的原因和解决方案吗?

My secondary concern is that when using choice 1, the minor part of the flickering occurs on the paintbox randomly. It is not very obvious but still observable if you rapidly resize the form. Furthermore, when using choice 2, this kind of flickering becomes much more severe. I didn't find the reason of this. Could some one help to comment on the possible reason and solution?

任何建议都值得赞赏!

    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:如何正确截取面板的背景色? ===
(如果我要在一个单独的问题中提出这个问题,请这样说,我将其删除.)

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

新建一个VCL应用程序,将示例代码粘贴到其中,附加FormCreate,然后运行调试.现在,将鼠标悬停在窗体上,您可以看到面板清楚地重新绘制了其背景.但是,如示例代码所示,我应该已经通过截获WM_ERASEBKGND消息来截获此行为.

New a VCL application, pasting the sample code in, attach the FormCreate, run debug. Now hover the mouse over the form, you could see the panel is clearly repainting its background. However, as shown in the sample code, I should already intercepted this behaviour by intercepting the WM_ERASEBKGND message.

请注意,如果我将这三行注释掉,

Note, if I comment out this three lines,

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

然后可以捕获WM_ERASEBKGND消息.我对这种区别一无所知.

then the WM_ERASEBKGND message can be captured. I have no clue about this difference.

有人可以对此行为的原因进行评论,以及如何正确地拦截WM_ERASEBKGND消息(当ParentBackground:= False时)?

Could some one help to comment on the reason of this behavior, and how to intercept WM_ERASEBKGND message correctly (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.

推荐答案

通常的方法是使用form.DoubleBuffered,我认为您已经在代码中进行过这项工作,因此,如果那样简单,我想您会已经解决了.

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

我认为,除了从屏幕外的位图直接拉伸到paintbox.Canvas之外,还可以避免在OnPaint中进行任何操作. OnPaint中的其他任何内容都可能引起闪烁.这意味着,在OnPaint内部无需修改TBitmap.我再说一次.不要在绘画事件中更改状态.绘制事件应包含位图分隔"操作,GDI矩形和线调用等,但除此之外没有其他内容.

I think one could also perhaps avoid any operation in the OnPaint other than a stretch-draw directly onto your paintbox.Canvas, from your offscreen bitmap. Anything else in OnPaint is a potentially flicker-inducing mistake. That means, no modification of the TBitmap from within the OnPaint. Let me say that a third time; Don't change state in paint events. Paint events should contain a "bitmap-blit" operation, GDI rectangle and line calls, etc, but nothing else.

我毫不犹豫地向任何人推荐他们尝试WM_SETREDRAW,但这是人们使用的一种技术.您可以捕获移动/调整大小窗口事件或消息,然后打开/关闭WM_SETREDRAW,但是这充满了复杂性和问题,我不建议这样做.您还可以调用各种Win32函数来锁定窗口,这些都是非常危险的操作,因此不建议这样做.

I hesitate to recommend to anyone that they experiment with WM_SETREDRAW, but it is one technique people use. You can catch the move/resize window events or messages, and turn WM_SETREDRAW on/off, but this is SO fraught with complications and problems, that I don't recommend it. You can also call various Win32 functions to lock a window, and these are all highly dangerous and not recommended.

这篇关于如何消除TPaintBox右边缘的闪烁(例如,调整大小时)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆