TGraphicControlは、独自のハンドルを持たないコントロールです。親を使用してコンテンツを表示します。つまり、コントロールの外観を変更すると、親も再描画されます。これにより、他のすべてのコントロールの再描画がトリガーされる場合もあります。
理論的には、コントロールXが配置されている親の部分のみを無効にする必要があるため、その部分とオーバーラップするコントロールのみを再描画する必要があります。しかし、それでも、これは連鎖反応を引き起こす可能性があり、これらのコントロールの1つで1つのピクセルを変更するたびに、多くのペイントメソッドが呼び出されます。
どうやら、表示領域の外側のアイコンも塗り直されています。アイコンが表示領域の外側にある場合は、アイコンのVisibleプロパティをFalseに設定することで、これを最適化できると思います。
これが機能しない場合は、まったく異なるアプローチが必要になる場合があります。単一のコントロールにすべてのアイコンをペイントして、画像をバッファリングできるようにするオプションがあります。アイコンをドラッグしている場合は、他のすべてのアイコンをビットマップに1回ペイントできます。マウスを動かすたびに、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;
次に、graphiccontrol自体:
{ 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;
これらのコントロールの1つを作成し、フォームに入力して、10000個のアイコンで初期化します。
procedure TForm1.FormCreate(Sender: TObject);
begin
DoubleBuffered := True;
with TIconControl.Create(Self) do
begin
Parent := Self;
Align := alClient;
Initialize;
end;
end;
少し速くて汚いですが、このソリューションが非常にうまく機能する可能性があることを示しています。ドラッグ(マウスダウン)を開始すると、バッファを通過するビットマップに10000個のアイコンが描画されるため、わずかな遅延が発生します。その後、再描画ごとに2つの画像のみが描画されるため(この場合は500ではなく)、ドラッグ中に目立った遅延は発生しません。