MSDN Magazin > Home > Ausgaben > 2007 > December >  VSTO: Erstellen Office-basierter Lösungen ...
VSTO
Erstellen Office-basierter Lösungen mittels WPF, WCF und LINQ
Andrew Whitechapel
Codedownload verfügbar unter: VSTONet2007_12.exe (419 KB)
Browse the Code Online

Dieser Artikel basiert auf einer Vorabversion von Visual Studio 2008. Änderungen an allen Informationen in diesem Artikel sind vorbehalten.
Themen in diesem Artikel:
  • Wie VSTO die Office-Entwicklung leistungsfähiger macht
  • Verwenden von WCF, WPF und LINQ in Office-Lösungen
  • Einfaches Hinzufügen erweiterter Features zu Ihren Office-Anwendungen
  • Erstellen von Diensten auf einfache Weise
In diesem Artikel werden folgende Technologien verwendet:
VSTO, WPF, WCF, LINQ
In Visual Studio® 2008 wird eine Palette neuer Features und Merkmale eingeführt, die für eine große Bandbreite von Kundenlösungstypen konzipiert sind. Jetzt können Sie eine VSTO-Lösung (Visual Studio-Tools für Office) erstellen, die WPF (Windows® Presentation Foundation), WCF (Windows Communication Foundation) und LINQ (Language Integrated Query Expressions) verwendet. All dies wird im folgenden Artikel erläutert.
Durch die neuen Technologien können interessante Lösungen mit einem Verhalten erstellt werden, das vorher nur schwer oder überhaupt nicht realisiert werden konnte. Beispielsweise kann trotz der Tatsache, dass Microsoft® Office Excel® 2007 bereits leistungsfähige Diagrammfeatures bietet, die Benutzerfreundlichkeit erhöht werden, indem das Berechnungsmodul von Excel mithilfe animierter 3D-Grafiken von WPF mit einer verbesserten Benutzeroberfläche und einer verbesserten Datenvisualisierung kombiniert wird.
Da Office immer mehr zu einer echten Entwicklungsplattform wird, werden auf Office basierende Lösungen immer ausgereifter, weniger dokumentorientiert und zunehmend lockerer verkoppelt. Der Bedarf nach einem unterstützenden Rahmen und einem Satz von Entwurfszeittools zum Erstellen dienstorientierter Lösungen, die einen umfangreichen Office-Client mit leistungsfähigen serverseitigen Funktionen und Remotedaten verknüpfen, wird von WCF umfassend gedeckt. Visual Studio 2008 stellt einen einfachen Assistenten mit grafischer Benutzeroberfläche bereit, mit dem Sie WCF-Dienste nutzen können, ohne sich um Dienstmetadaten, Protokolle oder XML-Konfigurationen kümmern zu müssen.
LINQ ermöglicht Entwicklern, zum Abfragen von Daten einen intuitiveren, stark vereinfachten Code zu erstellen. Eines der Features von LINQ, das Office-Entwickler besonders schätzen werden, ist die Verwendung von Erweiterungsmethoden zur Unterstützung der herkömmlichen Office-Objektmodellmethoden, die optionale oder explizite Verweisparameter besitzen.
Mit Visual Studio 2008 können Sie eine Lösung erstellen, in der die systemeigenen Fähigkeiten einer Office-Clientanwendung mit den hochentwickelten Benutzeroberflächenfähigkeiten von WPF kombiniert werden, die über WCF mit Remotedaten und -diensten verbunden ist und zum Manipulieren dieser Daten die RAD-Features von LINQ verwendet.

Beispiellösung
Die Beispiellösung für diesen Artikel wurde absichtlich einfach gehalten, sodass die einzelnen Technologien sowie die Integrationsprobleme im Vordergrund stehen, nicht die betriebliche Funktionalität. Abbildung 1 vermittelt Ihnen eine Vorstellung von der Benutzerfreundlichkeit. Zwei Prozesse sind darin eingebunden: Ein eigenständiger WCF-Dienst, der in einer Konsolenanwendung gehostet wird, und ein VSTO-Add-In, das in Word 2007 ausgeführt wird. Das Add-In stellt einen benutzerdefinierten Aufgabenbereich bereit, in dem ein benutzerdefiniertes WPF-Steuerelement enthalten ist. Wenn der Benutzer auf die im Steuerelement enthaltenen Schaltflächen klickt, reagiert das Add-In darauf, indem es den WCF-Dienst anweist, XML-Daten abzurufen. Das Add-In verarbeitet anschließend diese Daten auf verschiedene Weise mithilfe von LINQ und XLinq. Dabei werden Lambda-Ausdrücke und Ausdrucksstrukturen genutzt und Texte formatiert, die danach in das aktive Dokument eingefügt werden. Wenn Sie die Beispiellösung selbst durcharbeiten möchten, dann laden Sie die unter msdn2.microsoft.com/vstudio/aa700831 verfügbare Version Visual Studio 2008 Beta 2 herunter, und installieren Sie sie.
Abbildung 1 Laufzeitverhalten der Beispiellösung (Klicken Sie zum Vergrößern auf das Bild)
Dieses Beispiel dient nicht dem Zweck, die besondere Laufzeitfunktionalität zu veranschaulichen, sondern soll in erster Linie demonstrieren, dass alle neuen Technologien von Visual Studio 2008 (WPF, WCF und LINQ) reibungslos mit Office-Lösungen zusammenarbeiten. Darüber hinaus soll diese Beispiellösung Anregungen dazu bieten, neue Möglichkeiten der Benutzerfreundlichkeit zu schaffen.
In Abbildung 2 wird die Architektur der Lösung aufgezeigt. Die Verwendung von WCF und LINQ innerhalb einer VSTO-Anwendung stellt ein einfaches Verfahren dar, das sich nicht von der Art und Weise unterscheidet, wie diese Features in einer Windows Forms- oder einer Konsolenanwendung verwendet werden. Wie Sie jedoch noch sehen werden, ist die Verwendung von WPF interessanter.
Abbildung 2 Architektur der Beispiellösung (Klicken Sie zum Vergrößern auf das Bild)
Ein besonderer Aspekt von VSTO ist die Möglichkeit, Lösungen zu erstellen, die innerhalb systemeigener Office-Benutzeroberflächenfenster verwaltete Steuerelemente verwenden. VSTO unterstützt das Einfügen verwalteter (Windows Forms-) Steuerelemente in beliebigen Windows Forms-Dialogfeldern, in benutzerdefinierten Aufgabenbereichen auf Anwendungsebene, im Fensterbereich für Dokumentaktionen, in benutzerdefinierten Formularbereichen von Outlook® und in der Oberfläche eines Word- oder Excel-Dokuments. Microsoft .NET Framework 3.5 (im Lieferumfang von Visual Studio 2008 enthalten) umfasst die Datei „WindowsFormsIntegration.dll“, in der die Windows Forms-Integrationsklasse „ElementHost“ enthalten ist. Dies schlägt eine Brücke zwischen Windows Forms und WPF. Dadurch unterstützt VSTO überall da, wo Windows Forms-Steuerelemente eingefügt werden können, auch das Einfügen von WPF-Steuerelementen.
Eine der interessantesten Entwurfsentscheidungen, die Sie hier treffen, betrifft den Aspekt, wo Ereignisse behandelt werden. Wenn der Benutzer beispielsweise auf eines der WPF-Schaltflächensteuerelemente klickt, können Sie dies auf mehreren Ebenen behandeln: im WPF-UserControl-Steuerelement, im Windows Forms-UserControl-Steuerelement oder im Add-In. Wo Sie das Ereignis behandeln und ob Sie es an übergeordnete Steuerelemente weiterleiten, hängt weitgehend davon ab, wie wiederverwendbar die verschiedenen Steuerelemente sein sollen.

Erstellen des WCF-Diensts
Zunächst wurde für das Projekt eine einfache Konsolenanwendung namens „ImageServiceHost“ erstellt. Der nächste Schritt bestand darin, dem Projekt ein WCF-Dienstelement hinzuzufügen, das „ImageService“ genannt wurde. Führen Sie sich hierbei Folgendes vor Augen: Wenn in Visual Studio dem Projekt ein Windows Communication Foundation Service hinzugefügt wird, wird für den Dienst automatisch Startcode einschließlich einer Schnittstelle generiert, die den Dienstvertrag und die Klasse repräsentiert, von der diese Schnittstelle implementiert wird. Konfigurationseinträge, die Verhaltensweisen und Endpunkte für den Dienst festlegen, werden in einer standardmäßigen Anwendungskonfigurationsdatei bereitgestellt.
Der Dienstvertrag und seine Implementierung mussten geändert werden, um die vom Assistenten erstellte DoWork-Methode zu entfernen und durch eine neue Methode namens „GetDefinition“ zu ersetzen, die eine Schlüsselwortzeichenfolge entgegennimmt und eine Zeichenfolge von XML-Daten zurückgibt:
[ServiceContract()]
public interface IImageService
{
    [OperationContract]
    string GetDefinition(string myValue);
}
Die XML-Daten könnten aus einer beliebigen Quelle abgerufen werden. In einem realistischen Szenario wird der WCF-Dienst auf einem Server ausgeführt und ruft die Daten möglicherweise aus einer serverseitigen Datenbank oder einem Branchensystem wie SAP oder Siebel ab. In diesem Beispiel wird der Dienst jedoch auf dem lokalen Computer ausgeführt und die Daten aus einer statischen XML-Zeichenfolgenressource bezogen.
Die zu erstellende XML-Datenquelle wird eine Reihe von Elementen enthalten, von denen jedes einzelne eine einfache Zuordnung eines Schlüsselworts zu einer Definition repräsentiert:
<Dictionary>
    <Item>
        <Key>Frangipani</Key>
        <Definition>Some description goes here.</Definition>
    </Item>
    <Item>
        <Key>Toucan</Key>
        <Definition>...etc</Definition>
    </Item>
</Dictionary>
Wenn diese XML-Datei den Projektressourcen hinzugefügt wird, kann der Dienstkonstruktor implementiert werden, um die XML-Daten aus Ressourcen zu laden. Danach kann die Vertragsmethode „GetDefinition“ implementiert werden, um das Element zurückzugeben, das dem vom Benutzer angeforderten Schlüsselwort entspricht. Gleichzeitig wird die zurückgegebene Zeichenfolge im Konsolenfenster ausgegeben. Beachten Sie, dass der Beispielcode vereinfacht ist und nicht die Ausnahmebehandlung beinhaltet, die normalerweise eingebunden werden muss:
public string GetDefinition(string keyword)
{
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(Properties.Resources.ImageData);
    String xPath = String.Format(
        "descendant::Item[Key='{0}']", keyword);
    XmlNode node = xmlDoc.DocumentElement.SelectSingleNode(xPath);
    String nodeXml = node.OuterXml;
    Console.WriteLine(nodeXml);
    return nodeXml;
}
Anschließend muss die Konsolenanwendung lediglich den Dienst starten und stoppen.
static void Main(string[] args)
{
    ServiceHost s = new ServiceHost(typeof(ImageService));
    s.Open();
    Console.WriteLine("press ENTER to stop the service");
    Console.ReadLine();
    s.Close();
}
Jetzt ist der Dienst definiert, implementiert und konfiguriert, und dieser Teil der Lösung kann erstellt und ausgeführt werden. Der Dienst wurde so eingerichtet, dass er in der Konsolenanwendung ausgeführt wird und auf eingehende Anrufe wartet, bis der Benutzer im Konsolenfenster die Eingabetaste drückt.

Das WPF-UserControl-Steuerelement
Es gibt zwei Möglichkeiten, wie WPF in einer Anwendung verwendet werden kann: Zum Verbessern der Benutzeroberfläche und zur Bereitstellung einer leistungsfähigen Datenvisualisierung. Das Beispiel konzentriert sich auf die vielfältigen Möglichkeiten im Hinblick auf die Benutzeroberfläche. Der wichtigste Punkt hierbei ist, wie leicht WPF-Steuerelemente in VSTO-Lösungen eingebunden werden können. Dies kann mit einem sehr einfachen WPF-Steuerelement demonstriert werden, z. B. mit einem Raster, das eine Schaltfläche enthält, aber es wäre schade, die leistungsfähigen Grafikfunktionen von WPF nicht zu nutzen. Daher wird in diesem Beispiel ein benutzerdefiniertes WPF-UserControl-Steuerelement verwendet, das ein Fischaugenanimationsverhalten bereitstellt. Dabei wurde das HyperBar-Beispiel für Microsoft Expression BlendTM (verfügbar unter blogs.msdn.com/expression/articles/516599.aspx) zugrunde gelegt. Zusätzlich habe ich mich am FishEyePanel von Paul Tallett orientiert, das unter codeproject.com/WPF/Panels.asp beschrieben wird. Der hier eingeführte Hauptunterschied besteht darin, dass das WPF-Steuerelement in ein Fenster eingefügt wird, das kein WPF-Fenster ist, um aufzuzeigen, wie es in einem systemeigenen Office-Fenster gehostet wird.
Zunächst wird ein Windows WPF UserControl Bibliotheksprojekt erstellt. Dadurch wird Start-XAML und der entsprechende C#-Hintergrundcode generiert. Zuerst muss der UserControl-Klassenname von „UserControl1“ in etwas Aussagekräftigeres geändert werden. Der neue Name lautet „FishEyeControl“. Das FishEyeControl-Steuerelement soll eine Sammlung von Schaltflächen enthalten, die über den benutzerdefinierten FishEyePanel-Fensterbereich (aus System.Windows.Controls.Panel abgeleitet) verwaltet wird. In FishEyePanel wurden Ereignishandler für drei Mausereignisse (MouseMove, MouseEnter, MouseLeave) eingerichtet, damit der Fensterbereich deaktiviert und neu aufgebaut werden kann, sobald der Benutzer den Mauszeiger darauf richtet. Einer der Mausereignishandler sieht folgendermaßen aus:
public class FishEyePanel : Panel
{
    public FishEyePanel()
    {
        this.MouseMove += new
            MouseEventHandler(FishEyePanel_MouseMove);
    }

    private void FishEyePanel_MouseMove(object sender, 
        MouseEventArgs e)
    {
        this.InvalidateArrange();
    }
}
Um FishEyePanel mit FishEyeControl zu verbinden, musste für FishEyeControl die Eigenschaft ItemsControl wie folgt eingerichtet werden:
<UserControl x:Class="WPFFishEye.FishEyeControl"
    xmlns:uc="clr-namespace:WPFFishEye">
    <Grid>
        <ItemsControl>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <uc:FishEyePanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</UserControl>
Anschließend wurden für den Fensterbereich einige Datenressourcen vorbereitet. Statt zu versuchen, einen Webdienst oder eine Branchendatenquelle einzurichten, was realistischer wäre, werden als Ressourcen lediglich lokale Dateien verwendet. In diesem Beispiel wird für die Bilder auf den Schaltflächen eine einfache Liste von JPG-Dateien verwendet. Da diese JPG-Dateien externe Dateien sind, ist ein wenig Code erforderlich, um den einfachen Dateinamen (z. B. Frangipani.jpg) einzulesen und in einen vollqualifizierten Pfad zu konvertieren, der zur Laufzeit gültig ist. Zu diesem Zweck lässt sich eine Implementierung von IValueConverter definieren. Dieses Verfahren wird häufig zum Durchführen benutzerdefinierter Konvertierungen während der Datenbindung verwendet. Die IValueConverter-Schnittstelle definiert zwei Methoden, aber hier ist lediglich die Convert-Methode von Interesse. Das Datenbindungsmodul ruft diese Methode auf, wenn es einen Wert aus der Bindungsquelle an das Bindungsziel weitergibt. In der Beispielimplementierung wird einfach die CodeBase der aktuellen Assembly verwendet und dem Bilddateinamen als Pfad vorangestellt.
public class ImagePathConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter,
        System.Globalization.CultureInfo culture)
    {
        string path = 
            Assembly.GetExecutingAssembly().CodeBase.Substring
            ("file:///".Length);
        path = Path.GetDirectoryName(path) + "\\Images\\";
        return string.Format("{0}{1}", path, (string)value);
    }
}
Danach werden in FishEyeControl.xaml diese Stücke in einem ResourceDictionary für das Steuerelement verbunden und dieses ResourceDictionary in ItemsControl verwendet (siehe Abbildung 3). Beachten Sie, dass das DataTemplate für die Elemente innerhalb des Fensterbereichs als Schaltfläche mit einem Bild konfiguriert wird und das Bild während der Datenbindung den ImagePathConverter verwendet.
<UserControl.Resources>
    <ResourceDictionary>
        <XmlDataProvider x:Key="ItemImages" XPath="ItemImages/ItemImage">
            <x:XData>
                <ItemImages >
                    <ItemImage Image="Frangipani.jpg" Width="50"/>
                    <ItemImage Image="Toucan.jpg" Width="50"/>
                    <!-- etc -->
                </ItemImages>
            </x:XData>
        </XmlDataProvider>
        <uc:ImagePathConverter x:Key="ImagePathConverter" />
    </ResourceDictionary>
</UserControl.Resources>

<Grid>
    <ItemsControl 
    DataContext="{Binding Source={StaticResource ItemImages}}"
    ItemsSource="{Binding}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <uc:FishEyePanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <ItemsControl.ItemContainerStyle>
            <Style TargetType="{x:Type ContentPresenter}">
                <Setter Property="ContentTemplate">
                    <DataTemplate>
                        <Button Name="b1" Click="buttonClick">
                            <Image 
Source="{Binding Converter={StaticResource ImagePathConverter}, 
XPath=@Image}"/>
                        </Button>
                    </DataTemplate>
                </Setter>
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</Grid>

Diese Definition legt auch den buttonClick-Ereignishandler für jede Schaltfläche fest. Obwohl Sie das Ereignis innerhalb des WPF-Steuerelements selbst sowie innerhalb des hostenden Windows Forms-Steuerelements oder des Add-Ins behandeln könnten, wird es an dieser Stelle nur im WPF-Steuerelement und im Add-In behandelt. Die Ereignishandler in FishEyeControl.xaml.cs werden durch Extrahieren des Bildnamens der Schaltfläche und erneutes Auslösen des Ereignisses implementiert. Das VSTO-Add-In reagiert auf das Ereignis, indem es basierend auf dem Bildnamen den WCF-Dienst aufruft (siehe Abbildung 4).
public partial class FishEyeControl : System.Windows.Controls.UserControl
{
    public event FishEyeEvent FishEyeClickEvent;

    private void buttonClick(object sender, RoutedEventArgs e)
    {
        Button buttonSender = (Button)sender;
        Image buttonImage = (Image)buttonSender.Content;
        ImageSource imageSource = buttonImage.Source;
        String imageName = imageSource.ToString();
        int lastSlash = imageName.LastIndexOf('/') + 1;
        String buttonName = imageName.Substring(
            lastSlash, imageName.Length - lastSlash - ".jpg".Length);

        FishEyeEventArgs fe = new FishEyeEventArgs(buttonName);
        if (FishEyeClickEvent != null)
        {
            FishEyeClickEvent(sender, fe);
        }
    }
}

Als Nächstes definieren Sie den benutzerdefinierten EventArgs-Typ, damit der einfache Name des Schaltflächenbilds beim erneuten Auslösen des Ereignisses zum Listener gesendet werden kann, wie im Folgenden gezeigt:
public delegate void FishEyeEvent(object source, FishEyeEventArgs e);

public class FishEyeEventArgs : EventArgs
{
    public String ButtonName;

    public FishEyeEventArgs(String name)
    {
        ButtonName = name;
    }
}
Denken Sie daran, dass das FishEyePanel-Steuerelement deaktiviert wird, wenn der Benutzer mit dem Mauszeiger darauf zeigt. Außerdem soll gesteuert werden, wie sich die Bildschirmwiedergabe verhält, damit das Fischaugenanimationsverhalten bereitgestellt werden kann. Um die Kontrolle über die Bildschirmwiedergabe zu übernehmen, müssen zwei Überschreibungen (MeasureOverride und LayoutOverride) implementiert werden, durch die ein zweiphasiges Layoutsystem implementiert wird. Während der Messphase ruft das übergeordnete Steuerelement (FishEyeControl) das untergeordnete Steuerelement (FishEyePanel) auf, um zu ermitteln, wie viel Platz das untergeordnete Element benötigt. Das Standardverhalten besteht hier darin, dass das untergeordnete Steuerelement die ihm untergeordneten Steuerelemente abfragt, um herauszufinden, wie viel Platz sie brauchen, und anschließend das Ergebnis an das übergeordnete Steuerelement zurückgegeben wird. Da WPF ein Modell verwendet, bei dem Steuerelemente fast unendlich geschachtelt werden können, wird in diesem Kontext die gesamte Struktur auf ihre Platzanforderungen abgefragt und das Ergebnis an das höchste übergeordnete Steuerelement zurückgegeben. In der Layoutphase findet eine ähnliche Rekursion statt, bei der das übergeordnete Steuerelement die Größe der ihm untergeordneten Steuerelemente festlegt und nach unten weitergibt, wonach jedes untergeordnete Steuerelement diese Informationen wiederum an die ihm untergeordneten Steuerelemente weitergibt. Dabei wird die ArrangeOverride-Methode aufgerufen, um den untergeordneten Steuerelementen ihre Größe mitzuteilen und ihr Layout festzulegen.
In dieser Implementierung von MeasureOverride wird die UIElement.DesiredSize-Eigenschaft verwendet, um den untergeordneten Steuerelementen möglichst den gesamten erforderlichen Platz zuzuweisen. In ArrangeOverride fügen Sie jedem untergeordneten Steuerelement Größen- und Übersetzungstransformationen hinzu und berechnen, wie breit diese sein müssen. In diesem Beispiel wurde die untergeordnete Schaltfläche, die sich direkt unter der Maus befindet, größer als die anderen gestaltet. Die Schaltflächen zu beiden Seiten dieser Schaltfläche sind kleiner, aber trotzdem größer als alle anderen Schaltflächen. Die übrigen Schaltflächen werden im verbleibenden Platz gleich groß gestaltet. Weitere Details finden Sie im Codedownload zu diesem Artikel auf der MSDN® Magazin-Website (msdn.microsoft.com/msdnmag/code07.aspx). Der Endeffekt besteht darin, dass die Größe der Schaltflächen bei jeder Mausbewegung angepasst werden, um den vertrauten Fischaugeneffekt zu erzielen.

Verbinden des WPF-UserControl-Steuerelements mit VSTO
Die grundlegende VSTO-Lösung besteht aus einem Word 2007-Add-In namens „WordImageSelecter“. Sobald das ursprüngliche Add-In-Projekt erstellt ist, kann das WPF UserControl-Projekt der Lösung hinzugefügt werden. Dies ermöglicht eine Nutzung der verbesserten Entwurfszeitunterstützung in Visual Studio 2008 zum Integrieren von WPF-Steuerelementen innerhalb von Windows Forms-Projekten.
Der nächste Schritt besteht darin, innerhalb des Add-In-Projekts ein einfaches Windows Forms-UserControl-Steuerelement zu erstellen. Dieses Steuerelement wird am Ende das benutzerdefinierte WPF-UserControl-Steuerelement hosten. Da in Visual Studio 2008 alle WPF-UserControl-Steuerelemente in allen Projekten der Lösung in der Toolbox angezeigt werden, können Sie es, wie in Abbildung 5 zu sehen ist, mit Drag & Drop direkt auf die Entwurfsoberfläche des Windows Forms-UserControl-Steuerelements ziehen.
Abbildung 5 Windows Forms-Entwurfsoberfläche des WPF-Steuerelements 
Durch diese Aktion wird der gesamte Code generiert, der zum Hosten des WPF-Steuerelements im Windows Forms-Steuerelement (insbesondere über ein ElementHost-Objekt) benötigt wird. Das ElementHost-Objekt dient zum Integrieren von Windows Forms und WPF. In diesem Beispiel hostet das Windows Forms-UserControl-Steuerelement den ElementHost, der wiederum das WPF-UserControl-Steuerelement hostet. Dadurch werden auch die erforderlichen Verweise der benutzerdefinierten Datei „WPFFishEye.dll“ sowie den standardmäßigen DLL-Dateien „PresentationCore“, „PresentationFramework“, „UIAutomationProvider“, „WindowsBase“ und „WindowsFormsIntegration“ hinzugefügt. Das Windows Forms-Steuerelement ist sehr einfach. Da seine einzige Funktion darin besteht, das WPF-Steuerelement zu hosten, ist keine umfangreiche Funktionalität erforderlich. Beachten Sie, dass das gehostete WPF-FishEyeControl-Steuerelement als Eigenschaft verfügbar gemacht wird, damit das von FishEyeControl verfügbar gemachte FishEyeEvent-Steuerelement als Senke fungieren kann (siehe Abbildung 6).
private System.Windows.Forms.Integration.ElementHost elementHost;
private WPFFishEye.FishEyeControl fishEye;
public WPFFishEye.FishEyeControl FishEye
{
    get { return fishEye; }
}

private void InitializeComponent()
{
    this.elementHost = 
        new System.Windows.Forms.Integration.ElementHost();
    this.fishEye = new WPFFishEye.FishEyeControl();
    this.elementHost.Dock = System.Windows.Forms.DockStyle.Fill;
    this.elementHost.Child = this.fishEyeControl;
    this.Controls.Add(this.elementHost);
}

Jetzt muss das Windows Forms-Steuerelement mit einem benutzerdefinierten Aufgabenbereich in Word verbunden werden. Dazu wird die ThisAddIn_Startup-Methode implementiert, um den benutzerdefinierten Aufgabenbereich zu erstellen, der das Windows Forms-Steuerelement als das zu hostende Steuerelement festlegt, und das benutzerdefinierte FishEyeClickEvent-Steuerelement als Senke bestimmt:
private WFControl wfControl;

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    Microsoft.Office.Tools.CustomTaskPane taskPane= 
        this.CustomTaskPanes.Add(new WFControl(), "ImageSelecter");
    wfControl = (WFControl)taskPane.Control;
    wfControl.FishEye.FishEyeClickEvent +=
        new FishEyeEvent(FishEye_FishEyeClickEvent);
    taskPane.Visible = true;
}
In der Implementierung des FishEyeClickEvent-Handlers kann vorläufig einfach ein Meldungsfeld angezeigt werden. Wenn Sie bisher alles nachvollzogen haben, können Sie an dieser Stelle das Add-In testen, weil der benutzerdefinierte Aufgabenbereich und das WPF-Steuerelement jetzt vollständig sind:
private void FishEye_FishEyeClickEvent(object source, 
    FishEyeEventArgs e)
{
    System.Windows.Forms.MessageBox.Show(e.ButtonName);
}
Genauso können Sie in VSTO-Lösungen überall da, wo Windows Forms-Steuerelemente eingefügt werden können, ebenfalls verfahren: in beliebigen Windows Forms-Dialogfeldern, in benutzerdefinierten Aufgabenbereichen auf Anwendungsebene, im Fensterbereich für Dokumentaktionen, in benutzerdefinierten Formularbereichen von Outlook und in der Oberfläche eines Word- oder Excel-Dokuments. Beachten Sie, dass bei einem Word- oder Excel-Dokument zwar die reguläre Entwurfszeitunterstützung verwendet werden kann, um ein Windows Forms-Steuerelement zu erstellen, in dem Ihr WPF-Steuerelement enthalten ist, aber dann zur Entwurfszeit das Steuerelement doch nicht in die Dokumentoberfläche eingefügt werden kann, weil die speziellen VSTO-Designer für Excel und Word dies nicht unterstützen.
Abgesehen davon ist es nicht weiter schwer, einer gehosteten VSTO-Dokumentlösung ein gehostetes WPF-Steuerelement hinzuzufügen. Die VSTO-Laufzeitinfrastruktur unterstützt bereits beliebige Windows Forms-Steuerelemente, einschließlich aller Steuerelemente, die WPF-Steuerelemente hosten. Die Laufzeit ist seit Visual Studio 2005 bereits vorhanden. Daher muss nur noch programmiert werden, das äußere Windows Forms-Steuerelement, das Ihr WPF-Steuerelement über ElementHost hostet, zu instanziieren und der Lösung hinzuzufügen.
private void Sheet1_Startup(object sender, System.EventArgs e)
{
    string controlName = "MyWfControl";
    WFControl wfControl = new WFControl();
    wfControl.Tag = Controls.AddControl(wfControl, 50,50, 208,250,
        controlName);
    wfControl.Name = controlName;
}
Das WPF-Hosting innerhalb von VSTO-Lösungen bringt die Einschränkung mit sich, dass Sie Windows Vista AeroTM Glass nicht auf normale Weise verwenden können. Wenn Ihr WPF-Steuerelement also Aero Glass verwendet, ist es nur für das zugrunde liegende Windows Forms-Hostingsteuerelement transparent, aber nicht für die Dokumentoberfläche oder das systemeigene Office-Fenster. Sie können dieses Problem natürlich mindern, indem Sie als Bestandteil Ihres Steuerelements ein Hintergrundbild festlegen, wodurch alle Aero Glass-Steuerelemente im Vordergrund für dieses Bild transparent sein können.

Nutzung des WCF-Diensts in der VSTO-Lösung
Es gibt mehrere Verfahren, den erforderlichen clientseitigen Code zum Einsatz eines WCF-Diensts zu entwickeln. Sie können Metadatenaustauschinformationen und Clientproxycode aus der kompilierten Dienstassembly oder aus dem ausgeführten Dienst selbst generieren. Sie können svcutil.exe manuell ausführen, um den Code zu generieren, oder den Assistenten zum Hinzufügen von Dienstverweisen in Visual Studio 2008 verwenden.
Bei Visual Studio 2005 müssen Sie svcutil.exe manuell verwenden. Starten Sie dazu zunächst den Dienst, öffnen Sie ein Befehlsfenster, und navigieren Sie zum Clientprojektordner. Führen Sie anschließend svcutil aus, um den clientseitigen Proxycode zu generieren. Eine Beispielbefehlszeile sieht ungefähr so aus:
"C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil" /language:C#
/config:app.config http://localhost:8080/ImageServiceHost/ImageService/mex
/n:*,WordImageSelecter
Dadurch wird die URL für den ausgeführten Dienst sowie die Adresse des Metadatenaustauschendpunkts festgelegt. Darüber hinaus zeigt dies an, dass als Zielsprache C# verwendet werden muss, die Konfigurationsinformationen in einer Datei namens „app.config“ ausgegeben werden müssen und als Namespace „WordImageSelecter“ verwendet werden muss (in diesem Beispiel ist dies der Namespace für das Add-In-Projekt).
Wenn svcutil den C#-Proxycode und die Datei „app.config“ generiert hat, muss beides dem Add-In-Projekt hinzugefügt werden. Zusätzlich muss ein Verweis auf System.ServiceModel.dll hinzugefügt werden.
Visual Studio 2008 stellt einen grafischen Assistenten bereit, den Sie anstelle der manuellen Ausführung von svcutil verwenden können. Um diesen Assistenten zu verwenden, starten Sie zunächst den Dienst, klicken danach im Projektmappen-Explorer mit der rechten Maustaste auf das Add-In-Projekt und wählen anschließend im Kontextmenü die Option „Add Service Reference“ (Dienstverweise hinzufügen) aus. Geben Sie im Dialogfeld zum Hinzufügen des Dienstverweises die Dienst-URL und den Zielnamespace ein, und klicken Sie danach auf „Go“ (Start). Dadurch werden, wie in Abbildung 7 gezeigt, die Dienstvertrags- und Konfigurationsinformationen aus dem ausgeführten Dienst abgerufen und in die Listen der Dienste und Vorgänge eingelesen. Klicken Sie auf „OK“, um den Proxycode zu generieren.
Abbildung 7 Dialogfeld zum Hinzufügen eines Dienstverweises (Klicken Sie zum Vergrößern auf das Bild)
Jetzt können Sie der Add-In-Klasse ein Proxyklassenfeld hinzufügen, dieses Feld in ThisAddIn_Startup initialisieren und durch den Proxy die WCF-GetDefinition-Dienstmethode aufrufen. Ersetzen Sie im FishEyeClickEvent-Handler das Meldungsfeld durch einen Aufruf von GetDefinition, analysieren Sie die zurückgegebene XML-Datei, um „Key“ und „Definition“ zu extrahieren, und fügen Sie diese im Word-Dokument ein. Setzen Sie den Mauszeiger nach dem Einfügen des Key-Texts an das Ende der Einfügung, bevor Sie den Definition-Text hinzufügen, damit die vorherige Auswahl nicht überschrieben wird (siehe Abbildung 8).
internal ImageServiceClient serviceClient;

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    // Connect to the WCF service.
    serviceClient = new ImageServiceClient();

    // (previously discussed code omitted for brevity).
}

private void FishEye_FishEyeClickEvent(object source, FishEyeEventArgs e)
{
    //System.Windows.Forms.MessageBox.Show(e.ButtonName);
    // Invoke the WCF Service and parse the returned XML.
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(
        Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName));
    String keyText = xmlDoc.SelectSingleNode(
        "descendant::Item/Key").InnerText;
    String definitionText = xmlDoc.SelectSingleNode(
        "descendant::Item/Definition").InnerText;

    // Insert the Key text into the Word document.
    this.Application.Selection.InsertAfter(
        String.Format("{0}{1}{2}",
        Environment.NewLine, keyText, Environment.NewLine));
    object titleStyle = Word.WdBuiltinStyle.wdStyleTitle;
    this.Application.Selection.set_Style(ref titleStyle);

    // Move to the end of the insertion.
    object gotoItem = Word.WdGoToItem.wdGoToLine;
    object gotoDirection = Word.WdGoToDirection.wdGoToLast;
    this.Application.Selection.GoTo(
        ref gotoItem, ref gotoDirection, ref missing, ref missing);

    // Insert the Definition text into the Word document.
    Word.Range insertionStart = this.Application.Selection.Range;
    this.Application.Selection.InsertAfter(
        String.Format("{0}{1}", definitionText, Environment.NewLine));
    object headingStyle2 = Word.WdBuiltinStyle.wdStyleHeading2;
    this.Application.Selection.set_Style(ref headingStyle2);
}


Sicherheit
Auf Ihrem Entwicklungscomputer wird bereits beim Erstellen der Add-In-Lösung Sicherheit für die Lösung eingerichtet. Wenn Sie die Lösung bereitstellen, wird Sicherheit als Bestandteil des Veröffentlichungsvorgangs eingerichtet. In Office 2007 wurden neue Sicherheitsfeatures eingeführt und die Arbeitsweise von Sicherheit für Makros, ActiveX-Steuerelemente und Add-Ins optimiert. Alle installierten Add-Ins sind jetzt über das Office-Vertrauensstellungscenter zugänglich. VSTO hat auch beträchtliche Änderungen am Sicherheitsmodell für Visual Studio 2008 vorgenommen. Das Modell beruht jetzt nicht mehr auf dem versionsspezifischen CAS-Repository (Code Access Security, Codezugriffssicherheit), sondern ist stattdessen in ClickOnce-Modell integriert. Darüber hinaus bietet VSTO sowohl für Add-Ins als auch für Dokumentlösungen zusätzliche Sicherheitsschichten, die auf intelligente Weise digitale Zertifikate bzw. einen registrierungsgestützten Speicher der Entscheidungen des Benutzers über die Vertrauenswürdigkeit nutzen. VSTO-Lösungen erfordern immer die Einstellung „FullTrust“ (Voll vertrauenswürdig), weil sie mit nicht verwaltetem Code (dem Office-Host) zusammenarbeiten. Diese Anforderung erstreckt sich auch auf die WPF- und WCF-Bestandteile der Lösung auf dem Client. Bei Visual Studio 2005 und Projekten für Office 2003 in Visual Studio 2008 müssen Sie Sicherheit speziell für die clientseitige WCF-.config-Datei (die Datei „app.config“ für WordImageSelecter.dll) einrichten. Dies hat den Grund, dass die .config-Datei ein Einstiegspunkt in den Code ist und daher nach den CAS-Richtlinien explizit als vertrauenswürdig eingestuft werden muss. Beim Erstellen von Projekten für Office 2007 in Visual Studio 2008 ist dies nicht mehr erforderlich, weil Vertrauen einer Lösung einschließlich aller abhängigen Assemblys und sonstiger abhängiger Dateien, die Bestandteil dieser Lösung sind, zuerkannt wird. An dieser Stelle können Sie das Add-In erneut testen, denn der WPF-Bestandteil und der WCF-Bestandteil sind jetzt vorhanden.

Verwendung von LINQ, XLinq und Lambda-Ausdrücken in VSTO
Zum Schluss muss noch die Verwendung von LINQ im Kontext einer VSTO-Lösung untersucht werden. Bei Visual Studio 2008 werden in die meisten Projekte automatisch Verweise auf System.Core und System.Xml.Linq aufgenommen, in denen die meisten LINQ-Klassen definiert werden. LINQ ist eine Gruppe von Spracherweiterungen, die es Ihnen ermöglicht, auf typensichere Weise SQL-artige Abfragen an einer uneingeschränkten Palette von Datenquellen auszuführen. Im Beispiel-Add-In wird die gesamte LINQ-Arbeit im FishEyeClickEvent-Handler geleistet, in dem die vom WCF-Dienst zurückgegebenen XML-Daten einigen einfachen Analysen unterzogen werden. Obwohl das Beispiel einfach ist, veranschaulicht es die Leistungsfähigkeit dieses Features.
Zunächst kann XLinq dazu verwendet werden, aus der XML-Zeichenfolge „Key“ und „Definition“ zu extrahieren, statt dies wie zu Beginn mit dem unhandlicheren XPath durchzuführen:
XElement item = XElement.Parse(
    Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName));
String keyText = (string)item.Element("Key");
String definitionText = (string)item.Element("Definition");
Ein weiteres Feature von LINQ sind implizit typisierte lokale Variable. Der Typrückschluss wird aufgerufen, indem der Compiler mithilfe des var-Schlüsselworts angewiesen wird, den Typ einer Variablen anhand des Ausdrucks zu erschließen, in dem sie verwendet wird. Hier wird dieses Verfahren verwendet, um die Anzahl der im Definition-Text enthaltenen Zeichen abzurufen:
var chars = from c in definitionText
            select c;
