Table of Contents

Database Adapter

TTMSFNCTimelineDatabaseAdapter is a non-visual component that connects TTMSFNCTimeline to any TDataSource-backed dataset. Once connected and activated, the timeline loads its indicators and sections automatically from the database and keeps them in sync.


Setup

  1. Drop a TTMSFNCTimelineDatabaseAdapter onto the form.
  2. Assign it to TMSFNCTimeline1.Adapter.
  3. Configure Item to map database fields.
  4. Set Active := True to load items.
uses
  FMX.TMSFNCTimeline, FMX.TMSFNCTimelineDatabaseAdapter;

// Minimal setup — connect and activate.
procedure TFormMain.ConnectTimelineAdapter;
begin
  TMSFNCTimeline1.Adapter := TimelineAdapter;
  TimelineAdapter.Item.DataSource := DataSource1;
  // DBKey is mandatory: activating without it raises
  // 'DBKey and StartDate or StartValue are not set in
  //  TTMSFNCTimelineDatabaseAdapter item source.'
  TimelineAdapter.Item.DBKey := 'Id';
  TimelineAdapter.Item.StartDate := 'EventDate';
  TimelineAdapter.Item.Text := 'EventTitle';
  TimelineAdapter.Active := True;
end;

Field mapping

The Item property (TTMSFNCTimelineDatabaseAdapterItemSource) maps dataset columns to timeline properties.

Field Description
DataSource The TDataSource connected to the dataset.
DBKey Field containing the unique record identifier. Required — the adapter refuses to activate without it, together with either StartDate or StartValue.
StartDate Field with the event start date/time (for indicators and sections).
EndDate Field with the event end date/time. When empty or equal to StartDate, the record creates an indicator; otherwise a section.
StartValue Alternative to StartDate using numeric values. When both are set, StartValue takes priority.
EndValue Alternative to EndDate using numeric values.
Text Field whose value becomes the annotation text (indicator) or section label.
Fixed Boolean field: whether the item can be moved by the user.
Selectable Boolean field: whether the item can be selected.
AutoIncrementDBKey When True (default), a new GUID is generated for each inserted record.

Indicators vs sections

The adapter determines the item type from the field values at load time:

  • EndDate is empty or equals StartDateindicator
  • EndDate is after StartDatesection

The composite below shows the mapping in both directions: the source rows on top, and beneath them the timeline the adapter produced from exactly those rows. EventDate became each indicator's position and EventTitle its annotation text; because no EndDate is mapped, every row became an indicator rather than a section.

Source dataset rows above the timeline the database adapter built from them Source dataset rows above the timeline the database adapter built from them

Items arrive selected, and an annotation is drawn with its SelectedFill/SelectedFont while its indicator is selected — so style those members of Appearance.DefaultAnnotationAppearance, not only Fill and Font, when theming an adapter-fed timeline. The adapter also loads lazily: Timeline.Indicators is still empty immediately after setting Active, so per-item work belongs in OnItemsLoaded rather than in the line after activation.


Methods

Method Description
LoadItems Clears existing items and reloads from the dataset.
GetItems(PeriodFrom, PeriodTo) Loads items within a date range (non-destructive for items outside the range).
InsertItem(AIndicator) Writes a new indicator to the database.
InsertItem(ASection) Writes a new section to the database.
UpdateItem(AIndicator) Updates an existing indicator record.
UpdateItem(ASection) Updates an existing section record.
DeleteItem(AIndicator) Removes the indicator's record.
DeleteItem(ASection) Removes the section's record.
ReadItem(AIndicator) Reads a single indicator record from the database.
SelectItem(AIndicator) Selects a record in the underlying dataset.

Events

The adapter events fall into three groups: mapping events that translate between record fields and timeline items, key events that decide how a new record is identified, and notification events that fire after a database round-trip completed. Reach for the mapping events when the schema does not match the Item field names, for the key events when the database owns its own identifier scheme instead of GUIDs, and for the notification events to refresh surrounding UI once the timeline is in sync.

Event Description
OnIndicatorToFields Manually write indicator properties to database fields.
OnFieldsToIndicator Manually read database fields into indicator properties.
OnSectionToFields Manually write section properties to database fields.
OnFieldsToSection Manually read database fields into section properties.
OnSetFieldFromIndicatorDate Manipulate the value stored in the date field on write.
OnSetIndicatorDateFromField Manipulate the value read from the date field.
OnSetFieldFromSectionStartDate / OnSetFieldFromSectionEndDate Override section start/end date on write.
OnSetSectionStartDateFromField / OnSetSectionEndDateFromField Override section start/end date on read.
OnIndicatorLocate / OnSectionLocate Override how a record is located (default: by DBKey).
OnIndicatorCreateDBKey / OnSectionCreateDBKey Override the key generated for new records (default: GUID).
OnIndicatorInserted / OnSectionInserted Called after a record was inserted.
OnIndicatorUpdated / OnSectionUpdated Called after a record was updated.
OnIndicatorRead / OnSectionRead Called after a record was read.
OnItemsLoaded Called when LoadItems or GetItems completes.

The example below issues readable sequential keys instead of GUIDs and reports each insert and each completed load:

uses
  System.SysUtils, FMX.TMSFNCTimeline, FMX.TMSFNCTimelineDatabaseAdapter;

procedure TFormMain.WireTimelineAdapterEvents;
begin
  TimelineAdapter.OnIndicatorCreateDBKey := TimelineAdapterIndicatorCreateDBKey;
  TimelineAdapter.OnIndicatorInserted := TimelineAdapterIndicatorInserted;
  TimelineAdapter.OnItemsLoaded := TimelineAdapterItemsLoaded;
