Archive for the ‘Microsoft Office Programmierung’ Category

Erstellung eines AddIns für Word 2007 – Teil 6 (Anzeige der Ergebnisse in einem CustomTaskPane)

Sunday, November 16th, 2008

Hallo,

im letzten Teil dieser Serie habe ich beschrieben wie man bestimmte Fragmente eines Dokumentes anhand der Formatvorlage (engl. “style”) findet. Eine genaue Beschreibung der Überprüfung der damit gefunden OCL-Fragmente ist im Rahmen von postings über Microsoft Office Programmierung weniger interessant.

Relevanter wird es wieder bei der Frage nach der Darstellung der Ergebnisse.

Hierfür gibt es in den von mir verwendeten OCL libraries bereits ein UserControl “ErrorList”, das im Rahmen des Office-AddIns wiederverwendet werden soll. Dieses control stellt Fehler tabellarisch dar und signalisiert einen Doppelklick auf einen Fehlereintrag mit einem event:

Um ein solches control mit einem Dokument zu assoziieren bietet sich eine CustomTaskPane an.

Eine solche CustomTaskPane ist ein container für ein UserControl der gleichzeitig zu dem Dokument angezeigt wird:

CustomTaskPane mit UserControl in Word 2007 Dokument

Die CustomTaskPane kann vom Anwender per drag and drop an jeder Seite des Dokumentes gedockt werden. Bei Bedarf kann man davon auch per event benachrichtigt werden um z.B. das Layout im gehosteten UserControl anzupassen.

Unser AddIn gibt dem Benutzer die Möglichkeit die CustomTaskPane mit den Ergebnisse des OCL checks unsichtbar oder sichtbar zu machen (mit der “Display check results” checkbox im “OCL” tab des ribbons – siehe obiges Bild).

Dadurch wird das handling nicht völlig trivial, weshalb ich im folgenden einen möglichen Ansatz für den Umgang mit der CustomTaskPane vorschlagen will. Eine Überlegung besteht darin, dass die Steuerung der Sichtbarkeit nicht in den Eventhandlern des ribbons erfolgen sollte, sondern in der Klasse “ThisAddIn” die die Steuerung des AddIns zentral implementieren sollte.
Um dies zu erreichen, wurde für die generierte Ribbon-Klasse ein neuer event “OnDisplayResultTaskPane” eingeführt. Falls nun die checkbox “Display check result” geclickt wird, erzeugt das ribbon lediglich einen entsprechenden event. Die ThisAddIn-Klasse meldet sich für diesen event an und reagiert dann entsprechend indem sie die CustomTaskPane sichtbar oder unsichtbar macht.

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    ....
    ribbonTabOcl.Instance.OnDisplayResultTaskPane += this.OnDisplayResultTaskPane;
    ....
}

Das Erzeugen der CustomTaskPane ist so implementiert:

private ErrorList CreateCustomTaskPane()
{
    if (this.CustomTaskPanes.Contains(m_checkResultsTaskPane))
    {
        this.CustomTaskPanes.Remove(m_checkResultsTaskPane);
    }
 
    ErrorList el = new ErrorList();
 
    el.OnErrorSelected += this.OnErrorSelected;
    m_checkResultsTaskPane = this.CustomTaskPanes.Add(el, "OCL check results");
    m_checkResultsTaskPane.DockPosition =                            Microsoft.Office.Core.MsoCTPDockPosition.msoCTPDockPositionBottom;
    m_checkResultsTaskPane.VisibleChanged += new EventHandler(m_checkResultsTaskPane_VisibleChanged);
 
    return (el);
}

In Zeile 11 wird das CustomTaskPane-Objekt erzeugt. Im Konstruktor wird dabei das UserControl übergeben, das in der CustomTaskPane dargestellt werden soll. Es empfiehlt sich, das UserControl an den Aufrufer zurückzugeben, damit dieser dort Eventhandler registrieren kann und properties des UserControls setzen oder lesen kann.

