Einen Experten über IDE-Ereignisse benachrichtigen

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Erweitern der IDE mit speziellen Interfaces


Ein Experte sollte auf IDE-Ereignisse reagieren können. Insbesondere muss jeder Experte, der Modul-Schnittstellen verwendet, erkennen können, ob der Benutzer das Modul schließt, damit der Experte die Schnittstelle freigeben kann. Dazu benötigt der Experte eine entsprechende Benachrichtigung – Sie müssen also eine Notifier-Klasse erstellen.

Alle Notifier-Klassen implementieren eine oder mehrere Notifier-Schnittstellen. Diese definieren Rückrufmethoden: Der Experte registriert ein Notifier-Objekt bei der Tools API, und die IDE ruft den Notifier auf, sobald ein wichtiges Ereignis eintritt.

Jede Notifier-Schnittstelle ist von IOTANotifier abgeleitet, auch wenn deren Methoden in einem bestimmten Notifier nicht alle benötigt werden. Die folgende Tabelle enthält eine kurze Beschreibung aller Notifier-Schnittstellen.

Notifier-Schnittstellen:

Schnittstelle Beschreibung

IOTANotifier

Abstrakte Basisklasse für alle Notifier.

IOTABreakpointNotifier

Im Debugger wird ein Haltepunkt aktiviert oder geändert.

IOTADebuggerNotifier

Im Debugger wird ein Programm ausgeführt oder es werden Haltepunkte eingerichtet oder gelöscht.

IOTAEditLineNotifier

Im Quellcode-Editor werden Zeilen verschoben.

IOTAEditorNotifier

Eine Quelldatei wird geändert oder gespeichert, oder im Editor werden Dateien gewechselt.

IOTAFormNotifier

Ein Formular (oder Datenmodul) oder eine Formularkomponente wird gespeichert oder geändert.

IOTAIDENotifier

Ein Projekt wird geladen, Packages werden installiert, oder es ist ein anderes globales IDE-Ereignis aufgetreten.

IOTAMessageNotifier

In der Meldungsanzeige werden Meldungsgruppen hinzugefügt oder gelöscht.

IOTAModuleNotifier

Ein Modul wird geändert, gespeichert oder umbenannt.

IOTAProcessModNotifier

Ein Prozessmodul wird in den Debugger geladen.

IOTAProcessNotifier

Im Debugger werden Threads oder Prozesse erzeugt oder freigegeben.

IOTAThreadNotifier

Im Debugger wird ein Thread-Status geändert.

IOTAToolsFilterNotifier

Es wird ein Tool-Filter aktiviert.


Betrachten Sie als Beispiel für den Notifier-Einsatz das Beispiel unter Formulare und Projekte erstellen. Mithilfe von Modul-Creators wird im Beispiel ein Experte erstellt, der einen Kommentarblock in jede Quelldatei einfügt. Dieser enthält zwar den anfänglichen Namen der Unit, aber der Benutzer speichert die Datei fast immer unter einem anderen Namen. Deshalb wäre es bequem für den Benutzer, wenn der Experte den Kommentar aktualisieren und den tatsächlichen Namen eintragen würde.

Dazu benötigen Sie einen Modul-Notifier. Der Experte speichert die von CreateModule zurückgegebene Modulschnittstelle und registriert damit einen Modul-Notifier. Dieser wird benachrichtigt, sobald der Benutzer die Datei ändert oder speichert. Solche Ereignisse sind für diesen Experten jedoch nicht von Bedeutung, und deshalb besitzen AfterSave und alle ähnlichen Funktionen nur einen leeren Funktionsrumpf. Die wichtige Funktion ist ModuleRenamed, die von der IDE aufgerufen wird, wenn der Benutzer die Datei unter einem neuen Namen speichert. Die Deklaration für den Modul-Notifier lautet wie folgt:

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;        // Speichert den alten Modulnamen
int index;              // Notifier-Index
};

Sie können den Notifier so anlegen, dass er sich in seinem Konstruktor automatisch selbst registriert. Der Destruktor macht die Registrierung rückgängig. Bei einen Modul-Notifier ruft die IDE die Methode Destroyed auf, sobald der Nutzer die Datei schließt. In diesem Fall muss der Notifier seine Registrierung rückgängig machen und seine Referenz auf die Modulschnittstelle freigeben. Die IDE löscht ihre Referenz auf den Notifier, wodurch die Referenzzählung auf 0 gesetzt und das Objekt freigegeben wird. Sie müssen deshalb bei der Definition des Destruktors beachten, dass der Notifier möglicherweise schon nicht mehr registriert ist.

