如何让我的 TCustomControl 后代组件停止闪烁?

Posted

技术标签:

【中文标题】如何让我的 TCustomControl 后代组件停止闪烁?【英文标题】:How to make my TCustomControl descendant component stop flickering? 【发布时间】:2014-11-25 15:40:09 【问题描述】:

我有一个图形化的TCustomControl 后代组件,上面有一个TScrollBar。问题是当我按箭头键移动光标时,整个画布都以背景颜色绘制,包括滚动条区域,然后滚动条被重新绘制,这使得滚动条闪烁。我该如何解决这个问题?

这里是代码。无需安装组件或在主窗体上放东西,只需复制代码并分配TForm1.FormCreate事件:

Unit1.pas

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, SuperList;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
     Private declarations 
  public
     Public declarations 
  end;

var
  Form1: TForm1;
  List: TSuperList;

implementation

$R *.dfm

procedure TForm1.FormCreate(Sender: TObject);
begin
 List:=TSuperList.Create(self);
 List.Top:=50; List.Left:=50;
 List.Visible:=true;
 List.Parent:=Form1;
end;

end.

SuperList.pas

unit SuperList;

interface

uses Windows, Controls, Graphics, Classes, Messages, SysUtils, StdCtrls, Forms;

type

  TSuperList = class(TCustomControl)
  public
    DX,DY: integer;
    ScrollBar: TScrollBar;
    procedure   Paint; override;
    constructor Create(AOwner: TComponent); override;
    procedure   WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
    procedure   WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure   WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
  published
    property    OnMouseMove;
    property    OnKeyPress;
    property    OnKeyDown;
    property    Color default clWindow;
    property    TabStop default true;
    property    Align;
    property    DoubleBuffered default true;
    property    BevelEdges;
    property    BevelInner;
    property    BevelKind default bkFlat;
    property    BevelOuter;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Marus', [TSuperList]);
end;

procedure TSuperList.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
 inherited;
 Message.Result:= Message.Result or DLGC_WANTARROWS;
end;

procedure TSuperList.WMKeyDown(var Message: TWMKeyDown);
begin
 if Message.CharCode=VK_LEFT  then begin dec(DX,3); Invalidate; exit; end;
 if Message.CharCode=VK_RIGHT then begin inc(DX,3); Invalidate; exit; end;
 if Message.CharCode=VK_UP    then begin dec(DY,3); Invalidate; exit; end;
 if Message.CharCode=VK_DOWN  then begin inc(DY,3); Invalidate; exit; end;
 inherited;
end;

procedure TSuperList.WMLButtonDown(var Message: TWMLButtonDown);
begin
 DX:=Message.XPos;
 DY:=Message.YPos;
 SetFocus;
 Invalidate;
 inherited;
end;

constructor TSuperList.Create(AOwner: TComponent);
begin
 inherited;
 DoubleBuffered:=true;
 TabStop:=true;
 Color:=clNone; Color:=clWindow;
 BevelKind:=bkFlat;
 Width:=200;
 Height:=100;
 DX:=5; DY:=50;
 ScrollBar:=TScrollBar.Create(self);
 ScrollBar.Kind:=sbVertical;
 ScrollBar.TabStop:=false;
 ScrollBar.Align:=alRight;
 ScrollBar.Visible:=true;
 ScrollBar.Parent:=self;
end;

procedure TSuperList.Paint;
begin
 Canvas.Brush.Color:=Color;
 Canvas.FillRect(Canvas.ClipRect);
 Canvas.TextOut(10,10,'Press arrow keys !');
 Canvas.Brush.Color:=clRed;
 Canvas.Pen.Color:=clBlue;
 Canvas.Rectangle(DX,DY,DX+30,DY+20);
end;

end.

【问题讨论】:

你试过中间缓冲区位图吗?我们的想法是,在不可见的画布上完成所有绘图,然后在完成后将该图像绘制到您的控件中。 我曾说过,为滚动条设置父级将是一个问题。我认为你最好让系统处理它。在控件中将DoubleBuffered 设置为True 看起来很可疑。你不应该需要双重缓冲。 +1 一个非常好的问题,我们需要的所有代码都很好地减少了。 @JerryDodge 是的。启用DoubleBuffered 属性,所有绘图首先在不可见位图上绘制。 另一方面,我看不到任何闪烁。 @marusnebunu 您是否在虚拟机内部进行测试?如果是这样,请尝试在室外进行测试,看看是否仍然闪烁。 【参考方案1】:

我认为我要做的第一件事是删除滚动条控件。 Windows 带有现成的滚动条。您只需要启用它们。

因此,首先从组件中删除 ScrollBar。然后添加一个CreateParams 覆盖:

procedure CreateParams(var Params: TCreateParams); override;

这样实现:

procedure TSuperList.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := Params.Style or WS_VSCROLL;
end;

Yippee,您的控件现在有一个滚动条。

接下来你需要为WM_VSCROLL添加一个处理程序:

procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;

这是这样实现的:

procedure TSuperList.WMVScroll(var Message: TWMVScroll);
begin
  case Message.ScrollCode of
  SB_LINEUP:
    begin
      dec(DY, 3);
      Invalidate;
    end;
  SB_LINEDOWN:
    begin
      inc(DY, 3);
      Invalidate;
    end;
  ... 
  end;
end;

您需要填写其余的滚动代码。

我还建议您不要在组件的构造函数中设置DoubleBuffered。让用户根据需要进行设置。您的控件没有理由需要双缓冲。

【讨论】:

耶,就是这样!不再闪烁。非常感谢大卫赫弗南! :) 在滚动消息处理程序中,您应该更喜欢使用ScrollWindowEx 函数而不是Invalidate(即使您要使整个客户矩形无效)。 </nitpick>

以上是关于如何让我的 TCustomControl 后代组件停止闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

为 TCustomControl Delphi 捕获/创建 OnGetFocus/OnLostFocus 事件

如何在我的组件中添加对操作的支持

12.组件化开发2-非父子组件之间通信-祖先和后代之间的通信

ADT 树 - 是节点本身的祖先/后代吗?

样式化组件中的后代

如何让我的 javascript 动画更快?