int charCount = chars.Count();
Danach kann mithilfe eines Lambda-Ausdrucks die Anzahl der aus vier Buchstaben bestehenden Wörter im Definition-Text abgerufen werden. Ein Lambda-Ausdruck ähnelt einem anonymen Delegaten:
List<string> definitionWords =
    new List<string>(definitionText.Split(new char[] { ' ' }));
var fourLetterWords = definitionWords.FindAll(
    x => (x.Length == 4));
Abgesehen davon, anonyme Delegaten durch Lambda-Ausdrücke zu ersetzen, können Sie auch aus Lambda-Ausdrücken eine Ausdrucksstruktur erstellen. Dadurch kann Code (der Lambda-Ausdruck) wie Daten behandelt werden. Im folgenden Beispiel wird der Lambda-Ausdruck „y => (y.Length == 4)“ einem Objekt namens „Expression“ zugeordnet, um eine LISP-artige Übersetzung des Ausdrucks ausgeben zu können. In diesem Beispiel liefert der Ausdruck die Ausgabe „Equal Length 4“:
Expression<Func<string, bool>> f = y => (y.Length == 4);
BinaryExpression be = (BinaryExpression)f.Body;
MemberExpression me = (MemberExpression)be.Left;
ConstantExpression ce = (ConstantExpression)be.Right;

this.Application.Selection.InsertAfter(
    String.Format("{0} chars, {1} four-letter words [{2} {3} {4}] {5}",
    charCount, fourLetterWords.Count,
    be.NodeType, me.Member.Name, ce.Value,
    Environment.NewLine));
Das letzte hier verwendete Feature sind die Erweiterungsmethoden. Viele Leute beschweren sich, dass es sehr mühsam ist, in C# für Office Code zu entwickeln, weil viele Methoden in den Office-Objektmodellen eine große Anzahl optionaler Parameter akzeptieren und in vielen Fällen (vor allem bei Word) Parameter explizit nach Verweis übergeben werden müssen. Dies ist bei Visual Basic kein Problem, da Visual Basic Ihnen die Arbeit des Übersetzens abnimmt und die Komplexität dieses Vorgangs verbirgt. So musste zum Beispiel in dem an früherer Stelle aufgelisteten Code zum Verschieben der Auswahl an das Ende der vorherigen Einfügung die GoTo-Methode aufgerufen werden. Diese Methode akzeptiert vier Parameter, die explizit nach Verweis übergeben werden und von denen zwei Parameter optional sind.
Um dieses Problem zu reduzieren, können Sie eine Erweiterungsmethode schreiben, bei der es sich um eine statische Methode in einer benutzerdefinierten Klasse handelt. Wenn Sie diese Schnittstelle als Typ des ersten Parameters festlegen, der ebenfalls dieses Schlüsselwort verwenden muss, sieht es so aus, als ob eine Schnittstelle von Office erweitert würde. Diese Erweiterungsmethode kann intern die GoTo-Standardmethode von Office verwenden:
public static class RangeExtender
{
    public static void GoTo(this Word.Application wordApplication,
        Word.WdGoToItem gotoItem, Word.WdGoToDirection gotoDirection)
    {
        object what = gotoItem;
        object which = gotoDirection;
        object missing = Type.Missing;
        wordApplication.Selection.GoTo(
            ref what, ref which, ref missing, ref missing);
    }
}
Danach kann diese Erweiterungsmethode anstelle der vom Office-Objektmodell definierten Erweiterungsmethode verwendet werden:
this.Application.GoTo(
    Word.WdGoToItem.wdGoToLine, Word.WdGoToDirection.wdGoToLast);

Die Zukunft Office-basierter Anwendungen
Da mehr und mehr Entwickler Office-basierte Lösungen mit verwaltetem Code erstellen, wird es immer wichtiger, dass Features, die in .NET Framework und neuen Versionen von Visual Studio eingeführt werden, innerhalb eines Office-Kontexts reibungslos arbeiten. In diesem Artikel wurde erläutert, wie eine VSTO-Lösung erstellt werden kann, die WPF, WCF und LINQ verwendet, um innerhalb einer Office-Anwendung Dienste zu nutzen. In der Beispiellösung wird zum Abrufen statischer Daten ein einfacher WCF-Dienst verwendet, die Daten mit LINQ analysiert und WPF eingesetzt, um eine verbesserte Benutzeroberfläche bereitzustellen. In einer Lösung für ein realistischeres Szenario wird wahrscheinlich eine Palette von WCF-Diensten verwendet, die mit mehreren serverseitigen Branchensystemen zusammenarbeiten und möglicherweise WPF nutzen, um zusätzlich zu Elementen der Benutzeroberfläche eine leistungsfähige Datenvisualisierung bereitzustellen. Außerdem kann auf dem Server, in der mittleren Ebene oder auf dem Client LINQ verwendet werden, um eine intuitive, wartungsfreundliche Funktionalität für die Abfrage und Bearbeitung von Daten zu erstellen.
Aufgrund der Entwurfsverbesserungen für die WPF-Integration mit Windows Forms sowie der Verbesserungen des Compilers hinsichtlich sprachintegrierter Abfragen ist Visual Studio 2008 eine wertvolle Ergänzung der Entwicklungstoolbox Ihres Unternehmens.

Andrew Whitechapelhat viele Jahre als Berater für Architekten gearbeitet und Unternehmenslösungen für eine große Bandbreite von Kunden entwickelt. Heute ist er Program Manager Technical Lead für das VSTO-Team bei Microsoft.

Page view tracker