end;

// Replaces the default GUID with a readable, sequential key.
procedure TFormMain.TimelineAdapterIndicatorCreateDBKey(Sender: TObject;
  AIndicator: TTMSFNCTimelineIndicator; var ADBKey: string);
begin
  Inc(FNextEventNumber);
  ADBKey := Format('EVT-%.6d', [FNextEventNumber]);
end;

// Raised after the record has been written to the dataset.
procedure TFormMain.TimelineAdapterIndicatorInserted(Sender: TObject;
  AIndicator: TTMSFNCTimelineIndicator);
begin
  lblStatus.Text := Format('Stored "%s" as %s',
    [AIndicator.Annotation.Text, AIndicator.DBKey]);
end;

// Raised once, after LoadItems or GetItems finished filling the timeline.
procedure TFormMain.TimelineAdapterItemsLoaded(Sender: TObject);
begin
  lblStatus.Text := Format('%d indicator(s) and %d section(s) loaded',
    [TMSFNCTimeline1.Indicators.Count, TMSFNCTimeline1.Sections.Count]);
end;

OnIndicatorCreateDBKey runs before the record is written, so ADBKey must be unique — the adapter stores the value it returns in the indicator's DBKey and uses it to locate the record again on later updates and deletes. Assign the handlers before setting Active := True, otherwise the initial load happens without them.


Custom field mapping

Use OnFieldsToIndicator when the dataset schema does not map cleanly to the standard fields:

uses
  Data.DB, FMX.TMSFNCTimeline, FMX.TMSFNCTimelineDatabaseAdapter;

procedure TFormMain.TimelineAdapterFieldsToIndicator(Sender: TObject;
  AFields: TFields; AIndicator: TTMSFNCTimelineIndicator;
  var ADefaultSet: TTMSFNCTimelineDatabaseAdapterIndicatorFieldsParams);
begin
  AIndicator.TimelineDate := AFields.FieldByName('ScheduledAt').AsDateTime;
  AIndicator.Annotation.Text := AFields.FieldByName('Title').AsString;
  AIndicator.Tag := AFields.FieldByName('ID').AsInteger;
end;

Combining field mapping, custom reads, and load events

The example below wires the whole adapter path in one place: the mandatory field mapping, a custom read through OnFieldsToIndicator for a column the standard mapping does not cover, and OnItemsLoaded to do the per-item work that cannot run right after activation.

uses
  Data.DB,
  FMX.TMSFNCTypes, FMX.TMSFNCGraphics, FMX.TMSFNCGraphicsTypes,
  FMX.TMSFNCTimeline, FMX.TMSFNCTimelineDatabaseAdapter;

// The whole adapter path in one place: mapping, a custom read for a column the
// standard mapping does not cover, and load-time work.
procedure TFormMain.ConfigureTimelineAdapter;
begin
  TMSFNCTimeline1.Adapter := TimelineAdapter;

  // Mandatory mapping. Activating without DBKey, and without either StartDate
  // or StartValue, raises in the adapter's item source.
  TimelineAdapter.Item.DataSource := DataSource1;
  TimelineAdapter.Item.DBKey := 'Id';
  TimelineAdapter.Item.StartDate := 'EventDate';
  TimelineAdapter.Item.EndDate := 'EventEnd';   // set => sections, empty => indicators
  TimelineAdapter.Item.Text := 'EventTitle';

  TimelineAdapter.OnFieldsToIndicator := AdapterFieldsToIndicator;
  TimelineAdapter.OnItemsLoaded := AdapterItemsLoaded;

  TimelineAdapter.Active := True;
end;

// Reads a column the standard mapping has no slot for. Clearing a flag in
// ADefaultSet tells the adapter not to overwrite what this handler assigned.
procedure TFormMain.AdapterFieldsToIndicator(Sender: TObject; AFields: TFields;
  AIndicator: TTMSFNCTimelineIndicator;
  var ADefaultSet: TTMSFNCTimelineDatabaseAdapterIndicatorFieldsParams);
var
  kind: string;
begin
  kind := AFields.FieldByName('EventKind').AsString;

  // Shape carries the record's category, which no mapped field can express.
  if SameText(kind, 'release') then
    AIndicator.Appearance.Shape := tlisDiamond
  else
    AIndicator.Appearance.Shape := tlisCircle;

  // Build the label here instead of letting Item.Text set it verbatim.
  AIndicator.Annotation.Text := Format('%s (%s)',
    [AFields.FieldByName('EventTitle').AsString, kind]);
  ADefaultSet.Text := False;
end;

// Items are loaded lazily, so Timeline.Indicators is still empty right after
// Active := True. This is where per-item work belongs.
procedure TFormMain.AdapterItemsLoaded(Sender: TObject);
var
  i: Integer;
  ind: TTMSFNCTimelineIndicator;
begin
  TMSFNCTimeline1.BeginUpdate;
  try
    for i := 0 to TMSFNCTimeline1.Indicators.Count - 1 do
    begin
      ind := TMSFNCTimeline1.Indicators[i];
      // Adapter-loaded items arrive selected, so alternate labels above and
      // below the bar only after clearing that state.
      ind.Selected := False;
      if Odd(i) then
        ind.Annotation.Position := tlapBottomRight
      else
        ind.Annotation.Position := tlapTopLeft;
    end;
  finally
    TMSFNCTimeline1.EndUpdate;
  end;

  LabelStatus.Text := Format('%d items loaded',
    [TMSFNCTimeline1.Indicators.Count + TMSFNCTimeline1.Sections.Count]);
end;

See also