Jump to content
erva

TStringGrid Sorting

Recommended Posts

Simple question: How to sort values in TStringGrid based on one columns values?

 

Searched with google and find only articles that are 6-8 years old. Is TStringGrid evolved from those days or are info in these still relevant? I mean is there any easier solution for sorting than these old articles?

Share this post


Link to post

I don't know FMX, but normally a StringGrid is just a display and you sort the underlying data.

But I don't know FMX.

Share this post


Link to post

Hard to answer without knowing which articles you are actually reading.  But most likely, the way to sort a grid in FMX has likely not changed much, if at all.

Share this post


Link to post

Haven't used Delphi for a while, 15 years, and have managed to forget things little. But just remember based on @Attila Kovacsanwer that IndexDefFields is answer.

 

Already managed to sort records ascending order, now have to still find out how to get them descending order. Using RemObjects Data Abstract. Adding to IndexDefField "Column1:D" didn't work.

 

Now finally managed to get records in descending order creating index at IndexDef and setting IndexName property to created index.

 

But how to refresh index when changing records at StringGrid. Calling table.Close and after that table.Open methods didn't change records order. Tryed table.Refresh also but with no help.

 

I'am using RemObjects DA but i guess there's properties and methods like in other TDataSet related stuff like FireDAC. 

Share this post


Link to post

For TStringGrid once I have used merge sort implemented as an extension (it was VCL version of TStringGrid), if that can help, here it is:

procedure TStringGrid.MSort(const SortCol: integer; const DataType: TDataType; const Ascending: boolean);
begin

    var List:  TArray<integer>;
    var TempGrid: TStringGrid:=TStringGrid.create(nil);
    try
        TempGrid.RowCount :=RowCount;
        TempGrid.ColCount :=ColCount;
        TempGrid.FixedRows:=FixedRows;
        SetLength(List, RowCount - FixedRows);

        for var Index: integer:=FixedRows to RowCount - 1 do
        begin
            List[Index - FixedRows]:=Index;
            TempGrid.Rows[Index].Assign(Rows[Index]);
        end;

        TSorting.MergeSort(Self, List, SortCol, DataType, Ascending);

        for var Index: integer:=0 to RowCount - FixedRows - 1 do
        begin
            Rows[Index + FixedRows].Assign(TempGrid.Rows[List[Index]]);
        end;

        Row:=FixedRows;

    finally
        TempGrid.Free;
    end;

    SetLength(List, 0);

end;

And TSorting class contained with Quick Sort and Merge Sort, The Merge Sort part:

class procedure TSorting.MergeSort(Grid: TStringGrid; var Vals: array of integer; sortcol: integer; datatype: TDataType; ascending: boolean);

    // Temporary shared local array for integers.
    var Avals:  array of integer;

    // Helper nested method for comparision.
    function compare(val1, val2: string): integer;
    begin

        case datatype of

            TDataType.TString: result:=ANSIComparetext(val1, val2);

            TDataType.TInteger:
            begin

                var int1: int64:=strtointdef(val1, 0);
                var int2: int64:=strtointdef(val2, 0);

                if (int1 > int2) then result:= 1
                else if int1 < int2 then result:= -1 else result:=0;

            end;

            TDataType.TFloat:
            begin

                var errcode: integer;
                var float1:  extended;
                var float2:  extended;

                val(val1, float1, errcode);

                if errcode <> 0 then float1:=0;

                val(val2, float2, errcode);

                if errcode <> 0 then float2:=0;

                if float1 > float2 then result:= 1
                else if float1 < float2 then result:= -1 else result:=0;

            end;

            else result:=0;

        end;
    end;

    // Heper nested merge method.
    procedure Merge(ALo, AMid, AHi: integer);
    begin

        var j: integer;
        var k: integer;
        var m: integer;
        var n: integer;

        var i: integer:=0;

        // Copy lower half of "vals" into temporary array "avals"
        SetLength(Avals, AMid - ALo + 1);
        for j:=ALo to AMid do begin
            AVals[i]:=Vals[j];
            inc(i);
        end;

        // Initialize
        i:=0;
        j:=AMid + 1;
        k:=ALo;

        // ----------------------------------------------------------------------------
        // Compare upper half to copied version of the lower half and move
        // the appropriate value (smallest for ascending, largest for descending) into
        // the lower half positions, for equals use 'avals' to preserve original order.
        // ----------------------------------------------------------------------------

        // Execute moving
        while ((k < j) and (j <= AHi)) do
        begin
            with grid do n:=compare(Cells[sortcol, Vals[j]], Cells[sortcol, AVals[i]]);
            if ascending and (n >= 0) or ((not ascending) and (n <= 0)) then
            begin
                Vals[k]:=AVals[i];
                inc(i);
                inc(k);
            end
            else
            begin
                Vals[k]:=Vals[j];
                inc(k);
                inc(j);
            end;
        end;

        // Copy any remaning, unsorted elements
        for m:=k to j - 1 do begin
            Vals[m]:=AVals[i];
            inc(i);
        end;

    end;

    // Recursively split the value into two pieces and merge them back together as we unwind the recursion.
    procedure PerformMergeSort(ALo, AHi:Integer);
    begin

        var AMid:Integer;

        if (ALo < AHi) then
        begin
            AMid:=(ALo + AHi) shr 1;
            PerformMergeSort(ALo, AMid);
            PerformMergeSort(AMid + 1, AHi);
            Merge(ALo, AMid, AHi);
        end;

    end;

begin
    PerformMergeSort(0, high(Vals));
end;

Then the usage was simply on any string grid component, just call MSort with desired parameters and done 🙂  

Share this post


Link to post

Writing a sorting algorithm into a UI control is a bad idea. Keep the two separate. You'd only really need to use merge sort if you needed a stable sort. And then you'd have to take care that the merge sort algo was a stable one.

 

Surely there is perfectly usable built in sorting code? 

Share this post


Link to post
Posted (edited)
1 hour ago, David Heffernan said:

Writing a sorting algorithm into a UI control is a bad idea. Keep the two separate. You'd only really need to use merge sort if you needed a stable sort. And then you'd have to take care that the merge sort algo was a stable one.

 

Hmm... look again, sorting algorithm is in separate class already and it is not part of the UI (class procedure TSorting.MergeSort) 🙂 The component however is extended to allow easy usage, but I agree you can change the way you apply sorting to the control, I'm too lazy to do that, this is just an example 😉 . 

Edited by TomekCph

Share this post


Link to post

OK, I was sloppy. I still don't get why you wrote this sorting function rather than use built in algos. 

Share this post


Link to post
56 minutes ago, David Heffernan said:

OK, I was sloppy. I still don't get why you wrote this sorting function rather than use built in algos. 

 

Good question! I believe I was not aware of any built in algos other that TStringList.Sort method at the time and that code came from pure VCL project with no 3rd party libraries or other frameworks.

 

But of course you've got the point on built in solutions.

Share this post


Link to post

FMX has no build in option for sorting grids (like TMS-FMX-Grid). 

 

FMX StringGrid should be used for displaying and selecting (smaller portions of) data.

 

If you have more data to display you should consider to use The TGRID. Because this grid does not store the data itself, it displays the data only (in the Event "OnGetValue" you are asked to deliver the relevant data).

 

In this case a new sorting of the whole grid is easier to manage, because you have not to fill the whole grid again with data (or make a new sorting with tricks like above). Only the little part that is displayed will get an update.

 

At least only your data needs to be sorted (e.g. by a field, that represents names, dates or numbers) and you display it in the (T)Grid.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×