VSTO
Desenvolva soluções com base no Office usando WPF, WCF e LINQ
Andrew Whitechapel
Este artigo se baseia em uma versão de pré-lançamento do Visual Studio 2008. Todas as informações deste artigo estão sujeitas a alterações.
Este artigo aborda:
- Como o VSTO torna o desenvolvimento em Office mais poderoso
- Usando WCF, WPF e LINQ em soluções Office
- Adição fácil de recursos avançados aos seus aplicativos Office
- Desenvolvendo serviços da forma mais fácil
|
Este artigo utiliza as seguintes tecnologias:
VSTO, WPF, WCF, LINQ
|

Conteúdo
O Visual Studio® 2008 apresenta um conjunto de novos recursos voltados para uma ampla variedade de tipos de solução cliente. Agora você pode desenvolver uma solução VSTO (Visual Studio Tools for Office) que utiliza WPF (Windows® Presentation Foundation), WCF (Windows Communication Foundation) e LINQ (language integrated query expressions), que mostrarei em instantes.
As novas tecnologias oferecem oportunidades para a criação de soluções empolgantes com um comportamento antes difícil ou impossível de obter. Por exemplo, embora o ® Office Excel® 2007 tenha poderosos recursos de criação de gráficos, você pode desenvolver uma experiência ainda mais elaborada quando combina o mecanismo de cálculo do Excel à interface do usuário avançada e à visualização de dados usando os gráficos animados 3D do WPF.
À medida que o Office evolui para uma verdadeira plataforma de desenvolvimento, as soluções baseadas nele estão se tornando cada vez mais sofisticadas, menos voltadas para documentos e menos rígidas. A necessidade de uma estrutura de suporte e de um conjunto de ferramentas em tempo de design para a criação de soluções orientadas a serviço que conectam um cliente Office elaborado com uma funcionalidade poderosa do lado servidor e dados remotos é preenchida de modo ordenado pelo WCF. O Visual Studio 2008 oferece um assistente de GUI simples que permite a você utilizar serviços WCF sem ter de se preocupar sobre metadados de serviço, protocolos ou configuração do XML.
O LINQ permite que os desenvolvedores criem código mais intuitivo e muito mais simplificado para a consulta de dados. Um dos recursos do LINQ que os desenvolvedores de Office mais apreciarão será o uso dos métodos de extensão para oferecer suporte ao padrão de modelo de objeto do Office tradicional dos métodos que possuem parâmetros de referência opcionais ou explícitos.
Com o Visual Studio 2008, você pode criar uma solução que incorpore os recursos nativos de um aplicativo cliente do Office combinado aos sofisticados recursos WPF de interface do usuário. Esse aplicativo pode se conectar a dados e serviços remotos via WCF e utilizar os recursos RAD do LINQ na manipulação desses dados.
A solução de exemplo
A solução que vou desenvolver é deliberadamente simples para que eu possa me concentrar nas tecnologias individuais e nos problemas de integração em vez de na funcionalidade de negócios. A
Figura 1 dá uma idéia da experiência do usuário. Existem dois processos envolvidos: um serviço WCF autônomo, hospedado em um aplicativo de console, e um suplemento VSTO no Word 2007. O suplemento oferece um painel de tarefas personalizado que inclui um controle personalizado WPF. Quando o usuário clica nos botões do controle, o suplemento responde chamando o serviço WCF para a obtenção de dados XML. Em seguida, o suplemento processa seus dados de várias formas, usando LINQ e XLinq, incluindo expressões lambda e árvores de expressão, e formata texto que será inserido no documento ativo. Se você quiser trabalhar na solução de exemplo por conta própria, baixe e instale a versão Beta 2 do Visual Studio 2008, disponível em
msdn2.microsoft.com/vstudio/aa700831.
Figura 1 Comportamento em runtime da solução de exemplo (Clique na imagem para aumentar a exibição)
O objetivo deste exemplo não é mostrar a funcionalidade de runtime específica, mas sua idéia principal é demonstrar que todas as novas tecnologias do Visual Studio 2008 (WPF, WCF e LINQ) funcionam de maneira integrada com soluções do Office. Outro objetivo é oferecer meios para a criação de empolgantes e novas experiências de usuário.
A Figura 2 ilustra a arquitetura da solução. A utilização do WCF e do LINQ em um aplicativo VSTO oferece uma abordagem leve e não é diferente do uso desses recursos em um Windows Forms ou em um aplicativo de console. No entanto, usar o WPF é muito mais interessante, como veremos.
Figura 2 A arquitetura da solução de exemplo (Clique na imagem para aumentar a exibição)
Um aspecto do VSTO é a sua capacidade de desenvolver soluções com controles gerenciados em janelas nativas da interface do usuário do Office. O VSTO oferece suporte à inserção de controles gerenciados (Windows Forms) em caixas de diálogos arbitrárias do Windows Forms, em painéis de tarefas personalizados em nível de aplicativo, no painel de ações do documento, nas regiões do formulário personalizado do Outlook® e na superfície de um documento do Word ou do Excel. O Microsoft .NET Framework 3.5 (que vem com o Visual Studio 2008) inclui a WindowsFormsIntegration.dll, que contém a classe ElementHost para Integração de Windows Forms. Isso oferece uma ponte entre o Windows Forms e o WPF. Dessa forma, o VSTO também oferece suporte à colocação de controles WPF nos mesmos locais onde os controles Windows Forms podem ser inseridos.
Uma das decisões interessantes de design que você precisa fazer aqui é onde manipular eventos. Por exemplo, quando o usuário clica em um dos controles Button do WPF, é possível manipular essa ação em diversos níveis: no UserControl WCF, no UserControl Windows Forms ou no suplemento. O local onde você manipulará o evento, e se ele será utilizado em outras ocasiões, dependerá de quanto os seus diversos controles serão reutilizáveis.
Desenvolvendo o serviço WCF
Comecei a desenvolver o projeto com um aplicativo de console simples chamado ImageServiceHost. Minha próxima etapa foi adicionar um item Serviço WCF ao projeto, que decidi chamar de ImageService. É importante compreender que a adição de um Serviço Windows Communication Foundation ao projeto do Visual Studio faz com que o código inicial seja automaticamente gerado para o serviço, incluindo uma interface que representa o contrato de serviço e a classe que implementa a interface. As entradas de configuração que especificam comportamentos e pontos de extremidade para o serviço são fornecidas em um arquivo app.config padrão.
Tive de alterar o contrato de serviço e sua implementação para remover o método DoWork gerado pelo assistente e substituí-lo por um novo método, chamado GetDefinition, que obtém uma cadeia de caracteres de palavra-chave e retorna uma cadeia de caracteres de dados XML:
[ServiceContract()]
public interface IImageService
{
[OperationContract]
string GetDefinition(string myValue);
}
Eu poderia obter os dados XML de qualquer lugar; no mundo real, o serviço WCF seria executado em um servidor e poderia, talvez, recuperar seus dados de um banco de dados do lado servidor ou de algum sistema LOB (linha de negócios), como SAP ou Siebel. No entanto, em meu exemplo executarei o serviço na máquina local e a fonte dos dados será um recurso de cadeia de caracteres XML estático.
A fonte de dados XML que criarei vai conter alguns elementos de item, cada um representando um mapeamento simples de uma palavra-chave para uma definição:
<Dictionary>
<Item>
<Key>Frangipani</Key>
<Definition>Some description goes here.</Definition>
</Item>
<Item>
<Key>Toucan</Key>
<Definition>...etc</Definition>
</Item>
</Dictionary>
Se eu adicionar esse arquivo de dados XML aos recursos do projeto, poderei implementar o construtor do serviço para carregar os dados XML a partir dos recursos. Em seguida, serei capaz de implementar o método de contrato GetDefinition para retornar o item que corresponde à palavra-chave solicitada pelo usuário. Ao mesmo tempo, exibirei a cadeia de caracteres de retorno na janela do console. Observe que o código de exemplo foi simplificado e não inclui a manipulação de exceções que normalmente seria incorporada:
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;
}
Em seguida, o aplicativo de console simplesmente precisa iniciar e parar o serviço.
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();
}
Como o serviço agora foi definido, implementado e configurado, posso desenvolver essa parte da solução e colocá-la para funcionar. Eu a configurei para que o serviço fosse executado no aplicativo de console, aguardando chamadas de entrada, até que o usuário pressionasse Enter na janela do console.
O UserControl WPF
Existem duas formas óbvias de usar o WPF em um aplicativo: para aprimorar a interface do usuário e para oferecer visualização de dados sofisticada. O meu exemplo se concentra nos recursos sofisticados da interface do usuário. A questão importante aqui é como você pode incorporar com facilidade os controles WPF às soluções VSTO. Eu poderia demonstrar isso com um controle WPF extremamente simples — talvez uma grade com um botão — mas seria uma pena não aproveitar as vantagens dos recursos gráficos sofisticados do WPF e, portanto, usarei um UserControl WPF personalizado que oferece um comportamento de animação olho-de-peixe. Eu me baseei no exemplo HyperBar para o Microsoft Expression Blend
TM (disponível em
blogs.msdn.com/expression/articles/516599.aspx). O exemplo também se baseia no FishEyePanel de Paul Tallett descrito em
codeproject.com/WPF/Panels.asp. A principal diferença que eu introduzi foi colocar o controle WPF em uma janela não WPF para que você pudesse vê-lo hospedado em uma janela nativa do Office.
Para começar, criei um projeto de biblioteca do UserControl Windows WPF — isso produzirá um XAML inicial e o codebehind C# correspondente. A primeira coisa a fazer é alterar o nome da classe UserControl de UserControl1 para algo mais significativo — FishEyeControl. FishEyeControl conterá uma coleção de botões gerenciada por um painel personalizado, chamado FishEyePanel, que deriva de System.Windows.Controls.Panel. No FishEyePanel, configurei manipuladores de eventos para três eventos de mouse (MouseMove, MouseEnter, MouseLeave) para ser possível invalidar o painel e assim fazer com que seja processado novamente sempre que o usuário mover o mouse sobre ele. Este é um dos manipuladores de eventos de mouse:
public class FishEyePanel : Panel
{
public FishEyePanel()
{
this.MouseMove += new
MouseEventHandler(FishEyePanel_MouseMove);
}
private void FishEyePanel_MouseMove(object sender,
MouseEventArgs e)
{
this.InvalidateArrange();
}
}
Para conectar o FishEyePanel ao FishEyeControl, tive de configurar a propriedade ItemsControl no FishEyeControl, desta forma:
<UserControl x:Class="WPFFishEye.FishEyeControl"
xmlns:uc="clr-namespace:WPFFishEye">
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<uc:FishEyePanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
Em seguida, configurei alguns recursos de dados para o painel. Em vez de tentar configurar um Web service ou fonte de dados LOB mais realista, simplesmente usarei arquivos locais como recursos. Neste exemplo, usarei uma lista simples de arquivos .jpg como imagens dos botões. Esses JPGs estão em arquivos externos e, portanto, preciso de um pouco de código para obter um nome de arquivo simples (como Frangipani.jpg) e convertê-lo para um caminho totalmente qualificado que será válido em runtime. Para isso, posso definir uma implementação de IValueConverter. Essa é uma técnica comum para a execução de conversão personalizada durante a vinculação de dados. A interface IValueConverter define dois métodos, mas eu só me interesso por um deles, Convert. O mecanismo de vinculação de dados chama esse método quando ele propaga um valor da fonte de vinculação para o seu destino. Em minha implementação, eu simplesmente usarei o CodeBase do assembly atual e acrescentarei isso no início como o caminho para o nome do arquivo de imagem.
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);
}
}
Em seguida, de volta ao FishEyeControl.xaml, conectarei essas partes em um ResourceDictionary para o controle e usarei o ResourceDictionary no ItemsControl (consulte a Figura 3). Observe que o DataTemplate para os itens do painel foi definido como um Button com uma Image, e que Image utiliza ImagePathConverter durante a vinculação de dados.