Ich habe die Beobachtung gemacht, dass das managed CustomTaskPane-Objekt gelegentlich aufgrund der Benutzerinteraktionen sein natives Objekt “verliert” ohne das man dies durch einen event gemeldet bekommt. In einem solchen Fall gäbe es eine Exception beim Zugriff auf das CustomTaskPane-Objekt. Deshalb wird die Methode CreateCustomTaskPane() nach jedem click auf den “Check all expressions” button aufgerufen, obwohl dies streng genommen nicht nötig wäre.

Ein Schliessen der CustomTaskPane durch klicken auf ihren close button zerstört das Objekt nicht. In einem solchen Fall wird ein VisibleChanged event generiert den man benutzen kann um das GUI synchron zu halten (in unserem Fall die “Display check results” checkbox zu deselektieren).

Insgesamt bieten die CustomTaskPanes eine elegante Möglichkeit, beliebige controls mit einem Word-Dokument zu assoziieren.

Viele Grüße,
Andreas

Technorati Tags: , , ,

ClickOnce-Installation und der Fehler “Datei…wurde seit ihrer ersten Veröffentlichung geändert” Fehler

Monday, October 20th, 2008

Hallo,

bei einer ClickOnce-Installation einer Microsoft .NET Software kann bei der Erstellung des setup-Programmes in Visual Studio eine Option gewählt werden, die bewirkt dass vorausgesetzte Software (”redistributable prerequisites” – wie etwas das .NET framework selbst) automatisch als Teil der Installation geladen und installiert werden.

Dabei kann angegeben werden, ob die Komponenten von der Website des Hersteller oder von einer anderen Quelle geladen werden sollen.

Erforderliche Komponenten für ClickOnce Installation Dialog in Visual Studio

Im ersteren Fall kann folgendes Fehlerszenario entstehen:

Der Entwickler hat eines der vorausgesetzten Softwarepakete (z.B. das deutsche language pack vstor_lp_de_30.exe für die VSTO 3.0 runtime) lokal installiert. Beim Erstellen des setup-Programmes wird dieses Paket nicht integriert, wohl aber Information über die verwendete Version.

Nun wird die Installation auf einem beliebigen anderen Rechner gestartet. Der installer sieht dass auf der Website des Komponentenherstellers (also Microsoft für obiges Beispiel) eine andere Version der Komponente verfügbar ist. Genau dann wird diese Fehlermeldung generiert:

Error: Die Datei “C:\DOKUME~1\awa\LOKALE~1\Temp\VSD6.tmp\VSTOR30\vstor_lp_de_30.exe” wurde seit ihrer ersten Veröffentlichung geändert.

Die lokal auf dem Entwicklungsrechner mit Visual Studio 2008 SP1 installierte Version war vom 27.12.2007 während die Version auf der Microsoft Website vom 12.08.2008 war.

Dieses Verhalten ist ziemlich problematisch, kann doch ein update auf der Website des Komponentenherstellers grundsätzlich zu einem beliebigen Zeitpunkt erfolgen. Danach müssten alle ClickOnce-setups, die die aktualisierte Komponente benötigen, aktualisiert werden.

Es wäre sinnvoll, zumindest kleinere Abhängigkeiten direkt in das setup zu integrieren. Leider kann das Verhalten nicht für einzelne Abhängigkeiten individuell eingestellt werden, so dass in diesem Fall auch die .NET runtime in das setup integriert werden müsste, was nicht wirklich attraktiv ist.

Falls prerequisites ausgeschlossen werden bricht die Installation ohne konkrete Fehlermeldung ab, wenn die prerquisites nicht vorhanden sind bzw. nicht in der richtigen Version vorhanden sind.

Insgesamt würde ich empfehlen ausser den .NET runtimes alle erforderlichen prerequistes dem setup mit der Option “Erforderliche Komponenten vom demselben Speicherort wie die Anwendung herunterladen” hinzuzufügen. Die Installationsanleitung muss dann einen deutlichen Hinweis auf die erforderliche .NET runtime bzw. weitere nicht eingeschlossene prerequistes beinhalten.

Vielen Dank an Joe Wirtley für den Hinweis auf die root cause.

Viele Grüße,
Andreas

Technorati Tags: ,

Erstellung eines AddIns für Word 2007 – Teil 5 (Finden der OCL-Fragmente)

Saturday, September 27th, 2008

Hallo,

