Notification d'un expert des événements de l'EDI

De RAD Studio
Aller à : navigation, rechercher

Remonter à Utilisation d'interfaces spéciales pour étendre l'EDI


Un aspect important pour bien concevoir un expert consiste à le faire réagir aux événements de l'EDI. En particulier, si un expert suit les interfaces de module, il doit savoir quand l'utilisateur ferme le module, afin que l'expert puisse libérer l'interface. Pour ce faire, l'expert a besoin d'un notificateur, ce qui signifie que vous devez écrire une classe de notification.

Toutes les classes de notification implémentent une ou plusieurs interfaces de notification. Une interface de notification définit des méthodes de rappel ; l'expert recense un objet notificateur dans l'API Tools, et l'EDI rappelle le notificateur quand il se produit quelque chose d'important.

Chaque interface de notification hérite de IOTANotifier, même si toutes ses méthodes ne sont pas utilisées dans un même notificateur. Le tableau suivant liste toutes les interfaces de notification en les décrivant brièvement.

Interfaces de notification : :

Interface Description

IOTANotifier

Classe de base abstraite de tous les notificateurs

IOTABreakpointNotifier

Déclenchement ou modification d'un point d'arrêt dans le débogueur

IOTADebuggerNotifier

Exécution d'un programme dans le débogueur, ou l'ajout ou la suppression d'un point d'arrêt

IOTAEditLineNotifier

Suit le déplacement des lignes dans l'éditeur de code source

IOTAEditorNotifier

Modification ou enregistrement d'un fichier source ou passage d'un fichier à l'autre dans l'éditeur

IOTAFormNotifier

Enregistrement d'une fiche ou modification de la fiche ou de ses composants (ou d'un module de données)

IOTAIDENotifier

Chargement de projets, installation de packages et autres événements de l'EDI de portée globale

IOTAMessageNotifier

Ajout ou suppression d'onglets (groupes de messages) dans la vue des messages

IOTAModuleNotifier

Modification, enregistrement ou changement de nom d'un module

IOTAProcessModNotifier

Chargement d'un module de processus dans le débogueur

IOTAProcessNotifier

Création ou destruction de threads ou de processus dans le débogueur

IOTAThreadNotifier

Modification de l'état d'un thread dans le débogueur

IOTAToolsFilterNotifier

Appel d'un outil de filtrage


Pour voir comment utiliser des notificateurs, reportez-vous à l'exemple proposé dans Création de fiches et de projets. Grâce à des créateurs de module, l'exemple crée un expert qui ajoute un commentaire à chaque fichier source. Le commentaire comprend le nom initial du fichier de l'unité, mais l'utilisateur enregistre presque toujours le fichier sous un nom différent. Dans ce cas, ce serait un avantage pour l'utilisateur que l'expert actualise le commentaire pour correspondre au véritable nom du fichier.

Pour ce faire, vous devez utiliser un notificateur de module. L'expert enregistre l'interface de module que renvoie CreateModule et l'utilise pour recenser un notificateur de module. Le notificateur de module reçoit une notification quand l'utilisateur modifie le fichier ou l'enregistre, mais comme ces événements ne concernent pas l'expert, AfterSave et les fonctions associées sont laissées vides. La fonction importante est ModuleRenamed, elle est appelée par l'EDI quand l'utilisateur enregistre le fichier sous un nouveau nom. Voici la déclaration de la classe du notificateur de module :

TModuleIdentifier = class(TNotifierObject, IOTAModuleNotifier)
public
constructor Create(const Module: IOTAModule);
  destructor Destroy; override;
  function CheckOverwrite: Boolean;
  procedure ModuleRenamed(const NewName: string);
  procedure Destroyed;
private
  FModule: IOTAModule;
  FName: string;
  FIndex: Integer;
end;
class ModuleNotifier : public NotifierObject, public IOTAModuleNotifier
{
typedef NotifierObject inherited;
public:
__fastcall ModuleNotifier(const _di_IOTAModule module);
__fastcall ~ModuleNotifier();
// IOTAModuleNotifier
virtual bool __fastcall CheckOverwrite();
virtual void __fastcall ModuleRenamed(const AnsiString NewName);
// IOTANotifier
void __fastcall AfterSave();
void __fastcall BeforeSave();
void __fastcall Destroyed();
void __fastcall Modified();
protected:
// IInterface
virtual HRESULT __stdcall QueryInterface(const GUID&, void**);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
private:
_di_IOTAModule module;
AnsiString name;        // Stocke l'ancien nom du module.
int index;              // Indice du notificateur.
};

