Einen Experten über IDE-Ereignisse benachrichtigen
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.