menu
  Home  ==>  articles  ==>  prog_objet_composants  ==>  ecriture_de_composant   

Ecriture de Composant Delphi - John COLIBRI.

  • résumé : exemple complet de création de composant simple, avec propriétés, éditeur de propriété, éditeur de composant
  • mots clé : Ecriture de composant - Package - DesignIde - DesignIntf - DesignEditors
  • logiciel utilisé : Windows XP personnel, Delphi 6.0
  • matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
  • champ d'application : Delphi 5, Delphi 6, Delphi 7, Delphi 2005, Delphi 2006, Delphi 2007 sur Windows
  • niveau : développeur Delphi
  • plan :


1 - Ecriture de Composant Delphi

Nous allons présenter ici les étapes nécessaires pour créer un composant Delphi simple.

Notre composant permet de charger un fichier ASCII, et rechercher un mot dans ce texte, en visualisant en rouge les occurences du mot cherché.

Ce n'est pas le composant le plus révolutionnaire qui soit, et nous n'allons pas utiliser les algorithmes les plus pointus et les possibilités les plus subtiles de Delphi.

En réalité, notre objectif est tout autre: nous allons essentiellement nous attacher à montrer les manipulations à employer et l'organisation du développement d'un composant non trivial.




2 - Création d'un composant Delphi

2.1 - Les Etapes

Nous allons passer par trois étapes:
  • tout d'abord nous utilisons une application Delphi usuelle pour mettre en place la fonctionnalité de base. Dans notre cas, charger un fichier ASCII et effectuer des recherches dans une String
  • ensuite nous séparons les fonctionnalités visées dans dans une CLASS d'une UNIT séparée. L'objet correspondant sera créé par le projet de test dans un événement OnClick
  • finalement nous utilisons un Package qui contiendra notre composant, et ses fichiers annexes (éditeurs de propriétés et de composant)


2.2 - La fonctionnalité de base

Nous avions déjà présenté dans l'article Find Memo la technique de recherche dans un tMemo. Pour notre composant, nous souhaitions ajouter la couleur pour les mots correspondant au texte cherché. Nous avons donc tout naturellement utilisé un tRichEdit, qui permet de modifier la couleur (ainsi que le style, la police etc) de certaines parties du texte.

Notre tForm principale comporte pratiquement tous les éléments du futur composant: un tRichEdit pour le texte, un tEdit pour la chaîne à rechercher, des boutons pour chercher le suivant etc.

Le click d'un bouton charge un texte d'essai (le fichier .PAS):

var m_text_indexm_find_lengthm_text_lengthInteger;
    m_textm_find_stringString;

procedure TForm1.load_Click(SenderTObject);
  begin
    RichEdit1.Lines.LoadFromFile('..\01_search_memo_design\u_find_memo_trial.pas');

    m_find_string:= search_edit_.Text;
    m_find_length:= Length(m_find_string);
    m_text:= RichEdit1.Text;
    m_text_length:= Length(m_text);

    m_text_index:= 1;
  end// load_Click

Et le clic sur le tButton de recherche ">" effectue le coloriage de toutes les occurences du texte cherché. La recherche est effectuée par un algorithme très naif (recherche du premier caractère puis vérification des suivants) avec création d'un sélection (tRichEdit.SelStart et tRichEdit.SelLength) puis modification des attributs de cette sélection (tRichEdit.SelAttributes):

procedure TForm1.find_next_Click(SenderTObject);

  function f_find_rest(p_indexInteger): Boolean;
    var l_word_indexInteger;
    begin
      l_word_index:= 2;
      while (l_word_index<= m_find_length)
          and (p_indexl_word_index<= m_text_length)
          and (m_find_string[l_word_index]= m_text[p_indexl_word_index- 1]) do
        inc(l_word_index);

      Result:= l_word_indexLength(m_find_string);
    end// f_find_rest

  var l_first_characterChar;

  begin // find_next_occurence
    Inc(m_text_indexm_find_length);

    l_first_character:= m_find_string[1];

    while m_text_indexm_text_length do
    begin
      if m_text[m_text_index]= l_first_character
        then
          if f_find_rest(m_text_index)
            then begin
                RichEdit1.SelStart:= m_text_index- 1;
                RichEdit1.SelLength:= m_find_length;
                RichEdit1.SelAttributes.Color:= clRed;
                RichEdit1.SelAttributes.Style:= [fsBold];
                // Break
              end;

      inc(m_text_index);
    end// while pv_index
  end// find_next_occurence



Voici une vue de notre application p_01_search_memo_design:

image



Cette étape préliminaire nous a permis

  • de mettre en place les parties de notre composants
  • de tester certains fonctionnalités (la recherche, le coloriage)
