7 - Evénement personnalisé et sous-propriétés :
Dans cet exemple, nous allons voir comment mettre en oeuvre des sous propriétés,
c'est-à-dire vu de l'inspecteur d'objet, avoir une propriété qui contient
d'autres propriétés. Nous allons aborder aussi la création d'un événement
personnalisé.
Dans notre exemple nous allons créer un bouton avec un dégradé de couleur.
Une grande partie du code est semblable au bouton à télécharger au chapitre
précédant et à la case à cocher du même chapitre.
Nous ne reviendrons donc pas dessus.
Le code de la méthode Paint ne sera pas commenté non plus quoique que
légèrement différent, mais ce n'est que du dessin à l'aide de Canvas.
Fichier "CGiButton.h" :
#ifndef CGiButtonH
#define CGiButtonH
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <Controls.hpp>
//---------------------------------------------------------------------------
class TRGB : public TPersistent
{
private:
int FpasR, FpasG, FpasB;
TNotifyEvent FOnChange;
public:
__fastcall TRGB();
protected:
virtual void __fastcall Change();
virtual void __fastcall SetpasR(int Value);
virtual void __fastcall SetpasG(int Value);
virtual void __fastcall SetpasB(int Value);
__published:
__property int pasR = {read=FpasR, write=SetpasR, default = 1};
__property int pasG = {read=FpasG, write=SetpasG, default = 1};
__property int pasB = {read=FpasB, write=SetpasB, default = 1};
__property TNotifyEvent OnChange = {read=FOnChange, write=FOnChange};
};
//---------------------------------------------------------------------------
class PACKAGE TCGiButton : public TCustomControl
{
private:
TRGB *FRGB;
TFont *FFont;
bool BtnEnfonce;
bool BtnEnfonceBis;
bool MouseSurBtn;
bool ALeFocus;
protected:
void __fastcall RGBChange(TObject *Sender);
void __fastcall Paint();
DYNAMIC void __fastcall DoEnter();
DYNAMIC void __fastcall DoExit();
DYNAMIC void __fastcall MouseDown(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseUp(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall KeyDown(Word &Key, Classes::TShiftState Shift);
DYNAMIC void __fastcall KeyUp(Word &Key, Classes::TShiftState Shift);
bool __fastcall CanResize(int &NewWidth, int &NewHeight);
virtual void __fastcall SetFont(TFont *Value);
virtual void __fastcall SetRGB(TRGB *Value);
void __fastcall FontChanged(TObject *Sender);
virtual void __fastcall MouseLeave(TMessage &Msg);
virtual void __fastcall MouseEnter(TMessage &Msg);
virtual void __fastcall TextChanged(TMessage &Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(CM_MOUSELEAVE, TMessage, MouseLeave)
MESSAGE_HANDLER(CM_MOUSEENTER, TMessage, MouseEnter)
MESSAGE_HANDLER(CM_TEXTCHANGED, TMessage, TextChanged)
END_MESSAGE_MAP(TCustomControl)
public:
__fastcall TCGiButton(TComponent* Owner);
virtual __fastcall ~TCGiButton();
__published:
__property Caption;
__property OnClick;
__property OnKeyUp;
__property OnKeyPress;
__property OnKeyDown;
__property OnEnter;
__property OnExit;
__property TabOrder;
__property TabStop;
// nouvelles propriétés :
__property TFont *Font = {read=FFont, write=SetFont};
__property TRGB *CRGB = {read=FRGB, write=SetRGB};
};
//---------------------------------------------------------------------------
#endif
Comme vous pouvez le voir dans le fichier en-tête, ce composant possède 2 classes.
La classe TCGiButton qui en fait est le composant lui-même et la classe
TRGB qui contiendra nos sous-propriétés.
Pour les inclure dans le composant (TCGiButton) il suffit d'y déclarer une
propriété de ce type (TRGB). Le principe est exactement le même que pour
une propriété de type TFont.
Revenons à notre classe TRGB : elle nous servira à enregistrer les pas
d'incrémentation des couleurs primaires de notre dégradé de couleur.
Elle descendra de TPersistent car il faut qu'elle soit une classe de la VCL
afin d'avoir des propriétés et aussi pour qu'elle possède
la méthode héritée assign
utilisée dans la méthode TCGiButton::SetRGB.
(Pour qu'une classe puisse avoir des propriétés,
il faut au moins qu'elle descende de TObjet.)
Le pas des trois couleurs primaires sera sauvegardé dans les données
membres FposR, FposG, FposB. Comme nous voulons les avoir en tant que
propriétés on va créer leurs propriétés correspondantes et les méthodes "set"
qui vont avec.
Un problème se pose si l'on change une de ces propriétés, nous n'avons pas moyen
de le faire savoir. Il va donc nous falloir créer un événement personnalisé
qui nous indiquera que l'une de ces propriétés a changé.
C'est comme le Bitmap du TTapisseur du chapitre 4 qui génère un événement
OnChange quand il a changé.
Pour créer un événement personnalisé, rien de bien compliqué : ça fonctionne sur
le principe d'une donnée membre ordinaire qui aura le type TNotifyEvent.
Dans l'exemple nous la nommerons FOnChange. Nous déclarons aussi sa propriété
correspondante OnChange.
Passons maintenant à l'implémentation :
Fichier "CGiButton.cpp"
#include <vcl.h>
#pragma hdrstop
#include "CGiButton.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(TCGiButton *)
{
new TCGiButton(NULL);
}
//---------------------------------------------------------------------------
// Implémentation de la classe TRGB
//---------------------------------------------------------------------------
__fastcall TRGB::TRGB() : TPersistent()
{
FpasR = 1;
FpasG = 1;
FpasB = 1;
}
//---------------------------------------------------------------------------
void __fastcall TRGB::Change()
{
if(FOnChange) FOnChange(this);
}
//---------------------------------------------------------------------------
void __fastcall TRGB::SetpasR(int Value)
{
if (Value < 6 && Value >= 0) FpasR = Value;
Change();
}
//---------------------------------------------------------------------------
void __fastcall TRGB::SetpasG(int Value)
{
if (Value < 6 && Value >= 0) FpasG = Value;
Change();
}
//---------------------------------------------------------------------------
void __fastcall TRGB::SetpasB(int Value)
{
if (Value < 6 && Value >= 0) FpasB = Value;
Change();
}
//---------------------------------------------------------------------------
// Implémentation tu composant TCGiButton
//---------------------------------------------------------------------------
__fastcall TCGiButton::TCGiButton(TComponent* Owner)
: TCustomControl(Owner)
{
Width = 75;
Height = 25;
TabStop = true;
BtnEnfonce = false;
BtnEnfonceBis = false;
MouseSurBtn = false;
ALeFocus = false;
FFont = new TFont();
FFont->OnChange = FontChanged;
FRGB = new TRGB();
FRGB->OnChange = RGBChange;
DoubleBuffered = true;
}
//---------------------------------------------------------------------------
__fastcall TCGiButton::~TCGiButton()
{
delete FFont;
delete FRGB;
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::Paint()
{
RECT CoRect;
CoRect.left = 0;
CoRect.top = 0;
CoRect.right = Width;
CoRect.bottom = Height;
Canvas->Font = Font;
int h = Canvas->TextHeight(Caption);
int y = (Height-h)/2;
int l = Canvas->TextWidth(Caption);
int x = (Width-l)/2;
Canvas->Brush->Style = bsClear;
if (BtnEnfonce && MouseSurBtn || BtnEnfonceBis)
{
for (int y=0; y < Height; y++)
{
Canvas->MoveTo(0, y);
Canvas->LineTo(Width, y );
Canvas->Pen->Color= (TColor)(clWhite - (Height-y)*CRGB->pasR*0x10000 -
(Height-y)*CRGB->pasG*0x100 - (Height-y)*CRGB->pasB);
}
Canvas->TextOut(x+1,y+1,Caption);
}
else
{
for (int y=0; y < Height; y++)
{
Canvas->MoveTo(0, y);
Canvas->LineTo(Width, y );
Canvas->Pen->Color= (TColor)(clWhite - y*CRGB->pasR*0x10000 -
y*CRGB->pasG*0x100 - y*CRGB->pasB);
}
Canvas->TextOut(x,y,Caption);
}
Canvas->Pen->Color = clBlack;
Canvas->Brush->Color = clWhite;
if (ALeFocus)
{
TRect FocRect;
FocRect.Top = 4;
FocRect.Left = 4;
FocRect.Right = Width - 4;
FocRect.Bottom = Height - 4;
Canvas->DrawFocusRect(FocRect);
}
Canvas->Brush->Color = clBlack;
Canvas->FrameRect(CoRect);
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::MouseDown(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y)
{
BtnEnfonce = true;
MouseSurBtn = true;
SetFocus();
ALeFocus = true;
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::MouseUp(TMouseButton Button,
Classes::TShiftState Shift, int X, int Y)
{
BtnEnfonce = false;
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::KeyDown(Word &Key, Classes::TShiftState Shift)
{
TCustomControl::KeyDown(Key, Shift);
if (Key == 32)
{
BtnEnfonceBis = true;
Invalidate();
}
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::KeyUp(Word &Key, Classes::TShiftState Shift)
{
TCustomControl::KeyUp(Key, Shift);
if (Key == 32)
{
BtnEnfonceBis = false;
Click();
Invalidate();
}
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::DoEnter()
{
TCustomControl::DoEnter();
ALeFocus = true;
Invalidate(); // Pour afficher le rectangle de focus.
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::DoExit()
{
TCustomControl::DoExit();
ALeFocus = false;
Invalidate(); // Pour effacer le rectangle de focus.
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::SetFont(TFont *Value)
{
FFont->Assign(Value);
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::SetRGB(TRGB *Value)
{
FRGB->Assign(Value);
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::FontChanged(TObject *Sender)
{
Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::MouseLeave(TMessage &Msg)
{
if(BtnEnfonce)
{
MouseSurBtn = false;
Invalidate();
}
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::MouseEnter(TMessage &Msg)
{
if(BtnEnfonce)
{
MouseSurBtn = true;
Invalidate();
}
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::TextChanged(TMessage &Msg)
{
Invalidate(); // Pour afficher la modification du texte.
}
//---------------------------------------------------------------------------
void __fastcall TCGiButton::RGBChange(TObject *Sender)
{
int h ;
h = (CRGB->pasR > CRGB->pasG) ? CRGB->pasR : CRGB->pasG ;
h = (h > CRGB->pasB) ? h : CRGB->pasB;
if (h>0) if (Height > (255/h)) Height = 255/h;
Invalidate();
}
//---------------------------------------------------------------------------
bool __fastcall TCGiButton::CanResize(int &NewWidth, int &NewHeight)
{
int h ;
h = (CRGB->pasR > CRGB->pasG) ? CRGB->pasR : CRGB->pasG ;
h = (h > CRGB->pasB) ? h : CRGB->pasB;
if (h==0) if (NewHeight > (255/h)) NewHeight = 255/h;
return true;
}
//---------------------------------------------------------------------------
namespace Cgibutton
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TCGiButton)};
RegisterComponents("MesCompo", classes, 0);
}
}
La classe TCGIButton :
Dans le code de TCGIButton, rien de bien particulier.
Juste un petit commentaire sur la propriété DoubleBuffered que l'on met
à true dans son constructeur. Elle a pour effet que le contrôle est
dessiné sur un Bitmap avant d'être dessiné à l'écran, ceci afin
d'éviter les scintillements. Elle est héritée de TWinControl.
L'objet TRGB doit être créé par new car c'est un objet VCL.
On affecte une méthode à son événement OnChange selon le même principe
que pour l'objet TFont. Il sera utilisé dans la méthode Paint.
La classe TRGB :
Dans le constructeur de TRGB on met à 1 les trois données membres contenant
le pas des couleurs
primaires, afin que notre composant ait un dégradé de gris par défaut.
Dans leurs méthodes "set" on appelle la méthode Change
dans laquelle on déclenche l'événement personnalisé OnChange.
Toujours dans les méthodes "set" on limitera le pas à la valeur 5,
car si le pas est trop important on atteint trop
vite la limite haute de la couleur primaire, ce qui limite par conséquant
la hauteur du bouton. On ajustera d'ailleurs cette hauteur dans les méthodes
CanResize et RGBChange de TCGiButton.
Dans la définition des propriétés de couleurs primaires la valeur par défaut
doit être mentionnée "default = 1" sinon à l'exécution le composant ne tient pas compte
des valeurs préalablement initialisées dans l'inspecteur d'objet.
Vous pouvez maintenant utiliser ce composant et vous pouvez modifier
la couleur de son dégradé à l'aide des sous-propriétés de la propriété CRGB.
Bonne composition,
CGi
Avec la contribution d'Alacazam pour la relecture.
Télécharger les sources.
(Est inclus le fichier "Cgibutton.dcr" contient l'icône qui le représente
sur la palette de composants)
|