Référence Canvas [Résolu]

Signaler
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
-
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
-
Bonjour,
Je créé un composant dérivant de TgraphicControl qui dessine différentes formes dont je donne un extrait de l'une d'elle, plus bas. J'ai rendu publique une procédure paint qui fait appel à cette fonction, mais je ne suis pas sûr que ce soit la bonne méthode.
 Je souhaiterai supprimer la référence à Form1, mais je n'ai pas trouvé de solution.
 Dois-je créer un Handle de Canvas, mais comment dire ensuite au composant que le parent de TLed peut être quelconque. Dans la procedure de création de la fiche principale, je déclare bien  aLed.Parent  := Form1; , mais cela ne semble pas indiquer à la procedure Paint du composant que le Canvas de la Form1 est l'endroit où il faut dessiner!!!???!!

procedure TLed.DrawLedCircle(Centre:Tpoint;aDex,aDInt:Byte;ClEx,ClIn:TColor);
  var R : Byte;
begin  
  With Form1.Canvas do
  begin
    Brush.Color := ClEx;
    Pen.Color   := ClEx;
    Pen.Width   := 1;
    R := aDEx div 2;
    Ellipse(Centre.X-R,Centre.Y-R,Centre.X+R,Centre.Y+R);
    ...
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  aLed := TLed.create(Self); // création d'1 led de type TLed
  aLed.Parent  := Form1;
...
end;

 Voilà, je m'en remet à vos lumières delphistes.
Merci à vous!

22 réponses

Messages postés
3825
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
1 février 2021
40
Salut,

Normalement si ton TLed est dérivé de TGraphicControl il dispose de son propre Canvas ...

Donc
With Canvas Do Begin
...
End;

Devrait suffir
Ce qui aura quand même pour effet au final de dessiner au bon endroit

With Canvas as Owner as TForm do ... Mdr
 
@+
Cirec

<hr size="2" />
Messages postés
3825
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
1 février 2021
40
Oula oula ...
ne rien changer à ceci :
Constructor TLed.Create(AOwner: TComponent);
Begin
    Inherited Create(AOwner);
    ....
End;

Voilà ceci plus mon précedant post devrait règler ton problème  
@+
Cirec

<hr size="2" />
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Salut, petit problème de compréhension je présume.




Unité composant:



Constructor Tled.Create (AOwner : TComponent);
begin
  inherited Create(AOwner);
  ...
  Invalidate;
end;

procedure TLed.Paint;
begin
with Canvas do
 begin
   Pen   := FPen;
   Brush := FBrush;
   Case FTypeLed of
     sstCircle    : DrawLedCircle(Canvas,Centre,DiamExt,DiamInt,ColorExt,ColorInt);








     ...
   end;
   ...
end;
end;

La référence au Canvas est accessible partout dans les méthodes de TLed, c'est le principe même des objets !
Tu peux taper Self.Canvas si tu veux, cela revient au même.

D'ailleurs, tu n'as nullement besoin de mettre Led.Paint dans le OnPaint de ta fiche, tout est géré nativement. De plus, je ne te préconise pas l'appel à Invalidate dans le constructeur du composant. suivant les cas, tu peux ariver à un plantage (ressources pas encore allouées ou autres erreurs...). De toutes façons les composants sont automatiquement dessinés lorsque c'est nécéssaire, tu n'as donc pas trop à t'en faire, juste un Refresh lors des changements des propriétés.
Messages postés
991
Date d'inscription
samedi 25 octobre 2003
Statut
Membre
Dernière intervention
29 août 2013
7
as tu bien mis le override dans la declaration de la methode Paint?

essaye peut etre en derivant ta classe de TImage ou TCustomImage.
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
 Merci Guillemouze et Japee de votre participation. j'ai tardé à répondre car je voulais faire un résumé des nombreuses modifications du code en cours d'élaboration.







 Un grand merci à Cirec et Florenth qui ont relu mon code et procéder à des corrections.<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" /??>





 







L'erreur venait essentiellement de paramètres par défaut mal initialisés. Donc une vérification des bornes du composant ont été ajoutées.











 






 Je résume quelques conseils que Cirec et Florenth m’ont donné dans l’écriture du composant. Je les restitue ici aussi fidèlement que possible : 