wie erwartet war dieser Teil einfach zu lösen, da es im Internet genügend Beispiele gibt, wie man mit dem Word-Objektmodell arbeitet. Die OCL-Fragmente werden anhand ihrer Vorlage identifiziert. Die Vorlage muss “OCL” heißen, kann ansonsten aber beliebig gestaltet werden. Die Verwendung eines fonst mit fester Zeichenbreite (”fixed width”) empfiehlt sich.

Ein wenig mehr Zeit als nötig habe ich benötigt, weil mir anfangs nicht klar war, dass der englische Begriff für “Formatvorlage” “Style” ist und nicht “Format”.

Bei der Umsetzung merkt man, dass die Kompatibilität zu Visual Basic for Applications (VBA) kein Designziel für C# war. Ein komfortabler Umgang mit den Office-Objektmodellen setzt einige features voraus, die Visual Basic.NET hat, C# aber nicht (z.B. default parameter). Deshalb sollte man grössere Anbindungen von Office-Objektmodellen in eigenen Visual Basic.NET assemblies erstellen. Für unser kleines Word-AddIn spielt das aber keine Rolle, da wir nur minimal auf die Word-Objekte zugreifen.

Um die OCL-Fragement zu finden, wird das Word “Find”-Objekt benutzt:

IList<TextFragment> FindFragmentsByStyle(Word.Document doc, Object styleName)
{
            List<TextFragment> result = new List<TextFragment>();
 
            Object start = 0;
            Object end = doc.Characters.Count;
            Word.Range range = doc.Range(ref start, ref end);
            Word.Find find = range.Find;
 
            Object style = null;
 
            try
            {
                style = doc.Styles.get_Item(ref styleName);
            }
 
            catch (Exception)
            {
                /*
                 * Style not found.
                 */
                return (result);
            }
 
            find.set_Style(ref style);
            ExecuteFind(find);
            while (find.Found)
            {
                string text = range.Text;
                int linenum = GetRelativeLineNum(doc, range);
                int pagenum =     (int)range.get_Information(Microsoft.Office.Interop.Word.WdInformation.wdActiveEndPageNumber);
 
                result.Add(new TextFragment(text, linenum, pagenum));
                ExecuteFind(find);
            }
 
            return (result);
}

Die Methode ExecuteFind() enthält einige low level Details beim Suchen nach dem nächsten Treffer:

private bool ExecuteFind(Word.Find find, Object wrapFind, Object forwardFind)
{
            // Simple wrapper around Find.Execute:
            Object forward = forwardFind;
            Object wrap = wrapFind;
 
            return find.Execute(ref missing, ref missing, ref missing,
              ref missing, ref missing, ref missing,
              ref forward, ref wrap, ref missing, ref missing, ref missing,
              ref missing, ref missing, ref missing,
              ref missing);
}

Als Ergebnis gibt die Methode FindFragmentsByStyle() eine Liste von Objekten der (selbst erstellen) Klasse TextFragment zurück. Diese Klasse speichert den eigentlichen Text und Informationen zur Position des Textes im Dokument (Seiten- und Zeilennummer).

Viele Grüße,
Andreas

Technorati Tags: , ,

Erstellung eines AddIns für Word 2007 – Teil 4 (Erweiterung des Ribbons)

Wednesday, September 3rd, 2008

Hallo,

in diesem posting beschreibe ich, wie man dem Word Ribbon eine eigene RibbonGroup hinzufügt, mit dem man die AddIn-spezifische Funktionalität aufrufen kann.

Dazu muss man dem Projekt ein neues Objekt vom Typ “Multifunktionsleiste (Visueller Designer)” hinzufügen:

Add Ribbon 1

oclwordaddin_addribbon2.png

Der visuelle Designer hat gewisse funktionale Einschränkungen gegenüber der Erstellung einer XML-Datei zur Ribbon-Erweiterung aber für unsere Zwecke ist er völlig ausreichend und wir nutzen gerne den erheblichen Produktivitätsvorteil.

Der Designer zeigt uns nun das neue RibbonTab an. Wir setzen noch sein label ( “OCL” – der Anhang “(Integriert)” wird von Visual Studio hinzugefügt und ist nur im design mode sichtbar) und das label der automatisch erstellten RibbonGroup (”Check”):

Ribbon editieren

Projekt erstellen, Starten, und tatsächlich, unser RibbonTab wird angezeigt:

Leeres OCL Ribbon in Word

Jetzt wollen wir die buttons zum Laden einer XMI-Datei und zum anstoßen der Prüfung aller im Dokument enthaltenen OCL-Ausdrücke hinzufügen. VSTO stellt in der Toolbox den eigenen Abschnitt “Steuerelemente für Office-Multifunktionsleiste” bereit, aus dem wir uns bedienen können.

Wie gewohnt lassen sich die buttons mittels drag and drop dem ribbon hinzufügen, konfigurieren und mit einem Handler für das ‘Click’-Ereignis versehen.

An diesem Punkt stellt sich die erste Design-Frage. Die Eventhandler in der von Visual Studio erzeugten OfficeRibbon-Klasse werden zwar gerufen, aber die dabei implizit erzeugte Instanz hat keine Verbindung zu der ebenfalls impliziten Instanz der eigentlichen AddIn-Klasse “ThisAddin”. Es stellt sich also die Frage, wer die diversen events und Informationen orchestriert und wie events und Informationen zwischen den verschiedenen Instanzen ausgetauscht werden.

Hier gibt es sicherlich verschiedenste Wege, hier der den ich gewählt habe:

  • Die Orchestrierung soll durch die Klasse ThisAddIn erfolgen. Diese Klasse hat bereits Zugriff auf das Word-Objektmodell und bietet sich auch als zentrale Steuerklasse an.
  • Um Informationen von der OfficeRibbon-Instanz zur This AddIn-Instanz zu transportieren, werden der OfficeRibbon-Klasse mehrere events hinzugefügt. Weiterhin wird eine statische Property angelegt, die Zugriff auf die Instanz ermöglicht. Damit kann die ThisAddIn-Klasse in ihrer Initialisierung die nötigen eventhandler anmelden.
  • Diese Vorgehen hat den Nachteil, dass man sich von der zeitlichen Reihenfolge der Konstruktion und Initialisierung abhängig macht. Konkret geht man davon aus, dass im ThisAddIn_Startup()-Ereignis die OfficeRibbon-Instanz bereits erzeugt wurde. Es scheint mir legitim, anzunehmen dass alle Objekte die Visual Studio implizit erzeugt, vor dem Aufruf des ersten events konstruiert wurden.

Damit sieht die Initialisierungsroutine des AddIns so aus:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
 {
     Application.DocumentOpen += new Microsoft.Office.Interop.Word.ApplicationEvents4_DocumentOpenEventHandler(application_DocumentOpen);
     ribbonTabOcl.Instance.OnLoadXmi += this.OnLoadXmi;
     ribbonTabOcl.Instance.OnCheckAllExpressions += this.OnCheckAllExpressions;
     ribbonTabOcl.Instance.OnDisplayResultTaskPane += this.OnDisplayResultTaskPane;
     ConfigureLog4Net();
}

Viele Grüße,
Andreas

Technorati Tags: , ,

Erstellung eines AddIns für Word 2007 – Teil 3 (Erstellung des AddIns)

Wednesday, April 16th, 2008

Hallo,

dieses posting ist der dritte Artikel in einer Serie von Artikeln die die Erstellung eines AddIns für Microsoft Word 2007 mit C# beschreiben.

In diesem posting werde ich die eigentliche Erstellung des AddIns als Projekt in Visual Studio 2008 beschreiben.

Als Vorbereitung habe ich den Artikel Setting Up Development Environments for the 2007 Microsoft Office System auf der MSDN website gelesen. Diese Dokument bezieht sich in weiten Teilen auf die Installation der Entwicklungsumgebung, was bei Verwendung von Visual Studio 2008 nicht relevant ist. Visual Studio 2008 bringt alle erforderlichen Komponenten mit.