constructor TModuleNotifier.Create( const Module: IOTAModule);
begin
  FIndex := -1;
  FModule := Module;
  { Diesen Notifier registrieren. }
  FIndex := Module.AddNotifier(self);
  { Den alten Modulnamen speichern. }
  FName := ChangeFileExt(ExtractFileName(Module.FileName), );
end;
destructor TModuleNotifier.Destroy;
begin
  { Notifier austragen (sofern noch nicht erfolgt). }
  if Findex >= 0 then
    FModule.RemoveNotifier(FIndex);
end;
procedure TModuleNotifier.Destroyed;
begin
   { Die Modulschnittstelle ist zerstört, also den Notifier bereinigen. }
  if Findex >= 0 then
  begin
    { Notifier austragen. }
    FModule.RemoveNotifier(FIndex);
    FIndex := -1;
  end;
  FModule := nil;
end;
__fastcall ModuleNotifier::ModuleNotifier(const _di_IOTAModule module)
: index(-1), module(module)
{
// Diesen Notifier registrieren.
index = module->AddNotifier(this);
// Den alten Modulnamen speichern
name = ChangeFileExt(ExtractFileName(module->FileName), "");
}
__fastcall ModuleNotifier::~ModuleNotifier()
{
// Notifier austragen (sofern noch nicht erfolgt).
if (index >= 0)
module->RemoveNotifier(index);
}
void __fastcall ModuleNotifier::Destroyed()
{
// Die Modulschnittstelle ist zerstört, also den Notifier bereinigen.
if (index >= 0)
{
// Notifier austragen.
module->RemoveNotifier(index);
index = -1;
}
module = 0;
}

Die IDE ruft die Notifier-Funktion ModuleRenamed auf, wenn der Benutzer die Datei umbenennt. Dabei wird der neue Dateiname als Parameter übergeben. Diesen Namen trägt der Experte im Kommentarblock der Datei ein. Zur Bearbeitung des Quellpuffers benutzt der Experte eine EditPosition-Schnittstelle. Er ermittelt die richtige Position, prüft auf zweifache Weise, ob der richtige Text gefunden wurde, und ersetzt ihn durch den neuen Namen.

procedure TModuleNotifier.ModuleRenamed(const NewName: string);
var
  ModuleName: string;
  I: Integer;
  Editor: IOTAEditor;
  Buffer: IOTAEditBuffer;
  Pos: IOTAEditPosition;
  Check: string;
begin
  { Modulnamen aus dem neuen Dateinamen ermitteln. }
  ModuleName := ChangeFileExt(ExtractFileName(NewName), );
for I := 0 to FModule.GetModuleFileCount - 1 do
  begin
   { Jeden Quellpuffer aktualisieren. }
    Editor := FModule.GetModuleFileEditor(I);
    if Supports(Editor, IOTAEditBuffer, Buffer) then
    begin
      Pos := Buffer.GetEditPosition;
{ Der Modulname steht in der 2. Kommentarzeile.
        Führende Leerzeichen überspringen und den alten Modulnamen kopieren,
        dadurch doppelt prüfen, ob die Position richtig ist. }
      Pos.Move(2, 1);
      Pos.MoveCursor(mmSkipWhite or mmSkipRight);
Check := Pos.RipText(, rfIncludeNumericChars or rfIncludeAlphaChars);
if Check = FName then
      begin
        Pos.Delete(Length(Check));    // Den alten Namen löschen.
        Pos.InsertText(ModuleName);   // Den neuen Namen einfügen.
        FName := ModuleName;          // Den neuen Namen speichern.
      end;
    end;
  end;