1-
Suppression des surchages de méthodes natives utilisant Pen et Brush . En effet, le choix de  TgraphicControl  avait été fait parce qu’il possède déjà Canevas qui possède lui-même les entités Pen et Brush.





J’avais dans l’idée au départ d’utiliser les régions qui me semblent plus performantes pour la composition de dessins. Mais je pense que c’est une erreur car me semble t-il, les régions sont surtout utilisées lorsqu’il n’y a pas de canevas de disponible. Est-ce que je dis des conneries ?  
2- Dans les méthodes on utilise toujours les variables Private celles qui commencent
par la lettre F  Ex: FCentre au lieu de Centre







3-
Bien  initialiser toutes les variables dans le constructeur et ne jamais faire appel à Invalidate dans le constructeur.







4-
La méthode Paint doit toujours être placée dans la partie protected.








5-
 J’ai supprimé tous les passages de valeurs aux variables des méthodes de dessin car ces variables sont définies dans l’objet.





La procédure  DrawLedEllipse(Centre:Tpoint;aDex,aDInt:Byte;ClEx,ClIn:TColor); devient simplement procedure DrawLedEllipse ;
6- La Procedure Register ne nuit pas au fonctionnement en Objet si on n’a pas installé le composant.







7-
Published est réservé pour les propriétés ; aucune procédure ne doit y apparaître.






8-
Les règles d’écritures en Delphi : Lorsqu'on déclare des types énumérés, les premières lettres de chaque constante à l'intérieur sont les initiales en minuscule du nom du type. On écrira donc: TLedType  = (tlRectangle, tlSquare…);

Good Delphi
Messages postés
991
Date d'inscription
samedi 25 octobre 2003
Statut
Membre
Dernière intervention
29 août 2013
7
heuuuu je ne suis pas d'accord sur le point 8, j'aurai plutot ecrit TLedType = ( lt Rectangle, ltSquare)
tout comme TMonEnum = (meElement1, meElement2)
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Oui, c'est tout à fait ça Jean_Jean, bonne synthèse d'ailleurs, pratique pour ceux qui passeront par là.

A une seule chose près: les régions ne sont pas vraiment plus performantes que les fonction GDI correspondantes. Tout dépend l'effet voulu. Si tu veux un composant rond (comme une led tiens ... ^^) tu es obligé d'utiliser les régions pour lui donner sa forme, mais ensuite, tu dessines dans le canvas avec les fonctions GDI.

Attention, de plus, pour pouvoir utiliser les régions, il faut que ton composant dérive de TWinControl ou de ces descendants (qu'il aît un Handle en fait) et lui donner sa forme grâce à la procédure SetWindowRgn() lors de la création de la fenêtre ou de sa modification. Ce qui fait somme toute un truc assez compliqué pour pas grand chose. Surtout que les TWinControl consomment plus de mémoire (tout comme les régions).

Attention aussi à ce conseil de Cirec
"





2



-
Dans les méthodes on utilise toujours les variables Private celles qui commencent
par la lettre F  Ex: FCentre au lieu de Centre"
Dans certains cas, il est impératif ou alors plus pratique d'utiliser la propriété (publique ou publiée), lorsque celle ci a un Getter ou un Setter (ou alors appeler directement la bonne méthode). Maisce n'est pas un cas très fréquant. Je le dis juste pour l'info.

Bon Delphi à tous,
Flo
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
@ Guillemouze: arf, nos messages se sont croisés.
En effet, pour le point 8, il y a ambiguïté.

Il faut replacer le contexte dnas lequel je lui ai conseille cela.
Son type s'appellait TTypeLed, donc je lui ai conseillé de déclarer (tl..., tl...)

Mais à côté de cela, je trouvais que TLedType sonnait mieux. Alors dans ce cas, c'est bien sûr (lt..., lt...) qu'il faudrait déclarer.

Rien de méchant, faute de frappe peut-être...
Messages postés
3825
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
1 février 2021
40
Salut,

juste un petit truc au passage :

Florenth à dit : "Attention, de plus, pour pouvoir utiliser les régions, il faut que ton composant dérive de TWinControl...
"
Ce n'est pas obligatoire ... on peut appliquer une région à un Canvas

Ensuite l'appel à une propriété Publiée peut avoir de fâcheuses conséquences ... (boucle sans fin pour peut que l'on fasse appel a elle dans le Setter ou Getter ...  Voir plus loin )
Et de plus faire appel à une propriété publiée dans un composant ou objet provoque un double appel :

