6 - Contrôle fenêtré :
Un contrôle fenêtré est un contrôle qui peut recevoir le focus clavier,
qui possède un Handle de fenêtre et qui peut à l'occasion contenir d'autres contrôles.
Avec C++ Builder pour construire un contrôle fenêtré, il faut que le composant
soit un descendant de TWinControl.
Pour cet exemple nous allons construire un contrôle case à cocher dont la partie
cochée sera représentée soit par un carré, une croix ou une coche
dont on pourra choisir la couleur et dont
on pourra changer l'état par l'appui sur la touche espace s'il détient le focus.
Nous ferons donc descendre notre composant de TCustomControl (descendant direct de TWinControl)
car il possède en plus de TWinControl un Canvas permettant de dessiner.
Fichier "CaseACocher.h" :
#ifndef CaseACocherH
#define CaseACocherH
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <Controls.hpp>
//---------------------------------------------------------------------------
enum TTypeCoche {tcCarre, tcCroix, tcCoche};
class PACKAGE TCaseACocher : public TCustomControl
{
private:
bool FChecked;
TColor FCouleur;
TTypeCoche FCoche;
bool ALeFocus;
protected:
void __fastcall Paint();
DYNAMIC void __fastcall KeyPress(char &Key);
DYNAMIC void __fastcall DoEnter();
DYNAMIC void __fastcall DoExit();
DYNAMIC void __fastcall Click();
DYNAMIC void __fastcall MouseDown(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y);
virtual void __fastcall SetChecked(bool Value);
virtual void __fastcall SetCouleur(TColor Value);
virtual void __fastcall SetCoche(TTypeCoche Value);
virtual void __fastcall TextChanged(TMessage &Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(CM_TEXTCHANGED, TMessage, TextChanged)
END_MESSAGE_MAP(TCustomControl)
public:
__fastcall TCaseACocher(TComponent* Owner);
__published:
__property Caption;
__property OnClick;
__property OnEnter;
__property OnExit;
__property OnKeyUp;
__property OnKeyPress;
__property OnKeyDown;
__property TabOrder;
__property TabStop;
// nouvelles propriétés :
__property bool Checked = {read=FChecked, write=SetChecked};
__property TColor Couleur = {read=FCouleur, write=SetCouleur};
__property TTypeCoche Coche = {read=FCoche, write=SetCoche};
};
//---------------------------------------------------------------------------
#endif
Dans le fichier CaseACocher.h comme pour les autres chapitres : déclarations
des propriétés avec leurs données membres et méthodes de mise à jour respectives
pour les nouvelles propriétés, déclaration des méthodes à redéfinir,
déclaration de la méthode TextChanged. Elle nous servira à rafraîchir
l'affichage si le texte du contrôle est modifié.
Cette méthode existe dans la librairie CLX mais malheureusement pas dans la VCL.
Elle est appelée si le texte d'un contrôle a changé. Donc nous allons intercepter
le message Windows CM_TEXTCHANGED qui est envoyé au contrôle quand son texte a
été modifié, pour le lier à la méthode TextChanged dans la carte des
messages afin qu'elle soit appelée à l'interception de ce message. Nous y
déclarons aussi une donnée membre qui nous indiquera si notre contrôle détient
le focus. Nous aurons aussi comme au chapitre précédent un type enum pour
le choix du dessin de la coche.
Fichier "CaseACocher.cpp"
#include <vcl.h>
#pragma hdrstop
#include "CaseACocher.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck est utilisé pour vérifier que les composants créés n'ont
// aucune fonction virtuelle pure.
static inline void ValidCtrCheck(TCaseACocher *)
{
new TCaseACocher(NULL);
}
//---------------------------------------------------------------------------
__fastcall TCaseACocher::TCaseACocher(TComponent* Owner)
: TCustomControl(Owner)
{
Width = 100;
Height = 20;
TabStop = true;
FChecked = false;
FCouleur = clBlack;
FCoche = tcCarre;
ALeFocus = false;
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::Paint()
{
RECT CoRect;
CoRect.left = 2;
CoRect.top = 2;
CoRect.right = 18;
CoRect.bottom = 18;
Canvas->Brush->Color = clWindow;
Canvas->FillRect(CoRect);
DrawEdge(Canvas->Handle, &CoRect, EDGE_SUNKEN, BF_RECT );
Canvas->Brush->Color = Color;
Canvas->TextOut(25,2,Caption);
if(Checked)
{
switch (Coche)
{
case tcCarre :
Canvas->Brush->Color = Couleur;
RECT InRect;
InRect.left = 6;
InRect.top = 6;
InRect.right = 14;
InRect.bottom = 14;
Canvas->FillRect(InRect);
break;
case tcCroix :
Canvas->Pen->Color = Couleur;
Canvas->Pen->Width = 2;
Canvas->MoveTo(6,6);
Canvas->LineTo(14,14);
Canvas->MoveTo(6,14);
Canvas->LineTo(14,6);
break;
case tcCoche :
Canvas->Pen->Color = Couleur;
Canvas->Pen->Width = 2;
Canvas->MoveTo(5,9);
Canvas->LineTo(8,12);
Canvas->LineTo(14,6);
break;
}
}
if (ALeFocus)
{
Canvas->Brush->Color = Color;
TRect FocRect;
FocRect.Top = 1;
FocRect.Left = 22;
FocRect.Right = Width - 2;
FocRect.Bottom = Height - 2;
Canvas->DrawFocusRect(FocRect);
}
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::KeyPress(char &Key)
{
if (Key == 32) Checked = !Checked;
TCustomControl::KeyPress(Key);
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::Click()
{
TCustomControl::Click();
Checked = !Checked;
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::MouseDown(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y)
{
SetFocus();
ALeFocus = true;
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::DoEnter()
{
TCustomControl::DoEnter();
ALeFocus = true;
Invalidate(); // Pour afficher le rectangle de focus.
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::DoExit()
{
TCustomControl::DoExit();
ALeFocus = false;
Invalidate(); // Pour effacer le rectangle de focus.
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::TextChanged(TMessage &Msg)
{
Invalidate(); // Pour afficher la modification du texte.
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::SetChecked(bool Value)
{
FChecked = Value;
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::SetCouleur(TColor Value)
{
FCouleur = Value;
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCaseACocher::SetCoche(TTypeCoche Value)
{
FCoche = Value;
Invalidate();
}
//---------------------------------------------------------------------------
namespace Caseacocher
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TCaseACocher)};
RegisterComponents("MesCompo", classes, 0);
}
}
Dans le constructeur initialisation des données membres, propriétés héritées,
TabStop mise à true pour permettre au contrôle de recevoir le focus.
Dans la méthode Paint : dessin de la case à l'aide de la fonction de l'API Windows
DrawEdge, dessin du texte, dessin de la coche si la case est cochée en fonction
du choix fait dans la propriété Coche, puis dessin
du rectangle de focus à l'aide de la méthode du Canvas DrawFocusRect
si le contrôle détient le focus.
La méthode KeyPress change l'état de la case à cocher si la touche "Espace"
est enfoncée. La méthode Click change aussi l'état de la case à cocher et
la méthode MouseDown donne le focus au contrôle. On aurait pu lui donner
le focus dans sa méthode Click, mais je l'ai fait dans le MouseDown pour
que le comportement de notre case à cocher soit identique à la CkeckBox
de Windows.
Les méthodes DoEnter et DoExit rafraîchissent l'affichage du contrôle s'il prend
ou perd le focus et mettent à jour la donnée membre ALeFocus.
Pareil pour TextChanged si le texte du contrôle est modifié.
Dans les méthodes redéfinies on appelle la méthode de la classe ancêtre correspondante,
par exemple TCustomControl::Click() dans la méthode Click(), ceci afin de pouvoir
traiter les événements correspondants à ces méthodes.
Les méthodes SetChecked et SetCouleur changent l'état des nouvelles propriétés
et mettent à jour l'affichage du contrôle après leurs changements.
Avec cet exemple nous pouvons remarquer que la construction d'un contrôle fenêtré
n'est pas beaucoup plus compliqué qu'un contrôle ordinaire.
Télécharger les sources : Caseacocher.zip
(Est inclus le fichier "Caseacocher.dcr" contenant l'icône qui le représente
sur la palette de composants)
Je vous mets en prime un deuxième contôle fenêtré à télécharger : Colorbtn.zip
Il s'agit d'un contrôle semblable à un TButton auquel on peux changer sa couleur.
Je ne commente pas son code source qui est fort semblable au contôle précédant.
A bientôt pour la suite,
CGi
Avec la contribution d'Alacazam pour la relecture.
|