Figure 3 Modelo para controles de botões de ligações de dados
<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>
Essa definição também especifica o manipulador de eventos buttonClick para cada Button. Embora eu pudesse manipular o evento no próprio controle WPF, assim como no controle Windows Forms ou suplemento que o hospedasse, o farei somente no controle WPF e no suplemento. Os manipuladores de eventos são implementados em FishEyeControl.xaml.cs quando extraio o nome da Imagem do botão e disparo o evento novamente. O suplemento VSTO reagirá ao evento chamando o serviço WCF com base no nome da Image (consulte a Figura 4).

Figure 4 Manipulando o evento de clique FishEye
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);
}
}
}
Em seguida, definirei um tipo EventArgs personalizado para que, quando o evento for disparado novamente, eu possa enviar o nome simples da Image do botão para a escuta, como mostrado a seguir.
public delegate void FishEyeEvent(object source, FishEyeEventArgs e);
public class FishEyeEventArgs : EventArgs
{
public String ButtonName;
public FishEyeEventArgs(String name)
{
ButtonName = name;
}
}
Lembre-se de que estou invalidando o FishEyePanel quando o usuário move o mouse sobre ele. Também quero controlar o comportamento da renderização para poder oferecer o comportamento de animação olho-de-peixe. Para assumir o controle da renderização, devo implementar duas substituições, MeasureOverride e LayoutOverride, que, por sua vez, implementam um sistema de layout de dois passos. Durante o passo de medida, o pai (FishEyeControl) chama o filho (FishEyePanel) para descobrir de quanto espaço o filho precisa. O comportamento padrão aqui é que o controle filho consulte seus próprios filhos para descobrir de quanto espaço eles precisam e então repasse o resultado para o pai. O WPF usa um modelo onde os controles podem ser aninhados quase que indefinidamente e, portanto, neste contexto toda a árvore será consultada quanto a seus requisitos de espaço e as informações serão passadas até o último pai. No passo de layout, ocorre uma recursão similar, em que o pai decide o dimensionamento de seus filhos e repassa isso. Cada filho, por sua vez, passa as informações para seus próprios filhos. Isso chama o método ArrangeOverride, onde posso mostrar aos filhos qual é o seu tamanho e criar seu layout.
Em minha implementação de MeasureOverride, utilizo a propriedade UIElement.DesiredSize para tentar oferecer aos filhos todo o espaço de que eles precisam. Em ArrangeOverride, adiciono transformações em escala e transformações de conversão a todos os filhos e calculo sua largura necessária. Neste exemplo, dimensiono o botão filho que está diretamente sob o mouse para que ele seja maior do que os outros. Os dois botões em ambos os lados serão menores, mas ainda assim serão maiores do que todos os outros botões. Os botões restantes terão o mesmo tamanho e serão dispostos no espaço que sobrar. Para obter mais detalhes, examine o código fornecido no download que acompanha este artigo no site da MSDN
® Magazine (
msdn.microsoft.com/msdnmag/code07.aspx). O resultado líquido é que com cada movimento do mouse, ajustarei o tamanho dos botões para oferecer o conhecido efeito olho-de-peixe.
Conectando o UserControl WPF ao VSTO
A solução VSTO básica é um suplemento do Word 2007 chamado WordImageSelecter. Uma vez que o projeto do suplemento inicial já foi criado, posso adicionar o projeto UserControl WPF à solução. Isso me permite aproveitar as vantagens do suporte avançado em tempo de design do Visual Studio 2008 para a integração de controles WPF em projetos Windows Forms.
A próxima etapa será criar um UserControl Windows simples no projeto do suplemento. Por fim, ele hospedará o UserControl WPF personalizado. No Visual Studio 2008, qualquer UserControl SPF de qualquer projeto da sua solução será exibido na Caixa de Ferramentas e, portanto, você poderá arrastá-lo e soltá-lo diretamente na superfície de design do UserControl Windows Forms, como mostrado na Figura 5.
Figura 5 Superfície de design do Windows Forms para o controle WPF
Essa ação gera todo o código necessário para a hospedagem do controle WPF no controle Windows Forms, especificamente por meio de um objeto ElementHost. ElementHost foi projetado para a integração do Windows Forms com o WPF. Neste exemplo, o UserControl Windows Forms hospeda o ElementHost que, por sua vez, hospeda o UserControl WPF. Isso também adiciona as referências necessárias ao arquivo WPFFishEye.dll personalizado, assim como às DLLs padrão PresentationCore, PresentationFramework, UIAutomationProvider, WindowsBase e WindowsFormsIntegration. O controle Windows Forms é muito simples — sua única finalidade é hospedar o controle WPF e, portanto, não preciso desenvolver qualquer funcionalidade significativa. Observe que eu exibo o WPF FishEyeControl hospedado como uma propriedade para poder coletar o FishEyeEvent exposto pelo FishEyeControl (como mostrado na Figura 6).

