Why is a 500-component mold slow?

I create a form on which there are icons on the desktop, and they can move freely. Sometimes I want to show even 500 or more icons so that they work quickly. My badge:

TMyIcon = class (TGraphicControl)

therefore, it does not have a Windows handle. Drawing:

  • 1 x Canvas.Rectangle (about 64x32)
  • 1 x Canvas.TextOut (slightly smaller than the rectangle)
  • 1 x Canvas.Draw (32x32 Image)

The code for moving files is as follows: MyIconMouseMove:

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

There are usually 50 or so badges in the form - the rest are outside the visible area. When I have 100 icons, I can move them freely, and it works quickly. But when I create 500 badges, it becomes laggy, but the number of visible badges remains the same. How can I tell Windows to completely ignore invisible icons so that everything runs smoothly?

Or maybe there is a component that can display icons on the desktop with the ability to move them? Something like TShellListView with AutoArrange = False?

+7
source share
2 answers

TGraphicControl is a control that does not have its own handle. He uses his parent to display his content. This means that changing the look of your control will cause the parent to redraw as well. It can also cause a redraw of all other controls.

Theoretically, only the part of the parent where the X control is located should be invalid, so only the controls that overlap this part need to be repainted. But still, it can cause a chain reaction, calling many drawing methods every time one pixel is changed in one of these controls.

Apparently, the icons outside the visible area are repainted. I think you can optimize this by setting the Visible property of the icons to False if they are outside the visible area.

If this does not work, you may need a completely different approach: it is possible to draw all the icons on one control, allowing you to save images. If you drag the icon, you can draw all the other icons on the bitmap once. Each time you move the mouse, you just need to draw a buffered bitmap and a single icon that is dragged instead of 100 (or 500) individual icons. This should speed things up quite a bit, although it will take a little more effort.

You can implement it as follows:

 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; 

Then the graphical control itself:

 { 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 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; 

Create one of these controls, make it fill out the form and initialize it with 10,000 icons.

 procedure TForm1.FormCreate(Sender: TObject); begin DoubleBuffered := True; with TIconControl.Create(Self) do begin Parent := Self; Align := alClient; Initialize; end; end; 

It is a little quick and dirty, but it shows that this solution can work very well. If you start dragging (mouse down), you will notice a slight delay when the 10000 icons are drawn on the bitmap that transfers the buffer. After that, you don’t have a noticeable delay when dragging, because only two images are drawn on each redraw (instead of 500 in your case).

+6
source

You might want to check out this control that you requested.

rkView from RMKlever

It is basically a program for viewing thumbnails or photos, etc.

+1
source

All Articles