IUndoStack interface Core

Grid-wide undo/redo as a CONTRACT, not a concrete table type.

This is the pattern every serious grid and editor converges on: ONE history service holding inverse-operation entries (command pattern), fed at the model's commit choke point, with batch brackets so a composite gesture undoes as a single step. A template that owns mutations (a table, a timeline drag, a chart edit) implements this, and the grid wiring, the toolbar commands and the shortcut all talk to the contract.

IUndoStack.Record is the seam for STRUCTURAL changes that do not travel through a cell write (move an activity, split an interval, insert a row): the caller hands the two closures, the implementation owns ordering, capping and the redo-invalidation rule (a new entry clears the redo side). Implementations must ignore a IUndoStack.Record that happens while a replay is running — the undo of an undo is the redo entry it came from, not a fresh recording. Batch brackets stay on ISlotModel.BeginEditBatch/ISlotModel.EndEditBatch: they scope CELL commits and recordings alike, so a drag that writes two cells and records one structural entry still undoes as one step.

public interface IUndoStack
Namespace
ArionUI.Core
Package
ArionUI.Core · dotnet add package ArionUI.Core --prerelease

Properties

CanRedo

bool CanRedo { get; }

Is there an undone entry to re-apply?

CanUndo

bool CanUndo { get; }

Is there an entry to revert? The enabled state of an undo button.

RedoDepth

int RedoDepth { get; }

How many undone entries can be re-applied.

UndoDepth

int UndoDepth { get; }

How many entries can be reverted. An application that writes "Undo (3)" into its menu, or shows the history as a list, needs the NUMBER — IUndoStack.CanUndo only ever says "more than none".

Methods

Clear

void Clear();

Drop the whole history — e.g. after the app saved and replaced the items; stale references must not be replayed.

Record

void Record(Action undo, Action redo);

Push an inverse-operation pair onto the history. The closures capture item IDENTITY, not indexes, so the entry survives sort/filter/group reshapes.

Redo

bool Redo();

Re-apply the most recently undone entry. False when empty or refused.

Undo

bool Undo();

Revert the most recent entry. False when empty or refused (read-only).

Events

HistoryChanged

event Action? HistoryChanged;

Raised whenever IUndoStack.CanUndo/IUndoStack.CanRedo may have changed (record, undo, redo, clear, pruning). A bound button listens HERE instead of knowing which grid events happen to touch the history — that list is exactly what gets out of date.