IV. Collections▲
Nous allons donc maintenant créer la propriété qui devra enregistrer les différents quartiers à dessiner. Cette propriété sera une propriété de type collection. Nous allons voir pas à pas comment créer tout le système qui tourne autour.
IV-A. Qu'est-ce qu'une collection▲
Une collection est une classe qui hérite de la classe TCollection. Cette classe est déclarée dans l'unité Classes.
Cette classe n'est jamais utilisée directement, tout comme TComponent. On en dérive toujours pour créer une classe plus spécialisée.
Chaque collection est une liste d'objets de types TCollectionItem. On peut ainsi assimiler TCollection à TStrings et TCollectionItem à string, si vous voulez. Tout comme on n'utilise jamais directement TCollection, on crée toujours un descendant de TCollectionItem aussi pour l'utiliser.
À la création d'un TCollection, on doit indiquer la sous-classe de TCollectionItem à utiliser pour les éléments. À chaque sous-classe de TCollection correspond donc une sous-classe de TCollectionItem et inversement.
La classe enfant de TCollection sera le type de la propriété du composant que l'on écrit. La classe enfant de TCollectionItem est le type des différents éléments de la collection.
Un exemple de collection que vous utilisez certainement régulièrement est la collection TStatusPanels dont les éléments sont des TStatusPanel et qui est utilisée dans la propriété Panels du TStatusBar.
En mode conception, on utilise une collection en double-cliquant dessus. L'éditeur de collection apparaît alors :
Vous pouvez alors utiliser les boutons de la barre d'outils pour ajouter, supprimer et déplacer des éléments de la collection. Vous pouvez définir les propriétés de chaque élément via l'inspecteur d'objets.
IV-B. Les classes dont nous aurons besoin▲
Comme nous l'avons dit plus haut, une collection est caractérisée par deux classes : une pour la collection et une pour les éléments de la collection.
Ainsi, nous définirons une classe TChartQuarter héritée de TCollectionItem et une classe TChartQuarters héritée de TCollection :
type
TChartQuarter = class
(TCollectionItem)
end
;
TChartQuarters = class
(TCollection)
end
;
À cela, nous ajouterons une déclaration avancée (forward) de la classe TCircleChart, car nous devrons y faire référence dans ces deux classes :
type
TCircleChart = class
;
TChartQuarter = class
(TCollectionItem)
end
;
TChartQuarters = class
(TCollection)
end
;
Finalement, nous aurons besoin d'une petite classe (qui n'a rien à voir avec les collections) pour les informations de dessin de chaque quartier : TChartQuarterGraphics héritée de TPersistent.
type
TCircleChart = class
;
TChartQuarterGraphics = class
(TPersistent)
end
;
TChartQuarter = class
(TCollectionItem)
end
;
TChartQuarters = class
(TCollection)
end
;
IV-C. La classe TChartQuarterGraphics▲
Puisque cette classe est petite et n'a rien à voir avec les collections, nous allons la construire rapidement ici.
Cette classe sert à représenter les trois données graphiques (fond du quartier, fond tu texte, et police du texte) de chaque quartier dans chacun de ses trois états (normal, enfoncé ou désactivé).
Elle est composée uniquement de trois propriétés. Deux de type TBrush et une de type TFont.
À cela, on ajoutera un constructeur, un destructeur, et une surcharge de la méthode Assign déclarée dans TPersistent.
type
TChartQuarterGraphics = class
(TPersistent)
private
FBackgroundBrush : TBrush;
FTextBrush : TBrush;
FFont : TFont;
procedure
SetBackgroundBrush(New : TBrush);
procedure
SetTextBrush(New : TBrush);
procedure
SetFont(New : TFont);
public
constructor
Create(AOnChange : TNotifyEvent = nil
);
destructor
Destroy; override
;
procedure
Assign(Source : TPersistent); override
;
published
property
BackgroundBrush : TBrush read
FBackgroundBrush write
SetBackgroundBrush;
property
TextBrush : TBrush read
FTextBrush write
SetTextBrush;
property
Font : TFont read
FFont write
SetFont;
end
;
Voici aussi l'implémentation de cette classe :
////////////////////////////////////
/// Classe TChartQuarterGraphics ///
////////////////////////////////////
constructor
TChartQuarterGraphics.Create(AOnChange : TNotifyEvent = nil
);
begin
inherited
Create;
FBackgroundBrush := TBrush.Create;
FBackgroundBrush.OnChange := AOnChange;
FTextBrush := TBrush.Create;
FTextBrush.OnChange := AOnChange;
FFont := TFont.Create;
FFont.OnChange := AOnChange;
end
;
destructor
TChartQuarterGraphics.Destroy;
begin
FFont.Free;
FTextBrush.Free;
FBackgroundBrush.Free;
inherited
Destroy;
end
;
procedure
TChartQuarterGraphics.SetBackgroundBrush(New : TBrush);
begin
FBackgroundBrush.Assign(New);
end
;
procedure
TChartQuarterGraphics.SetTextBrush(New : TBrush);
begin
FTextBrush.Assign(New);
end
;
procedure
TChartQuarterGraphics.SetFont(New : TFont);
begin
FFont.Assign(New);
end
;
procedure
TChartQuarterGraphics.Assign(Source : TPersistent);
begin
if
Source is
TChartQuarterGraphics then
begin
with
TChartQuarterGraphics(Source) do
begin
Self
.FBackgroundBrush.Assign(FBackgroundBrush);
Self
.FTextBrush.Assign(FTextBrush);
Self
.FFont.Assign(FFont);
end
;
end
else
inherited
;
end
;
IV-D. La classe TChartQuarters▲
Nous allons maintenant développer la classe TChartQuarters. Comme dit plus haut, cette classe héritera de TCollection et sera donc la classe de collection.
Il y a plusieurs choses indispensables à réaliser lorsqu'on crée une collection. Tout d'abord, le constructeur doit passer en paramètre au constructeur hérité le type de classe des éléments. Commençons donc par le constructeur de TChartQuarters. Ce constructeur acceptera un paramètre de type TCircleChart qui identifiera le composant auquel la collection appartient :
constructor
TChartQuarters.Create(ACircleChart : TCircleChart);
begin
inherited
Create(TChartQuarter);
FCircleChart := ACircleChart;
end
;
La variable FCircleChart est bien entendu une variable privée de type TCircleChart.
Nous pouvons remarquer que nous avons passé TChartQuarter en paramètre au constructeur hérité, afin de désigner cette classe comme étant celle des éléments de la collection.
La classe de collection doit également surcharger la méthode GetOwner qui doit renvoyer le propriétaire de la collection, à savoir la valeur de FCircleChart.
protected
function
GetOwner : TPersistent; override
;
L'implémentation en est tout ce qu'il y a de plus simple :
function
TChartQuarters.GetOwner : TPersistent;
begin
Result := FCircleChart;
end
;
Ce sont les deux seules obligations de l'écriture d'une collection. Toutefois, il est utile de définir quelques méthodes spécialisées pour la manipulation côté programmation :
private
...
function
GetItem(Index
: integer
) : TChartQuarter;
procedure
SetItem(Index
: integer
; Value : TChartQuarter);
public
...
function
Add : TChartQuarter;
function
AddItem(Item : TChartQuarter; Index
: integer
) : TChartQuarter;
function
Insert(Index
: integer
) : TChartQuarter;
property
Items[index
: integer
] : TChartQuarter read
GetItem write
SetItem; default
;
Ces cinq méthodes sont très simples à implémenter puisqu'elles ne font qu'appeler les méthodes héritées de TCollection avec quelques transtypages, excepté AddItem, mais cette dernière ne mérite pas vraiment d'explications non plus.
function
TChartQuarters.GetItem(Index
: integer
) : TChartQuarter;
begin
Result := TChartQuarter(inherited
GetItem(Index
));
end
;
procedure
TChartQuarters.SetItem(Index
: integer
; Value : TChartQuarter);
begin
inherited
SetItem(Index
, Value);
end
;
function
TChartQuarters.Add : TChartQuarter;
begin
Result := TChartQuarter(inherited
Add);
end
;
function
TChartQuarters.AddItem(Item : TChartQuarter; Index
: integer
) : TChartQuarter;
begin
if
Item = nil
then
Result := Add
else
Result := Item;
if
Assigned(Result) then
begin
Result.Collection := Self
;
if
Index
< 0
then
Index
:= Count - 1
;
Result.Index
:= Index
;
end
;
end
;
function
TChartQuarters.Insert(Index
: integer
) : TChartQuarter;
begin
Result := AddItem(nil
, Index
);
end
;
Finalement, il est souvent utile de savoir qu'un des éléments de la collection a été modifié. Cela peut être fait en surchargeant la méthode Update de TCollection. Son paramètre de type TCollectionItem identifie l'élément qui a été modifié. Si ce paramètre vaut nil, alors la modification porte sur plusieurs éléments. Dans notre cas, cela importe peu puisque nous demandons simplement un rafraîchissement du contrôle.
protected
procedure
Update(Item : TCollectionItem); override
;
L'implémentation effectue juste un Repaint du composant propriétaire :
procedure
TChartQuarters.Update(Item : TCollectionItem);
begin
FCircleChart.Repaint;
end
;
IV-E. La classe TChartQuarter▲
La classe TChartQuarter est la classe des éléments de la collection. Il est donc évident que c'est la plus importante. Comme nous l'avons déjà dit plus haut, cette classe doit hériter de la classe TCollectionItem.
IV-E-1. Les indispensables▲
Tout comme pour la classe TChartQuarters, la classe TChartQuarter devra respecter deux obligations, mais elles sont différentes de celles de la collection.
La première est que le constructeur doit être la surcharge du constructeur Create déclaré dans TCollectionItem :
public
constructor
Create(Collection : TCollection); override
;
Le paramètre Collection identifie évidemment la collection à laquelle appartient l'élément. Sa valeur sera accessible en dehors du constructeur par la propriété Collection.
La seconde obligation n'est pas, paradoxalement, nécessaire, mais si vous voulez obtenir une collection un minimum correcte, je vous conseille de la remplir. Il s'agit de surcharger la méthode GetDisplayName de TCollectionItem afin de renvoyer une chaîne de caractère qui apparaîtra dans l'éditeur de collection.
Cette méthode sans paramètre et renvoie une chaîne :
protected
function
GetDisplayName : string
; override
;
Nous verrons plus loin comment implémenter le constructeur et GetDisplayName.
IV-E-2. Propriétés de la classe▲
Les propriétés publiées que nous donnerons à la classe TChartQuarter seront celles qui apparaîtront dans l'inspecteur d'objets. Pour l'exemple, nous utiliserons les propriétés suivantes :
Nom de la propriété |
Type |
Description |
---|---|---|
AutoCheck |
boolean |
Détermine s'il faut automatiquement inverser la propriété Down lorsqu'on clique sur ce quartier |
Text |
string |
Libellé du quartier (indication de valeur non comprise) |
Percent |
Single |
Valeur en pour cent du quartier |
Down |
boolean |
Indique si le quartier est abaissé |
Enabled |
boolean |
Détermine si le quartier est activé (pour les clics de souris) |
GroupIndex |
integer |
Indique le groupe de quartiers (même fonctionnement que pour les TToolButton) |
Hint |
string |
Indique le conseil du quartier |
ShowText |
boolean |
Indique s'il faut afficher le libellé et le pourcentage |
Graphics |
TChartQuarterGraphics |
Informations graphiques pour l'état normal |
DownGraphics |
TChartQuarterGraphics |
Informations graphiques pour l'état abaissé (Down = True) |
DisabledGraphics |
TChartQuarterGraphics |
Informations graphiques pour l'état désactivé (Enabled = False) |
Toutes ces propriétés auront un accès direct (variable FXXX) en lecture et un accès indirect (méthode SetXXX) en écriture excepté AutoCheck et Hint qui auront un accès direct en écriture aussi. Nous ajoutons également une méthode GraphicsChange de type TNotifyEvent pour intercepter les changements des graphiques.
De plus, nous aurons besoin d'une variable privée de type TCircleChart qui indiquera le composant auquel appartient ce quartier.
private
FCircleChart : TCircleChart;
FAutoCheck : boolean
;
FText : string
;
FPercent : Single
;
FDown : boolean
;
FEnabled : boolean
;
FGroupIndex : integer
;
FHint : string
;
FShowText : boolean
;
FGraphics : TChartQuarterGraphics;
FDownGraphics : TChartQuarterGraphics;
FDisabledGraphics : TChartQuarterGraphics;
procedure
SetText(const
New : string
);
procedure
SetPercent(New : Single
);
procedure
SetDown(New : boolean
);
procedure
SetEnabled(New : boolean
);
procedure
SetGroupIndex(New : integer
);
procedure
SetGraphics(New : TChartQuarterGraphics);
procedure
SetDownGraphics(New : TChartQuarterGraphics);
procedure
SetDisabledGraphics(New : TChartQuarterGraphics);
procedure
SetShowText(New : boolean
);
procedure
GraphicsChange(Sender : TObject);
published
property
AutoCheck : boolean
read
FAutoCheck write
FAutoCheck default
False
;
property
Text : string
read
FText write
SetText;
property
Percent : Single
read
FPercent write
SetPercent;
property
Down : boolean
read
FDown write
SetDown default
False
;
property
Enabled : boolean
read
FEnabled write
SetEnabled default
True
;
property
GroupIndex : integer
read
FGroupIndex write
SetGroupIndex default
0
;
property
Hint : string
read
FHint write
FHint;
property
ShowText : boolean
read
FShowText write
SetShowText default
True
;
property
Graphics : TChartQuarterGraphics read
FGraphics write
SetGraphics;
property
DownGraphics : TChartQuarterGraphics read
FDownGraphics write
SetDownGraphics;
property
DisabledGraphics : TChartQuarterGraphics read
FDisabledGraphics write
SetDisabledGraphics;
À tout cela nous ajouterons un événement OnClick de type TNotifyEvent :
private
FOnClick : TNotifyEvent;
published
property
OnClick : TNotifyEvent read
FOnClick write
FOnClick;
IV-E-3. Constructeur et destructeur▲
Les constructeur et destructeur de TChartQuarter n'ont rien de bien particulier. Ils initialisent les variables de la classe et les libèrent, c'est tout :
constructor
TChartQuarter.Create(Collection : TCollection);
begin
inherited
;
FCircleChart := TChartQuarters(Collection).FCircleChart;
FText := ''
;
FPercent := 0
.0
;
FDown := False
;
FEnabled := True
;
FGroupIndex := 0
;
FHint := ''
;
FShowText := True
;
FGraphics := TChartQuarterGraphics.Create(GraphicsChange);
FDownGraphics := TChartQuarterGraphics.Create(GraphicsChange);
FDisabledGraphics := TChartQuarterGraphics.Create(GraphicsChange);
end
;
destructor
TChartQuarter.Destroy;
begin
FDisabledGraphics.Free;
FDownGraphics.Free;
FGraphics.Free;
inherited
;
end
;
On peut être certain que Collection est de type TChartQuarters, car nous avons dit que chaque classe de collection correspond à une classe d'éléments et inversement ; et il n'y a aucune raison pour qu'une autre classe de collection crée des éléments de type TChartQuarter.
IV-E-4. Méthodes d'accès aux propriétés▲
Les méthodes d'accès aux diverses propriétés sont très simples. Elles ne font que modifier la variable privée correspondante et appeler la méthode Changed pour indiquer à la collection qu'un élément a changé. Le paramètre AllItems indique si la modification implique tous les éléments de la collection ou pas.
Dans notre exemple, seul le setter de Percent devra passer True à cette fonction, puisque le fait de modifier le pourcentage modifie également le décalage d'affichage des éléments suivants. Toutefois cela n'est que purement théorique, car nous n'avons pas utilisé le paramètre Item de TChartQuarters.Update.
J'inclus dans ce morceau de code la méthode GraphicsChange, qui a plus ou moins le même rôle.
procedure
TChartQuarter.SetText(const
New : string
);
begin
FText := New;
Changed(False
);
end
;
procedure
TChartQuarter.SetPercent(New : Single
);
begin
if
New < 0
.0
then
exit;
FPercent := New;
Changed(True
);
end
;
procedure
TChartQuarter.SetDown(New : boolean
);
var
I : integer
;
Quarter : TChartQuarter;
begin
if
New = FDown then
exit;
FDown := New;
if
(FGroupIndex > 0
) and
FDown then
for
I := 0
to
Collection.Count-1
do
begin
Quarter := TChartQuarters(Collection)[I];
if
(Quarter <> Self
) and
(Quarter.FGroupIndex = FGroupIndex) then
Quarter.Down := False
;
end
;
Changed(False
);
end
;
procedure
TChartQuarter.SetEnabled(New : boolean
);
begin
FEnabled := New;
Changed(False
);
end
;
procedure
TChartQuarter.SetGroupIndex(New : integer
);
begin
FGroupIndex := New;
end
;
procedure
TChartQuarter.SetGraphics(New : TChartQuarterGraphics);
begin
FGraphics.Assign(New);
end
;
procedure
TChartQuarter.SetDownGraphics(New : TChartQuarterGraphics);
begin
FDownGraphics.Assign(New);
end
;
procedure
TChartQuarter.SetDisabledGraphics(New : TChartQuarterGraphics);
begin
FDisabledGraphics.Assign(New);
end
;
procedure
TChartQuarter.SetShowCaption(New : boolean
);
begin
FShowCaption := New;
Changed(False
);
end
;
procedure
TChartQuarter.GraphicsChange(Sender : TObject);
begin
Changed(False
);
end
;
Seule la méthode SetDown mérite des explications. Celle-ci, au cas où Down est positionné à True et que GroupIndex est différent de 0, doit positionner à False la propriété Down de tous les autres éléments de la collection dont la propriété GroupIndex a la même valeur, tout comme le fait la méthode SetDown de TToolButton.
IV-E-5. Méthode Assign▲
Comme tout bon composant qui se respecte, nous allons redéfinir la méthode Assign de TPersistent.
public
procedure
Assign(Source : TPersistent); override
;
Voici comment l'implémenter, il n'y a rien de particulier là-dedans :
procedure
TChartQuarter.Assign(Source : TPersistent);
var
ChartQuarterSource : TChartQuarter;
begin
if
Source is
TChartQuarter then
begin
ChartQuarterSource := TChartQuarter(Source);
FText := ChartQuarterSource.FText;
FPercent := ChartQuarterSource.FPercent;
FDown := ChartQuarterSource.FDown;
FEnabled := ChartQuarterSource.FEnabled;
FGroupIndex := ChartQuarterSource.FGroupIndex;
FHint := ChartQuarterSource.FHint;
FShowCaption := ChartQuarterSource.FShowCaption;
FGraphics.Assign(ChartQuarterSource.FGraphics);
FDownGraphics.Assign(ChartQuarterSource.FDownGraphics);
FDisabledGraphics.Assign(ChartQuarterSource.FDisabledGraphics);
Changed(True
);
end
else
inherited
;
end
;
IV-E-6. Méthode Click▲
Nous allons ajouter une méthode Click semblable à la méthode Click de TButton, qui simulera un clic de souris sur le quartier.
public
procedure
Click;
Cette méthode, si Enabled est définie à True, doit déclencher un événement OnClick puis appeler la méthode ClickQuarter de TCircleChart que nous définirons tout à l'heure.
De plus, si AutoCheck est définie à True, il faut inverser la propriété Down (à moins que Down vaille True et que GroupIndex soit différent de 0, auquel cas on ne doit pas l'inverser, car il n'y aurait plus de quartier abaissé dans ce groupe).
procedure
TChartQuarter.Clic;
begin
if
Enabled then
begin
if
AutoCheck and
((not
Down) or
(GroupIndex = 0
)) then
Down := not
Down;
if
Assigned(FOnClick) then
FOnClick(Self
);
FCircleChart.ClickQuarter(Index
);
end
;
end
;
Nous en avons fini avec la classe TChartQuarter. Nous allons pouvoir revenir à la classe TCircleChart et la terminer.