delphi - 为什么包含 500 个组件的表单速度很慢?

标签 delphi

我正在创建一个表单,其中有图标 - 就像桌面上一样,它们可以自由移动。 有时我想显示 500 个或更多图标,因此它们需要快速工作。 我的图标是:

TMyIcon = class(TGraphicControl)

所以它没有 Windows 句柄。 绘制的图是:

  • 1 x Canvas.Rectangle (which is about 64x32)
  • 1 x Canvas.TextOut (a bit smaller than the rectangle)
  • 1 x Canvas.Draw (image is 32x32)

移动东西的代码如下: 我的图标鼠标移动:

Ico.Left := Ico.Left + X-ClickedPos.X;
Ico.Top  := Ico.Top  + Y-ClickedPos.Y;

表单上通常有 50 个左右的图标 - 其余的位于可见区域之外。 当我有 100 个图标时 - 我可以自由移动它们并且速度很快。但是当我创建 500 个图标时,它会变得滞后 - 但可见图标的数量仍然相同。 如何告诉 Windows 完全忽略不可见的图标,以便一切顺利进行?

或者也许有一个组件可以显示类似桌面的图标并能够移动它们?类似 TShellListView 且 AutoArrange = False 的东西?

最佳答案

TGraphicControl 是一个没有自己的句柄的控件。它使用其父级来显示其内容。这意味着,更改控件的外观也将强制重新绘制父控件。这也可能会触发重新绘制所有其他控件。

理论上,只有控件 X 所在的父级部分需要失效,因此只有与该部分重叠的控件才需要重新绘制。但是,这仍然可能会导致链式 react ,导致每次更改其中一个控件中的单个像素时都会调用大量绘制方法。

显然,可见区域之外的图标也被重新绘制。我认为如果图标位于可见区域之外,您可以通过将图标的 Visible 属性设置为 False 来优化此功能。

如果这不起作用,您可能需要一种完全不同的方法:可以选择在单个控件上绘制所有图标,从而允许您缓冲图像。如果您拖动一个图标,则可以在位图上绘制所有其他图标一次。每次鼠标移动时,您只需要绘制缓冲位图和拖动的单个图标,而不是 100(或 500)个单独的图标。尽管需要付出更多的努力来开发,但这应该会大大加快速度。

你可以这样实现:

type
  // A class to hold icon information. That is: Position and picture
  TMyIcon = class
    Pos: TPoint;
    Picture: TPicture;
    constructor Create(Src: TBitmap);
    destructor Destroy; override;
  end;

  // A list of such icons
  //TIconList = TList<TMyIcon>;
  TIconList = TList;

  // A single graphic controls that can display many icons and 
  // allows dragging them
  TIconControl = class(TGraphicControl)
    Icons: TIconList;
    Buffer: TBitmap;
    DragIcon: TMyIcon;

    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Initialize;
    // Painting
    procedure ValidateBuffer;
    procedure Paint; override;
    // Dragging
    function IconAtPos(X, Y: Integer): TMyIcon;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  end;


{ TMyIcon }

// Some random initialization 
constructor TMyIcon.Create(Src: TBitmap);
begin
  Picture := TPicture.Create;
  Picture.Assign(Src);
  Pos := Point(Random(500), Random(400));
end;

destructor TMyIcon.Destroy;
begin
  Picture.Free;
  inherited;
end;

然后,图形控件本身:

{ TIconControl }

constructor TIconControl.Create(AOwner: TComponent);
begin
  inherited;
  Icons := TIconList.Create;
end;

destructor TIconControl.Destroy;
begin
  // Todo: Free the individual icons in the list.
  Icons.Free;
  inherited;
end;

function TIconControl.IconAtPos(X, Y: Integer): TMyIcon;
var
  r: TRect;
  i: Integer;
begin
  // Just return the first icon that contains the clicked pixel.
  for i := 0 to Icons.Count - 1 do
  begin
    Result := TMyIcon(Icons[i]);
    r := Rect(0, 0, Result.Picture.Graphic.Width, Result.Picture.Graphic.Height);
    OffsetRect(r, Result.Pos.X, Result.Pos.Y);
    if PtInRect(r, Point(X, Y)) then
      Exit;
  end;
  Result := nil;
