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, 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" :
- 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. |