Overview
TeeGrid supports custom column sorting by clicking on column headers. You can implement your own sort logic for any data type or structure.
Enabling Sorting
uses
Tee.Grid.Header, Tee.Grid.Columns;
procedure TFormCustomSorting.FormCreate(Sender: TObject);
begin
// Enable header sorting
TeeGrid1.Header.Sortable := True;
// Create and assign sort render
TeeGrid1.Header.SortRender := CreateSortable;
// Set grid data
TeeGrid1.Data := TVirtualArrayData<TLocation>.Create(Locations);
end;
function TFormCustomSorting.CreateSortable: TSortableHeader;
begin
result := TSortableHeader.Create(TeeGrid1.Header.Changed);
// Optional: Customize appearance
result.Format.Brush.Color := TColors.Red;
result.Format.Stroke.Show;
// Set custom events
result.OnCanSort := CanSortBy;
result.OnSortBy := SortBy;
result.OnSortState := SortState;
end;
Implementing Sort Events
OnCanSort - Control Which Columns Are Sortable
procedure TFormCustomSorting.CanSortBy(const AColumn: TColumn;
var CanSort: Boolean);
begin
// Only allow sorting on specific columns
CanSort := (AColumn <> nil) and
(
(AColumn.Header.Text = 'Name') or
(AColumn.Header.Text = 'Country')
);
end;
OnSortState - Return Current Sort State
type
TLastSorted = record
public
Column: TColumn;
Ascending: Boolean;
end;
var
LastSorted: TLastSorted;
procedure TFormCustomSorting.SortState(const AColumn: TColumn;
var State: TSortState);
begin
if AColumn = LastSorted.Column then
if LastSorted.Ascending then
State := TSortState.Ascending
else
State := TSortState.Descending
else
State := TSortState.None;
end;
procedure TFormCustomSorting.SortBy(Sender: TObject; const AColumn: TColumn);
var
tmp: TSortState;
begin
// Get current sort order
SortState(AColumn, tmp);
// Toggle order (Ascending <-> Descending)
LastSorted.Ascending := tmp <> TSortState.Ascending;
// Sort the data
SortData(AColumn, LastSorted.Ascending);
// Remember last sorted column
LastSorted.Column := AColumn;
end;
Custom Sort Implementation
Using TComparer for Arrays
uses
System.Generics.Collections, System.Generics.Defaults;
type
TLocation = record
public
Name: String;
Country: String;
end;
TLocationField = (Name, Country);
TLocationComparer = class(TComparer<TLocation>)
public
Ascending: Boolean;
Field: TLocationField;
function Compare(const Left, Right: TLocation): Integer; override;
end;
function TLocationComparer.Compare(const Left, Right: TLocation): Integer;
var
A, B: String;
begin
if Field = TLocationField.Name then
begin
A := Left.Name;
B := Right.Name;
end
else
begin
A := Left.Country;
B := Right.Country;
end;
if Ascending then
result := CompareText(A, B)
else
result := CompareText(B, A);
end;
procedure TFormCustomSorting.SortData(const AColumn: TColumn;
const Ascending: Boolean);
var
Comparer: TLocationComparer;
begin
Comparer := TLocationComparer.Create;
try
Comparer.Ascending := Ascending;
if AColumn.Header.Text = 'Name' then
Comparer.Field := TLocationField.Name
else
Comparer.Field := TLocationField.Country;
// Sort the array in place
TArray.Sort<TLocation>(Locations, Comparer);
finally
Comparer.Free;
end;
end;
Complete Example
From Unit_Custom_Sorting.pas:
unit Unit_Custom_Sorting;
interface
uses
System.SysUtils, FMX.Forms, FMX.StdCtrls, FMX.Layouts,
FMXTee.Control, FMXTee.Grid,
Tee.Grid.Header, Tee.Grid.Columns, Tee.GridData.Rtti;
type
TLastSorted = record
public
Column: TColumn;
Ascending: Boolean;
end;
TFormCustomSorting = class(TForm)
TeeGrid1: TTeeGrid;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
private
LastSorted: TLastSorted;
procedure CanSortBy(const AColumn: TColumn; var CanSort: Boolean);
function CreateSortable: TSortableHeader;
procedure SortBy(Sender: TObject; const AColumn: TColumn);
procedure SortData(const AColumn: TColumn; const Ascending: Boolean);
procedure SortState(const AColumn: TColumn; var State: TSortState);
end;
implementation
uses
System.Generics.Collections, System.Generics.Defaults;
type
TLocation = record
public
Name: String;
Country: String;
end;
var
Locations: TArray<TLocation>;
procedure AddRandomData;
begin
SetLength(Locations, 10);
Locations[0].Name := 'New York';
Locations[1].Name := 'Barcelona';
Locations[2].Name := 'Tokyo';
Locations[3].Name := 'Sao Paulo';
Locations[4].Name := 'Santa Cruz';
Locations[5].Name := 'Oslo';
Locations[6].Name := 'Camberra';
Locations[7].Name := 'Delhi';
Locations[8].Name := 'Montreal';
Locations[9].Name := 'Beijing';
Locations[0].Country := 'USA';
Locations[1].Country := 'Catalonia';
Locations[2].Country := 'Japan';
Locations[3].Country := 'Brazil';
Locations[4].Country := 'USA';
Locations[5].Country := 'Norway';
Locations[6].Country := 'Australia';
Locations[7].Country := 'India';
Locations[8].Country := 'Canada';
Locations[9].Country := 'China';
end;
procedure TFormCustomSorting.FormCreate(Sender: TObject);
begin
AddRandomData;
// Enable header sorting
TeeGrid1.Header.Sortable := True;
TeeGrid1.Header.SortRender := CreateSortable;
// Bind data
TeeGrid1.Data := TVirtualArrayData<TLocation>.Create(Locations);
end;
function TFormCustomSorting.CreateSortable: TSortableHeader;
begin
result := TSortableHeader.Create(TeeGrid1.Header.Changed);
// Cosmetic customization
result.Format.Brush.Color := TColors.Red;
result.Format.Stroke.Show;
// Set events
result.OnCanSort := CanSortBy;
result.OnSortBy := SortBy;
result.OnSortState := SortState;
end;
procedure TFormCustomSorting.CanSortBy(const AColumn: TColumn;
var CanSort: Boolean);
begin
CanSort := (AColumn <> nil) and
(
(AColumn.Header.Text = 'Name') or
(AColumn.Header.Text = 'Country')
);
end;
procedure TFormCustomSorting.SortState(const AColumn: TColumn;
var State: TSortState);
begin
if AColumn = LastSorted.Column then
if LastSorted.Ascending then
State := TSortState.Ascending
else
State := TSortState.Descending
else
State := TSortState.None;
end;
procedure TFormCustomSorting.SortBy(Sender: TObject; const AColumn: TColumn);
var
tmp: TSortState;
begin
SortState(AColumn, tmp);
LastSorted.Ascending := tmp <> TSortState.Ascending;
SortData(AColumn, LastSorted.Ascending);
LastSorted.Column := AColumn;
end;
type
TLocationField = (Name, Country);
TLocationComparer = class(TComparer<TLocation>)
public
Ascending: Boolean;
Field: TLocationField;
function Compare(const Left, Right: TLocation): Integer; override;
end;
function TLocationComparer.Compare(const Left, Right: TLocation): Integer;
var
A, B: String;
begin
if Field = TLocationField.Name then
begin
A := Left.Name;
B := Right.Name;
end
else
begin
A := Left.Country;
B := Right.Country;
end;
if Ascending then
result := CompareText(A, B)
else
result := CompareText(B, A);
end;
procedure TFormCustomSorting.SortData(const AColumn: TColumn;
const Ascending: Boolean);
var
Comparer: TLocationComparer;
begin
Comparer := TLocationComparer.Create;
try
Comparer.Ascending := Ascending;
if AColumn.Header.Text = 'Name' then
Comparer.Field := TLocationField.Name
else
Comparer.Field := TLocationField.Country;
TArray.Sort<TLocation>(Locations, Comparer);
finally
Comparer.Free;
end;
end;
end.
Automatic Sorting with TeeBI
For automatic sorting without custom code, see the TeeBI demos. The BI.GridData.pas unit provides built-in sorting functionality.
uses
BI.Data.DB, BI.Grid.Data;
// TeeBI provides automatic sorting
TeeGrid1.Data := TBIGridData.FromList<TPerson>(MyPersons);
Database Sorting
For database grids, use the sortable dataset helpers:
uses
Tee.GridData.DB.Sortable;
// ClientDataSet
TSortableClientDataset.CreateSortable(TeeGrid1.Grid, ClientDataSet1);
// FireDAC
TSortableFireDACDataset.CreateSortable(TeeGrid1.Grid, FDMemTable1);
See the Database Grid example for details.
Visual Indicators
The TSortableHeader automatically displays:
- ▲ Triangle pointing up for ascending sort
- ▼ Triangle pointing down for descending sort
- No indicator for unsorted columns
Key Concepts
TSortState Enum
type
TSortState = (None, Ascending, Descending);
Sort Events Flow
- User clicks column header
OnCanSort - Check if column is sortable
OnSortBy - Perform the sort
OnSortState - Return current state (for visual indicator)
Custom Comparer Pattern
type
TMyComparer = class(TComparer<TMyType>)
public
Ascending: Boolean;
function Compare(const Left, Right: TMyType): Integer; override;
end;
Advanced Usage
Multi-Column Sort
type
TSortInfo = record
Column: TColumn;
Ascending: Boolean;
end;
var
SortColumns: TArray<TSortInfo>;
// Sort by multiple columns (e.g., Country then Name)
procedure MultiColumnSort;
begin
// Primary: Country
// Secondary: Name
// Implementation left as exercise
end;
Case-Sensitive Sort
function TLocationComparer.Compare(const Left, Right: TLocation): Integer;
begin
if Ascending then
result := CompareStr(Left.Name, Right.Name) // Case-sensitive
else
result := CompareStr(Right.Name, Left.Name);
end;
Numeric Sort
function TMyComparer.Compare(const Left, Right: TMyRecord): Integer;
begin
if Ascending then
begin
if Left.Value < Right.Value then result := -1
else if Left.Value > Right.Value then result := 1
else result := 0;
end
else
begin
if Left.Value > Right.Value then result := -1
else if Left.Value < Right.Value then result := 1
else result := 0;
end;
end;
Features Demonstrated
- Enabling sortable column headers
- Custom sort logic with TComparer
- Visual sort indicators (▲▼)
- Controlling which columns are sortable
- Toggling between ascending/descending
- Sorting arrays in place
Best Practices
- Implement OnCanSort to control which columns can be sorted
- Track sort state to toggle between ascending/descending
- Use TComparer for clean, reusable sort logic
- Sort in place when possible for best performance
- Refresh grid after sorting (usually automatic)
Next Steps