Ziel dieses Tutorials ist es, das zentrale Dispatch-Muster der WvdS Add-in Host Shell zu verstehen und korrekt anzuwenden. Im Unterschied zu klassischen Plugin-Architekturen, in denen der Host Fenster oder Views direkt erzeugt, delegiert die Shell jede Benutzeraktion über eine einzige Stelle: den ShellCommandRouter. Der Vorteil besteht darin, dass Builtin-Handler, WelcomePage-Links, Toolbar-Buttons und Tastenkürzel denselben Code-Pfad durchlaufen — Lazy-Loading, Aktivierung und Fehlerbehandlung greifen automatisch, ohne dass der Aufrufer etwas davon wissen muss.
Zurück zur Übersicht.
Die Shell besitzt einen einzigen Dispatch-Punkt für alle Command-Aufrufe: den ShellCommandRouter. Jeder Auslöser — ob Menüklick, Toolbar-Button, Tastenkürzel, WelcomePage-Link oder programmatischer Aufruf — landet am selben Router. Von dort übernimmt der Host:
Kein Aufrufer muss wissen, ob der Ziel-Command nativ, per Script oder noch gar nicht geladen ist.
Jeder Command, der über den Router erreichbar sein soll, muss im plugin.json stehen. Die Deklaration allein reicht aus, damit der Host einen Lazy-Stub registriert und den Command in der gesamten UI sichtbar macht.
{ "contributes": { "commands": [ { "command": "wis.proofExec.open", "title": "%cmdProofExecTitle%", "category": "WIS", "icon": "media/checklist.svg" } ] } }
Ab diesem Zeitpunkt erscheint der Command in der CommandPalette, kann in Menüs, Toolbar und Keybindings referenziert werden — obwohl noch keine DLL geladen ist.
In der Activate-Methode registriert das Add-in den echten Handler. Der Host ersetzt damit den Lazy-Stub.
type TOpenProofExecHandler = class(TInterfacedObject, ICommandHandler) private FHost: IHost; public constructor Create(const AHost: IHost); procedure Execute; end; procedure TOpenProofExecHandler.Execute; begin FHost.Documents.OpenDocument('wis.proofExec'); end; procedure TWISPlugin.Activate(const AContext: IExtensionContext); begin AContext.Subscribe( FHost.Commands.RegisterCommand('wis.proofExec.open', 'Prüfausführung öffnen', TOpenProofExecHandler.Create(FHost)) ); end;
Der Handler ruft FHost.Documents.OpenDocument auf — er erzeugt nicht selbst einen Frame oder ein Fenster. Das ist Aufgabe der registrierten IWvdSDocumentFactory (siehe Document-View Tutorial).
Wenn Shell-eigener Code oder ein anderes Add-in den Command auslösen will, genügt ein einziger Aufruf:
FHost.Commands.ExecuteCommand('wis.proofExec.open');
Dieser Aufruf funktioniert wie ein Benutzerklick: Er löst die Aktivierung des Ziel-Add-ins aus, falls es noch nicht geladen ist, und führt dann den Handler aus. Das ist der korrekte Weg, wie Builtin-Handler an Plugin-Commands delegieren.
Links auf der WelcomePage verwenden das command:-Schema. Beim Klick durchläuft der Link denselben Router:
Klick auf <a href="command:wis.proofExec.open"> → OnHotClick → Validierung des command:-Schemas → ShellCommandRouter.ExecuteCommand() → Lazy-Stub oder echter Handler
Es gibt keinen separaten Code-Pfad für WelcomePage-Links — sie sind gleichberechtigte Command-Auslöser.
Das folgende Muster ist falsch und darf nicht verwendet werden:
// FALSCH: Builtin-Handler erzeugt Plugin-View direkt procedure TShellBuiltinHandler.Execute; var LFrame: TWISProofExecFrame; begin LFrame := TWISProofExecFrame.Create(FTabContainer); LFrame.Parent := FTabContainer; LFrame.Initialize(FHost); end;
Drei Probleme entstehen:
DoCanClose, kein Context-Wechsel.Der vollständige Ablauf bei einem Command-Aufruf über den Router:
ExecuteCommand.TLazyCommandHandler ist: Stub entfernt sich, DLL wird geladen, ABI-Handshake, CreatePlugin, Activate — das Add-in registriert den echten Handler.ICommandHandler.Execute wird aufgerufen.Dieser Ablauf ist identisch für alle Auslöser. Es gibt keine Sonderwege.
| Richtig | Falsch |
FHost.Commands.ExecuteCommand('plugin.command') | Plugin-Klassen direkt instanziieren |
| Command-ID als einzige Schnittstelle | Plugin-DLL im Shell-Code importieren |
| Host übernimmt Lifecycle automatisch | Frame manuell in Container einbetten |
| Lazy-Loading funktioniert transparent | DLL muss beim Start geladen sein |
Das Routing über den ShellCommandRouter ist kein optionales Pattern, sondern die verbindliche Architektur. Jeder Builtin-Handler, jeder WelcomePage-Link und jeder programmatische Aufruf muss diesen Weg nehmen.
Weiter zum Script-Command Tutorial oder zurück zur Übersicht.