Der folgende Artikel befasst sich mit der Erstellung von Oberflächenelementen in Microsoft Office Addins, welche Probleme autreten können und wie man Sie lösen kann. Die Code-Snippets und das am Ende des Posts bereitgestellte Beispielprojekt beziehen sich auf ein Outlook-Addin, die verwendeten Techniken lassen sich aber auch direkt auf Word- oder Excel-Addins anwenden.
Designer vs. XML
Wenn man mit Visual Studio eine GUI für ein Microsoft Office Addin erstellen möchte hat man die Wahl, ob man mit dem grafischen Designer oder direkt im XML-Editor arbeiten möchte. Sicherlich ist die erste Variante einfacher, beschränkt sich aber auf das Erstellen von Ribbons. Manche Anpassungen der GUI, wie zum Beispiel das Kontextmenü, können somit nur per XML-Datei manipuliert werden. Leider lassen sich die unterschiedlichen Methoden nicht mischen¹, man muss in komplexeren Szenarien durchgängig die Oberfläche in XML beschreiben.
Neben dem geringeren Komfort für den Entwickler gibt es aber noch weitere Schattenseiten. Besonders dynamische Oberflächen, die Lokalisierung verwenden oder Elemente bedingt ausblenden, sind hiermit recht umständlich abzubilden. Der größte Nachteil kommt aber zum tragen, wenn man verschiedene Oberflächenelemente, wie Ribbon und Kontextmenu, unabhängig voneinander bei unterschiedlichen RiddonIDs verwenden möchte. Hierzu muss man nämlich für jede Kombination der Elemente eine eigene XML-Datei erstellen. Damit sinkt wiederum die Wartbarkeit und Wiederverwendbarkeit des Quellcodes. Doch dieses Problem lässt sich beheben. Warum beschreibt man nicht einfach jedes Oberflächenelement in einer XML-Datei und fügt sie dann im Programmcode zur konkreten Oberfläche zusammen?
Funktionsweise von Ribbon XML
Zum besseren Verständnis der ganzen Lösung beschreibe ich nochmal kurz die Funktionsweise von Ribbon XML. Wenn man dem AddIn-Projekt ein Ribbon XML Element hinzufügt wird nicht nur eine XML-Datei, sondern auch eine zugehörige Klasse erzeugt. Der Hook für die Ausführung von Ribbon XML ist die Funktion "CreateRibbonExtensibilityObject()" in der ThisAddin-Klasse.
Hiermit wird beim Laden der grafischen Oberfläche die Funktion "GetCustomUI" in der Klasse "CustomUIManager" aufgerufen und bietet damit die Möglichkeit eigene Oberflächenelemente zu erstellen. Wenn man mehrere getrennte Oberflächenelemente erzeugen können möchte und damit mehrere XML-Dateien verwendet, muss eine der automatisch erzeugten Klassen als Controller verwenden, die dann die Oberflächenerstellung übernimmt. Genau dort steckt auch der Kern der ganzen Lösung. Abhängig von der an die Funktion "GetCustomUI" übergebenen RibbonID, wird das entsprechende RibbonXML-String erzeugt und zurückgegeben. Die RibbonID ist ein String der angibt welcher Teil der Anwendungsoberfläche gerade geladen wird. Eine vollständige Liste der RibbonIDs in Microsoft Outlook kann im MSDN nachgelesen werden. Die restlichen Office-Anwendungen besitzen keine so komplexe Unterteilung der Oberfläche und verwenden jeweils nur eine RibbonID (im Einzelnen Microsoft.Word.Document, Microsoft.Excel.Workbook und Microsoft.PowerPoint.Presentation).
Auf den ersten Blick mag es vielleicht etwas unübersichtlich aussehen, ist aber ganz einfach. Jedes mal wenn die Oberfläche geladen wird, z.B. beim Anwendungsstart oder durch Interaktion des Nutzers, wird die Funktion "GetCustomUI" aufgerufen. In dieser wird dann die XML-Repräsenation der eigenen Oberflächenelemente erzeugt und zurückgegeben. Die einzelnen Schritte sind:
- Prüfen ob für die übergebene RibbonID eine eigene Oberfläche erzeugt werden soll.
- ja: weiter mit den folgenden Schritten 2 bis 4
- nein: sofort zurückkehren um die Ausführung nicht zu blockieren
- Lokalisieren der erzeugten Oberfläche mit der Funktion "LocalizeRibbonXML". Hierfür werden lediglich alle Einträge einer Ressourcendatei geladen um die Texte der Oberfläche zu setzen.
- Bedingtes Ausblenden von Elementen mit der Funktion "HideRibbonXMLElement". Hiermit könnnen einzelne Oberflächenelemente, falls sie aus bestimmten Gründen nicht angezeigt werden sollen, ausgeblendet werden.
- Zurückgeben des erzeugten XML-Strings.
Außerdem habe ich noch die Funktion "OnRibbonElement_Click" für das Eventhandling hinzugefügt um Nutzereingaben entsprechend behandeln zu können.
Das Vorgehen setzt natürlich vorraus, dass die verwendeten XML-Dateien keine XML-Header und CustomUI-Tags enthalten, der Inhalt der Datei "OUI_ContextMenu.xml" sieht bei mir zum Beispiel so aus:
Zum Ausprobieren, Weiterentwicklen und Wiederverwenden habe ich mein vollständiges Visual Studio 2010-DemoProjekt für ein Outlook 2010-AddIn zum Download angehängt. Das Konzept wurde ebenfalls mit Visual Studio 2012 und Office 2013 getestet, kann also auch in neueren Projekten angewendet werden.
1 Es ist nicht ganz richtig, dass sich Designer- und XML-Methode nicht in einem Projekt gemeinsam verwenden lassen. Wenn man die Funktion "CreateRibbonExtensibilityObject()" in der ThisAddin-Klasse, die für die Verwendung der XML-Methode benötigt wird.