Developpez.com - C
X

Choisissez d'abord la catégorieensuite la rubrique :



Création d'un composant C++ Builder

Par CGi

Le 1 octobre 2003


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)





Sommaire

1 - Création d'un composant pas à pas.
2 - Création d'un composant graphique.
3 - Propriété de type TStrings.
4 - Propriété de type TBitmap et contrôle du Owner.
5 - Propriété de type enum.
6 - Contrôle fenêtré.
7 - Evénement personnalisé et sous-propriétés.
8 - Composant pouvant interagir avec ses semblables.
9 - Ressources dans un composant.
10 - Boîte de dialogue dans un composant.
11 - Composant conteneur.
12 - Les éditeurs de composants. Nouveau



C/C++
  Les pointeurs du C/C++.   Les listes chaînées.             Liste simple.             Liste triée.             Liste double.   Les arbres.   Les tas.   Le C orienté objets ?

  1 - La fenêtre principale.   2 - Contrôles et messages.   3 - Les commandes.   4 - Dialogue std.   5 - Contexte de périph.   6 - Dessiner.   7 - Les ressources.   8 - Dialogue perso.   9 - Dialogue comm.   10 - Les accélérateurs.

Assembleur
  Assembleur sous Visual C++.

C++ BUILDER
  Trucs et astuces.   Composant.   TRichEdit.   TDrawGrid.   Application MDI.   TThread.   wxWidgets.   Style Win XP.

  Première application.   Construire un menu.   Dessiner.   Sisers, Timers...   Dialogues standards.   Dialogues perso.

DotNet
  Composant C# Builder.   Contrôle WinForm.   Application MDI.

Java
  Applet java.





Copyright 2002-2016 CGi - Tous droits réservés CGi. Toutes reproduction, utilisation ou diffusion de ce document par quelque moyen que ce soit autre que pour un usage personnel doit faire l'objet d'une autorisation écrite de la part de l'auteur, propriétaire des droits intellectuels.
Les codes sources de ce document sont fournis en l'état. L'utilisateur les utilise à ses risques et périls, sans garantie d'aucune sorte de la part de l'auteur. L'auteur n'est responsable d'aucun dommage subi par l'utilisateur pouvant résulter de l'utilisation ou de la distribution des codes sources de ce document.
De la même façon, l'auteur n'est en aucun cas responsable d'une quelconque perte de revenus ou de profits, ou de données, ou de tous dommages directs ou indirects, susceptibles de survenir du fait de l'utilisation des codes sources de ce document, quand bien même l'auteur aurait été averti de la possibilité de tels dommages. L'utilisation des codes sources de ce document vaut acceptation par l'utilisateur des termes de la licence ci-dessus.

Contacter le responsable de la rubrique C