使用 WPF、WCF 与 LINQ 构建基于 Office 的解决方案

代码下载位置: VSTONet2007_12.exe (419 KB) 
Browse the Code Online

本文以 Visual Studio 2008 的预发布版为基础。文中包含的所有信息均有可能变更。
本文讨论:
  • VSTO 如何使 Office 开发更强大
  • 在 Office 解决方案中使用 WCF、WPF 与 LINQ
  • 轻松将高级功能添加到 Office 应用程序
  • 简便地构建服务
本文使用了以下技术: 
VSTO, WPF, WCF, LINQ
Visual Studio® 2008 引入了一系列针对各种客户解决方案类型的新功能。现在,您可以构建 Visual Studio Tools for Office (VSTO) 解决方案。该解决方案使用 Windows® Presentation Foundation (WPF)、Windows Communication Foundation (WCF) 与语言集成查询表达式 (LINQ),稍后我会对此做相应介绍。
该项新技术使人们有可能构建令人振奋的解决方案,这些解决方案的行为在之前很难或不可能实现。例如,尽管 Microsoft® Office Excel® 2007 已经具有强大的绘图功能,但如果将 Excel 计算引擎与增强型 UI 和数据可视化配合使用(使用 WPF 3D 动画图形),甚至可以构建更为丰富的体验。
随着 Office 发展成为真正的开发平台,基于 Office 的解决方案变得越来越复杂、越来越不以文档为中心,且越来越松散耦合。要构建面向服务的解决方案来连接丰富的 Office 客户端与强大的服务器端功能以及远程数据,需要有一个支持框架和设计时工具集,而 WCF 完全可满足这一需求。Visual Studio 2008 提供了简单的 GUI 向导,让您可使用 WCF 服务,而无需担心服务元数据、协议或 XML 配置。
LINQ 让开发人员可以为查询数据构建更直观且大大简化的代码。Office 开发人员将会特别青睐的 LINQ 功能之一是可使用扩展方法来支持具有可选或显式引用参数的方法的传统 Office 对象模型模式。
使用 Visual Studio 2008,您可以构建这样的解决方案:融入了 Office 客户端应用程序的本地功能以及 WPF 复杂的 UI 功能,WPF 通过 WCF 与远程数据和服务连接;使用 LINQ 的 RAD 功能操作该数据。

示例解决方案
我将特意构建简单的解决方案,这样我可以重点介绍各项技术和集成问题,而不是业务功能。图 1 可让您了解用户体验。这涉及两个流程:控制台应用程序中承载的独立 WCF 服务与 Word 2007 中运行的 VSTO 加载项。该加载项提供自定义的任务窗格,其中包括一个 WPF 自定义控件。当用户单击控件中的按钮时,加载项通过调用 WCF 服务以提取一些 XML 数据进行响应。然后该加载项会使用 LINQ 与 XLinq 以各种方式(包括 lambda 表达式和表达式树)处理该数据,设置文本格式,然后将文本插入到活动文档中。如果想要自己演练一遍该示例解决方案,可从 msdn2.microsoft.com/vstudio/aa700831 下载并安装 Visual Studio 2008 Beta 2 版本。
图 1 示例解决方案的运行时行为 (单击该图像获得较大视图)
此示例的目的不是要展示特定的运行时功能;而是为了说明所有新的 Visual Studio 2008 技术(WPF、WCF 与 LINQ)可以同 Office 解决方案无缝协作。本示例解决方案的另一个目标是为构建令人振奋的新用户体验的设想提供材料。
图 2 说明了解决方案的体系结构。在 VSTO 应用程序内使用 WCF 和 LINQ 提供了一种轻便的方法,并且与在 Windows Forms 或控制台应用程序中使用这些功能没有任何差别。不过您会发现,使用 WPF 更有意思。
图 2 示例解决方案的体系结构 (单击该图像获得较大视图)
VSTO 的一个方面是能够构建可在本地 Office UI 窗口中使用托管控件的解决方案。VSTO 支持将托管的 (Windows Forms) 控件放在任意的 Windows Forms 对话框、应用程序级自定义任务窗格、文档操作窗格、Outlook® 自定义窗体区域以及 Word 或 Excel 文档的表面。Microsoft .NET Framework 3.5(Visual Studio 2008 附带)中包括 WindowsFormsIntegration.dll,它包含 Windows Forms Integration ElementHost 类。这就在 Windows Forms 与 WPF 之间架起了桥梁。因此,VSTO 还支持将 WPF 控件放在可以安放 Windows Forms 控件的任何位置。
您需要在此作出一些相关的设计决定,其中之一就是在何处处理事件。例如,当用户单击其中一个 WPF 按钮控件时,您可以在多个级别处理该事件:在 WPF UserControl、Windows Forms UserControl 或加载项中。在何处处理该事件以及是否将其进一步提升,在很大程度上取决于您希望各种控件达到何种程度的可重用性。

