Skip to content

Add bulk DeleteItems to TALDynamicListBox - #519

Open
Spelt wants to merge 2 commits into
MagicFoundation:masterfrom
Spelt:feature/bulk-delete-items
Open

Add bulk DeleteItems to TALDynamicListBox#519
Spelt wants to merge 2 commits into
MagicFoundation:masterfrom
Spelt:feature/bulk-delete-items

Conversation

@Spelt

@Spelt Spelt commented Aug 29, 2026

Copy link
Copy Markdown

Problem

Deleting a large range of items one by one via DeleteItemAtIndex is quadratic: every removal shifts the controls array, renumbers all trailing indexes and triggers a Realign. In a virtualized directory tree built on TALDynamicListBox, collapsing a branch with 24,000 children took ~3 seconds, while expanding the same branch was instant thanks to the existing bulk InsertItems.

Solution

DeleteItems(AIndex, ACount) on TView.TMainContent, TView and TALDynamicListBox — the bulk counterpart of InsertItems:

  • detach each doomed item in O(1): clear FOwner/FIndex, SetHost(nil) (releases hovered/captured refs), then ALFreeAndNil(..., true{delayed}) for the same reason DeleteItemAtIndex uses delayed destruction
  • one ALMove over the controls array, one renumber pass
  • the same index bookkeeping as InsertItems (first/last visible, first/last preloaded, download trigger), shifted down instead of up, with indexes inside the removed block collapsing onto AIndex
  • a single Realign(AIndex) + Repaint

Deleting a 24k range is now as fast as inserting it.

Tested on Delphi 13, Win64, with a file-explorer style tree: expand/collapse of 24k-item directories, selection, scrolling and re-expand behave correctly.

Deleting a large range of items one by one via DeleteItemAtIndex is
quadratic: every removal shifts the controls array, renumbers all
trailing indexes and triggers a realign. Collapsing a 24k-item branch
in a virtualized tree built on TALDynamicListBox took ~3 seconds.

DeleteItems(AIndex, ACount) on TMainContent/TView/TALDynamicListBox is
the bulk counterpart of the existing InsertItems: detach the doomed
items in O(1) each (owner/host cleared, delayed destruction like
DeleteItemAtIndex), then one array shift, one renumber, the same index
bookkeeping as InsertItems (visible/preloaded/download-trigger), and a
single Realign. Deleting a range becomes as fast as inserting one.
@Zeus64

Zeus64 commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

thanks @Spelt i will review it soon !

InsertItems: inserting inside the preloaded window used to extend the
window across the inserted (never prepared) items; the next
SetViewportPosition then called Unprepare once per inserted item (a 24k
insert = 24k calls) and the window no longer matched which items were
actually prepared. Clamp the window to the part before the insertion
point and unprepare the shifted prepared tail instead.

DeleteItems: when the deleted block overlaps the window, the prepared
range can split into a head and a shifted tail; keep the head and
unprepare the tail (or keep the shifted tail when there is no head).
Previously rows below the collapse point could stay unprepared while
visible.

TItem.Unprepare: fast exit when there is nothing to cancel or free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Spelt

Spelt commented Aug 29, 2026

Copy link
Copy Markdown
Author

Added a second commit that keeps the preloaded window consistent across bulk InsertItems/DeleteItems: inserting inside the window no longer marks the inserted (never prepared) items as prepared, deleting a block that overlaps the window keeps the prepared head and unprepares the shifted tail, and TItem.Unprepare exits early when there is nothing to cancel or free. Found while collapsing/expanding a 24k-item folder repeatedly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants