YOUSEFUL / DELPHI / COMPONENTS
TAtlasDBGrid
A data-aware grid with validated typed edits that never post on their own, CSV export of visible or selected rows, and a row height that follows the monitor DPI.
Description
TAtlasDBGrid is a TDBGrid descendant. Bind it to a TDataSource as usual; Columns, Options, ReadOnly, SelectedRows and every other standard grid member work unchanged and are not repeated here.
What it adds:
- Typed edits.
EditValueconverts invariant text to the field's type and checks the read-only, required, size and range rules before it touches the record. It then puts the dataset into edit state and assigns the value. It never posts: your code decides when to callPostorCancel. Moving the cursor to another record still posts a pending change, as it does for everyTDataSet. - CSV export.
ExportCSVwrites the visible columns of every record the dataset currently lets through, or of the selected rows only, and puts the cursor back where it was. It refuses to run while a record is being edited instead of posting it. - Row density.
LogicalRowHeightis the row height at 96 PPI. The grid multiplies it by the control's current PPI and follows per-monitor DPI changes, so rows keep the same physical size at 100 % to 400 % scaling.
The constructor adds dgMultiSelect and dgAlwaysShowSelection to Options, so rows can be selected with Ctrl and Shift clicks, and sets DefaultDrawing to False. Cells are filled with the window and highlight colours of the active VCL style and then drawn with DefaultDrawColumnCell; an OnDrawColumnCell handler runs after that. The column's font name, size and style are used, but column colours and font colours are replaced by the style colours.
Atlas Info adds no column classes, events, sorting, grouping, footers or printing to the grid: columns are the standard TColumn objects.
The grid's own checks raise EAtlasDatabaseError. Invalid numbers, Booleans and dates are reported by the Atlas VCL unit Atlas.Data with EAtlasDataError, and an unknown field name by Data.DB with EDatabaseError.
Members
| Member | Kind | What it does |
|---|---|---|
Create | constructor | Creates the grid with LogicalRowHeight 28, records the control's current PPI, sets DefaultRowHeight to ScaledRowHeight, adds dgMultiSelect and dgAlwaysShowSelection to Options and sets DefaultDrawing to False. |
EditValue | method | Writes Text to the field FieldName of the dataset's current record, converted to the field's type. |
ExportCSV | method | Returns the dataset's records as CSV text. |
ScaledRowHeight | method | Returns LogicalRowHeight scaled to the PPI the grid was created at, or the PPI of the last DPI change: MulDiv(LogicalRowHeight, PPI, 96). |
LogicalRowHeight | property | Row height in pixels at 96 PPI. |
Constructors
Create constructor
constructor Create(AOwner: TComponent); override;Creates the grid with LogicalRowHeight 28, records the control's current PPI, sets DefaultRowHeight to ScaledRowHeight, adds dgMultiSelect and dgAlwaysShowSelection to Options and sets DefaultDrawing to False.
Grid := TAtlasDBGrid.Create(Form);
Grid.Parent := Form;
Grid.DataSource := CustomerSource;Properties
LogicalRowHeight property
property LogicalRowHeight: Integer read FLogicalRowHeight write SetLogicalRowHeight default 28;Row height in pixels at 96 PPI. Default 28. Values outside 18..120 raise EAtlasDatabaseError (Logical row height must be 18..120). Setting it updates DefaultRowHeight to ScaledRowHeight and repaints the grid.
Set this property rather than DefaultRowHeight: the grid overwrites DefaultRowHeight on the next layout or DPI change.
Grid.LogicalRowHeight := 22; // denser rowsMethods
EditValue method
procedure EditValue(const FieldName, Text: string);Writes Text to the field FieldName of the dataset's current record, converted to the field's type. The record is never posted.
These checks run before the record is touched, so a refused value leaves the dataset state unchanged:
DataSourcehas an active dataset (An active data source is required).- The field exists (
EDatabaseErrorfromFieldByName). - The grid is not
ReadOnly, the datasetCanModify, and the field is a data field (not calculated or lookup) that is notReadOnly(Field is read-only: <name>). - The text converts to the field type:
- empty text sets the field to Null, unless the field is
Required(A value is required: <name>); - integer fields (
ftSmallint,ftInteger,ftWord,ftAutoInc,ftLargeint,ftShortint,ftByte,ftLongWord) take integer text and are range-checked for their type (Invalid integer,Integer out of range,Word out of rangeand so on); ftFloat,ftCurrency,ftBCD,ftFMTBcd,ftExtendedandftSingletake invariant number text with a.decimal separator, such as1250.75or1e3; text such as1,5orabc, NaN and infinity raiseEAtlasDataError(Invalid finite number: ...);ftBooleantakestrue,false,1or0, in any letter case;ftDatetakesYYYY-MM-DD;- string fields take text up to the field's
Size(Text exceeds field size);ftMemoandftWideMemotake any text; - every other type, including
ftDateTime,ftTime,ftTimeStampand blobs, raisesUse a typed dataset field API for this field type.
- empty text sets the field to Null, unless the field is
If the dataset was browsing, EditValue calls Edit, and cancels that edit again if assigning the value fails. If the dataset was already in edit or insert state it stays there, so several fields can be set before one Post.
Grid.EditValue('Balance', '1250.75'); // Customers.State is now dsEdit
Customers.Post; // or Customers.CancelExportCSV method
function ExportCSV(SelectedOnly: Boolean = False): string;Returns the dataset's records as CSV text. Nothing is written to disk; save the string yourself.
- Columns: every
Columnsitem that isVisibleand bound to a field, in column order. The header row uses each field'sDisplayLabel. With the default columns, setting a column'sTitle.Captionchanges thatDisplayLabel; a caption set on a customized column is not used. - Rows: with
SelectedOnlyFalse, every record fromFirsttoEof, so an active dataset filter, including TAtlasDataFilter, limits the export. WithSelectedOnlyTrue, the records inSelectedRows. That list is sorted by bookmark, so rows come out in dataset order, not in the order they were selected. An empty selection gives the header only. - Values: Null is an empty field. Floating-point and BCD values use invariant
FloatToStr(1200.5), Booleans aretrueorfalse,ftDateisYYYY-MM-DD, andftDateTimeandftTimeStampareYYYY-MM-DDThh:nn:ss.zzz. Everything else is the field'sAsString. A value containing a comma, quote, CR or LF is enclosed in quotes, with inner quotes doubled. - Every line, including the last, ends with CR LF.
The dataset must be active and browsing (Post or cancel before exporting) and bidirectional (Export requires a bidirectional dataset). At least one visible column must be bound to a field (No visible fields to export). A selected bookmark that is no longer valid raises A selected bookmark has expired. Controls are disabled during the export, and the current record is restored afterwards.
TFile.WriteAllText('customers.csv', Grid.ExportCSV, TEncoding.UTF8);ScaledRowHeight method
function ScaledRowHeight: Integer;Returns LogicalRowHeight scaled to the PPI the grid was created at, or the PPI of the last DPI change: MulDiv(LogicalRowHeight, PPI, 96). The grid sets DefaultRowHeight to this value after every layout change and DPI change.
Grid.LogicalRowHeight := 22;
// ScaledRowHeight is 22 at 96 PPI and 33 at 144 PPI (150 % scaling).Examples
Bind a grid and post an edit explicitly
Creates a grid for a customer table with denser rows, then sets a balance from text and posts it. EditValue validates the text before the record changes; the Post call is what makes the change permanent.
uses Data.DB, Vcl.Controls, Atlas.DBControls;
function AddCustomerGrid(Parent: TWinControl; Source: TDataSource): TAtlasDBGrid;
begin
Result := TAtlasDBGrid.Create(Parent);
Result.Parent := Parent;
Result.Align := alClient;
Result.DataSource := Source;
Result.LogicalRowHeight := 22; // at 96 PPI; 33 pixels at 144 PPI
end;
procedure SetBalance(Grid: TAtlasDBGrid; const Text: string);
begin
Grid.EditValue('Balance', Text); // validates, then leaves the record in edit state
Grid.DataSource.DataSet.Post; // EditValue never posts; your code does
end;Refuse bad input and cancel
Tries an edit and cancels it on any error. abc is refused with Invalid finite number: abc before the record is edited; 1e3 is stored as 1000.
uses System.SysUtils, Data.DB, Atlas.DBControls;
function TrySetBalance(Grid: TAtlasDBGrid; const Text: string): Boolean;
var
DataSet: TDataSet;
begin
DataSet := Grid.DataSource.DataSet;
try
Grid.EditValue('Balance', Text);
DataSet.Post;
Result := True;
except
on E: Exception do
begin
if DataSet.State in dsEditModes then
DataSet.Cancel;
Writeln('Rejected: ', E.Message);
Result := False;
end;
end;
end;Export visible or selected rows
Saves every record the dataset shows to a UTF-8 file, then returns the current record alone as CSV. Hide a column first (Grid.Columns[2].Visible := False) to leave it out of both.
uses System.SysUtils, System.IOUtils, Atlas.DBControls;
procedure SaveVisibleRows(Grid: TAtlasDBGrid; const FileName: string);
begin
// Visible columns only, every record the dataset's filter lets through.
TFile.WriteAllText(FileName, Grid.ExportCSV, TEncoding.UTF8);
end;
function SelectedAsCSV(Grid: TAtlasDBGrid): string;
begin
Grid.SelectedRows.CurrentRowSelected := True; // add the current record to the selection
Result := Grid.ExportCSV(True);
end;Related
TAtlasDBLookupCombo · TAtlasDataFilter · TAtlasDBRichEdit · EAtlasDatabaseError
Also in Atlas Info
TAtlasDBLookupCombo · TAtlasDBRichEdit · TAtlasDataFilter · TAtlasFilterEdit