构建 WCF 服务
我从一个名为 ImageServiceHost 的简单控制台应用程序开始着手构建项目。下一步是将 WCF 服务项添加到项目中,我决定将其称为 ImageService。将 Windows Communication Foundation 服务添加到 Visual Studio 中的项目会导致自动为该服务生成起始代码,包括代表服务约定的接口以及实现该接口的类。了解这一点很重要。标准的 app.config 文件提供了为服务指定行为和终结点的配置条目。
我必须更改服务约定及其实现以便删除向导生成的 DoWork 方法,将其替换成名为 GetDefinition 的新方法,新方法接受关键字字符串,返回 XML 数据字符串:
[ServiceContract()]
public interface IImageService
{
    [OperationContract]
    string GetDefinition(string myValue);
}
我可以从任何地方获取 XML 数据;实际上,WCF 服务将会在服务器上运行,且可能会从服务器端数据库或一些业务线 (LOB) 系统(如 SAP 或 Siebel)获取其数据。不过在我的示例中,我会在本地计算机上运行此服务,并从静态的 XML 字符串资源获取数据。
我将创建的 XML 数据源会包含大量的项元素,其中每个项元素代表关键字对定义的一个简单映射:
<Dictionary>
    <Item>
        <Key>Frangipani</Key>
        <Definition>Some description goes here.</Definition>
    </Item>
    <Item>
        <Key>Toucan</Key>
        <Definition>...etc</Definition>
    </Item>
</Dictionary>
如果我将此 XML 数据文件添加到项目资源,则可以实现服务构造函数以便从资源加载 XML 数据。然后就可以实现 GetDefinition 约定方法,返回与用户所请求关键字匹配的项。同时,我将返回的字符串回显到控制台窗口。注意,该示例代码已经过简化,其中不包括通常会包含的异常处理:
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;
}
接下来,控制台应用程序只需启动和停止服务即可。
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();
}
由于服务现在已定义、实现并配置,接着我可以构建这部分解决方案并开始运行它。我对它进行了设置,以使服务在控制台应用程序中运行,等待传入的调用,直到用户在控制台窗口按下 Enter 键。

WPF UserControl
要在应用程序中使用 WPF,有两种很简单的方法:增强用户界面和提供复杂的数据可视化。我的示例重点介绍丰富的 UI 功能。此处的要点是如何将 WPF 控件轻松集成到 VSTO 解决方案中。我可以借助一个非常简单的 WPF 控件(可能是一个带有按钮的网格)来说明,但是不利用 WPF 复杂的图形功能好像说不过去,因此我将使用一个可提供鱼眼动画行为的自定义 WPF UserControl。我将 Microsoft Expression BlendTM 的 HyperBar 示例(该示例可从 blogs.msdn.com/expression/articles/516599.aspx 获得)作为我示例的基础。它还基于 Paul Tallett 在 codeproject.com/WPF/Panels.asp 上介绍的 FishEyePanel。我将介绍的主要差别是在非 WPF 窗口中添加 WPF 控件,这样您可以看到它在本地 Office 窗口的承载情况。
首先,我将创建一个 Windows WPF UserControl Library 项目,该项目产生一些起始 XAML 和相应的 C# 代码隐藏。首先要做的是将 UserControl 类名从 UserControl1 更改为更有针对性的名称 — FishEyeControl。此 FishEyeControl 将包含一组按钮,由名为 FishEyePanel 的自定义面板管理,该面板从 System.Windows.Controls.Panel 派生而来。在 FishEyePanel 中,我为三个鼠标事件(MouseMove、MouseEnter、MouseLeave)设置了事件处理程序,这样当用户将鼠标移至其上时,我可以使面板失效,并使其重新呈现。下面是其中一个鼠标事件处理程序:
public class FishEyePanel : Panel
{
    public FishEyePanel()
    {
        this.MouseMove += new
            MouseEventHandler(FishEyePanel_MouseMove);
    }

