IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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 { Private declarations } FSortedCol: Integer; FSortOrder: TSortOrder; procedure SetSortedCol(const Value: Integer); procedure SetSortOrder(const Value: TSortOrder); public { Public declarations } procedure Sort; property SortOrder: TSortOrder read FSortOrder write SetSortOrder; property SortedCol: Integer read FSortedCol write SetSortedCol; end; var Form1: TForm1; implementation {$R *.dfm} type // On définit un type de fonction de comparaison qui sera utilisée // par la fonction de tri TCellCompare = function (const AStringGrid: TStringGrid; const SortOrder: TSortOrder; const Column, Index1, Index2: Integer): Integer; { Notre fonction de comparaison pour une cellule } function CellCompare(const AStringGrid: TStringGrid; const SortOrder: TSortOrder; const Column, Index1, Index2: Integer): Integer; begin // Si vous voulez ne pas tenir compte de la casse, utilisez AnsiCompareText // au lieu de AnsiCompareStr 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; { La fonction de tri proprement dite : un tri rapide (Quick Sort) } procedure QuickSort(const AStringGrid: TStringGrid; const SortOrder: TSortOrder; const Column: Integer; L, R: Integer; SCompare: TCellCompare); { Il nous faut une fonction pour échanger les lignes } procedure ExchangeItems(Index1, Index2: Integer); var s: string; begin // On utilise la propriété Rows qui nous retourne la ligne entière 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; { TForm1 } 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 // Appel de la fonction de tri avec la grille, le type de tri, l'indice de // la colonne à trier, les indices de premier et dernier élément et la // fonction de comparaison QuickSort(StringGrid1, FSortOrder, FSortedCol, 1, StringGrid1.RowCount - 1, CellCompare); finally Screen.Cursor:= crDefault; end; end; { L'événement OnMouseUp de la grille nous permet de détecter un clic dans un entête de colonne. } procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); { Une fonction qui nous sert à déterminer le type de tri en fonction du type de tri actuel. } 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 // On récupère la cellule dans laquelle le click a eu lieu gridCoord:= MouseCoord(X, Y); // On teste si c'est bien un entête de colonne éditable if (gridCoord.Y < StringGrid1.FixedCols) and (gridCoord.X >= StringGrid1.FixedRows) then begin // Si on a cliqué sur une colonne déja triée on inverse le type de tri if gridCoord.X = FSortedCol then FSortOrder:= InvertSort(FSortOrder) else // Sinon, on tri par ordre ascendant begin FSortedCol:= gridCoord.X; FSortOrder:= soUp; end; // L'appel à la méthode de tri Sort; end; end; end; end.