Pour un autre projet, j'avais besoin d'un éditeur pour des mémos formatés connectable aux données (TDBRichEdit) qui soit aussi utilisable dans toutes sortes d'applications. Je devais donc soit en faire un composant composite ou utiliser un cadre (TFrame), une nouvelle classe introduite dans Delphi 5. J'ai choisi cette dernière avenue.
Les difficultés qu'on rencontre lorsqu'on veut concevoir et mettre au point des composant composites (composants comprenant d'autres composants de la VCL et des composants d'autres sources) sont énormes car, lors d'un tel développement, le concepteur ne dispose plus de l'environnement de programmation visuelle de Delphi.
J'ai rencontré ce problème il y a de cela quelques années lorsque j'ai développé un gestionnaire d'informations personnelles basé sur une base de données Paradox dans une partie duquel l'utilisateur pouvait écrire du texte, le modifier et le formater à partir de divers boutons (un peu comme dans MS Word). J'ai alors utilisé le composant orienté- données (data-aware en anglais) TDBRichEdit qu'on retrouve dans la palette des composants de Delphi, composant que je devais entourer de boutons permettant l'édition et le formatage du texte.
Si j'avais eu à l'utiliser une seule fois, j'aurais probablement mis le composant sur une fiche et je l'aurais entouré des contrôles nécessaires à sa gestion. Le problème est que je voulais le réutiliser au moins à deux reprises dans cette applications et aussi le réutiliser ailleurs. J'avais alors un seul choix car je travaillais alors avec Delphi 3: concevoir un composant composite, une tâche réellement difficile en dehors de l'environnement visuel de Delphi.
J'ai attendu un peu et Delphi 5 m'a sauvé car il permettait une extension importante du vieux concept de l'héritage des fiches visuelles avec une nouvelle classe qui rendait désormais mon travail facile: le cadre.
Cadres
Le cadre, descendant de la classe TFrame, est une nouvelle classe apparue avec Delphi 5. Sur un cadre, on peut placer des composants, écrire des gestionnaires d'événements pour chaque composant et alors, ajouter le cadre à une fiche ou même à un autre cadre. En d'autres termes, un cadre est un peu semblable à une fiche car c'est un conteneur de composant. Là où le cadre se démarque de la fiche, c'est qu'un cadre définit non pas une fenêtre entière mais une portion de fenêtre et qu'il peut contenir un autre cadre.
Ce qui rend les cadres très intéressants c'est qu'ils se comportent presque comme des composants: on peut en créer diverses instances à la conception tout en permettant au concepteur de les concevoir visuellement. Je ne vais pas aller dans le détail des cadres ici car ça dépasse les intentions de cette page. Je recommande donc au lecteur le livre "Mastering Delphi 5" de Macro Cantu où on trouvera tout ce qu'il faut savoir à propos de cette classe.
Éditeur de mémos formatés connectable aux données
L'objectif de ce travail était de doter une application sur laquelle je travaillais à l'époque de la capacité d'éditer et de formater des mémos formatés connectable aux données (des composants TDBRichEdit) dans une portion de fenêtres en plus d'être capable d'utiliser le code à plusieurs endroits.
Pour l'essentiel, je voulais avoir un composant centré sur un composant TDBRichEdit comportant aussi des contrôles permettant d'éditer (couper, copier et coller ou ajouter du texte), de formater (mettre en gras, en italique, souligner, justifier et ajouter des "bullets" aux paragraphes), de rechercher des mots et d'en imprimer le contenu.
Composants requis pour l'éditeur
Le cadre contenant l'éditeur de mémos formatés connectable aux données est composé des composants suivants:
- un composant TDBRichEdit qui va chercher du texte formaté (rich edit) dans un champ mémo formaté de la table avec laquelle il est connecté;
- un composant TActionList donnant accès aux actions d'édition (EditUndo, EditCut, EditCopy, EditPaste) et de formatage (RichEditBold, RichEditItalic, RichEditUnderline, RichEditStrikeOut, RichEditBullet, RichEditAlignLeft, RichEditAlignRight et RichEditCenter) de Delphi ainsi que d'une catégorie originale d'actions que j'ai appelé GTroRichEdit (GtroRichEditBold, GtroRichEditItalic, GtroRichEditUnderline, GtroRichEditLeftJustify, GtroRichEditCenter, GtroRichEditRightJustify, GtroRichEditBullet et GtroRichEditColor)
- un composant TControlBar contenant les barres d'outil suivantes:
- la barre d'outils ToolBarEdit d'édition avec les boutons:
- un bouton TToolButton pour défaire le formatage fait précédemment. Ce bouton est connecté à l'action EditUndo;
- un bouton TToolButton pour couper le texte sélectionné. Ce bouton est connecté à l'action EditCut;
- un bouton TToolButton pour copier le texte sélectionné. Ce bouton est connecté à l'action EditCopy;
- la barre d'outil ToolBarStyle de formatage des sélections qui comptrend
- un bouton TToolButton pour mettre en gras le texte sélectionné. Ce bouton est connecté à l'action GtroRichEditBold;
- un bouton TToolButton pour mettre en italique le texte sélectionné. Ce bouton est connecté à l'action GtroRichEditItalic;
- un bouton TToolButton pour souligner le texte sélectionné. Ce bouton est connecté à l'action GtroRichEditUnderline;
- un bouton TToolButton pour colorer le texte sélectionné. Ce bouton est connecté à l'action GtroRichEditColor;
- la barre d'outil ToolBarPara de formatage des paragraphes qui comprend
- un bouton TToolButton pour justifier le texte du paragraphe à gauche. Ce bouton est connecté à l'action GtroRichEditLeftJustify;
- un bouton TToolButton pour centrer le texte du paragraphe. Ce bouton est connecté à l'action GtroRichEditCenter;
- un bouton TToolButton pour justifier le texte du paragraphe à droite. Ce bouton est connecté à l'action GtroRichEditRightJustify;
- un bouton TToolButton pour ajouter des bulles ("bullets") au paragraphe. Ce bouton est connecté à l'action GtroRichEditBullet;
Figure 1 - Affichage du cadre - la barre d'outil ToolBarOthers pour l'impression, la recherche de mots et la vérification de l'orthographe
qui comprend
- un bouton TToolButton pour imprimer le texte affiché dans le composant. L'action de ce bouton doit être accomplie par l'application cliente;
- un bouton TToolButton pour effectuer une recherche de mot dans le composant ou dans les champs mémos de la table;
- un bouton TToolButton pour vérifier l'orthographe du texte ou pour vérifier l'orthographe des mots sélectionnés.
- la barre d'outils ToolBarEdit d'édition avec les boutons:
- un composant TStatusBar déposé en bas du cadre;
- un composant TImageList contenant les images affichées sur les boutons;
- un composant TPopupMenu sur lequel apparaîssent toutes les fonctions d'édition, de formatage et autres exécutable à partir des boutons. Ces items de menu utilisent les mêmes actions et les mêmes images que les boutons;
- un composant TFindDialog servant à la recherche de mots; et
- un composant TColorDialog qui est utilisé lorsqu'on clique sur le bouton qui sert à colorer le texte sélectionné.
- un composant TSpeller téléchargé du site Web de Luzius Schneider.
À la Figure 1, on voit le cadre avec, de haut en bas, la barre de commande avec ses quatre barres d'outils et tous leurs boutons, le composant TDBRichEdit et la barre d'état qui indique la position du curseur dans l'éditeur.
Détails de l'implémentation
Une fois les composants et leurs constituants déposés sur le cadre, il ne me restait plus qu'à les intégrer avec le code approprié. Dans l'ordre, j'ai considéré les actions reliées à l'édition et au formatage, l'ajustement de la vue du texte lors de l'édition et de la sauvegarde de l'ensemble de données ainsi qu'une mesure de sécurité qui sert à éviter la corruption de l'ensemble de données lorsque l'application hôte est arrêtée inopinément alors que l'ensemble de données est encore ouvert et qu'il n'a pas terminé l'écriture des données (par exemple lors d' un "crash" du système d'exploitation). J'élabore aussi sur certaines capacités de l'éditeur: recherche de texte, vérification de l'orthographe et impression.
Actions d'édition et de formatage
Plutôt que de programmer les actions de formatage et d'édition de l'éditeur, j'ai utilisé les composants TActions et TActionList que Borland a introduit avec Delphi 4. Ces composants simplifient de beaucoup la programmation des actions qui doivent survenir lorsqu'un bouton est cliqué ou qu'un item de menu est choisi. Ces composants permettent aussi de déterminer l'état de tous les éléments et composants connectés aux actions. Pour plus de détails sur ces composants, je réfère le lecteur au Chapître 5 de la référence 1. J'en parle aussi plus longuement à l'Annexe intitulée "Actions et listes d'actions".
Pour implémenter les fonctions d'édition, i.e., pour les boutons d'édition (défaire, couper, copier, coller), j'ai utilisé les actions pré-programmées de Delphi qui sont des descendants de la classe TEditAction. Pour les fonctions de formatage (gras, italique, souligné, ...), j'ai pensé utiliser les descendants de la classe TRichEditAction de Delphi mais je me suis aperçu que ces actions, si elles effectuaient l'action demandée, ne mettaient pas l'ensemble de données sous-jacent en mode "édition" causant ainsi une exception lors de la sauvegarde (posting).
Il a donc fallu développer des actions qui, après avoir formaté le texte sélectionné, mette l'ensemble de données sous-jacent en mode édition (dsEdit). C'est ce que font les descendants de la classe TGtroRichEditAction. Le code de ces actions est le même que celui utilisé pour les descendants de la classe TRichEditAction de Delphi sauf pour ce qui est la mise en mode "édition" de l'ensemble de données sous-jacent. Comme le changement de mode de l'ensemble de données causait la perte de la selection de texte, il a aussi fallu envelopper la mise en mode "édition" par du code qui préserve la sélection de texte car celle-ci disparaît lors du changement de mode. Ce code s'exécute dans la méthode ExecuteTarget de la classe TGtroRichEditAction, méthode qui est appelée lors de l'exécution de la méthode de même nom des classes descendantes. Voici le code qui y est exécuté:
procedure TGtroRichEditAction.ExecuteTarget(Target: TObject);var
Start, Len: integer;
begin
if GetControl(Target) is TDBRichEdit then
begin
Start:= TDBRichEdit(Target).SelStart;
Len:= TDBRichEdit(Target).SelLength; // met la sélection en mémoire
TDBRichEdit(Target).DataSource.Edit; // met l'ensemble de données en mode "édition"
TDBRichEdit(Target).SelStart:= Start; // récupère la sélection
TDBRichEdit(Target).SelLength:= Len;
end;
end;
Ajustement de la vue lors de l'édition et la sauvegarde
Lors de l'utilisation des boutons de mise en forme ou lors de la sauvegarde de l'ensemble de données, le composant TDBRichEdit de l'éditeur avait un comportement que je trouvais indésirable et qu'il me fallait corriger: lors de la sauvegarde du record de table de donnée sous-jacent au composant ou lors de la mise en forme du texte, la vue offerte par le composant TDBRicheEdit se remmenait automatiquement à la première ligne du contenu plutôt que de laisser la vue inchangée, un comportement causé par le changement d'état de l'ensemble de données sous-jacent qui passe alors de l'état dsBrowse à l'état dsEdit lors de l' édition et passe d l'état dsEdit à l'état dsBrowse lors du Post.
Pour corriger ce comportement, j'ai ajouté des gestionnaires d'événement pour les événements BeforeEdit, BeforePost, AfterEdit et AfterPost du composant TDBRichEdit. Les deux premiers appellent la méthode BeforeEvent alors que les deux autres appellent la méthode AfterEvent. Voici le détail de ces changements.
Méthode BeforeEvent
Cette méthode qui est appelée par les gestionnaires d'événement BeforeEdit et BeforePost mémorise les caractéristiques de la vue des données du composant TDBRichEdit avant l'édition ou la sauvegarde en plaçant le début de la sélection dans la variable privée RemStart, la longueur de la sélection dans la variable privée RemSel et l'index de la première ligne affichée dans la vue des données dans la variable privée RemLast. Cette dernière opération est effectués en envoyant le message EM_GETFIRSTVISIBLELINE au composant comme décrit dans le code qui suit:
procedure TFrameEdit.BeforeEvent;
begin
RemStart:= DBRichEdit.SelStart;
RemSel:= DBRichEdit.SelLength;
RemLast:= DBRichEdit.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
end;
Méthode AfterEvent
Cette méthode qui est appelée par les gestionnaires d'événement AfterEdit et AfterPost ramène simplement la vue sur le contenu du composant TDBRichEdit là où elle était avant l'édition ou la sauvegarde.
procedure TFrameEdit.AfterEvent;
begin
DBRichEdit.Perform(EM_LINESCROLL, 0, RemLast);
DBRichEdit.SelStart:= RemStart;
DBRichEdit.SelLength:= RemSel
end;
Comme on le voit dans le code qui précède, après l'édition ou la sauvegarde, la vue des données est ramenés à sa position antérieure au moyen du message EM_LINESCROLL. La sélection antérieure est aussi rétablie grâce aux valeurs mémorisées dans les variable RemStart et RemSel.
Une mesure pour éviter des erreurs
Cet éditeur cadré, ainsi que le projet dont il faisait partie, avait une faiblesse occasionnelle: c'est dans une base de données Paradox que l'éditeur emmagasine ses données. Comme le record de l'éditeur dans la base de données est un BLOB de texte formaté qui est stocké dans deux fichiers: un fichier .db qui contient une partie de longueur fixe du record et un autre fichier .mb qui contient le reste. Or, si la BDE est fermée d'une façon irrégulière (par exemple: un "crash" du système d'opération) certaines mise à jour des fichiers peuvent ne pas se compléter et causer une erreur "BLOB has been modified" évoquant l'écriture d'un des fichiers s'est faite avec succès sans que la mise à jour de l'autre ne soit effectuée.
Cette faiblesse a été corrigé en utilisant la fonction BDE DBiSaveChanges() dans le gestionnaire d'événement AfterPost de l'ensemble de données auquel l'éditeur est lié. En voici le code::
procedure TFrameEdit.AfterPost(DataSet: TDataSet);
var
Table: TTable;
begin
AfterEvent;
Table:= DBRichEdit.DataSource.DataSet as TTable;
Check(DBiSaveChanges(Table.Handle)); // stockage sur disque immédiat
if Assigned(FAfterPost) then
FAfterPost(DataSet);
end; // TFrameEdit.AfterPost
où l'énoncé mis en évidence est celui qui a été ajouté pour que les mises à jour sur disque soient immédiates.
Exécution des actions de l'ensemble de données sous-jacent
Dans ce qui précède, j'ai définit comment l'éditeur cadré orienté-données réagissait aux événements survenant à l'ensemble de données sous-jacent au composant TDBRichEdit du cadre. En effet, le composant TDBRichEdit de l'éditeur cadré doit nécessairement être connecté à une source de données (TDataSource) qui est elle-même nécessairement connectée à un ensemble de données (TDataSet). Dans ce cas, il est nécessaire que les gestionnaires d'événement déjà associés à cet ensemble de données dans l'application hôte puisse être exécutés lorsque les événements d'édition et de sauvegarde surviennent dans l'éditeur cadré. C'est ce que la méthode Initialize() dont le code suit effectue pour les événements BeforePost, BeforeEdit, AfterPost et AfterEdit de l'ensemble de données connecté à l'éditeur.
procedure TFrameEdit.Initialize;
begin
if Assigned(DBRichEdit.DataSource) then // ajouté le 16 août 2001
begin
if Assigned(DBRichEdit.DataSource.DataSet) then
begin // l'éditeur est connecté à un ensemble de données
if Assigned(DBRichEdit.DataSource.DataSet.BeforePost) then
FBeforePost:= DBRichEdit.DataSource.DataSet.BeforePost;
DBRichEdit.DataSource.DataSet.BeforePost:= BeforePost;
if Assigned(DBRichEdit.DataSource.DataSet.BeforeEdit) then
FBeforeEdit:= DBRichEdit.DataSource.DataSet.BeforeEdit;
DBRichEdit.DataSource.DataSet.BeforeEdit:= BeforeEdit;
if Assigned(DBRichEdit.DataSource.DataSet.AfterPost) then
FAfterPost:= DBRichEdit.DataSource.DataSet.AfterPost;
DBRichEdit.DataSource.DataSet.AfterPost:= AfterPost;
if Assigned(DBRichEdit.DataSource.DataSet.AfterEdit) then
FAfterEdit:= DBRichEdit.DataSource.DataSet.AfterEdit;
DBRichEdit.DataSource.DataSet.AfterEdit:= AfterEdit;
end; // ...if
end; // if Assigned(DBRichEdit.DataSource)
end;
J'aurais voulu que ce cadre soit complètement encapsulé en mettant ce code dans le gestionnaire OnCreate du cadre mais la classe TFrame ne possède pas un tel événement et je dois utiliser un subterfuge: la méthode Initialize() est déclarée publique et doit être appelé par l'application hôte aussitôt après la création du cadre. Je suggère donc aux usagers d'appeler Initialize() dans le gestionnaire d'événement OnCreate de la fiche hôte afin d'être certain que le cadre a été construit avant l'appel.
Recherche de texte
Cet éditeur a, de plus, la capacité de rechercher des mots en utilisant la méthode FindText() de TDBRichEdit. La procédure FindDialogFind est le gestionnaire d'événement OnFind() du composant FindDialog de l'éditeur. Il est déclenché lorsque l'utilisateur, après avoir cliqué le bouton de recherche, clique sur "Suivant" dans la boîte de dialogue non-modale. Cette méthode utilise la méthode FindText() pour rechercher à l'intérieur du contenu du composant TDBRichEdit.
procedure TFrameEdit.FindDialogFind(Sender: TObject); // published
var
FoundAt: LongInt;
L: Integer;
StartPos, ToEnd, FirstLine: Integer;
Options: TSearchTypes;
begin
FStopSearch:= False;
repeat // jusqu'à ce qu'on lise la fin ou le début de fichier
Options:= []; // passe les options de la boîte de dialogue à DBRichEdit.FindText
if frMatchCase in FindDialog.Options
then Options:= Options + [stMatchCase]
if frWholeWord in FindDialog.Options
then Options:= Options + [stWholeWord];
with DBRichEdit do
begin
if SelLength <> 0 then // texte selectionné?
StartPos := SelStart + SelLength // positionne le curseur après la sélection
else
StartPos := 0;
ToEnd := Length(Text) - StartPos;
FoundAt := FindText(FindDialog.FindText, StartPos, ToEnd, Options);
if FoundAt <> -1 then // trouvé ...
begin
Show; // affiche le contenu du contrôle
FirstLine:= Perform(EM_GETFIRSTVISIBLELINE, 0, 0); // ajouté 13 mai 05
SelStart := FoundAt; // Début de la sélection du mot trouvé
L := Length(FindDialog.FindText);
SelLength := L; // longueur de la sélection
// déplace le contrôle pour que le mot trouvé soit visible
Perform(EM_LINESCROLL, 0, CaretPos.Y - FirstLine); // ajouté 13 mai 05
SetFocus;
Inc(NumFound);
StatusBar.Panels[0].Text:= 'Found = ' + IntToStr(NumFound);
if Assigned(FOnFind) then OnFind(Self, FoundAt, L);
Exit;
end // if FoundAt
else
begin // pas trouvé ...
Hide; // cache le contenu du contrôle
Application.ProcessMessages;
if FRecurseSearch then
if frDown in FindDialog.Options then
DataSource.DataSet.Prior
else
DataSource.DataSet.Next;
end; // else FoundAt
end;
until DBRichEdit.DataSource.DataSet.Eof or DBRichEdit.DataSource.DataSet.Bof
or not FRecurseSearch;
FindDialog.CloseDialog;
DBRichEdit.Visible:= true;
end;
La propriété RecurseSearch détermine si la recherche va se faire dans toute la table ou seulement dans le record affiché: La variable FStopSearch indique la fin de la recherche et elle prend la valeur "true" dans le gestionnaire d'événement OnClose de la boîte de dialogue de recherche qui est déclenché lorsque l'usager clique sur "Annuler".
Les énoncés
FirstLine:= Perform(EM_FIRSTLINEVISIBLE, 0, 0);
...
Perform(EM_LINESCROLL, 0, CaretPos.Y - FirstLine)
servent à maintenir la sélection du mot trouvé visible dans le contrôle. Ces énoncés remplacent
FirstLine:= Perform(EM_SCROLLCARET, 0, 0);
qui aurait dû fonctionner mais ne fonctionnait pas.
Vérification de l'orthographe
Je n'ai pas essayé de mettre au point un logiciel de vérification de l'orthographe. J'ai cherché sur Internet et j'ai trouvé le vérificateur d'orthographe TSpellChecker, Version 3.05 de Luzius Schneider sur le site Web de l'auteur. Ce composant est un composant non-visuel qui implémente la vérification d'orthographe à l'éditeur. Il utilise les dictionnaires ISpell installés avec "ISpell - L.S. Dictionary" ou les dictionnaires installés avec MSOffice 95 ou 97. Il est accompagné d'un composant compagnon TSpellLanguageComboBox qui sert à choisir les langages dont les dictionnaires sont disponibles pour la vérification d'orthographe. Je ne l'ai pas utilisé.
Impression du texte
Le cadre comporte un bouton permettant l'impression mais l'impression elle-même n'est pas implémentés dans le cadre. L'événement OnClick() du bouton "Impression" est disponible à l'application cliente et peut ainsi être implémenter pour imprimer non seulement le contenu du composant TDBRichEdit mais aussi les données contextuelles requises par l'application cliente.
Disponibilité du code source
Si vous êtes intéressé à obtenir le code source, vous pouvez le télécharger ici. Le composant TSpellChecker mentionné pplus haut pour la vérification de l'otrhographe n'est pas inclus dans le code source mais il doit être installé dans l'environnement Delphi. Vous pouvez le télécharger à partir du site de Luzius Schneider.
Conclusion
Le composant TDBRichEdit de Delphi est en soi un composant qui affiche du texte formaté en provenance d'un ensemble de données sans toutefois fournir l'interface usager permettant de formater le texte et les paragraphes. Nous en sommes donc arrivés à mettre dans une sorte de composant un éditeur presque complet comprenant ce genre d'interface usager. On aurait pu y rajouter d'autres outils comme un choix de police de caractère mais l'éditeur cadré décrit remplissait les exigences du logiciel sur lequel on travaillait. Je ne suis donc pas allé plus loin.
On pourrait cependant aller plus loin et obtenir encore plus de capacités. En effet, dans le cadre, nous avons utilisé un composant TDBRichEdit, un desdendant de TCustomRichEdit qui encapsule l'accès au format Microsoft Rich Edit 1.0 implémentée dans riched32.dll. Rich Edit 2.0 est une version améliorée de Rich Edit 1.0 qui est implémentée dans riched20.dll. J'ai lu que le composant TRxRichEdit de RXLib encapsule le format Rich Edit 2.0 et introduit un grand nombre de nouvelles méthodes et propriétés. Pour plus de détails, il faut voir "Using Rich Edit 2.0 With BCB"
Annexes
Références
- "Mastering Delphi 5" par Marco Cantu, Copyright © 1999 Sybex, Inc, ISBN: 0-7821-2565-4
Actions et listes d'actions
L'architecture d'événements de Delphi est très ouverte. On peut y écrire un gestionnaire d'événements et le connecter à un événement OnClick d'un bouton, d'une barre d'outils ou d'un menu. On peut par la suite connecter ce gestionnaire d'événement à d'autres boutons ou menus car le gestionnaire d'événement peut utiliser le paramètre Sender pour référer à l'objet qui a déclenché l'événement. Il est néanmoins plus difficile de synchroniser l'état du bouton ou de l'item de menu principalement lorsque deux boutons ou lorsque plusieurs items de menus basculent la même option. Il faut alors modifier l'état de tous les boutons et items de menu pour refléter le changement.
Objets d'action
C'est dans le but de vaincre ce problème que Borland a introduit dans Delphi 4 l'architecture des actions. Une action indique à la fois l'opération à effectuer lorsqu'un bouton ou un item de menu est cliqué mais il détermine en plus l'état de tous les éléments connectés à l'action.
Le rôle central dans ce contexte est tenu par les objets d'actions ou composants TAction. Comme tout autres composants, ces objets d'action ont des propriétés qui vont être appliquées aux contrôles liés appelés aussi contrôles clients. Ces propriétés incluent la caption, la représentation graphique (ImageIndex), l'état de l'action et les suggestions (hints).
Liste d'actions
La liste des objets d'actions disponibles est maintenue dans un composant liste d'actions (TActionList), la seule classe de cette architecture qui soit disponible dans la palette des composants de Delphi.
Les sous-classes de TGtroRichEditAction
Quoique Borland fournisse des objets d'action de formatage tels que RichEditBold, RichEditItalic, RichEditUnderline et quelques autres qui auraient pu être utilisées, j'ai décidé d'utiliser mes propres objets d'action de formatage (GtroRichEditBold ...) afin d'y ajouter un comportement que n'avaient pas les objets d'action correspondants de la catégorie "Format": la mise de la table sous-jacente en mode édition qui permet à l'usager de sauvegarder le contenu du contrôle TDBRichEdit au moyen de l'opération Post.
Les actions de catégorie GtroRichEdit sont définies dans l'unité GTActions.pas. En premier lieu, j'y définis la classe TGtroRichEditAction dérivée de TAction qui définit les comportements de base de chaque objet d'action. Cette classe est définie ainsi:
TGtroRichEditAction = class (TAction)
private
FControl: TCustomRichEdit;
procedure SetControl(Value: TCustomRichEdit);
procedure EditControl(Target: TObject);
protected
function GetControl(Target: TObject): TCustomRichEdit; virtual;
public
procedure ExecuteTarget(Target: TObject); override;
function HandlesTarget(Target: TObject): boolean; override;
property Control: TCustomRichEdit read FControl write SetControl;
end;
alors que chacun des objets d'action qu'elle contient est définie comme suit:
TGtroRichEdit.... = class (TGtroRichEditAction)
public
procedure ExecuteTarget(Target: TObject); override;
procedure UpdateTarget(Target: TObject); override;
end;
Comme je l'ai mentionné plus haut, la différence essentielle entre les objets d'action présentés ici et ceux de la catégorie Format de Borland se résume dans l'énoncé surligné de la méthode EditControl de la classe TGtroRichEditAction dont le code suit:
procedure TGtroRichEditAction.EditControl(Target: TObject);
var
Start, Len: integer;
begin
Start:= TDBRichEdit(Target).SelStart;
Len:= TDBRichEdit(Target).SelLength;
TDBRichEdit(Target).DataSource.Edit;
TDBRichEdit(Target).SelStart:= Start;
TDBRichEdit(Target).SelLength:= Len;
end;
Cet énoncé met la table de données sous-jacente en mode édition lorsqu'une des actions est accomplie. Ceci permet de sauvegarder (post) le contenu du composant TDBRichEdit avec le navigateur qui y est connecté dans l'application cliente.
Finalement, pour que les objets d'actions soient reconnus par l'environnement de programmation Delphi, il faut les enregistrer. Ça se fait au moyen de la procédure Register dont le code suit:
procedure Register;
begin
RegisterActions('GTroRichEdit', [TGtroRichEditBold, TGtroRichEditUnderline,
TGtroRichEditItalic, TGtroRichEditBullet, TGtroRichEditLeftJustify,
TGtroRichEditCenter, TGtroRichEditRightJustify, TGtroRichEditColor], nil);
end;
Cette procédure définit la catégorie des objets d'action et les énumère.
Encapsulation du format RTF
Le composant central de notre éditeur est de type TDBRichEdit. Il s'agit d'un contrôle de saisie multiligne qui peut afficher un champ texte mémo formaté d'un ensemble de données. Fondamentalement, le composant encapsule l'accès à RichEdit32.dll de Windows qui est la bibliothèque de liaison dynamique permettant l'édition de texte de format RTF (Rich Text Format ou format texte enrichi en français.
Il s'agit d'un format de fichier développé par la société Microsoft. Ce format descriptif non compressé est reconnu par la plupart des logiciels de traitement de texte. À l'origine ouvert, ce format est peu standardisé : il existe des incompatibilités entre logiciels, y compris entre les logiciels de la société inventrice Microsoft. De ce fait, ce format est peu utilisé pour la distribution de documents. Néanmoins, il s'agit d'un format commode pour échanger du texte agrémenté de mises en forme simples entre utilisateurs de logiciels différents.
Il est utilisé par défaut dans l'éditeur TextEdit de Mac OS X, dans WordPad de Microsoft Windows, et dans le traitement de texte Ted, courant sous les systèmes de type Unix. Pour plus de détails sur le format RTF, le lecteur est invité à voir l'article "Rich Text Format (RTF) Specification, version 1.6" du MSDN