    private void FishEyePanel_MouseMove(object sender, 
        MouseEventArgs e)
    {
        this.InvalidateArrange();
    }
}
要将 FishEyePanel 连接到 FishEyeControl,我必须在 FishEyeControl 上设置 ItemsControl 属性,如下所示:
<UserControl x:Class="WPFFishEye.FishEyeControl"
    xmlns:uc="clr-namespace:WPFFishEye">
    <Grid>
        <ItemsControl>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <uc:FishEyePanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</UserControl>
接下来,我为面板设置一些数据资源。我仅将本地文件用作资源,而不是尝试设置更实际的 Web 服务或 LOB 数据源。在本例中,我会将一系列简单的 .jpg 文件用作按钮上的图像。这些 JPG 位于外部文件中,因此我需要一小段代码获取简单的文件名(如 Frangipani.jpg)并将其转换为运行时有效的完全限定路径。为此,我可以定义一个 IValueConverter 实现。这是在数据绑定期间执行自定义转换常使用的技术。IValueConverter 接口定义了两个方法,但我只对其中一个感兴趣,即 Convert。数据绑定引擎在将值从绑定源传播到绑定目标时会调用此方法。在我的实现中,我只使用当前程序集的 CodeBase,并前置它作为图像文件名的路径。
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);
    }
}
然后,回到 FishEyeControl.xaml 中,我在控件的 ResourceDictionary 中连接这些代码,并在 ItemsControl 中使用 ResourceDictionary(请参见图 3)。注意,面板中项的 DataTemplate 会设置为带图像的按钮,且该图像在数据绑定期间会使用 ImagePathConverter。
<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>

此定义还为每个按钮指定了 buttonClick 事件处理程序。尽管可以在 WPF 控件自身以及承载 Windows Forms 控件或加载项中处理事件,但是我只在 WPF 控件和加载项中进行处理。我通过提取按钮图像名称和再次触发事件来实现 FishEyeControl.xaml.cs 中的事件处理程序。VSTO 加载项将根据图像名称调用 WCF 服务来对事件作出反应(请参见图 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);
        }
    }
}

接下来,我定义一个自定义的 EventArgs 类型,这样当我重新触发事件时,可以将按钮图像的简单名称向外发送到侦听程序,如下所示:
public delegate void FishEyeEvent(object source, FishEyeEventArgs e);

public class FishEyeEventArgs : EventArgs
{
    public String ButtonName;

    public FishEyeEventArgs(String name)
    {
        ButtonName = name;
    }
}
还记得吗,我打算在用户将鼠标移至 FishEyePanel 上面时使 FishEyePanel 失效。我还想要控制呈现的运作方式,以便提供鱼眼动画行为。要控制呈现,必须实现两个覆盖,MeasureOverride 与 LayoutOverride,这会实现一个两步布局系统。在度量这一步中,父项 (FishEyeControl) 调用子项 (FishEyePanel) 以了解子项需要多少空间。此处的标准行为是子项控件查询自己的子项以了解它们所需的空间,然后将结果传回父项。在 WPF 使用的模型中,控件几乎可以无限制地嵌套,所以在此环境下,会对整个树进行空间需求查询,并将信息传回最终父项。在布局这一步中,会发生相似的递归,其中父项决定其子项的大小并将该信息向下传递,每个子项接着会将该信息向下传递给其子项。这个过程会调用 ArrangeOverride 方法,在此可以断定子项大小并对其进行安排。
在 MeasureOverride 实现中,我使用 UIElement.DesiredSize 属性尝试为子项提供它们所需的全部空间。在 ArrangeOverride 中,我添加了比例转换,将转换结果翻译给每个子项,然后计算它们所需的宽度。在本例中,我调整了直接位于鼠标下的子项按钮,使其比其他按钮大。该子项按钮两侧的两个按钮要小一些,但是仍比其他的按钮大。剩下空间内的其余按钮均同样大小。有关进一步的详细信息,可查看《MSDN® 杂志》网站 (msdn.microsoft.com/msdnmag/code07.aspx) 上有关本文附带的下载中提供的代码。最终结果是,对于每一鼠标移动,我调整了按钮的大小以便提供熟悉的鱼眼效果。