Figure 6 Inicialize o controle
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);
}
Agora devo conectar o controle Windows Forms a um painel de tarefas personalizado do Word. Para fazer isso, preciso implementar o método ThisAddIn_Startup para criar o painel de tarefas personalizado, especificando o controle Windows Forms como o controle que hospedará e coletará o FishEyeClickEvent personalizado:
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;
}
Na implementação do manipulador de FishEyeClickEvent, por ora posso simplesmente exibir uma caixa de mensagem. Se você estiver me acompanhando, poderia testar o suplemente neste ponto, já que as partes do painel de tarefas personalizado e do controle WPF estão prontas:
private void FishEye_FishEyeClickEvent(object source,
FishEyeEventArgs e)
{
System.Windows.Forms.MessageBox.Show(e.ButtonName);
}
Você pode usar essa mesma técnica geral para o posicionamento de controles WPF em qualquer local de soluções VSTO em que puder inserir controles Windows Forms — em caixas de diálogo Windows Forms arbitrárias, em painéis de tarefas personalizados em nível de aplicativo, no painel de ações do documento, em regiões de formulários personalizados do Outlook e na superfície de um documento do Word ou do Excel. Observe que, para um documento do Word ou do Excel, você pode usar o suporte em tempo de design regular para a criação de um controle Windows Forms que incorpore o seu controle WPF, mas você não poderá colocar o controle na superfície do documento em tempo de design, já que os designers VSTO especiais do Excel e do Word não oferecem suporte a isso.
Isso posto, adicionar programaticamente um controle WPF hospedado em uma solução de documento VSTO não requer esforço algum. A infra-estrutura de runtime do VSTO já oferece suporte a qualquer controle Windows Forms arbitrário, incluindo controles que hospedam controles WPF. O runtime já está no lugar certo e isso acontece desde o Visual Studio 2005. Assim, a única tarefa de programação que resta é instanciar o controle Windows Forms externo (ou seja, hospedar o seu controle WPF por meio do ElementHost) e adicioná-lo à solução.
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;
}
Dada a natureza da hospedagem do WPF em soluções VSTO, deve ficar claro que uma das limitações é que você não pode usar o Windows Vista AeroTM Glass da forma normal. Ou seja, se o seu controle WPF utiliza o Aero Glass, ele só será transparente para o controle hospedeiro Windows Forms subjacente e não a superfície do documento ou a janela nativa do Office. É claro que você pode mitigar isso ao especificar a imagem de plano de fundo como parte do seu controle; então, quaisquer controles Aero Glass colocados por cima poderão ser transparentes para aquela imagem.
Consumindo o serviço WCF na solução VSTO
Existem diversas formas de desenvolver o código do lado cliente necessário ao consumo de um serviço WCF. Você pode gerar informações de troca de metadados e o código de proxy cliente a partir do assembly de serviço compilado ou a partir da execução do próprio serviço. Execute svcultil.exe manualmente para gerar o código ou utilize o assistente Add Service Reference (Adicionar Referência de Serviço) do Visual Studio 2008.
Com o Visual Studio 2005, você terá de usar o svcutil manualmente. Para fazer isso, primeiro inicie o serviço e abra uma janela de comando para navegar até a pasta do projeto cliente. Em seguida, execute svcutil para gerar o código de proxy do lado cliente. Uma linha de comando de exemplo poderia se assemelhar a:
"C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil" /language:C#
/config:app.config http://localhost:8080/ImageServiceHost/ImageService/mex
/n:*,WordImageSelecter
Isso especifica a URL para a execução do serviço bem como endereço de ponto de extremidade para troca de metadados. Indica também que o C# deve ser usado como linguagem de destino, que as informações de configuração devem ser enviadas para um arquivo chamado app.config e que WordImageSelecter deve ser o namespace (neste exemplo, ele é o namespace do projeto do suplemento).
Depois de svcutil gerar o código de proxy C# e o arquivo app.config, eles deverão ser adicionados ao projeto do suplemento. Além disso, adicione uma referência a System.ServiceModel.dll.
O Visual Studio 2008 oferece um assistente gráfico que poderá ser usado em vez da execução manual de svcutil. Para usar o assistente, inicie o serviço, clique com o botão direito do mouse no projeto do suplemento no Solution Explorer e selecione a opção Add Service Reference no menu de contexto. Na caixa de diálogo Add Service Reference, digite a URL do serviço e o namespace do destino e clique em Go (Ir). Isso obtém o contrato de serviço e as informações de configuração do serviço em execução e preenche as listas Services (Serviços) e Operations (Operações), como mostrado na Figura 7. Clique em OK para gerar o código de proxy.
Figura 7 Caixa de diálogo Add Service Reference (Clique na imagem para aumentar a exibição)
Agora você pode adicionar um campo de classe de proxy à classe de suplemento, inicializá-lo em ThisAddIn_Startup e chamar o método GetDefinition do Serviço WCF por meio do proxy. No manipulador de FishEyeClickEvent, substitua a caixa de mensagem por uma chamada a GetDefinition, analise o XML retornado para extrair a chave e a definição e as insira no documento do Word. Observe que depois de inserir o texto da chave, vou para o final da inserção e antes da adição do texto da definição — faço isso para não substituir a seleção anterior (consulte a Figura 8).