Private
  FCenter : TPoint;
  Procedure SetCenter(Value : TPoint);
  Function GetCenter: TPoint;
Published
  Center :  TPoint Read GetCenter Write SetCenter;
End;

Implementation

Procedure SetCenter(Value : TPoint);
Begin
  If Value <> FCenter Then Begin
    FCenter := Value;
    Invalidate ;

  End;
End;

Function GetCenter: TPoint;
Begin
  Result : = FCenter;
End;

Donc ... un appel à FCenter ne fait appel qu'a FCenter

Par contre utiliser directement Center fera en premier appel au Setter ou Getter donc à FCenter ... résultat deux appel pour une seul et même valeur ...

 Exemple type de ce qu'il ne faut pas faire sous peine de déclancher une boucle sans fin
Procedure SetCenter(Value : TPoint);
Begin

  If Value <> FCenter Then Begin

    Center := Value;
    Invalidate ;

  End;
End;

Le seul moment ou un appel à la propriété Publique est préférable c'est si une opération quelconque est faite en plus dans le Setter ... Mais en général c'est un défaut de conception (enfin c'est mon avis)
ps : Si votre Setter est Irlandais ... je vous conseil une vaccination anti-rabique d'urgence

 
@+
Cirec

<hr size ="2" />
Messages postés
1725
Date d'inscription
vendredi 27 décembre 2002
Statut
Modérateur
Dernière intervention
11 avril 2021
8
Salut,

Remplace "with Form1.Canvas do"
par "with Owner do"
pour voir...
Pas le temps de tester, mais logiquement ça devrait répondre à ce que tu veux faire.

Bonne prog'
Messages postés
1725
Date d'inscription
vendredi 27 décembre 2002
Statut
Modérateur
Dernière intervention
11 avril 2021
8
Oups...

C'est plutôt "with Owner.Canvas do" (mais ça doit pas marcher), ou "with (Owner as TForm).Canvas do", ou "with TForm(Owner).Canvas do".

Je me relis cette fois, et je balance...
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
Merci Japee pour ta réponse!
Oui, dans le code que tu avais déposé, j'ai vu cette référence .Create(AOwner: TComponent); souvent utilisée. Je l'ai Remplacé par aLed :
Constructor Tled.Create (aLed : TComponent);
begin 
  inherited Create(aLed);
  FPen   := TPen.Create;
  ...
end;
Bon, je vais étudié cette piste...
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
Bonjour Cirec,
j'ai fait la rectif de AOwner dans le constructor et les différents tests de vos propositions de With, ça ne dessine pas!
j'ai fait remonter la référence Form1.Canvasdans Paint plûtot que Draw...,mais ça ne change pas le problème. il veut une référence Canvas Valide. je récapitule le code:

fiche principale :
procedure TForm1.FormCreate(Sender: TObject);
begin
  aLed := TLed.create(Self); // création d'1 led de type TLed
  aLed.Parent  := Form1;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
  aLed.Paint;
end;
Unité composant:
Constructor Tled.Create (AOwner : TComponent);
begin
  inherited Create(AOwner);
  ...
  Invalidate;
end;
procedure TLed.Paint;
 var aCanv : Tcanvas;
begin
aCanv :=  Canvas as (aOwner as TComponent));// invalide ne connait pas aOwner et Erreur opérateur non autorisé...
 
with aCanv do
 begin
   Pen   := FPen;
   Brush := FBrush;
   Case FTypeLed of
     sstCircle    : DrawLedCircle(aCanv,Centre,DiamExt,DiamInt,ColorExt,ColorInt);// j'ai mis aCanv pour supprimer with Canvas ds la procédure
     ...
   end;
   ...
end;
end;
TLed dérive directement de TGraphicControl pour disposer du Canvas. Mais Je n'arrive pas intégrer AOwner ou Canvas dans le with de TLed.Paint. 
Je préférerai une référence de handle quelconque plutot que TForm uniquement avec With Canvas as Owner as TForm...

A suivre!
  

 
Messages postés
3825
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
1 février 2021
40
Peux tu nous poster l'unité complete qui contient le composant TLed
il semblerait qu'il y ait quelques petites erreurs dans le code ...

Puisque le Canvas devrait être directement accessible dans toutes les procédures et fonctions de TLed

 
@+
Cirec