将 WPF UserControl 连接到 VSTO
基本的 VSTO 解决方案是一个叫 WordImageSelecter 的 Word 2007 加载项。创建初始加载项项目后,我便可以将 WPF UserControl 项目添加到解决方案。这让我能够利用 Visual Studio 2008 中增强的设计时支持,将 WPF 控件集成到 Windows Forms 项目中。
下一步是在加载项项目中创建一个简单的 Windows Forms UserControl。这将最终承载自定义的 WPF UserControl。在 Visual Studio 2008 中,您解决方案中任何项目的任何 WPF UserControl 均将显示在工具箱中,所以您可以将其直接拖放到 Windows Forms UserControl 设计界面上,如图 5 所示。
图 5 WPF 控件 Windows Forms 设计界面 
此操作会生成在 Windows Forms 控件中承载 WPF 控件所需的全部代码(具体通过 ElementHost 对象实现)。ElementHost 用于集成 Windows Forms 和 WPF。在本例中,Windows Forms UserControl 承载 ElementHost,而 ElementHost 随之承载 WPF UserControl。这也会将所需的引用添加到自定义的 WPFFishEye.dll,以及标准的 PresentationCore、PresentationFramework、UIAutomationProvider、WindowsBase 和 WindowsFormsIntegration DLL。Windows Forms 控件非常简单 — 其唯一用途就是承载 WPF 控件,所以我不需要内置任何重要功能。注意,我将承载的 WPF FishEyeControl 公开为属性,以便可以接收由 FishEyeControl 公开的 FishEyeEvent(如图 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);
}

现在,我必须将 Windows Forms 控件连接到 Word 中的自定义任务窗格。要完成此操作,我需要实现 ThisAddIn_Startup 方法以创建自定义的任务窗格,同时将 Windows Forms 控件指定为要承载的控件,并接收自定义的 FishEyeClickEvent:
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;
}
在 FishEyeClickEvent 处理程序的实现中,现在我只能显示一个消息框。接下来,就可以在该点测试加载项,因为自定义任务窗格和 WPF 控件块是完整的:
private void FishEye_FishEyeClickEvent(object source, 
    FishEyeEventArgs e)
{
    System.Windows.Forms.MessageBox.Show(e.ButtonName);
}
您可以使用相同的一般方法,将 WPF 控件添加到 VSTO 解决方案中任何允许放置 Windows Forms 控件的地方 — 包括任意的 Windows Forms 对话框、应用程序级自定义任务窗格、文档操作窗格、Outlook 自定义窗体区,以及 Word 或 Excel 文档表面。注意,对于 Word 或 Excel 文档,您可以使用常规的设计时支持以构建包含您的 WPF 控件的 Windows Forms 控件,但是之后必须在设计时停止在文档表面上实际放置控件,因为特殊的 VSTO Excel 和 Word 设计器不支持它。
总之,通过编程方式将承载的 WPF 控件添加到 VSTO 文档解决方案完全不费力。VSTO 运行时基础结构已支持所有任意的 Windows Forms 控件,包括承载 WPF 控件的控件。运行时已可用,且自 Visual Studio 2005 以来一直可用。所以,您需要完成的唯一编程任务是实例化外部 Windows Forms 控件(它正通过 ElementHost 承载您的 WPF 控件),并将其添加到解决方案中。
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;
}
考虑到 VSTO 解决方案中 WPF 承载的特性,应当明确的是,限制之一就是无法按正常方式使用 Windows Vista AeroTM Glass。也就是说,如果 WPF 控件使用 Aero Glass,则它只对底层 Windows Forms 承载控件是透明的,而对文档表面或本地 Office 窗口是不透明的。当然,可以通过指定背景图像作为控件的一部分来缓解该问题;这样任何放置到顶层的 Aero Glass 控件对该图像都可以是透明的。

