VIII. Propriétés héritées▲
Le fait que nous ayons hérité d'une classe abstraite fait que toutes les propriétés classiques des contrôles ne sont pas présentes.
Heureusement, nous n'avons pas besoin de les implémenter : elles existent, mais cachée (protégées ou publiques selon les cas).
La classe TControl propose des dizaines de propriétés protégées et publiques, qu'il ne tient qu'à vous de rendre publiées.
Il est facile de rendre les propriétés protégées et publiques publiées. Il suffit de les redéclarer, avec juste le mot-clef property et le nom de la propriété, sans son type ni ses accès.
Vous pouvez choisir les propriétés que vous voulez rendre publiées. Dans notre exemple, nous utiliserons les propriétés suivantes. Vous remarquerez qu'il est possible de modifier les propriétés par défaut ou les spécifications de stockage. Nous utiliserons cette technique pour les propriétés Color (rappelez-vous que nous avons mis clNone dans le constructeur), ParentColor (puisque le fait de modifier la propriété Color entraîne la mise à False de ParentColor) et AutoSize (dont nous n'avons pas encore parlé).
published
property
AutoSize default
True
;
property
Color default
clNone;
property
DragKind;
property
DragCursor;
property
DragMode;
property
ParentBiDiMode;
property
ParentColor default
False
;
property
ParentShowHint;
property
PopupMenu;
property
Align;
property
Anchors;
property
BiDiMode;
property
Constraints;
property
DockOrientation;
property
ShowHint;
property
Visible;
property
OnClick;
property
OnConstrainedResize;
property
OnContextPopup;
property
OnDblClick;
property
OnDragDrop;
property
OnDragOver;
property
OnEndDock;
property
OnEndDrag;
property
OnMouseActivate;
property
OnMouseDown;
property
OnMouseMove;
property
OnMouseUp;
property
OnMouseWheel;
property
OnMouseWheelDown;
property
OnMouseWheelUp;
property
OnResize;
property
OnStartDock;
property
OnStartDrag;
Nous allons tout de suite modifier le constructeur pour positionner AutoSize à True.
constructor
TCircleChart.Create(AOwner : TComponent);
begin
inherited
;
FClickedQuarter := -1
;
AutoSize := True
;
Color := clNone;
FSpoke := 100
;
FBrush := TBrush.Create;
FBrush.OnChange := GraphicsChange;
FPen := TPen.Create;
FPen.OnChange := GraphicsChange;
FQuarters := TChartQuarters.Create(Self
);
FBaseAngle := 90
;
end
;
IX. Effet de la propriété AutoSize▲
Il est temps de nous intéresser à la façon dont AutoSize fait effet. Comment savoir en effet comment redimensionner le contrôle, ni même quand le faire ?
Dans TControl, il existe une méthode AdjustSize virtuelle qui doit redimensionner le contrôle en fonction des diverses propriétés. Voilà la réponse à notre première question.
Pour ce qui est de la deuxième, c'est simple : lorsque AutoSize est positionnée à True, la méthode AdjustSize est appelée (cela se fait automatiquement). De plus, dans les setter des propriétés influant sur la taille, il vous suffit d'appeler vous aussi AdjustSize.
Notre taille sera contrôlée par la propriété Spoke : la largeur et la hauteur n'ont aucune raison d'être différentes du double de cette propriété.
Nous allons donc modifier la méthode SetSpoke pour ajouter un appel à AdjustSize :
procedure
TCircleChart.SetSpoke(New : integer
);
begin
if
New <= 0
then
exit;
FSpoke := New;
if
AutoSize then
AdjustSize;
Invalidate;
end
;
Nous allons également surcharger la méthode AdjustSize pour faire correspondre la taille avec le rayon du disque.
protected
procedure
AdjustSize; override
;
Son implémentation est plus que simple. Mais il ne faut pas oublier à veiller que l'on n'est pas en train de charger le composant à partir d'un flux, au risque d'interférer avec les valeurs enregistrées.
procedure
TCircleChart.AdjustSize;
begin
if
not
(csLoading in
ComponentState) then
begin
Width := Spoke*2
;
Height := Spoke*2
;
end
;
end
;
X. Améliorer le système de menu pop-up▲
Le simple fait d'avoir rendu publiée la propriété PopupMenu a fait que l'on peut lui assigner un menu pop-up qui surgira automatiquement en cas de clic droit.
Toutefois, il est impossible, pour l'instant, à l'application de savoir sur quel quartier on a enfoncé le bouton droit, ce qui pourrait toutefois être très utile.
Nous allons donc surcharger la méthode DoContextPopup déclarée elle aussi dans TControl. Cette méthode appelle le gestionnaire d'événement OnContextPopup, mais permet aussi d'effectuer des actions avant que le menu soit affiché.
protected
procedure
DoContextPopup(MousePos : TPoint; var
Handled : boolean
); override
;
Dans cette méthode, nous allons renseigner une variable privée FPopupQuarter de type TChartQuarter avec le quartier se trouvant à la position MousePos. Nous ajouterons une propriété publique en lecture seule pour que l'application puisse accéder à cette information.
private
FPopupQuarter : TChartQuarter;
public
property
PopupQuarter : TChartQuarter read
FPopupQuarter;
Nous initialiserons cette variable à nil dans le constructeur :
constructor
TCircleChart.Create(AOwner : TComponent);
begin
inherited
;
FClickedQuarter := -1
;
FPopupQuarter := nil
;
Color := clNone;
FSpoke := 100
;
FBrush := TBrush.Create;
FBrush.OnChange := GraphicsChange;
FPen := TPen.Create;
FPen.OnChange := GraphicsChange;
FQuarters := TChartQuarters.Create(Self
);
FBaseAngle := 90
;
AutoSize := True
;
end
;
Voici l'implémentation de DoContextPopup :
procedure
TCircleChart.DoContextPopup(MousePos : TPoint; var
Handled : boolean
);
begin
FPopupQuarter := PointToQuarter(MousePos);
inherited
;
end
;
XI. Afficher un hint selon le quartier pointé▲
Il reste une dernière chose à faire : nous n'avons pas encore utilisé la propriété Hint de TChartQuarter.
Le but que nous poursuivons est que dans le cas où la souris stationne au-dessus d'un quartier, ce soit le hint de ce quartier qui soit affiché en lieu et place du hint du TCircleChart.
Tous les cas que nous avons rencontrés jusqu'à présent ont pu être réglés grâce à la VCL. Malheureusement, ce n'est pas le cas de ce nouveau problème !
Nous devrons donc nous débrouiller autrement. Dans ce genre de cas, une étude des sources de la VCL pourra souvent se révéler très instructive sur la manière de procéder.
Après une étude intensive de ces sources, j'ai remarqué qu'il était possible d'implémenter cette fonctionnalité en interceptant le message CM_HINTSHOW.
Pour intercepter un message Windows (envoyé via SendMessage ou PostMessage), il faut écrire une méthode qui possède la directive message. Ce type de méthode doit accepter un unique paramètre variable (var), mais son type peut être quelconque ; en pratique, il se nomme Message et de type TMessage ou TWMXXX ou TCMXXX. Ces TWMXXX et TCMXXX sont des records de la même taille que TMessage, mais avec des valeurs de types différents, permettant de récupérer plus facilement la signification des paramètres L et R du message. Ces méthodes sont très souvent privées.
Le message qu'intercepte la méthode est déterminé par une constante d'identificateur de message placée après la directive message. Par exemple : message WM_LBUTTONDOWN;
Nous déclarerons donc une méthode CMHintShow privée comme suit :
private
procedure
CMHintShow(var
Message
: TCMHintShow); message
CM_HINTSHOW;
Cette méthode sera appelée automatiquement lorsque notre contrôle interceptera un message de type CM_HINTSHOW.
Dans le code d'implémentation de telles méthodes, on peut utiliser l'instruction inherited, bien qu'elles ne soient pas des méthodes surchargées, pour appeler la méthode correspondante (qui ne porte pas forcément le même nom ni n'accepte le même paramètre, mais qui intercepte le même message) dans la plus proche classe parente qui en possède une.
Notre méthode CMHintShow devra, si la position de la souris (récupérée grâce à Message.HintInfo.CursorPos) se trouve sur un quartier qui possède un hint, modifier le champ Message.HintInfo.HintStr pour refléter cette valeur.
procedure
TCircleChart.CMHintShow(var
Message
: TCMHintShow);
var
Quarter : TChartQuarter;
begin
inherited
;
if
Message
.Result <> 0
then
exit;
Quarter := PointToQuarter(Message
.HintInfo.CursorPos);
if
Assigned(Quarter) and
(Quarter.Hint <> ''
) then
Message
.HintInfo.HintStr := Quarter.Hint;
end
;