end;


procedure TIconControl.Initialize;
var
  Src: TBitmap;
  i: Integer;
begin
  Src := TBitmap.Create;
  try
    // Load a random file.
    Src.LoadFromFile('C:\ff\ff.bmp');

    // Test it with 10000 icons.
    for i := 1 to 10000 do
      Icons.Add(TMyIcon.Create(Src));

  finally
    Src.Free;
  end;
end;

procedure TIconControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  if Button = mbLeft then
  begin
    // Left button is clicked. Try to find the icon at the clicked position
    DragIcon := IconAtPos(X, Y);
    if Assigned(DragIcon) then
    begin
      // An icon is found. Clear the buffer (which contains all icons) so it
      // will be regenerated with the 9999 not-dragged icons on next repaint.
      FreeAndNil(Buffer);

      Invalidate;
    end;
  end;
end;

procedure TIconControl.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(DragIcon) then
  begin
    // An icon is being dragged. Update its position and redraw the control.
    DragIcon.Pos := Point(X, Y);

    Invalidate;
  end;
end;

procedure TIconControl.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  if (Button = mbLeft) and Assigned(DragIcon) then
  begin
    // The button is released. Free the buffer, which contains the 9999
    // other icons, so it will be regenerated with all 10000 icons on
    // next repaint.
    FreeAndNil(Buffer);
    // Set DragIcon to nil. No icon is dragged at the moment.
    DragIcon := nil;

    Invalidate;
  end;
end;

procedure TIconControl.Paint;
begin
  // Check if the buffer is up to date.
  ValidateBuffer;

  // Draw the buffer (either 9999 or 10000 icons in one go)
  Canvas.Draw(0, 0, Buffer);

  // If one ican was dragged, draw it separately.
  if Assigned(DragIcon) then
    Canvas.Draw(DragIcon.Pos.X, DragIcon.Pos.Y, DragIcon.Picture.Graphic);
end;

procedure TIconControl.ValidateBuffer;
var
  i: Integer;
  Icon: TMyIcon;
begin
  // If the buffer is assigned, there's nothing to do. It is nilled if
  // it needs to be regenerated.
  if not Assigned(Buffer) then
  begin
    Buffer := TBitmap.Create;
    Buffer.Width := Width;
    Buffer.Height := Height;
    for i := 0 to Icons.Count - 1 do
    begin
      Icon := TMyIcon(Icons[i]);
      if Icon <> DragIcon then
        Buffer.Canvas.Draw(Icon.Pos.X, Icon.Pos.Y, Icon.Picture.Graphic);
    end;
  end;
end;

创建其中一个控件,使其填充表单并使用 10000 个图标对其进行初始化。

procedure TForm1.FormCreate(Sender: TObject);
begin
  DoubleBuffered := True;

  with TIconControl.Create(Self) do
  begin
    Parent := Self;
    Align := alClient;
    Initialize;
  end;
end;

这有点快而且脏,但它表明这个解决方案可能工作得很好。如果您开始拖动(按下鼠标),您会注意到在传递缓冲区的位图上绘制了 10000 个图标时出现了短暂的延迟。之后,拖动时不会出现明显的延迟,因为每次重绘时只绘制两个图像(而不是您的情况下的 500 个图像)。

关于delphi - 为什么包含 500 个组件的表单速度很慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13128578/

相关文章:

delphi - Indy 在不处理 302 的情况下获取响应文本

delphi - 如何在运行时在 TTreeView 的选定项旁边放置一个按钮?

c# - 将二进制文件读入struct但得到全0?

delphi - 如何在子报表周围绘制超出其父区域的框架?

ios - 带有 Firemonkey 的 iAd?

delphi - Delphi XE 2 中的代码完成不适用于 Delphi 7 中的 .pas 文件

json - 使用 StringReplace 从 JSON 响应中删除不需要的字符的替代方法

delphi - 如何让音乐在Delphi 7中播放?

delphi - 为什么通过 dbExpress 重新连接到 Oracle 数据库会导致访问冲突?

delphi - 强制将 Real 的默认值写入 WriteComponent()