L'un des moyens d'écrire un notificateur consiste à le faire se recenser lui-même dans son constructeur. Le destructeur annule le recensement du notificateur. Dans le cas d'un notificateur de module, l'EDI appelle la méthode Destroyed quand l'utilisateur ferme le fichier. Dans ce cas, le notificateur doit lui-même annuler son recensement et libérer sa référence à l'interface de module. L'EDI libère sa référence au notificateur, ce qui ramène à zéro son compteur de références et libère l'objet. Il n'est donc pas nécessaire d'écrire le destructeur défensivement : le notificateur n'est peut être déjà plus recensé.

constructor TModuleNotifier.Create( const Module: IOTAModule);
begin
  FIndex := -1;
  FModule := Module;
  { Recenser ce notificateur. }
  FIndex := Module.AddNotifier(self);
  { Stocker l'ancien nom du module. }
  FName := ChangeFileExt(ExtractFileName(Module.FileName), );
end;
destructor TModuleNotifier.Destroy;
begin
  { Annuler le recensement du notificateur si ce n'est pas déjà fait. }
  if Findex >= 0 then
    FModule.RemoveNotifier(FIndex);
end;
procedure TModuleNotifier.Destroyed;
begin
   { L'interface du module est détruite, nettoyage du notificateur. }
  if Findex >= 0 then
  begin
    { Annuler le recensement du notificateur. }
    FModule.RemoveNotifier(FIndex);
    FIndex := -1;
  end;
  FModule := nil;
end;
__fastcall ModuleNotifier::ModuleNotifier(const _di_IOTAModule module)
: index(-1), module(module)
{
// Recense ce notificateur.
index = module->AddNotifier(this);
// Stocke l'ancien nom du module.
name = ChangeFileExt(ExtractFileName(module->FileName), "");
}
__fastcall ModuleNotifier::~ModuleNotifier()
{
// Annule le recensement du notificateur si ce n'est pas déjà fait.
if (index >= 0)
module->RemoveNotifier(index);
}
void __fastcall ModuleNotifier::Destroyed()
{
// L'interface du module est détruite, nettoyage du notificateur.
if (index >= 0)
{
// Annule le recensement du notificateur.
module->RemoveNotifier(index);
index = -1;
}
module = 0;
}

L'EDI rappelle la fonction ModuleRenamed du notificateur quand l'utilisateur renomme le fichier. La fonction attend comme paramètre le nouveau nom qui est utilisé par l'expert pour actualiser le commentaire dans le fichier. Pour modifier le tampon source, l'expert utilise une interface de position d'édition. L'expert recherche la position appropriée, vérifie si le texte trouvé est le bon et remplace ce texte par le nouveau nom.

procedure TModuleNotifier.ModuleRenamed(const NewName: string);
var
  ModuleName: string;
  I: Integer;
  Editor: IOTAEditor;
  Buffer: IOTAEditBuffer;
  Pos: IOTAEditPosition;
  Check: string;
begin
  { Obtenir le nom du module à partir du nouveau nom de fichier. }
  ModuleName := ChangeFileExt(ExtractFileName(NewName), );
for I := 0 to FModule.GetModuleFileCount - 1 do
  begin
   { Mettre à jour tous les tampons de l'éditeur de code source. }
    Editor := FModule.GetModuleFileEditor(I);
    if Supports(Editor, IOTAEditBuffer, Buffer) then
    begin
      Pos := Buffer.GetEditPosition;
{ Le nom du module se trouve dans la ligne 2 du commentaire.
        Sauter les espaces de début et copie l'ancien nom de module,
        pour vérifier si c'est le bon emplacement. }
      Pos.Move(2, 1);
      Pos.MoveCursor(mmSkipWhite or mmSkipRight);
Check := Pos.RipText(, rfIncludeNumericChars or rfIncludeAlphaChars);
if Check = FName then
      begin
        Pos.Delete(Length(Check));    // Supprimer l'ancien nom.
        Pos.InsertText(ModuleName);   // Insérer le nouveau nom.
        FName := ModuleName;          // Mémoriser le nouveau nom.
      end;
    end;
  end;
end;
void __fastcall ModuleNotifier::ModuleRenamed(const AnsiString NewName)
{
// Obtient le nom du module à partir du nouveau nom de fichier.
AnsiString ModuleName = ChangeFileExt(ExtractFileName(NewName), "");
for (int i = 0; i < module->GetModuleFileCount(); ++i)
{
// Actualise tous les tampons de l'éditeur de code source.
_di_IOTAEditor editor = module->GetModuleFileEditor(i);
_di_IOTAEditBuffer buffer;
if (editor->Supports(buffer))
{
_di_IOTAEditPosition pos = buffer->GetEditPosition();
// Le nom du module se trouve dans la ligne 2 du commentaire.
// Saute les espaces de début et copie l'ancien nom de module,
// pour vérifier si c'est le bon emplacement.
pos->Move(2, 1);
pos->MoveCursor(mmSkipWhite | mmSkipRight);
AnsiString check = pos->RipText("", rfIncludeNumericChars | rfIncludeAlphaChars);
if (check == name)
{
pos->Delete(check.Length());    // Supprimer l'ancien nom.
pos->InsertText(ModuleName);    // Insérer le nouveau nom.
name = ModuleName;              // Mémoriser le nouveau nom.
}
}
}

Que se passe-t'il si l'utilisateur insère des commentaires au-dessus du nom de module ? Dans ce cas, vous devez utiliser le notificateur de modification de ligne pour suivre le numéro de la ligne dans laquelle se trouve le nom du module. Pour ce faire, utilisez les interfaces IOTAEditLineNotifier et IOTAEditLineTracker.

Que se passe-t'il si l'utilisateur insère des commentaires au-dessus du nom de module ? Dans ce cas, vous devez utiliser le notificateur de modification de ligne pour suivre le numéro de la ligne dans laquelle se trouve le nom du module. Pour ce faire, utilisez les interfaces Destroyed, ce qui signifie que le notificateur doit annuler lui-même son recensement et libérer toutes ses références au module. Ensuite, le notificateur doit également se retirer de la liste des notificateurs de l'expert.

Voici la version définitive de la fonction Execute de l'expert. Elle crée le nouveau module, utilise l'interface du module, crée un notificateur de module puis enregistre le notificateur de module dans une liste d'interfaces (TInterfaceList).

procedure DocWizard.Execute;
var
  Svc: IOTAModuleServices;
  Module: IOTAModule;
  Notifier: IOTAModuleNotifier;
begin
  { Renvoyer le projet en cours. }
  Supports(BorlandIDEServices, IOTAModuleServices, Svc);
  Module := Svc.CreateModule(TCreator.Create(creator_type));
  Notifier := TModuleNotifier.Create(Module);
list.Add(Notifier);
end
void __fastcall DocWizard::Execute()
{
_di_IOTAModuleServices svc;
  BorlandIDEServices->Supports(svc);
_di_IOTAModule module = svc->CreateModule(new Creator(creator_type));
_di_IOTAModuleNotifier notifier = new ModuleNotifier(module);
list->Add(notifier);
}

Le destructeur de l'expert parcourt la liste d'interfaces et annule le recensement de chaque notificateur de la liste. Il ne suffit pas de laisser la liste d'interfaces libérer les interfaces qu'elle contient car l'EDI contient également les mêmes interfaces. Vous devez demander à l'EDI de libérer les notificateurs d'interface afin de libérer les objets notificateur. Dans ce cas, le destructeur fait croire aux notificateurs que leurs modules ont été détruits. Dans une situation plus compliquée, il peut s'avérer plus simple d'écrire une fonction Unregister distincte pour la classe du notificateur.

destructor DocWizard.Destroy; override;
var
  Notifier: IOTAModuleNotifier;
  I: Integer;
begin
  { Annuler le recensement de tous les notificateurs de la liste. }
for I := list.Count - 1 downto 0 do
  begin
    Supports(list.Items[I], IOTANotifier, Notifier);
    { Faire comme si l'objet associé a été détruit.
      Ce qui oblige le notificateur à se libérer lui-même. }
    Notifier.Destroyed;
    list.Delete(I);
  end;
  list.Free;
  FItem.Free;
end;
__fastcall DocWizard::~DocWizard()
{
// Annule le recensement de tous les notificateurs de la liste.
for (int i = list->Count; --i >= 0; )
{
_di_IOTANotifier notifier;
    list->Items[i]->Supports(notifier);
// Fait comme si l'objet associé a été détruit.
// Ce qui oblige le notificateur à se libérer lui-même.
notifier->Destroyed();
list->Delete(i);
}
delete list;
delete item;
}

Le reste de l'expert gère les détails du recensement de l'expert, l'installation des options de menu, etc.

Voir aussi