The bulk of the plumbing was done for multi-sort. When MultiSort is enabled and multiple columns are sorted (Ctrl+Click on headers), the grid tracks sorted columns in FSortedList and displays sort arrows, but the Sort method only sorted by a single column.
I updated the sort algorithm to handle N number of selected columns.
NxCells6.pas
procedure TNxCells6.Sort(const Index: Integer; SortKind: TNxSortKind; Resort: Boolean);
var
ALessCompFunc: TNxCompareFunc;
AGreaterCompFunc: TNxCompareFunc;
function GetChildCell(ACol, ARow: Integer): INxBase;
begin
Result := (Self.ChildRow[ARow] as INxCellsRow).Cells[ACol];
end;
procedure Exchange(Pos1, Pos2: Integer);
var
P: IInterface;
begin
P := FChildRowsList[Pos1];
FChildRowsList[Pos1] := FChildRowsList[Pos2];
FChildRowsList[Pos2] := P;
end;
procedure InverseSort;
var
i, Middle, Count: Integer;
begin
if RowCount > 0 then
begin
Middle := Pred(ChildRowCount div 2);
{ Optimize }
Count := ChildRowCount;
for i := 0 to Middle do Exchange(i, Pred(Count) - i);
end;
end;
// New
function MultiCompare(Row1, Row2: Integer): Integer;
var
K: Integer;
Col: TNxColumn6;
C1, C2: INxBase;
begin
Result := 0;
for K := 0 to Control.Columns.MultiSortedCount - 1 do
begin
Col := Control.Columns.MultiSortedColumn[K];
C1 := GetChildCell(Col.Index, Row1);
C2 := GetChildCell(Col.Index, Row2);
case Col.SortType of
stAlphabetic:
if C1.AsString < C2.AsString then Result := -1
else if C1.AsString > C2.AsString then Result := 1;
stUnicodeAlphabetic:
Result := WideCompareStr(C1.AsString, C2.AsString);
stCaseInsensitive:
if LowerCase(C1.AsString) < LowerCase(C2.AsString) then Result := -1
else if LowerCase(C1.AsString) > LowerCase(C2.AsString) then Result := 1;
stNumeric:
if C1.AsFloat < C2.AsFloat then Result := -1
else if C1.AsFloat > C2.AsFloat then Result := 1;
stDate:
if C1.AsDateTime < C2.AsDateTime then Result := -1
else if C1.AsDateTime > C2.AsDateTime then Result := 1;
stBoolean:
if C1.AsBoolean < C2.AsBoolean then Result := -1
else if C1.AsBoolean > C2.AsBoolean then Result := 1;
stCustom:
DoCellCompare(C1, C2, Result);
stIP:
if IsIPSmaler(C1.AsString, C2.AsString) then Result := -1
else if IsIPSmaler(C2.AsString, C1.AsString) then Result := 1;
end;
if Result <> 0 then
begin
if Col.SortKind = skDescending then
Result := -Result;
Exit;
end;
end;
end;
function DivideAsc(l, r: Integer; LessCompFunc,
GreaterCompFunc: TNxCompareFunc): Integer;
var
i, j: Integer;
pivot: INxBase;
begin
pivot := GetChildCell(Index, l + 1 + Random(r - l));
i := l - 1;
j := r + 1;
repeat
repeat Inc(i) until LessCompFunc(pivot, GetChildCell(Index, i));
repeat Dec(j) until GreaterCompFunc(pivot, GetChildCell(Index, j));
if GetChildCell(Index, j) <> GetChildCell(Index, i)
then Exchange(i, j)
until j <= i;
if GetChildCell(Index, j) <> GetChildCell(Index, i)
then Exchange(i, j);
Result := i;
end;
function DivideDesc(l, r: Integer; LessCompFunc,
GreaterCompFunc: TNxCompareFunc): Integer;
var
i, j: Integer;
pivot: INxBase;
begin
pivot := GetChildCell(Index, l + 1 + Random(r - l));
i := l - 1;
j := r + 1;
repeat
repeat Inc(i) until GreaterCompFunc(pivot, GetChildCell(Index, i));
repeat Dec(j) until LessCompFunc(pivot, GetChildCell(Index, j));
if (GetChildCell(Index, j) <> GetChildCell(Index, i)) then Exchange(i, j)
until j <= i;
if (GetChildCell(Index, j) <> GetChildCell(Index, i)) then Exchange(i, j);
Result := i;
end;
procedure Quicksort(Lft, Rgt: Integer);
var
Middle: Integer;
begin
if Lft < Rgt then
begin
if FSortedColumn.SortKind = skAscending
then Middle := DivideAsc(Lft, Rgt, ALessCompFunc, AGreaterCompFunc)
else Middle := DivideDesc(Lft, Rgt, ALessCompFunc, AGreaterCompFunc);
Quicksort(Lft, Middle - 1);
Quicksort(Middle, Rgt);
end;
end;
// New
function DivideMulti(l, r: Integer): Integer;
var
i, j, pivotPos: Integer;
begin
pivotPos := l + 1 + Random(r - l);
i := l - 1;
j := r + 1;
repeat
repeat Inc(i) until MultiCompare(pivotPos, i) <= 0;
repeat Dec(j) until MultiCompare(pivotPos, j) >= 0;
if i < j then
begin
Exchange(i, j);
if pivotPos = i then pivotPos := j
else if pivotPos = j then pivotPos := i;
end;
until j <= i;
Result := i;
end;
// New
procedure QuicksortMulti(Lft, Rgt: Integer);
var
Middle: Integer;
begin
if Lft < Rgt then
begin
Middle := DivideMulti(Lft, Rgt);
QuicksortMulti(Lft, Middle - 1);
QuicksortMulti(Middle, Rgt);
end;
end;
begin
FSortedColumn := Control.Columns[Index];
case FSortedColumn.SortType of
stAlphabetic:
begin
ALessCompFunc := AlphabeticLessCompare;
AGreaterCompFunc := AlphabeticGreaterCompare;
end;
stBoolean:
begin
ALessCompFunc := BooleanLessCompare;
AGreaterCompFunc := BooleanGreaterCompare;
end;
stCaseInsensitive:
begin
ALessCompFunc := CaseInsensitiveLessCompare;
AGreaterCompFunc := CaseInsensitiveGreaterCompare;
end;
stCustom:
begin
ALessCompFunc := CustomLessCompare;
AGreaterCompFunc := CustomGreaterCompare;
end;
stNumeric:
begin
ALessCompFunc := NumericLessCompare;
AGreaterCompFunc := NumericGreaterCompare;
end;
stDate:
begin
ALessCompFunc := DateLessCompare;
AGreaterCompFunc := DateGreaterCompare;
end;
stIP:
begin
ALessCompFunc := IPLessCompare;
AGreaterCompFunc := IPGreaterCompare;
end;
stUnicodeAlphabetic:
begin
ALessCompFunc := AlphabeticUnicodeLessCompare;
AGreaterCompFunc := AlphabeticUnicodeGreaterCompare;
end;
end;
if (Index = FSortedCol) and FSorted and (SortKind <> FSortKind)
and not Resort and (Control.Columns.MultiSortedCount <= 1) then // Guard against MultiSortedCount <= 1
begin
FSortKind := SortKind;
InverseSort;
end else
begin
FSortKind := SortKind;
if Control.Columns.MultiSortedCount > 1 then // This is multi-sort
QuicksortMulti(0, Pred(ChildRowCount)) // Call QuickSortMulti to sort by all selected columns
else // fall back to single column sort
Quicksort(0, Pred(ChildRowCount));
end;
ResortBranches;
FSortedCol := Index;
FSortKind := SortKind;
FSorted := True;
end;
Usage:
Interactive -- User Clicks Column(s)
-----
The grid must have MultiSort = True.
- Click "Name" column header --> sorts by Name ascending
- Click "Name" again --> sorts by Name descending
- Ctrl+Click "City" header --> adds City as secondary sort (Name desc, City asc)
- Ctrl+Click "City" again --> City toggles to descending
- Ctrl+Click "City" again --> City removed from sort (Name desc only)
Result: rows sort by Name first; rows with equal Name sort by City.
Programmatic -- Add secondary sort at runtime
-----
// Sort by Status column first
Grid.Columns[StatusColIndex].SetMultiSorted(True, False);
// Add=False clears existing sorts, makes Status the primary sort
// Add SubId as secondary tiebreaker (preserves Status sort)
Grid.Columns[SubIdColIndex].SortKind := skAscending;
Grid.Columns[SubIdColIndex].SetMultiSorted(True, True);
// Add=True preserves existing sorts, adds SubId as secondary
// Add=True preserves existing sorts, adds SubId as secondary
// Rows now sort by Status first, then SubId for equal Status values.
Programmatic -- Re-sort after data update
-----
// After updating grid cell data, re-apply the active multi-sort:
if Grid.Columns.MultiSortedCount > 0 then
begin
SortCol := Grid.Columns.MultiSortedColumn[
Grid.Columns.MultiSortedCount - 1];
SortCol.Resort;
end;
// Resort triggers Sort() which detects MultiSortedCount > 1
// and uses the multi-column QuicksortMulti path automatically.
Programmatic -- Query current sort state
-----
// How many columns are currently sorted?
Count := Grid.Columns.MultiSortedCount;
// Get the Nth sorted column (0-based, in priority order):
Col := Grid.Columns.MultiSortedColumn[0]; // primary sort column
Col := Grid.Columns.MultiSortedColumn[1]; // secondary sort column
// Check if a specific column is in the sort:
if MyColumn.Sorted then ...
// Get a column's position in the sort priority:
Idx := Grid.Columns.SortedIndexOf(MyColumn); // -1 if not sorted



Find content
Not Telling
