Tri d'un TStringGrid
Date de publication : 19/02/2005
Par
Pierre Castelain (Contributions)
Exemple de tri par colonne d'un composant TStringGrid à l'aide d'un algorithme de tri rapide (quick sort)
I. Introduction
II. Comment ça marche
III. Source
I. Introduction
Il arrive très fréquemment que des développeurs plus ou moins débutants veuillent trier
une grille sans savoir comment y parvenir. En effet, le composant TStringGrid de base
ne propose pas cette fonctionnalité.
Je vous propose donc un exemple d'implémentation de tri basé sur la comparaison des chaînes
de caractères de la grille. L'algorithme utilisé est un tri rapide qui, comme son nom
l'indique, est un des plus rapides pour ce genre de traitement. Il n'est pas trivial à
comprendre mais ce n'est pas le propos de cet exemple.
Pour que cet exemple soit le plus utile possible, il démontre comment utiliser
un clic sur un des entêtes de colonne pour déterminer la colonne à utiliser
comme base du tri ainsi que le sens de celui-ci (ascendant ou descendant).
Le source suivant ne modifie pas l'aspect visuel des entêtes comme le font la majorité
des composants dédiés pour indiquer la colonne triée. A vous d'implémenter cette
fonctionnalité.
L'exemple évite volontairement de dériver un nouveau composant afin de rester aussi accessible
que possible. Vous aurez bien évidemment tout intérêt à le faire afin de pouvoir réutiliser
facilement ce code.
II. Comment ça marche
Voyons rapidement les mécanismes mis en oeuvre dans cet exemple.
Pour pouvoir déterminer la colonne à trier ainsi que le sens du tri, il nous faut
stocker ces deux informations. Nous le faisons en ajoutant deux propriétés à la
fenêtre : SortedCol qui stocke l'indice de la colonne triée et SortOrder
qui contient le sens du tri.
L'exemple utilise l'événement OnMouseUp de la grille pour détecter le clic sur un
des entêtes de colonne.
Il nous faut ensuite une méthode de tri. Celle-ci s'appelle Sort et appelle une
procédure de tri plus générale qui permet de l'utiliser sur plusieurs grilles et/ou fenêtres.
C'est la procédure QuickSort qui implémente le tri rapide en utilisant la fonction
CellCompare pour comparer le contenu de deux cellule grâce à l'utilisation de
la fonction AnsiCompareStr que vous pourrez remplacer si vous désirez trier différemment vos
données.
Pour tester cet exemple, créez un nouveau projet et ajoutez un composant TStringGrid à la
fenêtre principale. Branchez ensuite l'événement OnMouseUp du composant à la méthode
StringGrid1MouseUp de l'exemple.
Vous pouvez également charger le projet de démonstration ici : démonstration
III. Source
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grids, StdCtrls;
type
TSortOrder = (soNone, soUp, soDown);
TForm1 = class(TForm)
StringGrid1: TStringGrid;
procedure StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FSortedCol: Integer;
FSortOrder: TSortOrder;
procedure SetSortedCol(const Value: Integer);
procedure SetSortOrder(const Value: TSortOrder);
public
procedure Sort;
property SortOrder: TSortOrder read FSortOrder write SetSortOrder;
property SortedCol: Integer read FSortedCol write SetSortedCol;
end;
var
Form1: TForm1;
implementation
type
TCellCompare = function (const AStringGrid: TStringGrid;
const SortOrder: TSortOrder;
const Column, Index1, Index2: Integer): Integer;
function CellCompare(const AStringGrid: TStringGrid;
const SortOrder: TSortOrder;
const Column, Index1, Index2: Integer): Integer;
begin
with AStringGrid do
case SortOrder of
soUp: result:= AnsiCompareStr(Cells[Column, Index1], Cells[Column, Index2]);
soDown: result:= AnsiCompareStr(Cells[Column, Index2], Cells[Column, Index1]);
else
result:= 0;
end;
end;
procedure QuickSort(const AStringGrid: TStringGrid;
const SortOrder: TSortOrder;
const Column: Integer; L, R: Integer; SCompare: TCellCompare);
procedure ExchangeItems(Index1, Index2: Integer);
var
s: string;
begin
s:= AStringGrid.Rows[Index1].Text;
AStringGrid.Rows[Index1].Text:= AStringGrid.Rows[Index2].Text;
AStringGrid.Rows[Index2].Text:= s;
end;
var
I, J, P: Integer;
begin
repeat
I := L;
J := R;
P := (L + R) shr 1;
repeat
while SCompare(AStringGrid, SortOrder, Column, I, P) < 0 do Inc(I);
while SCompare(AStringGrid, SortOrder, Column, J, P) > 0 do Dec(J);
if I <= J then
begin
ExchangeItems(I, J);
if P = I then
P := J
else if P = J then
P := I;
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSort(AStringGrid, SortOrder, Column, L, J, SCompare);
L := I;
until I >= R;
end;
procedure TForm1.SetSortedCol(const Value: Integer);
begin
if Value <> FSortedCol then
begin
FSortedCol := Value;
Sort;
end;
end;
procedure TForm1.SetSortOrder(const Value: TSortOrder);
begin
if Value <> FSortOrder then
begin
FSortOrder := Value;
Sort;
end;
end;
procedure TForm1.Sort;
begin
Screen.Cursor:= crHourGlass;
try
QuickSort(StringGrid1, FSortOrder, FSortedCol,
1, StringGrid1.RowCount - 1, CellCompare);
finally
Screen.Cursor:= crDefault;
end;
end;
procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
function InvertSort(ASortOrder: TSortOrder): TSortOrder;
begin
case ASortOrder of
soUp: result:= soDown;
soDown: result:= soUp;
else
result:= soUp;
end;
end;
var
gridCoord: TGridCoord;
begin
with Sender as TStringGrid do
begin
gridCoord:= MouseCoord(X, Y);
if (gridCoord.Y < StringGrid1.FixedCols) and
(gridCoord.X >= StringGrid1.FixedRows) then
begin
if gridCoord.X = FSortedCol then
FSortOrder:= InvertSort(FSortOrder)
else
begin
FSortedCol:= gridCoord.X;
FSortOrder:= soUp;
end;
Sort;
end;
end;
end;
end.
|