end;
void __fastcall ModuleNotifier::ModuleRenamed(const AnsiString NewName)
{
// Modulnamen aus dem neuen Dateinamen ermitteln.
AnsiString ModuleName = ChangeFileExt(ExtractFileName(NewName), "");
for (int i = 0; i < module->GetModuleFileCount(); ++i)
{
// Jeden Quellpuffer aktualisieren.
_di_IOTAEditor editor = module->GetModuleFileEditor(i);
_di_IOTAEditBuffer buffer;
if (editor->Supports(buffer))
{
_di_IOTAEditPosition pos = buffer->GetEditPosition();
// Der Modulname steht in der 2. Kommentarzeile.
// Führende Leerzeichen überspringen und den alten Modulnamen kopieren,
// dadurch doppelt prüfen, ob die Position richtig ist.
pos->Move(2, 1);
pos->MoveCursor(mmSkipWhite | mmSkipRight);
AnsiString check = pos->RipText("", rfIncludeNumericChars | rfIncludeAlphaChars);
if (check == name)
{
pos->Delete(check.Length());    // Alten Namen löschen
pos->InsertText(ModuleName);    // Neuen Namen einfügen.
name = ModuleName;              // Neuen Namen merken.
}
}
}
}

Wenn Sie die Möglichkeit berücksichtigen wollen, dass der Benutzer zusätzliche Zeilen über der Namenszeile eingibt, müssen Sie einen Notifier benutzen, der bemerkt, wenn sich die Nummer der Zeile mit dem Modulnamen ändert. Verwenden Sie hierzu die Schnittstellen IOTAEditLineNotifier und IOTAEditLineTracker.

Beim Schreiben von Notifier-Schnittstellen ist Vorsicht geboten. Wichtig ist, dass kein Notifier seinen Experten überlebt. Erstellt der Benutzer z. B. mit einem Experten eine neue Unit und entlädt er anschließend den Experten, darf kein Notifier mehr mit der Unit verknüpft sein. Ansonsten würde die IDE wahrscheinlich abstürzen, zumindest würden aber unvorhersehbare Reaktionen auftreten. Der Experte muss somit die Registrierung seiner sämtlichen Notifier aufheben, bevor er zerstört wird. Schließt der Benutzer dagegen zuerst die Datei, erhält der Modul-Notifier darüber eine Destroyed-Benachrichtigung, worauf hin er seine eigene Registrierung aufheben und alle Referenzen auf das Modul freigeben muss. Zudem muss sich der Notifier auch aus der Notifier-Hauptliste des Experten austragen.

Der folgende Quelltext ist die endgültige Version der Execute-Funktion des Experten. Sie erstellt ein neues Modul, benutzt die Modulschnittstelle, erzeugt einen Modul-Notifier und speichert diesen in einer Schnittstellenliste (TInterfaceList).

procedure DocWizard.Execute;
var
  Svc: IOTAModuleServices;
  Module: IOTAModule;
  Notifier: IOTAModuleNotifier;
begin
  { Aktuelles Projekt zurückgeben. }
  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);
}

Der Destruktor des Experten durchläuft die Schnittstellenliste und hebt die Registrierung aller enthaltenen Notifier auf. Eine Aufhebung durch die Schnittstellenliste selbst wäre nicht ausreichend, da die IDE dieselben Schnittstellen speichert. Sie müssen die IDE deshalb anweisen, die Notifier-Schnittstellen und damit die Notifier-Objekte freizugeben. Im vorliegenden Fall veranlasst der Destruktor die Notifier, davon auszugehen, dass ihre Module bereits zerstört sind. Für komplexere Fälle sollten Sie eine separate Unregister-Funktion für die Notifier-Klasse erstellen.

destructor DocWizard.Destroy; override;
var
  Notifier: IOTAModuleNotifier;
  I: Integer;
begin
  { Registrierung aller Notifier in der Liste aufheben. }
for I := list.Count - 1 downto 0 do
  begin
    Supports(list.Items[I], IOTANotifier, Notifier);
    {Vorgeben, dass das verknüpfte Objekt zerstört ist.
      Dadurch bereinigt der Notifier sich selbst. }
    Notifier.Destroyed;
    list.Delete(I);
  end;
  list.Free;
  FItem.Free;
end;
__fastcall DocWizard::~DocWizard()
{
// Registrierung aller Notifier in der Liste aufheben.
for (int i = list->Count; --i >= 0; )
{
_di_IOTANotifier notifier;
    list->Items[i]->Supports(notifier);
// Vorgeben, dass das verknüpfte Objekt zerstört ist.
// Dadurch bereinigt der Notifier sich selbst.
notifier->Destroyed();
list->Delete(i);
}
delete list;
delete item;
}

Der restliche Teil des Experten erledigt die Registrierung, die Installation der Menübefehle usw.

Siehe auch