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)
-----
// Add=True preserves existing sorts, adds SubId as secondary