在 VSTO 解决方案中使用 WCF 服务
开发使用 WCF 服务所必需的客户端代码有多种方法。可以从编译过的服务程序集或从正在运行的服务本身生成元数据交换信息和客户端代理代码,也可以手动执行 svcutil.exe 来生成代码,还可以使用 Visual Studio 2008 中的“添加服务引用”向导。
就 Visual Studio 2005 而言,必须手动使用 svcutil。为此,首先启动服务,接着打开命令窗口并导航到客户端项目文件夹。然后,运行 svcutil 生成客户端代理代码。示例命令行可能如下所示:
"C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil" /language:C#
/config:app.config http://localhost:8080/ImageServiceHost/ImageService/mex
/n:*,WordImageSelecter
该代码指定了运行服务的 URL 以及元数据交换终结点地址。它还指明应使用 C# 作为目标语言,配置信息应输出到名为 app.config 的文件,并且应使用 WordImageSelecter 作为命名空间(在本例中,这是用于加载项项目的命名空间)。
一旦 svcutil 生成 C# 代理代码和 app.config,就必须将它们添加到加载项项目中。还必须添加对 System.ServiceModel.dll 的引用。
Visual Studio 2008 提供了图形向导供您使用,因此不必手动运行 svcutil。要使用该向导,先启动服务,接着在“解决方案资源管理器”中右击加载项项目,然后从上下文菜单中选择“添加服务引用”选项。在“添加服务引用”对话框中,输入服务 URL 和目标命名空间,然后单击“执行”。这将从运行中的服务获取服务约定和配置信息,并填充服务和操作列表,如图 7 所示。单击“确定”生成代理代码。
图 7 “添加服务引用”对话框 (单击该图像获得较大视图)
现在,您可以将代理类字段添加到加载项类,然后在 ThisAddIn_Startup 中初始化它,并通过代理调用 WCF Service GetDefinition 方法。在 FishEyeClickEvent 处理程序中,使用对 GetDefinition 的调用替换消息框,分析返回的 XML 以提取 Key 与 Definition,然后将它们插入到 Word 文档中。注意,在插入 Key 文本之后,我会在添加 Definition 文本之前移到插入末尾 — 这样,我就不会覆盖之前的选择(请参见图 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);
}

安全性
在您的开发机器上,构建加载项解决方案时需要设置其安全性。在部署解决方案时,会将安全性设置为发布操作的一部分。Office 2007 引入了新的安全功能,并优化了安全性对宏、ActiveX 控件和加载项起作用的方式。现在,所有已安装的加载项均可通过 Office 信任中心访问。VSTO 还对其用于 Visual Studio 2008 的安全性模型做了极大的更改。该模型不再依赖特定于版本的代码访问安全 (CAS) 存储库,而是将其集成到 ClickOnce 模型中。VSTO 还为加载项和文档解决方案增加了一层额外的安全性,智能化地使用数字证书和/或基于注册表的用户信任决策存储。VSTO 解决方案始终要求 FullTrust,因为它们与未托管的代码(Office 主机)进行互操作,且该需求延伸到作为客户端上解决方案的组成部分的 WPF 和 WCF 片段。对于 Visual Studio 2005 和 Visual Studio 2008 中用于 Office 2003 的项目,您需要专门为客户端 WCF .config 文件(用于 WordImageSelecter.dll 的 app.config)设置安全性。这是因为 .config 文件是代码的入口点,必须在 CAS 策略中得到显式信任。当在 Visual Studio 2008 中构建 Office 2007 的项目时,这不再需要,因为信任已授予给解决方案,包括构成该解决方案一部分的任何依赖性程序集和其他辅助文件。此时,您可以再次测试加载项,因为 WPF 与 WCF 片段都已可用。