Figure 8 Usando o proxy do lado cliente do serviço WCF
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);
}
Segurança
Em nossa máquina de desenvolvimento, a segurança é configurada para a solução do suplemento durante a sua criação. Quando você implantar a solução, a segurança será configurada como parte da operação de publicação. O Office 2007 apresentou novos recursos de segurança e simplificou o funcionamento da segurança para macros, controles ActiveX e suplementos. Todos os suplementos instalados agora podem ser acessados por meio da Central de Confiabilidade do Office. O VSTO também fez muitas alterações significativas em seu modelo de segurança para o Visual Studio 2008. O modelo não se baseia mais na repositório do CAS (segurança de acesso do código) específico de versão e, em vez disso, é integrado ao modelo do ClickOnce. O VSTO também estende segurança adicional aos suplementos e às soluções de documento que utilizam com inteligência certificados digitais e/ou armazenamento baseado em registro das decisões de confiança do usuário. As soluções VSTO sempre exibem FullTrust porque interoperam com código não gerenciado (o host do Office) e essa exigência se estende às partes do WPF e WCF que formam a solução no cliente. Com o Visual Studio 2005 e com projetos para o Office 2003 no Visual Studio 2008, você precisa configurar segurança especificamente para o arquivo .config do WCF do lado cliente (o app.config para WordImageSelecter.dll). Isso acontece porque o arquivo .config é um ponto de entrada no código e deve ser explicitamente confiável na diretiva de CAS. Durante a criação de projetos para o Office 2007 no Visual Studio 2008, isso não será mais necessário porque a confiança é concedida para uma solução, incluindo qualquer assembly dependente e outros arquivos auxiliares que façam parte da solução. Neste ponto, você poderá testar o suplemento novamente, já que as partes do WPF e do WCF estão no lugar certo.
Usando LINQ, Xlinq e expressões lambda no VSTO
A parte final a ser explorada é a utilização de LINQ no contexto de uma solução VSTO. Com o Visual Studio 2008, a maioria dos projetos inclui automaticamente referências a System.Core e a System.Xml.Linq, onde a maioria das classes LINQ é definida. O LINQ é um conjunto de extensões de linguagem que permite a você executar consultas similares a SQL e um intervalo aberto de fontes de dados de uma forma de tipos seguros. No suplemento de exemplo, todo o trabalho do LINQ é executado no manipulador FishEyeClickEvent, onde executo algumas análises simples nos dados XML retornados do serviço WCF. Embora o exemplo seja simples, ilustra como o recurso é poderoso.
Para começar, posso usar XLinq para extrair a chave e a definição da cadeia de caracteres XML em vez do XPath mais complicado usado inicialmente:
XElement item = XElement.Parse(
Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName));
String keyText = (string)item.Element("Key");
String definitionText = (string)item.Element("Definition");
Outro recurso do LINQ são as variáveis de local digitadas implicitamente. A inferência de tipos é chamada usando a palavra-chave var para instruir o compilador para inferir o tipo de uma variável a partir da expressão em que ela é usada. Aqui, usaremos essa técnica para obtermos a contagem de caracteres no texto da definição:
var chars = from c in definitionText
select c;
int charCount = chars.Count();
Em seguida, podemos usar a expressão lambda para obtermos o número de palavras de quatro letras do texto da definição. Uma expressão lambda é similar a um delegado anônimo:
List<string> definitionWords =
new List<string>(definitionText.Split(new char[] { ' ' }));
var fourLetterWords = definitionWords.FindAll(
x => (x.Length == 4));
Além de usar expressões lambda em lugar de delegados anônimos, você também pode desenvolver uma árvore de expressões a partir de expressões lambda. Isso permite que você trate o código (a expressão lambda) como se ele fosse dados. No exemplo a seguir, pego a expressão lambda y => (y.Length == 4) e a mapeio para um objeto de Expression para poder obter uma saída para a tradução de estilo LISP da expressão. Neste exemplo, essa expressão produzirá a saída "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));
O último recurso a ser usado são os métodos de extensão. Muitas pessoas reclamam que o desenvolvimento de código em C# para o Office é doloroso porque muitos métodos dos modelos de objeto do Office precisam de um número muito grande de parâmetros opcionais e (especialmente no Word), em muitos casos, os parâmetros devem ser passados explicitamente para referência. Isso não acontece com o Visual Basic porque ele cuida da tradução para você e oculta a complexidade subjacente. Por exemplo, o código listado anteriormente para mover a seleção para o final da inserção anterior exigia a chamada ao método GoTo. Esse método obtém quatro parâmetros, todos explicitamente passados por referência e inclui dois que são opcionais.
Para mitigar esse problema, posso escrever um método de extensão, que é um método estático em uma classe personalizada. Posso fazer com que pareça que ele estende uma das interfaces do Office ao especificar essa interface como o tipo do primeiro parâmetro, que também deve usar a palavra-chave this. Esse método de extensão pode usar internamente o método GoTo padrão do Office:
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);
}
}
Assim, posso usar esse método de extensão no lugar do definido no modelo de objeto do Office:
this.Application.GoTo(
Word.WdGoToItem.wdGoToLine, Word.WdGoToDirection.wdGoToLast);
O futuro dos aplicativos baseados em Office
À medida que mais e mais desenvolvedores criam soluções baseadas em Office com código gerenciado, se torna cada vez mais importante que os recursos apresentados no .NET Framework e em novas versões do Visual Studio funcionem de forma integrada em um contexto do Office. Neste artigo, você viu como criar uma solução VSTO que utiliza WPF, WCF e LINQ para usar serviços de um aplicativo Office. A solução de exemplo utiliza um serviço WCF simples para recuperar dados estáticos, analisa os dados com LINQ e usa o WPF para oferecer uma interface do usuário avançada. Uma solução do mundo real mais típica provavelmente usaria um intervalo de serviços WCF para o trabalho com vários sistemas LOB do lado servidor e poderia usar o WPF para oferecer visualização de dados sofisticada, além de elementos da interface do usuário. O LINQ poderia ser usado no servidor, na camada intermediária ou no cliente para criar funcionalidade intuitiva e de fácil manutenção para consulta e manipulação de dados.
Fica claro que os aperfeiçoamentos de design para a integração do WPF ao Windows Forms e os aperfeiçoamentos de compilador para a consulta integrada à linguagem fazem do Visual Studio 2008 uma adição obrigatória à sua caixa de ferramentas de desenvolvimento corporativo.
Andrew Whitechapel foi consultor e arquiteto por muitos anos e criou soluções empresariais para uma ampla variedade de clientes; agora é gerente de programa e líder técnico da equipe do VSTO da Microsoft.