|
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_index, m_find_length, m_text_length: Integer;
m_text, m_find_string: String;
procedure TForm1.load_Click(Sender: TObject);
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(Sender: TObject);
function f_find_rest(p_index: Integer): Boolean;
var l_word_index: Integer;
begin
l_word_index:= 2;
while (l_word_index<= m_find_length)
and (p_index+ l_word_index<= m_text_length)
and (m_find_string[l_word_index]= m_text[p_index+ l_word_index- 1]) do
inc(l_word_index);
Result:= l_word_index> Length(m_find_string);
end; // f_find_rest
var l_first_character: Char;
begin // find_next_occurence
Inc(m_text_index, m_find_length);
l_first_character:= m_find_string[1];
while m_text_index< m_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:
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:
tSearchMemo= Class(tPanel)
Private
m_c_panel: tPanel;
m_c_previous_button: tButton;
m_c_search_edit: tEdit;
m_c_next_button: tButton;
m_c_line_number_label: tLabel;
m_c_richedit: tRichEdit;
m_find_string: String;
m_find_length: Integer;
m_text: String;
m_text_index: Integer;
m_text_length: Integer;
_m_did_hit_control: Boolean;
procedure set_panel_color(p_color: Integer);
function f_memo_line_of_index(p_text_index: Integer): Integer;
procedure set_memo_top_line_index(p_top_line_index: Integer);
procedure find_next_occurence(p_color: Integer);
procedure handle_richedit_keypress(p_c_sender: TObject;
var pv_key: Char);
procedure handle_memo_and_find_edit_keydown(p_c_sender: TObject;
var pv_scan_code: Word; p_shift_state: TShiftState);
procedure handle_next_click(p_c_sender: tObject);
procedure set_text(p_text: String);
procedure set_search_text(p_search_text: String);
Public
constructor Create(p_c_owner: tComponent); Override;
Destructor Destroy; Override;
Property Text: String write set_text;
Property SearchText: String 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_owner: tComponent);
procedure create_panel;
const k_button_height= 25;
var l_x: Integer;
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_x, m_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_memo: tSearchMemo= Nil;
procedure TForm1.create_search_memo_Click(Sender: TObject);
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(Sender: TObject);
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:
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:
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:
|
|
cliquez "Package" (encerclé en rouge)
|
|
Delphi crée un nouveau Package
|
|
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_panel: tPanel;
// -- ...ooo...
Public
constructor Create(p_c_owner: tComponent); Override;
Destructor Destroy; Override;
Published
Property Text: String write set_text;
Property SearchText: String write set_search_text;
end; // tSearchMemo_simple
procedure Register;
implementation
Uses Windows, Messages, SysUtils
;
procedure Register;
begin
RegisterComponents('ip', [tSearchMemo_simple]);
end; // Register
// -- tSearchMemo_simple
constructor tSearchMemo_simple.Create(p_c_owner: tComponent);
// -- ...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é:
|
|
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:
|
|
cliquez sur "Compile"
|
|
le compilateur nous signale que nous devons ajouter certaines unités au
Package:
|
|
cliquez "Ok"
|
|
vcl.dcp a été ajouté à la clause Requires:
|
|
cliquez "Compile"
|
|
cliquez "Install"
|
|
un dialogue nous informe des composants installés:
|
|
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):
Nous pouvons donc déposer ce composant dans n'importe quelle tForm.
- et si nous affichons "Component | Install Packages"
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:
- finalement nous pouvons aussi utiliser l'outil de réorganisation de la
Palette, en sélectionnant "Component | Configure Palette", puis notre page
"ip":

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:
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
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:
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:
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

|
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

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:
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_set: Set Of Char;
g_word_character_set:= ['a'..'z', 'A'..'Z'];
|
Nous allons créer un Editeur qui accepte la syntaxe simplifiée suivante:
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 Classes, DesignIntf, DesignEditors;
type c_set_of_char_property_editor=
class(TStringProperty)
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
end; // c_set_of_char_property_editor
implementation
function c_set_of_char_property_editor.GetAttributes: TPropertyAttributes;
begin
Result:= [paValueList];
end; // GetAttributes
procedure c_set_of_char_property_editor.GetValues(Proc: TGetStrProc);
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 TSearchMemo= Class(tWinControl)
Private
m_word_character_string: String;
m_word_character_set: Set Of Char;
procedure set_word_character_string(p_word_character_string: String);
// -- ...ooo...
Published
Property WordCharacters: String
read m_word_character_string
write set_word_character_string;
end; // tStearchMemo
implementation
Uses Windows, Messages, SysUtils
;
// -- TSearchMemo
// -- ...ooo...
procedure TSearchMemo.set_word_character_string(p_word_character_string: String);
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:
TSearchMemo= Class(tWinControl)
Private
// -- ...ooo...
m_path, m_file_name: String;
Public
procedure load_from_file(p_full_file_name: String);
Published
Property Path: String read m_path write m_path;
Property FileName: String
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 Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, FileCtrl, StdCtrls, ExtCtrls
, DesignEditors
, u_c_search_memo
;
type c_search_memo_editor=
class(TComponentEditor)
function GetVerbCount: Integer; override;
function GetVerb(Index: Integer): string; override;
procedure ExecuteVerb(Index: Integer); override;
end; // c_search_memo_editor
Tsearch_memo_dialog=
class(TForm)
Panel1: TPanel;
DirectoryListBox1: TDirectoryListBox;
FileListBox1: TFileListBox;
Splitter1: TSplitter;
Panel2: TPanel;
Ok: TButton;
Cancel: TButton;
private
public
end; // Tsearch_memo
implementation
{$R *.dfm}
// -- c_search_memo_editor
function c_search_memo_editor.GetVerbCount: Integer;
begin
Result:= 1;
end; // GetVerbCount
function c_search_memo_editor.GetVerb(Index: Integer): string;
begin
case Index of
0: Result:= 'Load Search Memo...';
end;
end; // GetVerb
procedure c_search_memo_editor.ExecuteVerb(Index: Integer);
procedure display_search_menu_dialog;
var l_c_search_memo_dialog: Tsearch_memo_dialog;
l_c_search_memo: TSearchMemo;
l_full_file_name: String;
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 ShowModal= mrOK
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
, DesignIntf, DesignEditors
, 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(TSearchMemo, c_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
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:
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 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.
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" :
- 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 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 la
développement de projets pour
ses clients, le conseil 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, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|