YOUSEFUL / DELPHI / COMPONENTS
TAtlasTable
An in-memory table with a fixed, typed schema, a sorted and filtered view, change notifications and CSV import and export.
Description
TAtlasTable (unit Atlas.Data) is the data model behind TAtlasGrid, TAtlasPivot and TAtlasPrintDocument. It keeps rows of strings in memory. Each column has a TAtlasColumnKind that decides which strings are accepted and how they are stored (12.50 becomes 12.5, 1 becomes true, dates are YYYY-MM-DD). Empty values are allowed in every column.
Schema first. Call AddColumn for every column before the first row; once the table holds rows, AddColumn raises. Columns cannot be removed or renamed. Only OnChange is published, so columns are always defined in code.
View and source. Sort and SetFilter build a view. RowCount, Cell, SetCell, DeleteRow and SourceIndex take zero-based view row indices, which change with every sort, filter, insert and delete. SourceIndex returns the current position in the underlying list, not a persistent ID; keep a business key in a column of its own. SourceRowCount counts all rows.
Notifications. Every change (AddColumn, AddRow, SetCell, DeleteRow, Clear, Sort, SetFilter, ImportCSV) calls each listener registered with AddListener, then OnChange. Grids register themselves as listeners, so several views can share one table.
Errors. Invalid input raises EAtlasDataError and leaves the table unchanged; ImportCSV validates the whole text before it replaces any row.
Unit functions. Atlas.Data also declares function AtlasParseCSV(const Text: string): TArray<TAtlasRow>; (quoted fields, doubled quotes, CR, LF or CRLF line ends; a leading byte order mark is skipped; malformed quoting raises EAtlasDataError), function AtlasCSVField(const Text: string): string; (quotes a field that contains a comma, quote, CR or LF) and function AtlasNormalizeValue(Kind: TAtlasColumnKind; const Value: string): string; (validates one value and returns its stored form, exactly as AddRow does).
The table is meant for the VCL main thread and is not thread-safe. It has no database connection, no undo and no persistence other than CSV text.
Members
| Member | Kind | What it does |
|---|---|---|
Create | constructor | Creates an empty table: no columns, no sort (SortColumn = -1, Ascending = True) and no filter (FilterColumn = -1). |
Destroy | destructor | Frees the rows. |
AddColumn | method | Adds a column and returns its zero-based index. |
AddRow | method | Validates one value per column, stores the normalized values and returns the new row's position in the underlying list. |
Clear | method | Removes every row and notifies. |
DeleteRow | method | Deletes the row shown at view index ViewRow. |
SourceIndex | method | Returns the position in the underlying list of the row at view index ViewRow. |
Cell | method | Returns the stored value in Column of the row at view index ViewRow: the normalized form (12.5, true), or '' for an empty value. |
SetCell | method | Validates Value and stores it in Column of the row at view index ViewRow, then rebuilds the view: the row may move (when the sorted column changed) or disappear (when it no longer matches the filter). |
Sort | method | Sorts the view by Column; -1 restores source order. |
SetFilter | method | Shows only the rows whose stored value contains Text, ignoring case (ContainsText, Unicode-aware): in column Column, or in any column when Column is -1. |
ExportCSV | method | Returns CSV text: a header record with the column names, then one record per row, CRLF line ends, and fields quoted only when they contain a comma, quote, CR or LF. |
ImportCSV | method | Replaces all rows with the records of a CSV text. |
AddListener | method | Registers a method that is called, with the table as Sender, after every change and before OnChange. |
RemoveListener | method | Removes a listener registered with AddListener. |
ColumnCount | property | Number of columns. |
RowCount | property | Number of rows in the current view, after the filter. |
SourceRowCount | property | Number of rows in the table, whatever the filter. |
Columns | property | A copy of the definition of the column at Index, as a TAtlasColumn record; changing the copy does not change the table. |
SortColumn | property | The column the view is sorted by, or -1 when it is in source order. |
Ascending | property | The direction of the current sort (True after Create). |
FilterText | property | The current filter text; '' when there is no filter. |
FilterColumn | property | The column the filter searches, or -1 for all columns. |
OnChange | event | Called after every change, after the listeners. |
Constructors
Create constructor
constructor Create(AOwner: TComponent); override;Creates an empty table: no columns, no sort (SortColumn = -1, Ascending = True) and no filter (FilterColumn = -1).
Destructors
Destroy destructor
destructor Destroy; override;Frees the rows. Grids that show the table receive a free notification and set their Table to nil.
Properties
ColumnCount property
property ColumnCount: Integer read GetColumnCount;Number of columns.
RowCount property
property RowCount: Integer read GetRowCount;Number of rows in the current view, after the filter.
SourceRowCount property
property SourceRowCount: Integer read GetSourceRowCount;Number of rows in the table, whatever the filter.
Columns property
property Columns[Index: Integer]: TAtlasColumn read GetColumn;A copy of the definition of the column at Index, as a TAtlasColumn record; changing the copy does not change the table. Raises EAtlasDataError for an invalid index.
Caption := Table.Columns[0].Name;SortColumn property
property SortColumn: Integer read FSortColumn;The column the view is sorted by, or -1 when it is in source order.
Ascending property
property Ascending: Boolean read FAscending;The direction of the current sort (True after Create).
FilterText property
property FilterText: string read FFilter;The current filter text; '' when there is no filter.
FilterColumn property
property FilterColumn: Integer read FFilterColumn;The column the filter searches, or -1 for all columns.
Events
OnChange event
property OnChange: TNotifyEvent read FOnChange write FOnChange;Called after every change, after the listeners. Use it for one application hook such as a status line; use AddListener when several objects need notifications.
Methods
AddColumn method
function AddColumn(const Name: string; Kind: TAtlasColumnKind = ackText; Width: Integer = 120; Editable: Boolean = True): Integer;Adds a column and returns its zero-based index. Kind decides validation and sorting (default ackText). Width is the preferred grid width in pixels at 96 dpi, at least 16 (default 120). Editable = False makes the column read-only for SetCell and in grids, while AddRow and ImportCSV can still fill it. Raises EAtlasDataError when the table already holds rows, when Name is blank, when Width is below 16, or when another column has the same name (compared ignoring the case of ASCII letters). Notifies listeners.
Table.AddColumn('Invoice', ackText, 100, False); // read-only key
Table.AddColumn('Amount', ackNumber);AddRow method
function AddRow(const Values: array of string): Integer;Validates one value per column, stores the normalized values and returns the new row's position in the underlying list. That is not its view index: a sorted view places the row where it belongs and a filter may hide it. Raises EAtlasDataError when the table has no columns, when the number of values differs from ColumnCount, or when a value does not fit its column; nothing is added then.
Table.AddRow(['INV-001', '125.50']); // stored as INV-001, 125.5Clear method
procedure Clear;Removes every row and notifies. Columns, sort and filter settings are kept. Because the table is empty afterwards, more columns may be added.
DeleteRow method
procedure DeleteRow(ViewRow: Integer);Deletes the row shown at view index ViewRow. Raises EAtlasDataError when the index is out of range. The source positions of later rows move down by one.
SourceIndex method
function SourceIndex(ViewRow: Integer): Integer;Returns the position in the underlying list of the row at view index ViewRow. Raises EAtlasDataError when the index is out of range. The position changes when earlier rows are deleted or the table is reloaded, so do not store it as an identifier.
Cell method
function Cell(Column, ViewRow: Integer): string;Returns the stored value in Column of the row at view index ViewRow: the normalized form (12.5, true), or '' for an empty value. Raises EAtlasDataError for an invalid column or row index.
if Table.Cell(3, Row) = 'true' then ...SetCell method
procedure SetCell(Column, ViewRow: Integer; const Value: string);Validates Value and stores it in Column of the row at view index ViewRow, then rebuilds the view: the row may move (when the sorted column changed) or disappear (when it no longer matches the filter). Raises EAtlasDataError for an invalid index, for a read-only column (Column is read-only) or for a value that does not fit the column; the old value is kept then.
Table.SetCell(1, 0, '99.90');Sort method
procedure Sort(Column: Integer; Ascending: Boolean = True);Sorts the view by Column; -1 restores source order. ackNumber columns compare numerically; the other kinds compare as text with CompareText (ordinal, ignoring the case of ASCII letters), so ISO dates sort chronologically. Empty values come first when ascending and last when descending. Rows with equal keys keep their source order in both directions. The table keeps the sort and reapplies it after every change. Only one sort column is supported. Raises EAtlasDataError for an index other than -1 that is out of range.
Table.Sort(1, False); // largest amount first
Table.Sort(-1); // back to source orderSetFilter method
procedure SetFilter(const Text: string; Column: Integer = -1);Shows only the rows whose stored value contains Text, ignoring case (ContainsText, Unicode-aware): in column Column, or in any column when Column is -1. An empty Text shows every row. The filter matches the normalized text (true, 12.5), not what was originally typed, and there are no expressions or multiple conditions. The filter is kept and reapplied after every change. Raises EAtlasDataError for an invalid column index.
Table.SetFilter('north'); // any column
Table.SetFilter('false', 3); // column 3 onlyExportCSV method
function ExportCSV(VisibleOnly: Boolean = True): string;Returns CSV text: a header record with the column names, then one record per row, CRLF line ends, and fields quoted only when they contain a comma, quote, CR or LF. With VisibleOnly = True (the default) it writes the current view in view order; with False, every row in source order. Values are written raw: text starting with =, +, - or @ is not escaped, so apply your own policy before untrusted data reaches spreadsheet software. The string has no byte order mark; TFile.WriteAllText(Name, Text, TEncoding.UTF8) writes one.
ImportCSV method
procedure ImportCSV(const Text: string);Replaces all rows with the records of a CSV text. The first record must name every column exactly, in order and with the same case; every later record must have exactly ColumnCount fields and every field must fit its column. The whole text is parsed and validated before any row is replaced, so on failure it raises EAtlasDataError (CSV header does not match the table schema, CSV column does not match: <name>, CSV row <n> has an incorrect field count, or the value error) and the table keeps its rows. A header-only text clears the rows; a leading byte order mark is skipped. A blank line is a record with one empty field, so a blank line in a file with several columns fails the field count (one final line break is fine). Sort and filter settings are kept. Load large data with one ImportCSV rather than many AddRow calls: the view is rebuilt and listeners are notified once.
AddListener method
procedure AddListener(const Listener: TNotifyEvent);Registers a method that is called, with the table as Sender, after every change and before OnChange. Adding the same method of the same object again has no effect; nil is ignored. Remove the listener before its object is freed. TAtlasGrid follows its table this way.
Table.AddListener(Watcher.TableChanged);RemoveListener method
procedure RemoveListener(const Listener: TNotifyEvent);Removes a listener registered with AddListener. A listener may remove itself while it is being called.
Examples
Define a schema, load rows, filter and sort
A read-only key, text, number, boolean and date columns. Prints INV-001 Harbor Books 125.5, INV-003 Harbor Books 42 and 2 of 3 rows shown.
uses System.SysUtils, Atlas.Data;
procedure ListUnpaidInvoices;
var
Invoices: TAtlasTable;
I: Integer;
begin
Invoices := TAtlasTable.Create(nil);
try
Invoices.AddColumn('Number', ackText, 100, False); // read-only business key
Invoices.AddColumn('Customer');
Invoices.AddColumn('Amount', ackNumber);
Invoices.AddColumn('Paid', ackBoolean);
Invoices.AddColumn('Due', ackDate);
Invoices.AddRow(['INV-001', 'Harbor Books', '125.50', 'false', '2026-10-01']);
Invoices.AddRow(['INV-002', 'Blue Mill', '980', '1', '2026-09-15']);
Invoices.AddRow(['INV-003', 'Harbor Books', '42', '0', '']);
Invoices.SetFilter('false', 3); // unpaid rows only
Invoices.Sort(2, False); // largest amount first
for I := 0 to Invoices.RowCount - 1 do
Writeln(Invoices.Cell(0, I), ' ', Invoices.Cell(1, I), ' ', Invoices.Cell(2, I));
Writeln(Invoices.RowCount, ' of ', Invoices.SourceRowCount, ' rows shown');
finally
Invoices.Free;
end;
end;Follow changes with a listener
Any number of objects can listen to one table. The watcher recomputes a total after every change: 42.50, then 30.00 once the filter hides two rows.
uses System.SysUtils, System.Classes, Atlas.Data;
type
TTotalWatcher = class
Total: Double;
procedure TableChanged(Sender: TObject);
end;
procedure TTotalWatcher.TableChanged(Sender: TObject);
var
Table: TAtlasTable;
I: Integer;
begin
Table := Sender as TAtlasTable;
Total := 0;
for I := 0 to Table.RowCount - 1 do
if Table.Cell(1, I) <> '' then
Total := Total + StrToFloat(Table.Cell(1, I), TFormatSettings.Invariant);
end;
procedure WatchTotal;
var
Table: TAtlasTable;
Watcher: TTotalWatcher;
begin
Table := TAtlasTable.Create(nil);
Watcher := TTotalWatcher.Create;
try
Table.AddColumn('Item');
Table.AddColumn('Amount', ackNumber);
Table.AddListener(Watcher.TableChanged);
Table.AddRow(['Paper', '12.5']);
Table.AddRow(['Ink', '30']);
Table.AddRow(['Pens', '']);
Writeln(Watcher.Total:0:2); // 42.50
Table.SetFilter('ink', 0);
Writeln(Watcher.Total:0:2); // 30.00: the watcher sums the visible rows
Table.RemoveListener(Watcher.TableChanged);
finally
Watcher.Free;
Table.Free;
end;
end;Save and reload CSV
Export every row, then import it back; a file with an invalid value is rejected as a whole and the table keeps its rows.
uses System.SysUtils, System.IOUtils, Atlas.Data;
procedure SaveToCsv(Table: TAtlasTable; const FileName: string);
begin
// False: every row in source order, whatever the filter and sort.
TFile.WriteAllText(FileName, Table.ExportCSV(False), TEncoding.UTF8);
end;
procedure ReloadFromCsv(Table: TAtlasTable; const FileName: string);
begin
try
Table.ImportCSV(TFile.ReadAllText(FileName, TEncoding.UTF8));
except
on E: EAtlasDataError do
Writeln('Not loaded, previous rows kept: ', E.Message);
end;
end;Related
TAtlasColumn · TAtlasColumnKind · TAtlasRow · EAtlasDataError · TAtlasGrid · TAtlasValueEdit · TAtlasPivot · TAtlasPrintDocument
Also in Atlas VCL
TAtlasGrid · TAtlasValueEdit · TAtlasTree · TAtlasWorkbook · TAtlasSheetGrid · TAtlasPivot · TAtlasInspector · TAtlasRichEdit · TAtlasSpellChecker · TAtlasChart · TAtlasGauge · TAtlasThemer · TAtlasWizard · TAtlasWorkspace · TAtlasCommandBar · TAtlasActionTile · TAtlasTileBoard · TAtlasNavigation · TAtlasMap · TAtlasGraph · TAtlasDiagram · TAtlasPrintDocument · TAtlasSchedule · TAtlasScheduler · TAtlasTaskPlan · TAtlasGantt · TAtlasPdfDocument · TAtlasPdfViewer