Couleur dans un TTreeView

Exposé du problème

J'ai trois tables
  1. La première "Site" où j'ai le nom du site, son identifiant et la date de la prochaine visite,
  2. La deuxieme "Batiment" ou j\'ai le nom du batiment, son identifiant, la date de la prochaine visite et l'identifiant du site auquel il appartient,
  3. La troisieme "Article" ou j'ai le nom de l'article, son identifiant, la date de la prochaine visite et l'identifiant du batiment auquel il appartient.
J'ai donc mis trois ADOTable.
Et donc je voudrais que si la date de visite d\'un élément est dépassé seul cette élément soit en rouge.
Et que son ou ses parents si eux même ne sont pas déjà rouges soient violets.
J'espère avoir été le plus clair possible.
 

En clair, voici ce que nous souhaitons obtenir :


Avant de nous attaquer au codage, réfléchissons un peu aux différentes méthodes possibles.
Comme chaque noeud d'un composant TTreeView dispose d'une propriété Data qui est un pointeur non typé, on serait tenté de l'utiliser pour pointer sur des valeurs.
Or, il s'agit ici de conserver un minimum d'information et cette information est stockée dans des enregistrements des tables d'une base de données.

Alors, comment faire ?

Faut-il utiliser une structure du genre :

Code 1