<hr size="2" />
Messages postés
3825
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
1 février 2021
40
ps : "With Canvas as Owner as TForm do ..." c'était une plaisenterie

 
@+
Cirec

<hr size="2" />
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
 Merci Florenth et Cirec pour vos conseils!

D'ailleurs, c'est sur 1 idée de Florenth que je me lance dans l'aventure des composants! Vilain qu'il est de me donner du fil à retordre. Mais tu as raison, l'écriture de composant, c'est quand même sympa, même si je n'ai pas beaucoup de temps, je me détends en faisant du delphi. En plus j'ai retrouver mon D5. alors, je peux y balancer des codes à tester!
Je compulse la doc, mais c'est un univers!...
Dans la doc, j'avais lu que invalidate forçait l'évènement onpaint ou un refresh! Mais si tu dis que ça pose pb!?!, je supprime.
ec
Je veux bien vous balancer l'unité complète, mais elle n'est pas très avancée et enconbrante pour un post.

Je reprendrai contact ce soir, je dois m'absenter!

Merci.
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
 Bon ça fait une heure que j'essaye de comprendre l'erreur! NO comprendo!
     En regardant des exemples de compo, je ne vois pas grande différence avec mon code. Sauf pour paint qui se trouve en général dans la partie protected et que j'ai mis en publique pour lancer le dessin. D'ailleurs, en replaçant Paint dans la zone protected, même en remettant Form1.Canvas dans la procédure de dessin, ça ne dessine plus, ce qui est logique.

Mais comme l'a dit Cirec, normalement, avec la création dans la fiche principale, l'affichage est automatique.

A +
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
Merci à tous.

Pour les Régions, c'est le fait de changer d'ancêtre qui m'a freiné.
Par contre je me posai la question effectivement de la précision du dessin et j'envisage d'augmenter la résolution sur le canvas de Graphiccontrol
Je ne sais si c'est possible sur ce composant, alors que pour les régions, je sais que c'est possible.

Bien à vous
Messages postés
615
Date d'inscription
dimanche 13 août 2006
Statut
Membre
Dernière intervention
13 décembre 2018
3
 Merci Cirec pour ces précisions.



 Je vais introduire les fonctions  Getxxx qui gagnent en lisibilité.
Bonne nouvelle pour les régions. Je vais essayer sans d'abord. Je pense utiliser PixelFormat := pf24bit pour introduire une meilleure résolution.
 Je suis toujoursà réfléchir sur les fonctionnalités à écrire pour en faire un outil sympha.

@+ 
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Et juste un autre petit truc au passage:

Si ton Setter ressemble à ça :






procedure TMyClass.SetObject(Value: TObject)
begin
  if Value <> FObject then
  begin
     FObject.Free;
     FObject := Value;
     if Assigned(FObject) then
       FObject.OnXXxxxx : = doSth;
  end;
end;


L'affectation directe de la propriété permet d'éviter le FObject.OnXXxx := doSth à chaque fois qu'on change sa valeur. Sinon, c'est vrai que les Getter/Setters sont à utiliser avec parcimonie mais dnas certains cas, ils sont bien pratiques.


"si une opération quelconque est faite en plus dans le Setter ... Mais en général c'est un défaut de conception (enfin c'est mon avis) "
> Pas convaincu. Regarde TStringList.SetCapacity, ils font bien des choses en plus. Est-pour autant mal conçu, je n'en suis pas sûr...

"boucle sans fin pour peut que l'on fasse appel a elle dans le Setter ou Getter"
> Il faut vraiment le faire exprès pour ne pas s'en rendre compte...


"Par contre utiliser directement Center fera en premier appel au Setter
ou Getter donc à FCenter ... résultat deux appel pour une seul et même
valeur ... "
> Tout à fait d'accord, c'est du temps de perdu pour rien ! Sauf pour les Getters de tableau où c'est la seule solution pour avoir un accès correct sans ce casser la tête. Tu peux très bien faire GetItem(I).xxx ce qui t'évites de faire (FItems[I] as Txxx).xxx dans la plupart des cas.

"on peut appliquer une région à un Canvas"
> Tu peux utiliser FillRgn() et les autres avec un Handle de Canvas. Par contre, tu ne peux pas donner une forme ronde avec SetWindowRgn(). Il te faut impérativement un Handle de fenêtre.