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

Partie III : Créer un composant graphique

Partie III : Créer un composant graphique


précédentsommairesuivant

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 :

Éditeur de collection
Éditeur de collection

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éments 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
////////////////////////////////////
/// 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 plusieurs choses indispensables à réaliser lorsqu'on crée un 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 :

 
Sélectionnez
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.

 
Sélectionnez
protected
  function GetOwner : TPersistent; override;

L'implémentation en est tout ce qu'il y a de plus simple :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
protected
  procedure Update(Item : TCollectionItem); override;

L'implémentation effectue juste un Repaint du composant propriétaire :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 pourcent 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 changement des graphiques.

De plus, nous aurons besoin d'une variable privée de type TCircleChart qui indiquera le composant auquel appartient ce quartier.

 
Sélectionnez
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 :

 
Sélectionnez
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 classes et les libèrent, c'est tout :

 
Sélectionnez
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é la 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.

 
Sélectionnez
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.

 
Sélectionnez
public
  procedure Assign(Source : TPersistent); override;

Voici comment l'implémenter, il n'y a rien de particulier là-dedans :

 
Sélectionnez
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 un méthode Click semblable à la méthode Click de TButton, qui simulera un click de souris sur le quartier.

 
Sélectionnez
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).

 
Sélectionnez
procedure TChartQuarter.Click;
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.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005 Sébastien Doeraene. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.