type   PDateCode = ^TDateCode;   TDateCode = record   NextVisit: TDateTime;   end;   {puis dans le code ensuite, allouer de la mémoire à chaque fois que l\'on crée un noeud} var   DateCode : PDateCode; begin   Getmem(DateCode, SizeOf(DateCode));   //etc end;

Si pour l'allocation tout se passe à peu près bien, qu'en est-il lorsque :


On voit tout de suite qu'il n'est pas facile de répondre à ces questions.
Un autre approche consiste à adopter une méthode plus orientée objet :

( Les puristes diront qu'une classe comporte des propriétés, des champs et des méthodes et qu'ici, ce n'est qu'un membre ordinaire. Je ne leur donne pas complètement tort.)

Code 2

type   TDateVisite = class(TObject)   public   NextVisite: TDateTime;   end;   {Puis, à chaque création d'un noeud} var   DateVisite: TDateVisite; begin   DateVisite := TdateVisite.Create(TreeView1);   Node.data := DateVisite;   //etc.

Cette approche est déjà plus satisfaisante mais la mise à jour du membre NextVisit est lourde à gérer dans le code par la suite. Et nous sommes toujours confrontés au même problème :

C'est là un des problèmes majeurs.
Alors, je me suis dit : n'y aurait-il vraiment pas un moyen de conserver l'information avec chaque noeud ?

Le miracle de l'héritage

Et si l'on sous-classait la classe TTreeNode ?
Bingo !...
Au lieu d'ajouter des TTreeNode à chaque noeud de l'arbre, on pourrait ajouter un objet équivalent mais en mieux, bien sûr ;o)
Aussitôt dit, aussitôt fait...
Déclarons donc un descendant de TTreeNode :
Code 3
type   { Classe dédiée à la gestion des dates dans les noeuds d'un arbre. Les   méthodes de lecture sont déclarées virtuelles pour pouvoir être surchargées   par des classes héritant de TDateNode.}   TDateNode = class(TTreeNode)   private   FVisitDate: TDateTime;   protected   function GetVisitDate: TDateTime; virtual;   procedure SetVisitDate(const Value: TDateTime); virtual;   public   property VisitDate: TDateTime   read GetVisitDate   write SetVisitDate;   end;
Et là, tout s'éclaircit, vous allez voir !
Je passe les détails de l'implémentation de cette classe car même un débutant est en mesure de le faire.
Pour servir de support aux tests du code qui va suivre, j'ai utilisé une fiche dont voici la déclaration :
Code 4
type   TDbTreeviewMainForm = class(TForm)   tvDataBase: TTreeView;   Panel1: TPanel;   Panel2: TPanel;   Panel3: TPanel;   btnShowTree: TButton;   lblNextVisit: TLabel;   Label1: TLabel;   procedure tvDataBaseCreateNodeClass(Sender: TCustomTreeView;   var NodeClass: TTreeNodeClass);   procedure btnShowTreeClick(Sender: TObject);   procedure tvDataBaseClick(Sender: TObject);   procedure tvDataBaseCustomDrawItem(Sender: TCustomTreeView;   Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);   private   { Fonction récursive renvoyant vrai si un au moins un des noeuds enfant   de StartNode a une date postérieure à la date du jour }   function CheckSubNodes(StartNode: TTreeNode):boolean;   { Renvoie vrai si ADate est postérieure à la date courante }   function IsOutDated(const ADate: TDateTime):boolean;   public   { Indique la classe du noeud à créer }   CurrentNodeClass: TTreeNodeClass;   { Dessin de l'arborescence de la base de données}   procedure DrawDbTreeView;   { Mettre à jour le contrôle lblNextVisit}   procedure UpdateLblNextVisit;   end; var   DbTreeviewMainForm: TDbTreeviewMainForm;
Détaillons maintenant les méthodes ajoutées à cette classe, dans leur ordre d'apparition ci-dessus :
Code 5
function TDbTreeviewMainForm.CheckSubNodes(StartNode: TTreeNode): boolean; var   TempNode: TTreeNode;   i: integer; begin   //on considère d'emblée que nous sommes sur le   //dernier noeud de la branche   Result := (StartNode as TdateNode).VisitDate > date;   //puis on regarde si le noeud a des enfants   if StartNode.HasChildren then   //dans ce cas, on examine chaque noeud   for i := 0 to StartNode.Count - 1 do   begin   TempNode := StartNode.Item[i];   OutDated := OutDated or CheckSubNodes(TempNode);   Result := OutDated;   end; end;
Vous l'aurez compris, cette fonction s'appelle elle-même, autrement dit nous avons là une fonction récursive.
Si la fonction est récursive, le parcours de l'arbre l'est tout autant.

Code 6
procedure TDbTreeviewMainForm.DrawDbTreeView; var   { Noeuds correspondant aux trois niveaux dont nous avons   besoin pour cette démo }   Node1, Node2, Node3: TDateNode; begin   //Au départ, on positionne à faux   OutDated := False;   with tvDataBase.Items do   begin   Clear;   BeginUpdate;   end;   with dmDemo do   with tblSite do   begin   First;   while not Eof do   begin   //Création des noeuds du premier niveau   CurrentNodeClass := TDateNode;   Node1 := tvDataBase.Items.AddChild(nil, FieldByName('Nom').AsString) as   TDateNode;   Node1.VisitDate := FieldByName('NextVisit').AsDateTime;   //création des noeuds du niveau 2   while not tblBatiment.Eof do   begin   CurrentNodeClass := TDateNode;   Node2 := tvDataBase.Items.AddChild(Node1,   tblBatiment.FieldByName('Nom').AsString) as TDateNode;   Node2.VisitDate := tblBatiment.FieldByName('NextVisit').AsDateTime;   //Création des noeuds du niveau 3   while not tblArticle.Eof do   begin   CurrentNodeClass := TDateNode;   Node3 := tvDataBase.Items.AddChild(Node2,   tblArticle.FieldByName('Nom').AsString) as TDateNode;   Node3.VisitDate := tblArticle.FieldByName('NextVisit').AsDateTime;   tblArticle.Next;   end; { with tblArticle }   tblBatiment.Next;   end; { with tblBatiment }   tblSite.Next;   Application.ProcessMessages;   end;   end; { with tblSite }   with tvDataBase do   begin   Items.EndUpdate;   FullExpand;   if Items.Count > 0 then   Selected := Items[0];   end;   UpdateLblNextVisit; end;
Cette méthode sera appelée à chaque fois que nous devrons redessiner l'arbre en entier.
Code 7
procedure TDbTreeviewMainForm.UpdateLblNextVisit; begin   with tvDataBase do   if Assigned(Selected) then   lblNextVisit.Caption :=   FormatDateTime('"Prochaine visite : "dddd dd mmmm yyyy', (Selected as TDateNode).VisitDate); end;
Cette méthode est appelée à deux moments :
  1. à la fin de la création de l'arbre
  2. quand on clique sur le composant TTreeView
Je voudrais aussi vous parler de l'évènement OnCreateNodeClass de la classe TTreeView. Grâce à cette méthode, nous appliquerons le type de noeud souhaité à chaque fois que nous aurons besoin d'en créer un nouveau. C'est pour cela que figurent les lignes :
CurrentNodeClass := TDateNode;

à différents endroits du code, juste avant les appels à AddChild.
Code 8

procedure TDbTreeviewMainForm.tvDataBaseCreateNodeClass(   Sender: TCustomTreeView; var NodeClass: TTreeNodeClass); begin   NodeClass := CurrentNodeClass; end;

Pour le reste, c'est du classique dans la gestion des interfaces sauf, peut-être :

Code 9

procedure TDbTreeviewMainForm.tvDataBaseCustomDrawItem(   Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); var   i: integer; begin   OutDated := False;     //Examiner la descendance à la recherche d'une date non échue   //Si le noeud courant a des enfants   if Node.HasChildren then   begin   //le noeud courant est-il lui même "périmé" ?   if IsOutDated((Node as TDateNode).VisitDate) then   tvDataBase.Canvas.Font.Color := clRed   else   //examiner chaque enfant   for i := 0 to Node.Count - 1 do   begin   //Si un seul a une date non échue   if CheckSubNodes(Node.Item[i]) then   begin   //écriture de couleur fuchsia   tvDataBase.Canvas.Font.Color := clFuchsia;   //Inutile de poursuivre l'exploration   Break;   end;   end;   end   else   //nous sommes donc sur le dernier noeud de l'arbre,   //autrement dit, nous sommes sur une feuille :o)   if IsOutDated((Node as TDateNode).VisitDate) then   tvDataBase.Canvas.Font.Color := clRed; end;

Ne perdons pas de vue que le but du jeu était, avant tout, de colorier les caractères des noeuds de l'arbre !

Pour les reste, le "stuff" comme disent les anglophones, je vous laisse découvrir le code une fois que vous l'aurez téléchargé.

Je vous souhaite une bonne journée.

board_d_home.gif 


Pour télécharger le code source complet, cliquez ici. (Dernière mise à jour du code : 27 September 2006 à 21:47)
Cet article a été vu 928 fois.