TGraphicControl 是一个没有自己的句柄的控件。它使用其父级来显示其内容。这意味着,更改控件的外观也会强制重绘父控件。这也可能触发重新绘制所有其他控件。
理论上,只有控件 X 所在的父级部分需要失效,因此只有与该部分重叠的控件才需要重新绘制。但是,这可能会导致连锁反应,导致每次更改其中一个控件中的单个像素时都会调用许多绘制方法。
显然,可见区域之外的图标也被重新绘制。我认为您可以通过将图标的 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 个)。