Interessant waren die Hinweise zur Erweiterung des Ribbons und auf das Konzept “document level AddIn” und “application level AddIn“. Ein “application level AddIn” ist ein AddIn welches bereits beim Laden der Anwendung – ohne dass ein Dokument geladen wurde – aktiv ist. Der in dem screen shot weiter unten gewählte Projekttyp erstellt ein application lavel AddIn (erst möglich mit VSTO 3.0, enthalten in Visual Studio 2008).

Beim Anlegen des Projektes muss der passende Typ ausgewählt werden:

Projekt erstellen, breakpoints in ThisAddIn_Startup() und ThisAddIn_Shutdown() gesetzt und F5 (Debuggen) gedrückt. Wie erwartet startet Word und nach dem Start wird der breakpoint in der ThisAddIn_Startup() Methode getroffen. Nach beenden von Word wird der breakpoint in ThisAddIn_Shutdown() getroffen. Damit ist Teilaufgabe 1 – Erstellung des AddIns – erledigt.

Da wir mit jedem Word-Dokument ein eigenes XMI-file assoziieren wollen, ist es nötig das Öffnen, schließen und Neuanlegen eines Dokumentes zu überwachen. Diese event handler werden wir später hinzufügen.

Im nächsten Teil wird es spannender, dort wollen wir das Ribbon erweitern um in unserem AddIn spezifische Funktionalität aufzurufen.

Viele Grüße,
Andreas

Erstellung eines AddIns für Word 2007 – Teil 2 (zu lösende Probleme)

Saturday, April 12th, 2008

Hallo,

dieses posting ist das zweite in einer Serie von postings über die Erstellung eines AddIns für Microsoft Word 2007. In Teil 1 habe ich die Hintergründe und Anforderungen an das AddIn beschrieben.

In diesem posting möchte ich nun eine genauere Vorstellung gewinnen, welche Probleme gelöst werden müssen.

Erstellung des AddIns

Dazu zählt das Erstellen des Projektes in Visual Studio 2008 (also mit VSTO 3.0), das starten von Word und das Abfangen zentraler Ereignisse. Damit wäre der Rahmen für die Programmierung und das Debuggen des AddIns geschaffen.

Erweiterung des Word Ribbons

Zweiter Schritt wäre die Erweiterung des Word-Ribbons. Es soll ein neues RibbonTab mit zwei buttons hinzugefügt werden. Die buttons haben die Funktionalitäten “Load XMI-file” und “Check OCL fragments”. Breakpoints in den Ereignishandlern dieser buttons verifizieren den korrekten Anschluss an das GUI. Auf diesen Punkt bin ich besonders neugierig, verspricht Visual Studio 2008 doch einen visuellen Designer zur Erweiterung des Ribbons.

Finden der OCL-Fragmente

Hierzu muss auf das Word-Objektmodell zugegriffen werden. Ich gehe davon aus, dass es eine relativ einfache Aufgabe ist, alle Fragmente eines Dokumentes zu finden, die eine bestimmte Formatvorlage haben. Diese Funktionalität ist ja bereits in der normalen, interaktiven Suche enthalten.

Aktivierung von log4net

Die verwendeten Komponenten benutzen log4net und es wäre sinnvoll, wenn das AddIn das Schreiben der logs unterstützen würde.

Implementierung der Prüfung der OCL-Ausdrücke

Diese Aufgabe ist unabhängig von Word 2007 zu lösen. Eine derartige Funktionalität wurde bereits mehrfach implementiert und ist mehr oder weniger trivial in dem AddIn einzubinden (unter Verwendung der bestehenden Komponenten, versteht sich).

Präsentation der Ergebnisse

In einer ersten Version werden die Ergebnisse lediglich in einem separaten Dialogfenster, welches das AddIn bereitstellt, angezeigt. Ergonomischer – aber auch aufwendiger – wäre die Darstellung in einer Word Task Pane.

Persistenz des Namens des XMI-files

Hierzu müsste das AddIn dem Dokument eigene Daten hinzufügen. Ich gehe davon aus, dass eine solche Funktionalität existiert und dass sie relativ einfach zu nutzen ist.

Deployment

Hierzu zählt das Erstellen einer einfachen Dokumentation und eines Setup-Programms.

Im nächsten posting werden ich die Erstellung des AddIns beschreiben.

Viele Grüße,
Andreas

Technorati Tags: , ,