tout en ayant encore tous les moyens de mise au point à notre disposition (affichages, logs, essais partiels sur d'autres tButton etc)



2.3 - La séparation dans une CLASS

Nous allons à présent encapsuler nos fonctionnalités dans une CLASS. Pour tester cette CLASS nous utiliserons un projet qui placera une instance de notre CLASSe sur la tForm en créant l'objet dans un événement OnClick.



Notre CLASS est définie par:

tSearchMemoClass(tPanel)
               Private
                 m_c_paneltPanel;
                   m_c_previous_buttontButton;
                   m_c_search_edittEdit;
                   m_c_next_buttontButton;
                   m_c_line_number_labeltLabel;
                 m_c_richedittRichEdit;

                 m_find_stringString;
                 m_find_lengthInteger;

                 m_textString;
                 m_text_indexInteger;
                 m_text_lengthInteger;

                 _m_did_hit_controlBoolean;

                 procedure set_panel_color(p_colorInteger);
                 function f_memo_line_of_index(p_text_indexInteger): Integer;
                 procedure set_memo_top_line_index(p_top_line_indexInteger);
                 procedure find_next_occurence(p_colorInteger);

                 procedure handle_richedit_keypress(p_c_senderTObject;
                     var pv_keyChar);
                 procedure handle_memo_and_find_edit_keydown(p_c_senderTObject;
                     var pv_scan_codeWordp_shift_stateTShiftState);

                 procedure handle_next_click(p_c_sendertObject);

                 procedure set_text(p_textString);
                 procedure set_search_text(p_search_textString);
               Public
                 constructor Create(p_c_ownertComponent); Override;
                 Destructor DestroyOverride;

                 Property TextString write set_text;
                 Property SearchTextString write set_search_text;
             end// tStearchMemo



Les nouveautés sont:

  • set_panel_color: colorie le tPanel (rouge lorsqu'il n'y a plus d'occurences)
  • f_memo_line_of_index: calcule le numéro de ligne en fonction de l'indice
  • set_memo_top_line_index: assure le positionnement de la chaîne trouvée en haut du tRichEdit
  • handle_richedit_keypress: gestion de la frappe clavier pour détecter une nouvelle recherche
  • handle_memo_and_find_edit_keydown et _m_did_hit_control: détection de la touche Ctrl pour la répétition de la recherche par frappe de Ctrl-L
  • handle_next_click: traitement du click sur le bouton "suivant"
  • Text (et set_text): définition du texte de base
  • SearchText (et set_search_text): définition de la chaîne à rechercher


La partie la plus intéressante est la création des parties de notre CLASSe (partiel):

constructor tSearchMemo.Create(p_c_ownertComponent);

  procedure create_panel;
    const k_button_height= 25;
    var l_xInteger;
    begin
      m_c_panel:= tPanel.Create(Self);

      with m_c_panel do
      begin
        Parent:= Self;
        Height:= k_button_height+ 2;
        Align:= alTop;
      end// with m_c_panel

      l_x:= 15;

      m_c_previous_button:= tButton.Create(Self);
      with m_c_previous_button do
      begin
        Parent:= m_c_panel;
        Caption:= '<';
        SetBounds(l_x, 2, 20, k_button_height);

        Inc(l_xm_c_previous_button.Width+ 5);
      end// with m_c_previous_button

      // -- ...ooo...
    end// create_panel

  procedure create_richedit;
    begin
      m_c_richedit:= trichedit.Create(Self);
      with m_c_richedit do
      begin
        Parent:= Self;
        Align:= alClient;
        ScrollBars:= ssVertical;

        // "has no parent window"  HideSelection:= False;
      end// with m_c_richedit
    end// create_richedit

  begin // Create
    Inherited Create(p_c_owner);

    Align:= alClient;

    create_panel;
    create_richedit;
  end// Create

et:

  • la création de chaque composant se fait en fournissant le "propriétaire" qui assurera sa destruction. Pour tSearchMemo, ce sera le tPanel de la Forme principale, et pour tous les autres sous-composants, c'est tSearchMemo le propriétaire
  • l'affichage dans une fenêtre fille (avec clipping) est spécifiée par Parent, qui est tSearchMemo pour le tPanel et le tRichEdit, et le tPanel pour les composants placés sur ce tPanel


Le projet principal crée un objet de type tSearchText:

var g_c_search_memotSearchMemoNil;

procedure TForm1.create_search_memo_Click(SenderTObject);
  begin
    g_c_search_memo:= tSearchMemo.Create(search_memo_panel_);

    g_c_search_memo.Parent:= search_memo_panel_;
  end// create_search_memo_Click

et le chargement du texte est effectué par:

procedure TForm1.load_Click(SenderTObject);
  begin
    with tStringList.Create do
    begin
      LoadFromFile('..\02_search_memo_test\u_c_search_memo_test.pas');
      g_c_search_memo.Text:= Text;
      g_c_search_memo.SearchText:= 'begin';
      Free;
    end;
  end// load_Click



Voici l'image de l'application p_02_search_memo_test:

image



2.4 - Notez que:

  • tout n'a pas encore été mis en place. En particulier, les éléments que nous avons placés dans les éditeurs de propriétés ne sont pas encore présent
  • tout ne fonctionne pas comme pour un véritable composant. Par exemple:
    • nous n'avons pas pu utiliser tRichEdit.HideSelection
    • notre composant descent actuellement de tPanel (d'où la bordure un peu trop épaisse) alors que le futur composant descendra de tWinControl
Mais cette étape nous a permis de mettre en place les premières propriétés, des fonctionnalités plus détaillées, tout en conservant nos précieux outils de mise au point (affichage et log)



2.5 - La création du composant

2.5.1 - Un composant simple

Pour placer un composant sur la Palette, et l'utiliser dans nos applications, il faut:
  • placer le composant dans une UNIT
  • cette UNIT doit contenir une procedure Register, qui indique dans quelle page de la Palette le composant doit être placé:

    procedure Register;
      begin
        RegisterComponents('my_page', [t_my_component]);
      end;

    Notez que Register doit avoir un R majuscule (la mécanique d'enregistrement étant sensible à la casse pour cette procédure)

  • incorporer le code de l'UNITé dans une .DLL ayant un format particulier, appelée Package.

    Nous avons déjà présenté un article sur les Packages Delphi. Mais nous détaillerons à nouveau ici les étapes utilisées pour la création de composants.

    Notre Package sera créé en utilisant l'Editeur de Package. Et cet Editeur permet

    • de compiler le Package
    • d'exécuter la procédure Register, ce qui en fait place le composant sur la Palette


Schématiquement, cela peu se présenter ainsi:

image



2.5.2 - Création du Package

Nous commençons par créer un Package vide:
   sélectionnez "File | New | Other"
   Delphi présente plusieurs fichiers:

image

   cliquez "Package" (encerclé en rouge)
   Delphi crée un nouveau Package

image

   sélectionnez "File | Save as" et renommez le Package pk_search_memo_simple
   à titre de vérification, vous pouvez compiler en cliquant "compile"
   le répertoire contient (en Delphi 6) les fichiers .DPK, .RES, .DCU, .DOF et .CFG. Les fichiers .DCP et .BPL sont dans le répertoire
  C:\Program Files\Borland\Delphi6\Projects\Bpl
et sont ainsi visible par l'IDE Delphi
   chargez dans Delphi l'UNITé qui contient tSearchMemo, ajoutez la procédure Register:

unit u_c_search_memo_simple;
  interface
    uses
        Classes // tShiftState
        , Graphics
        , Controls // tMouseButton
        , StdCtrls // tEdit
        , ComCtrls // tRichEdit
        , ExtCtrls // tPanel
        ;

     type tSearchMemo_simple=
              Class(tPanel)
                Private
                  m_c_paneltPanel;

                  // -- ...ooo...
                Public
                  constructor Create(p_c_ownertComponent); Override;
                  Destructor DestroyOverride;

                Published
                  Property TextString write set_text;
                  Property SearchTextString write set_search_text;
              end// tSearchMemo_simple

   procedure Register;

  implementation
    Uses WindowsMessagesSysUtils
       ;

    procedure Register;
      begin
        RegisterComponents('ip', [tSearchMemo_simple]);
      end// Register

    // -- tSearchMemo_simple

    constructor tSearchMemo_simple.Create(p_c_ownertComponent);

    // -- ...ooo...

Puis sauvegardez sous u_c_search_memo_simple.pas (dans le même répertoire que le .DPK)

   sélectionnez l'Editeur de Package, vérifiez que c'est bien Contains qui est sélectionné, et cliquez sur "Add"
   l'onglet de chargement de fichier est affiché:

image

   cliquez "Browse"
   un dialogue de chargement est affiché
   sélectionnez u_c_search_memo_simple.pas et cliquez "Ouvrir", "Ok"
   l'unité du composant est intégrée dans les fichiers contenus dans le Package:

image

   cliquez sur "Compile"
   le compilateur nous signale que nous devons ajouter certaines unités au Package:

image

   cliquez "Ok"
   vcl.dcp a été ajouté à la clause Requires:

image

   cliquez "Compile"
   cliquez "Install"
   un dialogue nous informe des composants installés:

image

   cliquez "Ok"


Nous pouvons vérifier que le composant a été installé:
  • la Palette contient une nouvelle page "ip" qui contient notre composant tSearchMemo_simple (l'icône est celle d'un tPanel, car c'est actuellement l'ancêtre de notre composant):

    image

    Nous pouvons donc déposer ce composant dans n'importe quelle tForm.

  • et si nous affichons "Component | Install Packages"

    image

    Delphi ouvre un dialogue de gestion des composants, qui contient bien notre Package. Si nous sélectionnons cette ligne et cliquons sur "Components", nous pouvons afficher tous les composants de ce Package:

    image

  • finalement nous pouvons aussi utiliser l'outil de réorganisation de la Palette, en sélectionnant "Component | Configure Palette", puis notre page "ip":

    image



Pour résumer les manipulations:
  • nous créons une UNITé qui contient
    • la CLASS du composant
    • une procédure Register
  • nous incorporons l'UNITé à un Package, qui est compilé et installé


Pour supprimer le composant de la Palette, nous pouvons utiliser soit l'outil de configuration de la Palette, soit le dialogue "Install Component" présentés ci-dessus



2.6 - Gestion du Composant final

2.6.1 - Unité d'enregistrement

Il est fréquent de regrouper plusieurs UNITés contenant 0 ou plusieurs composants dans un Package. Dans ce cas, il est recommandé de regrouper tous les enregistrements dans une UNITé qui contiendra uniquement la procédure Register.

Cette UNITé contient un nom contenant "reg" ou "register", et est simplement ajoutée au Package en utilisant le bouton "Add" de l'Editeur de Package



2.6.2 - Editeurs de Propriété et Editeur de Composant

Delphi contient déjà de nombreux Editeurs de Propriétés: si nous ajoutons une PROPERTY de type Integer ou String, l'Inspecteur d'Objet saura déjà comment afficher et modifier la valeur de cette propriété.

Si nous créons une propriété pour laquelle il n'existe pas d'Editeur pré-défini, nous pouvons en créer un, ce que nous ferons ci-dessous.

Un problème d'organisation des fichiers se pose alors. En effet, depuis Delphi 6, Borland interdit le déploiement de fichiers utilisant des Editeurs de Package. Cette mesure visait essentiellement à éviter la création trop facile de clones de Delphi. Et par conséquent les UNITés qui seront dans l'.EXE final ne peuvent pas dans leur clauses USES contenir des unités telles que DesignIntf ou DesignEditors.

La solution est simple: il suffit de

  • placer les Editeurs dans des UNITés séparées dont la clause USES contient DesignIntf ou DesignEditors
  • ces Editeurs sont enregistrés par une UNITé d'enregistrement séparée
  • les UNITés contenant les composants sont ainsi vierge de toute référence aux éditeurs
Ceci peut être représenté par le schéma suivant:

image

et:

  • les éléments faisant appels aux UNITés "design" (au trait rouge sur notre schéma) sont utilisés pour placer le composant et ses éditeurs sur la Palette et dans l'Inspecteur d'Objet
  • les UNITés contenant nos composant (en trait noir) peuvent être incorporées à n'importe quel .EXE livrable au clients


2.7 - Clause Requires des Packages

Dans la clause Requires d'un Package se trouvent les autres Packages nécessaires à la compilation de notre Package.

Nous avons déjà vu plus haut que le Compilateur analysait automatiquement les UNITés dont notre composant a besoin, et qu'il nous proposait d'ajouter les Packages nécessaires.

Les noms placés dans Requires sont des fichiers .DCP (Delphi Component Package, qui est une .DLL Windows).

Comme les formats internes des fichiers binaires change en général entre chaque version Delphi, il faut modifier manuellement le nom des fichiers de la clause Requires et recompiler.

Depuis Delphi 6, cette recompilation a été simplifiée:

  • les .DCP de Requires contient des noms génériques
  • nous pouvons demander à l'Editeur de Package d'ajouter un suffixe de version pour trouver le fichier .BPL.

    Pour cela
       ouvrez l'Editeur de Package, sélectionnez "Options | Description"
       Delphi présente les paramètres du Package
       tapez la version de Delphi, "60" dans notre cas:

    image

    Dans ces conditions:

    • la clause Requires contient le nom des .DCP sans le suffixe. Par exemple
        designide.dcp
    • le Package qui est utilisé sera celui correspondant à la version de Delphi. Dans notre cas:
        designide60.bpl
    Notez d'ailleurs que dans notre cas, "designide.dcp" n'a pas été suggéré automatiquement par le Compilateur: il a fallu l'ajouter nous-mêmes en utilisant le bouton "Add" (ou en tapant la ligne dans le .DPK)


Finalement il faut s'assurer que le Package est bien un "Package de Conception". Les autres types de Packages dits "Package d'exécution" servent à exclure du fichier .EXE le code correspondant au Package, ce qui n'est pas notre objectif ici.

Pour forcer un Package à être un Package de conception, séléctionnez "Editeur de Package | Options | Description" et cliquez "Design Time Only" ou "Design Time and Runtime:

image



Notez que:

  • en général nous incluons dans le nom du Package "design" ou "dsgn" pour que le type du Package soit évident
  • si vous souhaitiez en plus que le code du Package puisse être séparé du fichier .Exe, il faut créer un second Package (dont le nom contiendrait "run"), et qui ne devra contenir AUCUNE UNITé avec DesignIntf ou DesignEditors.


2.7.1 - Utilisation d'un Groupe Delphi

Finalement, pour pouvoir facilement passer de la compilation du composant à son test dans un projet, il est souhaitable d'utiliser un Groupe Delphi.

Pour cela, il suffit

  • d'utiliser "File | New | Other" et dans l'onglet "New", "Project Group"
  • d'ajouter l'Editeur de Package et notre Projet au groupe


2.8 - Le Composant tSearchMemo

2.8.1 - Création du Groupe Delphi

Pour créer notre groupe:
   créez un répertoire qui contiendra essentiellement notre projet de démonstration, et nommez-le, par exemple, "05_search_memo_demo\"
   sélectionnez "File | New | Other" et dans l'onglet "New" sélectionnez "Project Group"

image

   Delphi crée un nouveau groupe et l'affiche dans le gestionnaire de projet

   créez un nouveau projet par "File | New Application" et sauvegardez le dans le nouveau répertoire en le renommant, par example, p_05_search_memo_demo
   le projet est ajouté au groupe

image

2.8.2 - Le Package

Commençons par créer notre Package
   créez un nouveau répertoire qui contiendra tous les fichiers du composant, par exemple "04_component\"
   sélectionnez "File | New | Other"
   Delphi présente les nouveaux fichiers
   cliquez "Package"
   Delphi crée un nouveau Package
   sélectionnez "File | Save as" et renommez le Package pk_search_memo_design dans le répertoire du composant
   dans l'Editeur de Package, sélectionnez "Options | Description"
   Delphi présente les paramètres du Package
   tapez la version de Delphi, "60" dans notre cas et vérifiez que soit "DesignTimeOnly" soit "DesignTime and Runtime" sont bien sélectionnés
   cliquez "Ok"
Puis
   sélectionnez le gestionnaire de projet, sélectionnez le groupe, et par "clic droit | ajouter projet existant", ajoutez le Package au groupe

image

Par la suite:
  • le projet pourra être compilé en sélectionnant le projet, puis "clic droit | compile"
  • le Package pourra être compilé en sélectionnant le package, puis "click droit | compile", ou "install" pour installer le composant

    image

Pour compiler, vous pouvez continuer à utiliser le menu principal Delphi et l'Editeur de Package si vous le préférez. Vous pouvez aussi docker l'Editeur de Package dans la même fenêtre que le gestionnaire de projet.



2.8.3 - Création de l'Unité d'Enregistrement:

Commençons par extraire la procédure Register de notre UNITé de composant en la plaçant dans une unité u_register_search_memo:
   créez une UNITé u_register_search_memo et placez-y la procédure Register
   ajoutez l'UNITé sans Register au Package, en utilisant l'Editeur de Package ("Add | Browse "...)
Nous avons aussi renommé la CLASSE, modifié son ancêtre en remplaçant tPanel par tWinControl, et renommé l'UNITé u_c_search_memo

type TSearchMemoClass(tWinControl)
                    Private
                      m_c_paneltPanel;
                      // -- ...ooo... 

Ceci ne changer rien au reste de la CLASSe

   ajoutez l'UNITé d'enregistrement an Package
   compilez et installez le Package
   le nouveau composant est installé sur la Palette


2.8.4 - Editeur de Propriété

Supposons que nous souhaitions distinguer l'affichage de l'occurence d'un mot exact ou d'un mot qui contient la chaîne recherchée. Pour cela, il faut définir les caractères qui délimitent un mot, ou les caractères qui font partie d'un mot. Par exemple,
  • nous recherchons "text"
  • nous considérons comme des mots les suites de lettres
  • dans le texte suivant

      Property text: String write settext;

    la première occurence " text:" est considérée comme un mot isolé, alors que la seconde "settext" fait partie d'un mot

Il nous faut par conséquent définir un SET OF CHAR qui contient l'ensemble des caractères d'un mot. Pour définir les lettres, notre variable Pascal sera, par exemple

Var g_word_character_setSet Of Char;

  g_word_character_set:= ['a'..'z''A'..'Z'];

Nous allons créer un Editeur qui accepte la syntaxe simplifiée suivante:

 
    a..z A..Z _ 0..9
 

donc en gros la même chose que Pascal, moins les guillemets et les virgules (et avec un problème pour définir l'espace, que nous avons glissé sous le tapis dans notre exemple simple)

Pour faciliter la saisie, nous allons proposer à l'utilisateur plusieurs combinaisons possibles. Par exemple:

 
    A..Z
    a..z
    a..z A..Z
    a..z A..Z 0..9
    a..z A..Z 0..9 _
 

Notre éditeur

  • sera un descendant de tStringProperty
  • utilisera l'attribut paValueList, qui permet d'afficher nos exemples dans une ComboBox DropDown. Cette valeur sera fournie par la fonction GetAttributes
  • les valeurs proposées sont ajoutées par la procédure GetValues
Voici le texte de notre éditeur:

unit u_c_set_of_char_property_editor;
  interface
    uses ClassesDesignIntfDesignEditors;

    type c_set_of_char_property_editor=
            class(TStringProperty)
              public
                function GetAttributesTPropertyAttributesoverride;
                procedure GetValues(ProcTGetStrProc); override;
            end// c_set_of_char_property_editor

  implementation

    function c_set_of_char_property_editor.GetAttributesTPropertyAttributes;
      begin
        Result:= [paValueList];
      end// GetAttributes

    procedure c_set_of_char_property_editor.GetValues(ProcTGetStrProc);
      begin
        Proc('A..Z');
        Proc('a..z');
        Proc('a..z A..Z');
        Proc('a..z A..Z 0..9');
        Proc('a..z A..Z 0..9 _');
      end// GetValues

    end.



Par conséquent:
   tapez cet éditeur dans une unité
   ajoutez l'unité au Package


Notre CLASSe comportera une propriété correspondant à la chaîne de l'Editeur, et lorsque l'utilisateur tapera la valeur dans l'Inspecteur d'Objet, cette chaîne sera fournie à la méthode set_word_character_string qui analysera la chaîne et construira l'ensemble de caractères correspondant.

Voici les parties du composant concernées par cet éditeur:

unit u_c_search_memo;
  interface
    uses // ...ooo...

     type TSearchMemoClass(tWinControl)
                         Private
                           m_word_character_stringString;
                           m_word_character_setSet Of Char;

                           procedure set_word_character_string(p_word_character_stringString);
                           // -- ...ooo...

                         Published
                           Property WordCharactersString 
                              read m_word_character_string 
                              write set_word_character_string;
                       end// tStearchMemo

  implementation
    Uses WindowsMessagesSysUtils
       ;

    // -- TSearchMemo

    // -- ...ooo...

    procedure TSearchMemo.set_word_character_string(p_word_character_stringString);
      begin
        m_word_character_string:= p_word_character_string;
        m_word_character_set:= f_string_to_set_of_char(m_word_character_string);
      end// set_word_character_string

    // -- ...ooo...

  end.

La fonction f_string_to_set_of_char qui convertit la chaîne en un Set of Char est dans le .Zip téléchargeable, et cet ensemble de caractères est utilisé pour détester si les caractères qui précèdent et suivent une occurence sont ou non dans cet ensemble.



2.8.5 - Un Editeur de Composant

Nous allons aussi ajouter un Editeur de composant. Il sera appelé chaque fois que l'utilisateur effectue un clic droit sur le composant ou clique deux fois sur ce composant.

Nous avons choisi un simple dialogue de recherche, mais nous aurions pu ajouter au dialogue d'autres paramètres (recherche vers l'avant, vers l'arrière, en tenant compte de la casse ou non). En fait, c'est la nature du composant et de ses propriétés qui détermine si un éditeur de composant est utile ou non. Notre exemple est un peu artificiel, mais montre comment procéder.



Notre Editeur de Composant est donc un dialogue comportant une tDirectoryListBox, une tFileListBox et deux boutons "Ok" et "Cancel". Le choix d'un fichier, et le clic sur "Ok" initialise les propriétés Path et FileName de notre composant, et charge le fichier dans le tRichEdit.



Voici les propriétés ajoutées à notre composant:

TSearchMemoClass(tWinControl)
               Private
                 // -- ...ooo...

                 m_pathm_file_nameString;

                Public
                  procedure load_from_file(p_full_file_nameString);
                Published
                  Property PathString read m_path write m_path;
                  Property FileNameString
                      read m_file_name write m_file_name;
              end// tStearchMemo

et voici le texte de l'éditeur de composant:

unit u_f_search_memo_editor;
  interface
    uses WindowsMessagesSysUtilsVariantsClassesGraphics
        ControlsFormsDialogsFileCtrlStdCtrlsExtCtrls
        , DesignEditors
        , u_c_search_memo
        ;

    type c_search_memo_editor
            class(TComponentEditor)
                 function GetVerbCountIntegeroverride;
                 function GetVerb(IndexInteger): stringoverride;
                 procedure ExecuteVerb(IndexInteger); override;
            end// c_search_memo_editor

         Tsearch_memo_dialog=
             class(TForm)
                 Panel1TPanel;
                 DirectoryListBox1TDirectoryListBox;
                 FileListBox1TFileListBox;
                 Splitter1TSplitter;
                 Panel2TPanel;
                 OkTButton;
                 CancelTButton;
               private
               public
             end// Tsearch_memo

  implementation

    {$R *.dfm}

    // -- c_search_memo_editor

    function c_search_memo_editor.GetVerbCountInteger;
      begin
        Result:= 1;
      end// GetVerbCount

    function c_search_memo_editor.GetVerb(IndexInteger): string;
      begin
        case Index of
          0: Result:= 'Load Search Memo...';
        end;
      end// GetVerb

    procedure c_search_memo_editor.ExecuteVerb(IndexInteger);

      procedure display_search_menu_dialog;
        var l_c_search_memo_dialogTsearch_memo_dialog;
            l_c_search_memoTSearchMemo;
            l_full_file_nameString;
        begin
          l_c_search_memo_dialog:= Tsearch_memo_dialog.Create(Application);

          try
            l_c_search_memo:= Component as tSearchMemo;

            with l_c_search_memo_dialog do
            begin
              DirectoryListBox1.Directory:= l_c_search_memo.Path;
              FileListBox1.FileName:= l_c_search_memo.FileName;

              if ShowModalmrOK
                then begin
                    l_c_search_memo.Text:= 'after dialog';

                    with DirectoryListBox1 do
                      l_c_search_memo.Path:= GetItemPath(ItemIndex)+ '\';

                    with FileListBox1 do
                      if ItemIndex>= 0
                        then begin
                            l_c_search_memo.FileName:= Items[ItemIndex];
                            l_c_search_memo.Text:= 'after 'Items[ItemIndex];

                            l_full_file_name:= l_c_search_memo.FileName
                                + l_c_search_memo.Text;

                            if FileExists(l_full_file_name)
                              then l_c_search_memo.LoadFromFile(l_full_file_name);
                          end;
                    Self.Designer.Modified;
                  end;
            end// with l_c_search_memo_dialog do

          finally
            l_c_search_memo_dialog.Free;
          end// try finally
        end// display_search_menu_dialog

      begin // ExecuteVerb
        case Index of
          0: display_search_menu_dialog;
        end// case Index
      end// ExecuteVerb

    end.

et:

  • la clause USES importe l'unité du composant, car il faudra pouvoir accéder à l'instance du composant qui sera sur la tForm
  • la CLASSe qui contient le dialogue est purement statique (aucune méthode)
  • la CLASSe de l'éditeur comporte 3 méthode:
    • GetVerbCount qui indique combien de sous-menu il faut ajouter au menu contextuel
    • GetVerb qui fournit le texte du sous-menu
    • ExecuteVerb qui récupère les valeurs du dialogue et transfert ces valeurs dans les propriétés du composant. Ce transfert est effectué en surtypant la propriété Component de l'éditeur de propriété


2.8.6 - Mise à jour de Register

Il faut à présent enregistrer nos deux éditeurs:
   ajoutez aux USES de u_c_register_search_memo les UNITés contenant les éditeurs
   enregistrez les deux éditeurs dans la procédure Register
Voici le texte complet de l'enregistrement:

unit u_register_search_memo;
  interface

    procedure Register;

  implementation
    uses Classes
        , u_c_search_memo
        , DesignIntfDesignEditors
        , u_c_set_of_char_property_editor
        , u_f_search_memo_editor
        ;

    procedure Register;
      begin
        RegisterComponents('ip', [tSearchMemo]);
        RegisterPropertyEditor(TypeInfo(String),
            TSearchMemo'WordCharacters',
            c_set_of_char_property_editor);
        RegisterComponentEditor(TSearchMemoc_search_memo_editor);
      end//  Register

    end.



Il faut mettre à jour le Package:
   sélectionnez l'Editeur de Package
   sélectionnez Contains, puis "clic droit | Add" et ajoutez u_f_search_memo_editor
   sélectionnez Requires, puis "clic droit | Add" et tapez
    DesignIde.Dcp
   compilez en cliquant "Compile"


Pour voir l'éditeur en action
   sélectionnez la tForm
   ajoutez un tPanel qui contiendra notre tSearchMemo (nous avons aussi mis sur notre tForm un tPanel à gauche et un tMemo en bas)
   sélectionnez dans l'onglet "ip" le tSearchMemo et posez le sur le tPanel
   le composant est affiché:

image

   le clic droit souris sur le composant affiche le menu contextuel avec au début notre sous-menu "Load Search Memo..."
   cliquez ce sous-menu (ou cliquez deux fois sur SearchMenu1 directement)
   le dialogue de chargement apparaît:

   sélectionnez un fichier ASCII, par exemple u_c_search_memo et cliquez "Ok"
   le texte de l'unité est chargé:

image



2.8.7 - Le composant à l'exécution

Et voici une image du composant montrant la différence entre une recherche exacte ou une chaîne inclue:

image




3 - Quelques Commentaires

Dans cet article, nous avons essayé de présenter les différentes étapes de la création d'un composant. Et nous nous sommes placés dans le cas le plus général d'un composant avec éditeur de propriété et de composant. Ceci a permi de proposer une structure des différentes unités et une présentation des outils pour gérer ces unités.

Quelques remarques:

  • Pour un composant sans éditeur de propriété, c'est relativement simple.

    Si vous décidez d'ajouter un éditeur de propriété, les restrictions de déploiement Delphi nécessitent une séparation des UNITés.

    Vous rencontrerez d'ailleurs cet obstacle des Package pour la conception et pour l'exécution chaque fois que vous essayez d'installer des librairies récupérées sur le Web ou achetées à des fournisseurs. Dans le meilleur des cas, vous trouverez plusieurs séries de Packages correspondant aux différentes versions de Delphi, ainsi qu'une séparation entre les composants sensibles aux données ou non. Et dans le pire des cas, vous aurez des sources pour une version de Delphi qui n'est pas la vôtre, et il faudra alors adapter les Packages à votre version

  • Sur ce site, nous avons pour tous les projets proposés surtout utilisé des CLASSes, et les objets correspondants ont été créés dynamiquement par l'utilisateur de la CLASSe (en général le projet principal). Cette technique offre les avantages suivants:
    • nous évitons l'installation sur la Palette (qui doit être effectuée à chaque nouvelle version Delphi, ou sur chaque nouvelle machine)
    • la CLASSe fait partie du projet, et peut être modifiée à volonté, en communiquant directement avec les autres UNITés.
    Les inconvénients sont les suivants:
    • la CLASSe n'a pas besoin d'être aussi bien encapsulée, car elle communique plus facilement avec le projet. Une partie des fonctionnnalités peut être assurée par les autres CLASSes ou par le projet principal
    • de ce fait, la réutilisation de la CLASSe sera plus laborieuse
    Si nous créons un composant à partir d'une CLASSe, il faut donc s'assurer qu'elle est complètement autonome et cohérente. Prenons notre exemple: pour charger le texte du tRichEdit, nous avons commencé par ajouter la propriété Text. Puis nous avons ajouté les propriétés Path et FileName avec la procédure load_from_file. Mais il faudrait que notre implémentation (qui n'est pas complète) soit plus cohérente: si le composant a des propriétés de nom de fichier, il faudrait pouvoir les utiliser à l'exécution (alors que nous ne les avons employées que pendant la conception)

    Notre exemple de "tSearchMemo" n'est dans ce domaine, pas un modèle du genre. Nous l'avons uniquement utilisé pour illustrer les manipulations à effectuer.

  • si votre seul objectif est uniquement de regrouper quelques composants, une autre solution est d'utiliser des tFrames. Ces tFrames permettent de créer des sortes de "morceaux de tFormes" réutilisables

  • si vous décidez de placer le composant sur la Palette, les manipulations à effectuer ont été présentées en détail.

    Mais beaucoup plus important, cependant, il faut souligner que nous avons pratiquement escamoté la discussion principale et beaucoup plus importante à nos yeux concernant le choix du type de composants, et les difficultés qui surviennent pour écrire le code des différents types de composants.

    Certes ce n'était pas notre objectif en rédigeant cet article, mais soyez conscients que l'écriture de composant nécessite une bonne connaissance

    • des techniques objets en général
    • des spécificités Windows (les librairies Windows)
    • de Delphi et de son fonctionnement interne (les traitements en mode conception, les flux, les messages internes, le mécanisme de liaison aux données)
    • l'organisation de la VCL (pour les composants dont nous souhaitons hériter, et pour les éditeurs de propriété et de composants)
Ces points sont naturellement abordés lors des Formations Delphi que nous organisons régulièrement à l'Institut Pascal, et en particulier


4 - Télécharger le code source Delphi

Vous pouvez télécharger:

Ce .ZIP qui comprend:

  • le .DPR, la forme principale, les formes annexes eventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.

La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La Notation Alsacienne



Comme d'habitude:

  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



5 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Créé: mar-07. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
    + prog_objet_composants
      – dump_interface
      – packages_delphi
      – ecriture_de_composant
      – c_list_of_double
      – interfaces_delphi
      – delphi_generics
      – delphi_rtti
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Formation Rave Report Constructions d'états, avec prévisualisation, génération .HTML ou .PDF - 2 jours
Formation Threads Delphi et Multi Tâche Les Threads Delphi : création, synchronisation, communication entre threads - 1 jour