Introduction
To assign a data source to the FireDAC VCL component of List & Label in the Embarcadero RAD Studio, you typically use the built-in dialog for the DataController > DetailSources property of the List & Label component:
The decisive factor here is that the data source must already be known and configured at design time, i.e. when the application is created. But how does this work dynamically at runtime, so that you don’t have to use the dialog at design time of the application, for example, in order to provide a degree of flexibility for the later users of the application? This is exactly the aspect we will show in a simplified form below.
Data Source Dynamically in Runtime Code
To do this, we use two TDataSource objects to map the two simple tables Customers and Orders. In addition, a relational structure between these two tables will be defined later to map a master-detail relationship.
Define Customers and Orders and Fill Them With Data
Using TDataSource objects, the data structure is defined and simultaneously filled with data:
procedure GetDataSource(var DataSourceCustomers, DataSourceOrders: TDataSource);
var
DataSetCutomers: TClientDataSet;
DataSetOrders: TClientDataSet;
begin
{* Create DataSet/DataSource for "Customers" with Data *}
DataSetCutomers := TClientDataSet.Create(nil);
with DataSetCutomers do
begin
Name := 'Customers';
FieldDefs.Add('Id', ftInteger);
FieldDefs.Add('Name', ftString, 255);
CreateDataSet;
IndexDefs.Add('PK_Test', 'Id', [ixPrimary, ixUnique]);
Append;
FieldByName('Id').AsInteger := 1;
FieldByName('Name').AsString := 'Lars';
Append;
FieldByName('Id').AsInteger := 2;
FieldByName('Name').AsString := 'Dirk';
Append;
FieldByName('Id').AsInteger := 3;
FieldByName('Name').AsString := 'Torsten';
Post;
end;
DataSourceCustomers := TDataSource.Create(nil);
DataSourceCustomers.DataSet := DataSetCutomers;
{* Create DataSet/DataSource for "Orders" with Data and
define its relation to "Customers" *}
DataSetOrders := TClientDataSet.Create(nil);
with DataSetOrders do
begin
Name := 'Orders';
FieldDefs.Add('Id', ftInteger);
FieldDefs.Add('CustomerID', ftInteger);
FieldDefs.Add('Amount', ftFloat);
CreateDataSet;
IndexDefs.Add('PK_InnerTable', 'Id', [ixPrimary, ixUnique]);
IndexFieldNames := 'CustomerID';
Append;
FieldByName('Id').AsInteger := 1;
FieldByName('CustomerID').AsInteger := 1;
FieldByName('Amount').AsFloat := 99.25;
Append;
FieldByName('Id').AsInteger := 2;
FieldByName('CustomerID').AsInteger := 1;
FieldByName('Amount').AsFloat := 125.99;
Append;
FieldByName('Id').AsInteger := 3;
FieldByName('CustomerID').AsInteger := 2;
FieldByName('Amount').AsFloat := 12.98;
Post;
end;
DataSourceOrders := TDataSource.Create(nil);
DataSourceOrders.DataSet := DataSetOrders;
DataSetOrders.MasterSource := DataSourceCustomers;
DataSetOrders.MasterFields := 'Id';
end;
Assign Created Data Source to the List & Label Object
Once the structure has been created with the data, it can be assigned to the List & Label object:
{* Create the List & Label object and assign the DataSource *}
LL := TListLabel25.Create(self);
GetDataSource(DataSourceCustomers, DataSourceOrders);
LL.DataController.DataSource := DataSourceCustomers;
Define Relation in the Data Source
Then the relation must be defined in the DataController, which is then to be used by List & Label in the Designer and during printing:
{* Prepare the relational structure between the DataSources within
the List & Label DataController *}
with LL.DataController.DetailSources.Add do
begin
Name := 'Customers';
DataSource := DataSourceCustomers;
PrimaryKeyField := 'Id';
with TDetailSourceItem(AddChildNode) do
begin
Name := 'Orders';
DataSource := DataSourceOrders;
MasterKeyField := 'Id';
DetailKeyField := 'CustomerID';
end;
end;
Start Designer and Verify Data Sources
Now you can simply call the Design function to check the result of the provided data sources and create the report:
// Start the Designer
LL.Design();
Conclusion
This makes it quick and easy to provide the later user of the application with a quite flexible approach to data design in reporting.