Overview
TeeGrid supports master-detail relationships where clicking an expand icon reveals a nested sub-grid. This works with databases, arrays, and any data source.Basic Master-Detail
Setting Up the Master Grid
uses
FMXTee.Control, FMXTee.Grid, Tee.GridData.DB, Tee.Grid.RowGroup;
procedure TMasterDetail.FormCreate(Sender: TObject);
begin
// Open the master dataset (Customers)
SampleData.CustomersTable.Open;
// Bind to grid
TeeGrid1.DataSource := SampleData.CustomersTable;
// Enable the expander
CBEnabledChange(Self);
end;
Creating the Expander
From the FireMonkey Master-Detail demo:procedure TMasterDetail.EnableDisableSubGrid(const AGroup: TRowGroup;
const AEvent: TExpanderGetDataEvent);
var
Expander: TExpanderRender;
begin
if CBEnabled.IsChecked then
begin
// Create the expander control
Expander := AGroup.NewExpander;
// Set the event that provides detail data
Expander.OnGetData := AEvent;
// Always show expand icon (we don't know in advance if detail exists)
Expander.AlwaysExpand := True;
// Add expander to first column
if AGroup.Columns.Count > 0 then
AGroup.Columns[0].Render := Expander;
end
else
begin
// Remove all detail grids
AGroup.Rows.Children.Clear;
// Remove expander from first column
if AGroup.Columns.Count > 0 then
AGroup.Columns[0].Render := nil;
end;
end;
Providing Detail Data
// Called when a master row is expanded
procedure TMasterDetail.GetOrders(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
// Return a new dataset for the detail grid
AData := TVirtualDBData.From(SampleData.OrdersOfCustomer(ARow + 1));
// Mark data as owned by the grid (will be auto-freed)
TVirtualDBData(AData).OwnsData := True;
end;
Multi-Level Master-Detail
You can nest multiple levels (Customer -> Orders -> Order Details):procedure TMasterDetail.FormCreate(Sender: TObject);
begin
SampleData.CustomersTable.Open;
TeeGrid1.DataSource := SampleData.CustomersTable;
// Level 1: Customer -> Orders
EnableDisableSubGrid(TeeGrid1.Grid.Current, GetOrders);
// Handle when a new detail grid is created
TeeGrid1.Grid.Current.OnNewDetail := DetailNewGroup;
end;
// Called when first detail level is created (Orders)
procedure TMasterDetail.DetailNewGroup(const Sender, NewGroup: TRowGroup);
var
tmpTot: TColumnTotals;
begin
// Level 2: Orders -> Order Details
EnableDisableSubGrid(NewGroup, GetOrderItems);
// Handle when sub-detail grid is created
NewGroup.OnNewDetail := SubDetailNewGroup;
// Add totals to the detail grid footer
tmpTot := TColumnTotals.Create(NewGroup.Footer);
tmpTot.Calculation.Add(NewGroup.Columns[0], TColumnCalculation.Count);
tmpTot.Calculation.Add('EmployeeID', TColumnCalculation.Sum);
// Add totals header
TTotalsHeader.CreateTotals(NewGroup.Footer, tmpTot);
// Customize appearance
NewGroup.Rows.Back.Brush.Color := TAlphaColors.Bisque;
NewGroup.Rows.Back.Brush.Visible := True;
NewGroup.Cells.Format.Font.Color := TAlphaColors.Darkblue;
end;
// Level 2 detail data provider
procedure TMasterDetail.GetOrderItems(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
AData := TVirtualDBData.From(SampleData.OrderDetailsOfOrder(ARow + 1));
TVirtualDBData(AData).OwnsData := True;
end;
Complete Example
FromMaster_Detail_FireDAC.pas:
unit Master_Detail_FireDAC;
interface
uses
FMX.Forms, FMX.StdCtrls, FMX.Layouts, Data.DB,
FMXTee.Control, FMXTee.Grid,
Tee.Grid.RowGroup, Tee.Renders;
type
TMasterDetail = class(TForm)
TeeGrid1: TTeeGrid;
CBEnabled: TCheckBox;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure CBEnabledChange(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
procedure DetailNewGroup(const Sender, NewGroup: TRowGroup);
procedure SubDetailNewGroup(const Sender, NewGroup: TRowGroup);
procedure EnableDisableSubGrid(const AGroup: TRowGroup;
const AEvent: TExpanderGetDataEvent);
procedure GetOrders(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
procedure GetOrderItems(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
end;
var
MasterDetail: TMasterDetail;
Open: Boolean;
implementation
uses
Tee.Grid.Totals, Tee.GridData.DB, Tee.Grid.Columns,
System.UIConsts, Customer_Orders;
procedure TMasterDetail.FormCreate(Sender: TObject);
begin
// Open master table
SampleData.CustomersTable.Open;
TeeGrid1.DataSource := SampleData.CustomersTable;
Open := False;
// Initialize expander
CBEnabledChange(Self);
// Set up detail creation event
TeeGrid1.Grid.Current.OnNewDetail := DetailNewGroup;
end;
procedure TMasterDetail.Button1Click(Sender: TObject);
begin
// Toggle expand/collapse all
if Open then
begin
TeeGrid1.Grid.Current.ShowHideAllDetail(0, False);
Open := False;
end
else
begin
TeeGrid1.Grid.Current.ShowHideAllDetail(0, True);
Open := True;
end;
end;
procedure TMasterDetail.EnableDisableSubGrid(const AGroup: TRowGroup;
const AEvent: TExpanderGetDataEvent);
var
Expander: TExpanderRender;
begin
if CBEnabled.IsChecked then
begin
// Create expander
Expander := AGroup.NewExpander;
Expander.OnGetData := AEvent;
Expander.AlwaysExpand := True;
if AGroup.Columns.Count > 0 then
AGroup.Columns[0].Render := Expander;
end
else
begin
// Remove detail grids
AGroup.Rows.Children.Clear;
if AGroup.Columns.Count > 0 then
AGroup.Columns[0].Render := nil;
end;
end;
procedure TMasterDetail.CBEnabledChange(Sender: TObject);
begin
EnableDisableSubGrid(TeeGrid1.Grid.Current, GetOrders);
end;
procedure TMasterDetail.DetailNewGroup(const Sender, NewGroup: TRowGroup);
var
tmpTot: TColumnTotals;
begin
// Set up second level expander
EnableDisableSubGrid(NewGroup, GetOrderItems);
NewGroup.OnNewDetail := SubDetailNewGroup;
// Add totals
tmpTot := TColumnTotals.Create(NewGroup.Footer);
tmpTot.Calculation.Add(NewGroup.Columns[0], TColumnCalculation.Count);
tmpTot.Calculation.Add('EmployeeID', TColumnCalculation.Sum);
TTotalsHeader.CreateTotals(NewGroup.Footer, tmpTot);
// Styling
NewGroup.Rows.Back.Brush.Color := TAlphaColors.Bisque;
NewGroup.Rows.Back.Brush.Visible := True;
NewGroup.Cells.Format.Font.Color := TAlphaColors.Darkblue;
end;
procedure TMasterDetail.SubDetailNewGroup(const Sender, NewGroup: TRowGroup);
begin
// Style the third level
NewGroup.Rows.Back.Brush.Color := TAlphaColors.Lavender;
NewGroup.Rows.Back.Brush.Visible := True;
NewGroup.Cells.Format.Font.Color := TAlphaColors.Blueviolet;
end;
procedure TMasterDetail.GetOrders(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
// Return orders for customer
AData := TVirtualDBData.From(SampleData.OrdersOfCustomer(ARow + 1));
TVirtualDBData(AData).OwnsData := True;
end;
procedure TMasterDetail.GetOrderItems(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
// Return order items for order
AData := TVirtualDBData.From(SampleData.OrderDetailsOfOrder(ARow + 1));
TVirtualDBData(AData).OwnsData := True;
end;
end.
Key Concepts
Expander Render
TheTExpanderRender displays the expand/collapse icon:
Expander := AGroup.NewExpander;
Expander.OnGetData := GetDetailData; // Called when expanded
Expander.AlwaysExpand := True; // Always show icon
OnGetData Event
procedure GetDetailData(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
// Create and return data for the detail grid
AData := TVirtualDBData.From(MyDetailDataset);
// Grid will free the data when row is collapsed
TVirtualDBData(AData).OwnsData := True;
end;
OnNewDetail Event
Called when a new detail grid is created:procedure DetailCreated(const Sender, NewGroup: TRowGroup);
begin
// Customize the new detail grid
NewGroup.Rows.Back.Brush.Color := TAlphaColors.LightBlue;
// Add totals, custom bands, etc.
// ...
end;
Adding Totals to Detail Grids
uses
Tee.Grid.Totals;
procedure AddTotalsToDetail(const AGroup: TRowGroup);
var
tmpTot: TColumnTotals;
begin
// Create totals band
tmpTot := TColumnTotals.Create(AGroup.Footer);
// Add calculations
tmpTot.Calculation.Add(AGroup.Columns[0], TColumnCalculation.Count);
tmpTot.Calculation.Add('Amount', TColumnCalculation.Sum);
tmpTot.Calculation.Add('Price', TColumnCalculation.Average);
// Add header
TTotalsHeader.CreateTotals(AGroup.Footer, tmpTot);
// Style the totals
tmpTot.Format.Font.Style := [fsBold];
end;
Expand/Collapse All
// Expand all detail rows at level 0
TeeGrid1.Grid.Current.ShowHideAllDetail(0, True);
// Collapse all detail rows
TeeGrid1.Grid.Current.ShowHideAllDetail(0, False);
Styling Detail Grids
procedure StyleDetailGrid(const AGroup: TRowGroup);
begin
// Background color
AGroup.Rows.Back.Brush.Color := TAlphaColors.Bisque;
AGroup.Rows.Back.Brush.Visible := True;
// Font color
AGroup.Cells.Format.Font.Color := TAlphaColors.Darkblue;
// Border
AGroup.Rows.RowLines.Color := TAlphaColors.Gray;
AGroup.Rows.RowLines.Show;
end;
Features
- Multiple Levels: Nest as many levels as needed
- Lazy Loading: Detail data loaded only when expanded
- Automatic Cleanup: Detail grids freed when collapsed
- Custom Styling: Each level can have different appearance
- Totals Support: Add summary calculations to detail grids
- Expand/Collapse All: Programmatic control
Common Patterns
Database Master-Detail
// Master: Customers
TeeGrid1.DataSource := CustomersTable;
// Detail: Orders filtered by CustomerID
procedure GetOrders(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
OrdersQuery.Close;
OrdersQuery.ParamByName('CustomerID').AsInteger := ARow + 1;
OrdersQuery.Open;
AData := TVirtualDBData.From(OrdersQuery);
TVirtualDBData(AData).OwnsData := True;
end;
Array Master-Detail
type
TOrderItem = record
Product: String;
Quantity: Integer;
end;
TOrder = record
OrderID: Integer;
Items: TArray<TOrderItem>;
end;
procedure GetOrderItems(const Sender: TExpanderRender;
const ARow: Integer; out AData: TObject);
begin
// Return array data for detail
AData := TVirtualArrayData<TOrderItem>.Create(Orders[ARow].Items);
end;
Best Practices
- Set OwnsData := True so grid automatically frees detail data
- Use OnNewDetail to customize newly created detail grids
- Enable AlwaysExpand when you can’t determine if detail exists
- Style each level differently for visual hierarchy
- Add totals to detail grids for better insight
Next Steps
- Learn about Database Grid basics
- Explore Custom Bands for headers/footers
- See Themes for styling
