Delphi Sort StringGrid
I am currently coding an application in Delphi, one that requires the handling of a StringGrid. Naturally, I wanted to enhance this data-rich object with a sorting capability, but - not to my surprise - Delphi doesn't provide one so you has to code your own. After I spent some time researching (1) how to detect a click on a StringGrid header (since the ordinary onClick doesn't cover that) and (2) how to sort a StringGrid once the chosen column has been determined. I am pretty much bewildered that there is so few documentation about it-- well, okay, one of the tasks. But the other seems completely unsolveable to the Interwebs.
Anyway! Here's what I did, and it does what is requested. [...]
(show me)(don't show me)
First, I use the private variable ClickCol in unison with the StringGrid methods onMouseDown and onMouseUp to detect a click on the header row. If that value hasn't changed during the time between those methods then, for my peronal taste, a click can be assumed. By analyzing the X|Y coordinates I can even determine the column that has been clicked on via my selfmade function Convert_X2Column().
Once that column is not an unknown anymore, I start the preparation for the sorting. First I flatten the Grid into a StringList which already offers a sort function. I achieve this flattening by creating one "big" string that holds all the relevant cells for one row, separated by a constant that is unlikely to be found in the actual data. I just need to make sure that the column we want the grid to be sorted by becomes the first instance in the helping string. Then I create a StringList of all the rows which can easily be sorted.
Finally I need to bloaten the flattened information back into StringGrid-friendly information. For that I use my selfmade procedure Split(). Well, and some more work, but look for yourself.
<<
>> # top #
Anyway! Here's what I did, and it does what is requested. [...]
(show me)(don't show me)
First, I use the private variable ClickCol in unison with the StringGrid methods onMouseDown and onMouseUp to detect a click on the header row. If that value hasn't changed during the time between those methods then, for my peronal taste, a click can be assumed. By analyzing the X|Y coordinates I can even determine the column that has been clicked on via my selfmade function Convert_X2Column().
Once that column is not an unknown anymore, I start the preparation for the sorting. First I flatten the Grid into a StringList which already offers a sort function. I achieve this flattening by creating one "big" string that holds all the relevant cells for one row, separated by a constant that is unlikely to be found in the actual data. I just need to make sure that the column we want the grid to be sorted by becomes the first instance in the helping string. Then I create a StringList of all the rows which can easily be sorted.
Finally I need to bloaten the flattened information back into StringGrid-friendly information. For that I use my selfmade procedure Split(). Well, and some more work, but look for yourself.
<<
procedure TFKunden.sgKundenMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// register a potential click or drag
If ( Y < sgKunden.DefaultRowHeight ) Then
ClickCol := Convert_X2Column( X, sgKunden );
end;
procedure TFKunden.sgKundenMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var i: Integer;
begin
// only react to click on header (= first and only FixedRow)
If ( Y < sgKunden.DefaultRowHeight ) Then
begin
i := Convert_X2Column( X, sgKunden );
// if click, not drag
If ( i = ClickCol ) Then
Sort_SG_by_Col( i, sgKunden );
end;
ClickCol := -1;
end;
// converts a given X-coordinate to its correspondant Column-Integer
// value within a given StringGrid.
Function Convert_X2Column( const X: Integer; const SG: TStringGrid ): Integer;
var i, AccumulatedWidths: Integer;
Begin
// start at first cell from the left, in dependance of
// the horizontal scrollbar.
i := SG.LeftCol;
AccumulatedWidths := 0;
While ( ( i < SG.ColCount ) AND ( AccumulatedWidths < X ) ) do
begin
AccumulatedWidths := AccumulatedWidths + SG.ColWidths[i];
Inc(i);
end;
// special-special-case: you can click the very first pixel and
// I don't want "-1" to be a possible Result.
If ( i = 0 ) Then
Result := i
Else
Result := i - 1;
End;
// sorts a given StringGrid by a given Column.
Procedure Sort_SG_by_Col( const SortCol: Integer; const SG: TStringGrid );
const Separator = 'AssWorship#11';
var r, c: Integer;
Hilf: AnsiString;
Rows, Cols: TStringList;
Begin
Rows := TStringList.Create;
Cols := TStringList.Create;
try
Rows.Sorted := False;
// flatten Grid to StringList
For r := SG.FixedRows to SG.RowCount -1 do
begin
// put the SortCol at the beginning, since it is the sorting criterium
Hilf := SG.Cells[ SortCol, r ];
For c := SG.FixedCols to SG.ColCount -1 do
If ( c <> SortCol ) Then
Hilf := Hilf+ Separator+ SG.Cells[ c, r ];
Rows.Add( Hilf );
end;
// actual sort call
Rows.Sort;
// sorts a bit awkwardly sometimes - i.e. empty-string-cells almost
// arbitrarily, and of course not numerically correct.
// bloat the flattened List into a Grid again
Hilf := '';
For r := 0 to Rows.Count -1 do
begin
Cols.Clear;
Split( [ Separator ], Rows[r], Cols );
// mid-check-point: restore original column-order
Cols.Insert( SortCol +1, Cols[0] );
Cols.Delete(0);
For c := 0 to Cols.Count -1 do
SG.Cells[ SG.FixedCols + c, SG.FixedRows + r ] := Cols[c];
end;
finally
Rows.Free;
Cols.Free;
end;
End;
// splits a given Input string into a given StringList, using a given Delimiter.
Procedure Split( const Delimiters: Array of String; const Input: String; var
Parts: TStringList );
Begin
Parts.Clear;
Split_recursive( Delimiters, 0, Input, Parts );
End;
// splits a given Input string into a given StringList, using a given Delimiter.
Procedure Split_recursive( const Delimiters: Array of String; const Index:
Integer; const Input: String; var Parts: TStringList );
var i: Integer;
Hilf: String;
Begin
// loop termination
If ( Index >= Length( Delimiters ) ) Then
begin
Parts.Add( Input )
end
Else // Index is within the definition range of Delimiters
begin
i := Pos( Delimiters[ Index ], Input );
// if Input does not contain the current Delimiter
If ( i = 0 ) Then
Split_recursive( Delimiters, Index +1, Input, Parts )
Else
// split Input at the current Delimiter, search for the next Delimiter in the
// first part and continue searching for the current Delimiter in the latter part.
begin
Hilf := Copy( Input, 1, i -1 );
Split_recursive( Delimiters, Index +1, Hilf, Parts );
Hilf := Copy( Input, i +Length( Delimiters[ Index ] ), Length( Input ) );
Split_recursive( Delimiters, Index, Hilf, Parts );
end;
end;
End;
>> # top #
posted by Woodrow at 11/13/2010 01:11:00 AM
0 comments
0 Comments:
Post a Comment