head.WriteLine()

Mittwoch, Januar 30, 2008

Ein Command-Modell für System.AddIn, Teil 1

Nachdem ich hier und hier bereits die Grundlage für erweiterbare Anwendungen auf Basis von Windows Forms gelegt habe, möchte ich nun einen Schritt weiter gehen. Denn die Integration von Add-Ins in die Host-Anwendung macht erst so richtig Sinn, wenn ein gemeinsames Command-Modell existiert, über das eine Bereitstellung von Menü- und Toolbar-Elementen möglich ist.

Im ersten Teil möchte ich zunächst das Modell und die Host-Integration beschreiben, während es im zweiten Teil um die Add-In-Seite gehen soll.

Die Basis des Command-Modells bilden die Interfaces ICommand und UICommand, deren Member in der folgenden Abbildung dargestellt sind:

Commands

Während ICommand die allgemeinen Eigenschaften einer hierarchischen Command-Struktur definiert, legt IUICommand die grafische Repräsentation fest.

Hierbei bildet der Host zunächst seine Menü- und Toolbar-Struktur über Commands ab und übergibt diese an die Add-Ins. Diese können sich daraufhin in die Hierarchie integrieren und eigene Commands anbieten. Für die Manipulation der Struktur werden die Interfaces ICommandService und IUICommandService bereitgestellt. Beide werden über ICommandContext zusammengefasst und während der Aktivierung an das Add-In übergeben.

CommandServices

Während mit ICommandService die benötigten Command-Objekte erstellt werden, ist IUICommandService für die Generierung einer grafischen Repräsentation in Form von Menü- und Toolbar-Elementen verantwortlich. Über die ShowSurface()-Methode von ICommandContext hat das Add-In zusätzlich die Möglichkeit seine Oberfläche an den Host zu übertragen (beispielsweise wenn ein Command aktiviert wurde).

Die Kommunikation zwischen Host und Add-Ins erfolgt über das IAddInCommandContract-Interface, welches von der entsprechenden Host-View-Klasse implementiert wird. Zusätzlich findet sich dessen Signatur auch im jeweiligen Add-In-Contract (hier IAddInContract genannt).

CommandContracts

Nachdem ein Add-In geladen wurde, ruft der Host die InitializeCommands()-Methode auf und übergibt ein ICommandContext-Objekt. Daraufhin registriert sich das Add-In in der Command-Hierarchie. Beim Auslösen eines Commands informiert der Host das Add-In über die NotifyCommandExecuted()-Methode. Optional hat die Host-Anwendung über die GetSurface()-Methode die Möglichkeit, die Oberfläche des Add-In explizit anzufordern.

Was nun noch fehlt ist eine Komponente die auf Host-Seite die Integration der Commands in die jeweilige Menü- und Toolbar-Struktur übernimmt. Hier kommt AddInHost zum Einsatz. Es leitet von Component ab und kann daher direkt aus der Toolbox auf eine Form gezogen werden. AddInHost bekommt über die Eigenschaften MainMenuStrip und MainToolStrip die primäre Menüleiste bzw. Toolbar übergeben. Zusätzlich kann ihr eine Instanz von WindowProxyPanel übergeben werden, welches zur Darstellung der Add-In-Oberfläche dient (Details hier). Auf diese Weise kann AddInHost nicht nur die entsprechenden Commands in Menü und Toolbar anlegen, sondern die Oberfläche auch automatisch binden, wenn das Add-In dies über ICommandContext.ShowSurface() veranlasst. Zusätzlich bietet die Komponente über die ShowAddInManager()-Methode die Möglichkeit den Add-In-Konfigurationsdialog zu öffnen, der für das Laden und Entladen der Add-Ins zuständig ist (Details hier).

CommandComponents

Durch die enge Verzahnung der Komponenten, wird die Implementierung des Add-In-Supports für die Host-Anwendung zum Kinderspiel. Sie muss lediglich die folgenden Komponenten zu Verfügung stellen:

  • MenuStrip
  • ToolStrip
  • WindowProxyPanel
  • AddInHost

Nachdem alle Komponenten mit AddInHost verbunden wurden, sind lediglich die folgenden Codezeilen erforderlich:

// Add-In-Pfad zuweisen
addInHost1.AddInPath = Environment.CurrentDirectory;

// Add-In-Store erstellen/aktualisieren
AddInStore.Rebuild(addInHost1.AddInPath);

// Registrierte Add-Ins vom Typ AddInHostView ermitteln
addInHost1.AvailableAddIns = AddInStore.FindAddIns(
    typeof(AddInHostView), addInHost1.AddInPath);

// Commands für Menü- u. Toolbar-Elemente erstellen
addInHost1.CreateCommands();

Hier wird zunächst der Add-In-Store neu erstellt und daraufhin die verfügbaren Add-Ins über AddInStore.FindAddIns() ermittelt. Die zurückgegebene Liste von AddInToken-Objekten kann daraufhin an AddInHost übergeben werden.

Durch den Aufruf von CreateCommands() erstellt AddInHost automatisch entsprechende ICommand- und IUICommand-Objekte für die vorhandenen Menü- und Toolbar-Elemente.

Für die Erstellung eines Commands wird ein eindeutiger Name benötigt, über den das Add-In später Referenzieren kann. Hierfür stellt AddInHost die Extender Property CommandName bereit. Sie wird automatisch jedem ToolStripItem-Element der Form angehängt und ermöglicht so eine leichte Zuordnung.

CommandNameExtender

Darüber hinaus können gleichartige Menü- und Toolbar-Elemente unter einem Command zusammengefasst werden. Wenn ein Add-In beispielsweise den Command "Host.File.New" deaktiviert (Command.Enabled-Eigenschaft), wird sowohl das Menü, als auch der zugehörige Toolbar-Button automatisch deaktiviert.

Zusätzlich abonniert AddInHost das Click-Event des jeweiligen Elements und informiert seinerseits über das CommandExecuted-Event über dessen Aktivierung. Daher benötigen Sie lediglich einen Event Handler für alle Menü- und Toolbar-Elemente.

Das Öffnen des Add-In-Manager-Dialogs kann beispielsweise über einen Menüpunkt erfolgen und durch den folgenden Event Handler verarbeitet werden:

private void addInHost1_CommandExecuted(
    object sender, CommandExecutedEventArgs e)
{
  if (e.Command.Name == "Host.Extras.AddInManager")
  {
    // Add-In-Manager-Dialog anzeigen
    addInHost1.ShowAddInManager(this, false);

  }
}

Der Dialog kümmert sich nun automatisch darum, die vom Benutzer ausgewählten Add-Ins zu aktivieren und über die InitializeCommands()-Methode die entsprechenden Commands anzufordern. AddInHost integriert daraufhin die bereitgestellten Commands in Menü und Toolbar und somit schließt sich der Kreis.

Für den Host wird die Sache somit denkbar einfach. Welche Arbeiten auf Add-In-Seite zu verrichten sind, erkläre ich im zweiten Teil.

Sourcen inkl. Beispielanwendung gibt's hier.