Inhaltsverzeichnis
Tutorial: Document-View Add-in
Dieses Tutorial erweitert das Hello World Add-in um eine Dokumentansicht. Das Add-in registriert einen Dokumenttyp mit einer Factory und stellt einen TWvdSDocumentFrame bereit, den der Host als Tab in die Shell einbettet.
Zurück zur Übersicht.
Was gebaut wird
Ein Add-in namens „Asset Editor“, das:
- einen Command „Anlage öffnen“ registriert
- eine
IWvdSDocumentFactorybereitstellt - bei Aufruf des Commands einen Dokument-Tab öffnet
- Dirty-State, Titel und Speichern unterstützt
Manifest
{ "id": "demo.asset-editor", "name": "asset-editor", "displayName": "Asset Editor", "version": "1.0.0", "publisher": "demo", "kind": "native", "main": "bin/AssetEditor.dll", "engineVersion": "^1.0.0", "activationEvents": [ "onCommand:assets.open" ], "contributes": { "commands": [ { "command": "assets.open", "title": "Anlage öffnen", "category": "Assets", "icon": "media/asset-open.svg" }, { "command": "assets.save", "title": "Anlage speichern", "category": "Assets" } ], "menus": { "menuBar/file": [ { "command": "assets.open", "group": "open", "order": 10 } ] }, "toolbar": [ { "toolbarId": "actionbar", "command": "assets.save", "icon": "media/save.svg", "tooltip": "Anlage speichern", "when": "activeDocument == assets.editor && isDirty" } ], "keybindings": [ { "command": "assets.save", "key": "Ctrl+S", "when": "activeDocument == assets.editor" } ] } }
Hier fallen mehrere Dinge zusammen: Der Save-Command ist nur in der ActionBar sichtbar, wenn ein Asset-Editor aktiv ist und ungespeicherte Änderungen vorliegen. Das Tastenkürzel Ctrl+S ist ebenfalls kontextgebunden. Dadurch stört das Add-in nicht, wenn ein anderer Dokumenttyp aktiv ist.
Document Factory
Die Factory erzeugt auf Anfrage einen neuen Frame und teilt dem Host den Dokumenttyp mit.
unit AssetEditorFactory; {$mode objfpc}{$H+} interface uses Classes, WvdS.Document.Host.Api, AssetEditorFrame; type TAssetEditorFactory = class(TInterfacedObject, IWvdSDocumentFactory) private FHost: IHost; public constructor Create(const AHost: IHost); function CreateFrame(AOwner: TComponent): TWvdSDocumentFrame; function GetDocumentType: string; function GetDocumentKind: TDocumentKind; function GetDisplayName: string; function GetIcon: string; end; implementation constructor TAssetEditorFactory.Create(const AHost: IHost); begin inherited Create; FHost := AHost; end; function TAssetEditorFactory.CreateFrame(AOwner: TComponent): TWvdSDocumentFrame; var Frame: TAssetEditorFrame; begin Frame := TAssetEditorFrame.Create(AOwner); Frame.Initialize(FHost); Result := Frame; end; function TAssetEditorFactory.GetDocumentType: string; begin Result := 'assets.editor'; end; function TAssetEditorFactory.GetDocumentKind: TDocumentKind; begin Result := dkMultiInstance; end; function TAssetEditorFactory.GetDisplayName: string; begin Result := 'Asset Editor'; end; function TAssetEditorFactory.GetIcon: string; begin Result := 'media/asset-open.svg'; end; end.
dkMultiInstance erlaubt mehrere Tabs desselben Typs. Wenn nur ein einziger Tab erlaubt sein soll (etwa für eine Übersichtsliste), wäre dkSingleInstance die richtige Wahl.
Document Frame
Der Frame ist ein gewöhnlicher LCL-TFrame, der von TWvdSDocumentFrame erbt. Er enthält die eigentliche Benutzeroberfläche.
unit AssetEditorFrame; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Controls, Forms, StdCtrls, WvdS.Document.Host.Api; type TAssetEditorFrame = class(TWvdSDocumentFrame) private FHost: IHost; FContext: IDocumentContext; FNameEdit: TEdit; FDescMemo: TMemo; procedure HandleChange(Sender: TObject); protected function DoCanClose: Boolean; override; procedure DoSave; override; procedure DoActivate; override; procedure DoDeactivate; override; public constructor Create(AOwner: TComponent); override; procedure Initialize(const AHost: IHost); procedure SetDocumentContext(const AContext: IDocumentContext); end; implementation uses Dialogs; constructor TAssetEditorFrame.Create(AOwner: TComponent); var Lbl: TLabel; begin inherited; Lbl := TLabel.Create(Self); Lbl.Parent := Self; Lbl.Caption := 'Bezeichnung:'; Lbl.Left := 12; Lbl.Top := 12; FNameEdit := TEdit.Create(Self); FNameEdit.Parent := Self; FNameEdit.Left := 12; FNameEdit.Top := 32; FNameEdit.Width := 300; FNameEdit.OnChange := @HandleChange; Lbl := TLabel.Create(Self); Lbl.Parent := Self; Lbl.Caption := 'Beschreibung:'; Lbl.Left := 12; Lbl.Top := 64; FDescMemo := TMemo.Create(Self); FDescMemo.Parent := Self; FDescMemo.Left := 12; FDescMemo.Top := 84; FDescMemo.Width := 300; FDescMemo.Height := 200; FDescMemo.OnChange := @HandleChange; end; procedure TAssetEditorFrame.Initialize(const AHost: IHost); begin FHost := AHost; end; procedure TAssetEditorFrame.SetDocumentContext(const AContext: IDocumentContext); begin FContext := AContext; FContext.SetTitle('Neue Anlage'); end; procedure TAssetEditorFrame.HandleChange(Sender: TObject); begin if Assigned(FContext) then begin FContext.SetDirty(True); FHost.Context.SetContext('isDirty', 'true'); end; end; function TAssetEditorFrame.DoCanClose: Boolean; begin if Assigned(FContext) and FContext.GetDirty then begin case MessageDlg('Änderungen speichern?', mtConfirmation, [mbYes, mbNo, mbCancel], 0) of mrYes: begin DoSave; Result := True; end; mrNo: Result := True; else Result := False; end; end else Result := True; end; procedure TAssetEditorFrame.DoSave; begin // Hier: Daten speichern (Datei, Datenbank, API) FHost.Notifications.ShowInfo('Anlage gespeichert: ' + FNameEdit.Text); if Assigned(FContext) then FContext.SetDirty(False); FHost.Context.SetContext('isDirty', ''); end; procedure TAssetEditorFrame.DoActivate; begin FHost.Context.SetContext('activeDocument', 'assets.editor'); if Assigned(FContext) and FContext.GetDirty then FHost.Context.SetContext('isDirty', 'true') else FHost.Context.SetContext('isDirty', ''); end; procedure TAssetEditorFrame.DoDeactivate; begin FHost.Context.SetContext('activeDocument', ''); FHost.Context.SetContext('isDirty', ''); end; end.
Die vier virtuellen Methoden bilden den Vertrag zwischen Frame und Host:
DoCanClose— Der Host fragt vor dem Schließen. Gibt der FrameFalsezurück, bleibt der Tab offen.DoSave— Der Host ruft diese Methode beiCtrl+Sauf. Der Frame speichert und setztDirty := False.DoActivate— Der Host ruft diese Methode beim Tab-Wechsel auf. Der Frame setzt Context Keys, damit die ActionBar und die Menüs korrekt reagieren.DoDeactivate— Der Host ruft diese Methode auf, wenn ein anderer Tab aktiviert wird. Der Frame räumt seine Context Keys auf.
Plugin-Klasse
unit AssetEditorPlugin; {$mode objfpc}{$H+} interface uses WvdS.Document.Host.Api; type TAssetEditorPlugin = class(TInterfacedObject, IPlugin) private FHost: IHost; public constructor Create(const AHost: IHost); procedure Activate(const AContext: IExtensionContext); procedure Deactivate; end; implementation uses AssetEditorFactory; type TOpenAssetHandler = class(TInterfacedObject, ICommandHandler) private FHost: IHost; public constructor Create(const AHost: IHost); procedure Execute; end; TSaveAssetHandler = class(TInterfacedObject, ICommandHandler) private FHost: IHost; public constructor Create(const AHost: IHost); procedure Execute; end; { TAssetEditorPlugin } constructor TAssetEditorPlugin.Create(const AHost: IHost); begin inherited Create; FHost := AHost; end; procedure TAssetEditorPlugin.Activate(const AContext: IExtensionContext); begin // Document Factory registrieren AContext.Subscribe( FHost.Documents.RegisterFactory('assets.editor', TAssetEditorFactory.Create(FHost)) ); // Command-Handler registrieren AContext.Subscribe( FHost.Commands.RegisterCommand('assets.open', 'Anlage öffnen', TOpenAssetHandler.Create(FHost)) ); AContext.Subscribe( FHost.Commands.RegisterCommand('assets.save', 'Anlage speichern', TSaveAssetHandler.Create(FHost)) ); end; procedure TAssetEditorPlugin.Deactivate; begin end; { TOpenAssetHandler } constructor TOpenAssetHandler.Create(const AHost: IHost); begin inherited Create; FHost := AHost; end; procedure TOpenAssetHandler.Execute; begin FHost.Documents.OpenDocument('assets.editor'); end; { TSaveAssetHandler } constructor TSaveAssetHandler.Create(const AHost: IHost); begin inherited Create; FHost := AHost; end; procedure TSaveAssetHandler.Execute; begin // Der Host leitet den Save-Command an den aktiven Frame weiter end; end.
In Activate werden drei Dinge registriert: die Document Factory und zwei Command-Handler. Alle drei Registrierungen geben ein IDisposable zurück, das an AContext.Subscribe übergeben wird. Beim Deaktivieren des Add-ins räumt der Host alles automatisch auf.
Zusammenspiel
Der Ablauf beim Öffnen eines Dokuments:
- Benutzer wählt „Anlage öffnen“ im Menü.
- Host ruft
TOpenAssetHandler.Executeauf. - Handler ruft
FHost.Documents.OpenDocument('assets.editor')auf. - Host fragt
TAssetEditorFactory.GetDocumentKindab →dkMultiInstance. - Host ruft
TAssetEditorFactory.CreateFrame(TabContainer)auf. - Factory erzeugt
TAssetEditorFrameund gibt ihn zurück. - Host bettet den Frame in einen neuen Tab ein.
- Host ruft
SetDocumentContextauf. Der Frame setzt den Titel. - Host ruft
DoActivateauf. Der Frame setzt Context Keys. - Die ActionBar zeigt den Save-Button (wegen
when:activeDocument == assets.editor).
Weiter zum Sidebar-View Tutorial oder zurück zur Übersicht.