J'ai trois tablesJ'ai donc mis trois ADOTable.
- La première "Site" où j'ai le nom du site, son identifiant et la date de la prochaine visite,
- 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,
- 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.
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.
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.
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 :
( 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 :
Et là, tout s'éclaircit, vous allez voir !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;
Détaillons maintenant les méthodes ajoutées à cette classe, dans leur ordre d'apparition ci-dessus :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;
Vous l'aurez compris, cette fonction s'appelle elle-même, autrement dit nous avons là une fonction récursive.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;
Cette méthode sera appelée à chaque fois que nous devrons redessiner l'arbre en entier.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 est appelée à deux moments :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;
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.