====== Tutorial: Sidebar-View Add-in ======
Dieses Tutorial zeigt, wie ein Add-in ein Panel in der Seitenleiste der Shell bereitstellt. Sidebar-Views eignen sich für navigierende Inhalte: Baumstrukturen, Listen, Filteransichten. Der Host bettet den Frame in die Seitenleiste ein und steuert das Accordion-Verhalten.
Zurück zur [[..:start|Übersicht]].
===== Was gebaut wird =====
Ein Add-in namens "Asset Navigator", das:
* ein Sidebar-Panel im Accordion "Assets" registriert
* einen TreeView mit Anlagekategorien anzeigt
* Kontextmenü-Aktionen für Baumknoten bereitstellt
* Titelleisten-Buttons für Aktualisieren und Hinzufügen bietet
===== Manifest =====
{
"id": "demo.asset-navigator",
"name": "asset-navigator",
"displayName": "Asset Navigator",
"version": "1.0.0",
"publisher": "demo",
"kind": "native",
"main": "bin/AssetNavigator.dll",
"engineVersion": "^1.0.0",
"activationEvents": ["onStartup"],
"contributes": {
"commands": [
{
"command": "assets.nav.refresh",
"title": "Aktualisieren",
"category": "Assets",
"icon": "media/refresh.svg"
},
{
"command": "assets.nav.add",
"title": "Anlage hinzufügen",
"category": "Assets",
"icon": "media/add.svg"
},
{
"command": "assets.nav.edit",
"title": "Bearbeiten",
"category": "Assets"
},
{
"command": "assets.nav.delete",
"title": "Löschen",
"category": "Assets"
}
],
"views": {
"sidebar": [
{
"id": "assets.navigator",
"name": "Anlagen",
"icon": "media/navigator.svg",
"containerId": "assets-explorer"
},
{
"id": "assets.categories",
"name": "Kategorien",
"icon": "media/categories.svg",
"containerId": "assets-explorer"
}
]
},
"menus": {
"view/title": [
{
"command": "assets.nav.refresh",
"when": "view == assets.navigator",
"group": "navigation"
},
{
"command": "assets.nav.add",
"when": "view == assets.navigator",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "assets.nav.edit",
"when": "view == assets.navigator && viewItem == asset",
"group": "modification",
"order": 1
},
{
"command": "assets.nav.delete",
"when": "view == assets.navigator && viewItem == asset",
"group": "modification",
"order": 2
}
]
},
"viewsWelcome": [
{
"viewId": "assets.navigator",
"content": "Noch keine Anlagen vorhanden.\n[Erste Anlage erstellen](command:assets.nav.add)",
"when": "!hasAssets",
"group": "main",
"order": 1
}
]
}
}
Einige Dinge fallen hier auf:
* **Accordion** — Beide Views (''assets.navigator'' und ''assets.categories'') haben dieselbe ''containerId'' (''assets-explorer''). Der Host gruppiert sie zu einem Accordion in der Seitenleiste.
* **Titelleisten-Buttons** — Die Menüeinträge unter ''view/title'' mit ''when'': ''view == assets.navigator'' erscheinen als Buttons in der Titelleiste des Navigator-Panels.
* **Kontextmenü** — Die Einträge unter ''view/item/context'' mit ''viewItem == asset'' erscheinen nur beim Rechtsklick auf einen Baumknoten vom Typ "asset".
* **Welcome-Inhalt** — Wenn keine Anlagen vorhanden sind (''!hasAssets''), zeigt das Panel einen Begrüßungstext mit einem Link, der den Hinzufügen-Command auslöst.
* **onStartup** — Das Add-in wird beim Start aktiviert, weil das Sidebar-Panel sofort sichtbar sein soll. Für Sidebar-Views ist ''onStartup'' der übliche Weg.
===== Navigator Frame =====
unit AssetNavigatorFrame;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Controls, ComCtrls,
WvdS.Document.Host.Api;
type
TAssetNavigatorFrame = class(TWvdSDocumentFrame)
private
FHost: IHost;
FTree: TTreeView;
procedure HandleTreeSelectionChanged(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
procedure Initialize(const AHost: IHost);
procedure RefreshData;
end;
implementation
constructor TAssetNavigatorFrame.Create(AOwner: TComponent);
begin
inherited;
FTree := TTreeView.Create(Self);
FTree.Parent := Self;
FTree.Align := alClient;
FTree.OnSelectionChanged := @HandleTreeSelectionChanged;
end;
procedure TAssetNavigatorFrame.Initialize(const AHost: IHost);
begin
FHost := AHost;
RefreshData;
end;
procedure TAssetNavigatorFrame.RefreshData;
var
RootNode, ChildNode: TTreeNode;
begin
FTree.Items.Clear;
RootNode := FTree.Items.Add(nil, 'Fahrzeuge');
FTree.Items.AddChild(RootNode, 'PKW-001');
FTree.Items.AddChild(RootNode, 'LKW-002');
RootNode := FTree.Items.Add(nil, 'Gebäude');
FTree.Items.AddChild(RootNode, 'Hauptgebäude');
FTree.Items.AddChild(RootNode, 'Lager West');
if FTree.Items.Count > 0 then
FHost.Context.SetContext('hasAssets', 'true')
else
FHost.Context.SetContext('hasAssets', '');
end;
procedure TAssetNavigatorFrame.HandleTreeSelectionChanged(Sender: TObject);
begin
if Assigned(FTree.Selected) and (FTree.Selected.Level > 0) then
// Blattknoten = eine Anlage → Kontextmenü "asset" aktivieren
FHost.Context.SetContext('viewItem', 'asset')
else
// Gruppenknoten → kein Kontextmenü
FHost.Context.SetContext('viewItem', '');
end;
end.
Der Frame setzt Context Keys, die das Kontextmenü steuern. Wenn ein Blattknoten ausgewählt ist, wird ''viewItem'' auf ''asset'' gesetzt, und die Menüeinträge mit ''viewItem == asset'' werden sichtbar. Bei Gruppenknoten verschwindet das Kontextmenü.
===== Plugin-Klasse =====
unit AssetNavigatorPlugin;
{$mode objfpc}{$H+}
interface
uses
WvdS.Document.Host.Api;
type
TAssetNavigatorPlugin = class(TInterfacedObject, IPlugin)
private
FHost: IHost;
FNavigatorFrame: TObject; // Referenz für Commands
public
constructor Create(const AHost: IHost);
procedure Activate(const AContext: IExtensionContext);
procedure Deactivate;
end;
implementation
uses
AssetNavigatorFrame;
type
TRefreshHandler = class(TInterfacedObject, ICommandHandler)
private
FFrame: TAssetNavigatorFrame;
public
constructor Create(AFrame: TAssetNavigatorFrame);
procedure Execute;
end;
{ TAssetNavigatorPlugin }
constructor TAssetNavigatorPlugin.Create(const AHost: IHost);
begin
inherited Create;
FHost := AHost;
end;
procedure TAssetNavigatorPlugin.Activate(const AContext: IExtensionContext);
begin
// Sidebar-Panel registrieren
AContext.Subscribe(
FHost.SideBar.RegisterPanel('assets.navigator', 'Anlagen',
'media/navigator.svg', TAssetNavigatorFrame)
);
// Command-Handler registrieren
AContext.Subscribe(
FHost.Commands.RegisterCommand('assets.nav.refresh', 'Aktualisieren',
TRefreshHandler.Create(nil)) // Frame-Referenz wird später gesetzt
);
AContext.Subscribe(
FHost.Commands.RegisterCommand('assets.nav.add', 'Anlage hinzufügen',
TAddHandler.Create(FHost))
);
AContext.Subscribe(
FHost.Commands.RegisterCommand('assets.nav.edit', 'Bearbeiten',
TEditHandler.Create(FHost))
);
AContext.Subscribe(
FHost.Commands.RegisterCommand('assets.nav.delete', 'Löschen',
TDeleteHandler.Create(FHost))
);
end;
procedure TAssetNavigatorPlugin.Deactivate;
begin
end;
{ TRefreshHandler }
constructor TRefreshHandler.Create(AFrame: TAssetNavigatorFrame);
begin
inherited Create;
FFrame := AFrame;
end;
procedure TRefreshHandler.Execute;
begin
if Assigned(FFrame) then
FFrame.RefreshData;
end;
end.
===== Zusammenspiel =====
- **Start** — Der Host registriert die Views und Commands aus dem Manifest.
- **Aktivierung** — Wegen ''onStartup'' wird die DLL sofort geladen und ''Activate'' aufgerufen.
- **Seitenleiste** — Der Host erzeugt das Accordion "assets-explorer" mit den Panels "Anlagen" und "Kategorien".
- **Titelleiste** — Die Buttons "Aktualisieren" und "Hinzufügen" erscheinen in der Titelleiste des Anlagen-Panels.
- **Baumknoten auswählen** — Der Frame setzt ''viewItem'', und das Kontextmenü wird kontextabhängig aufgebaut.
- **Rechtsklick** — Bei ''viewItem == asset'' zeigt der Host "Bearbeiten" und "Löschen".
Weiter zum [[menu-merge|Menü/Toolbar-Merge Tutorial]] oder zurück zur [[..:start|Übersicht]].