在 VSTO 中使用 LINQ、XLinq 与 Lambda 表达式
要探讨的最后部分是在 VSTO 解决方案环境下如何使用 LINQ。对于 Visual Studio 2008,大多数项目会自动包含对 System.Core 和 System.Xml.Linq 的引用,此处定义了大多数 LINQ 类。LINQ 是一套语言扩展,允许您以类型安全的方式在范围不受限制的数据源上执行类似于 SQL 的查询。在示例加载项中,所有 LINQ 工作均在 FishEyeClickEvent 处理程序中完成。我在此对从 WCF 服务返回的 XML 数据执行一些简单分析。尽管该示例非常简单,但是它说明了该功能如何强大。
首先,我可以使用 XLinq 从 XML 字符串中提取 Key 和 Definition,而不是从我起初使用的更繁琐的 XPath:
XElement item = XElement.Parse(
    Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName));
String keyText = (string)item.Element("Key");
String definitionText = (string)item.Element("Definition");
LINQ 的另一个功能是隐式类型化局部变量。通过使用 var 关键字可调用类型引用,以指示编译器通过使用变量的表达式推断变量的类型。这里,我们将使用该技术来获取 Definition 文本中的字符数量:
var chars = from c in definitionText
            select c;
int charCount = chars.Count();
接下来,我们可以使用 lambda 表达式来获取 Definition 文本中包含四个字符的单词的数量。lambda 表达式与匿名委托相似:
List<string> definitionWords =
    new List<string>(definitionText.Split(new char[] { ' ' }));
var fourLetterWords = definitionWords.FindAll(
    x => (x.Length == 4));
除了使用 lambda 表达式代替匿名委托之外,还可以从 lambda 表达式构建表达式树。这让您能够像处理数据一样处理代码(lambda 表达式)。在下例中,我使用了 lambda 表达式 y => (y.Length == 4) 并将其映射到 Expression 对象,这样便可以输出经过 LISP 风格转换的表达式。在本示例中,此表达式将生成输出“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));
要使用的最后一项功能是扩展方法。许多人抱怨将 C# 用于 Office 代码开发非常痛苦,因为 Office 对象模型中的许多方法需要大量的可选参数,而且(特别是 Word)在许多情况下,参数必须由引用显式传递。对于 Visual Basic 来说,这不是个问题,因为 Visual Basic 负责为您转换并隐藏底层的复杂性。例如,上文所列出的要将选择的内容移至之前插入末尾的代码需要调用 GoTo 方法。该方法需要四个全部由引用显式传递的参数,且包含两个可选参数。
要缓解该问题,我可以编写一个扩展方法,它是自定义类中的静态方法。我可以使它看起来象扩展其中一个 Office 接口,方法是将该接口指定为第一个参数的类型,而且还必须使用 this 关键字。此扩展方法可在内部使用标准的 Office GoTo 方法:
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);
    }
}
然后,我可以使用此扩展方法取代一个由 Office 对象模型定义的方法:
this.Application.GoTo(
    Word.WdGoToItem.wdGoToLine, Word.WdGoToDirection.wdGoToLast);

基于 Office 的应用程序的未来
随着越来越多的开发人员使用托管代码构建基于 Office 的解决方案,.NET Framework 与 Visual Studio 新版本中引入的功能在 Office 环境下能无缝工作变得越来越重要。在本文中,您已了解可以如何构建使用 WPF、WCF 与 LINQ 的 VSTO 解决方案,从而在 Office 应用程序中使用服务。示例解决方案使用了简单的 WCF 服务检索静态数据,使用 LINQ 分析数据,并使用 WPF 提供增强的 UI。更典型的现实解决方案则有可能使用一系列 WCF 服务与多个服务器端 LOB 系统配合工作,而且除了 UI 元素之外,还可能使用 WPF 提供复杂的数据可视化。LINQ 既可在服务器上使用,也可在中间层或者客户端上使用,为数据查询和操作构建直观且易于维护的功能。
很显然,凭借针对 WPF 与 Windows Forms 集成的设计器增强功能以及针对语言集成查询的编译器增强功能,Visual Studio 2008 正成为您企业开发工具箱强有力的助手。

Andrew Whitechapel作为架构师和顾问,多年来一直在为众多客户构建企业解决方案。他目前担任 Microsoft 的 VSTO 团队的项目经理兼技术主管。
原文地址:https://www.cnblogs.com/Leo_wl/p/2222675.html