Introduction
In this article, I will show you some basic functionality of Visual Studio Tools for Office, XLINQ, .NET framework v3.5 and the new Ribbon Designer in Visual Studio 2008 Professional. The end product will be a Microsoft Word 2007 add-in that will allow users to add images from Yahoo Images straight into their documents via a custom task panel and to find the keywords in their documents. The Yahoo REST web services will be used, and XLINQ will be used to query the XML results. The ribbon designer will be used to add buttons to the ribbon.
Visual Studio Tools for Office, part of Visual Studio 2008 Professional, allow developers to create document-centric solutions in .NET that integrate with Word, Excel, Outlook, etc with managed code.
Creating the code
To make a new Word add-in, go to New Project, Office, 2007, Word Add-in.
This should present you with:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
namespace WordAddIn1
{
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
To search Yahoo Images for images to add to documents, we need to create a user control to put inside a CustomTaskPane
. Using WPF would be nice, and allow a more flexible UI, but we will need a bit of simple extra code to get WPF to display, since the Office requires a Windows Forms user control (later). I created a Windows Forms UserControl
and left it blank. I then created a WPF user control, and laid out a simple UI to allow text entry, for searching, with a stackpanel to hold the results later.
The Yahoo Image Search API allows rest queries of the form http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=[APPID]&query=[QUERY]&start=[START]. Once the URI has been formed, we will be using XLINQ to query the results to load into a class with the fields we want.
var xraw = XDocument.Load(uri);
var imageArray = from image in xraw.Elements().First().Elements()
select new YahooImage
{
Title = image.Element(
"{urn:yahoo:srchmi}Title").Value,
LargeUrl = image.Element(
"{urn:yahoo:srchmi}ClickUrl").Value,
Thumbnail = image.Element(
"{urn:yahoo:srchmi}Thumbnail")
.Element("{urn:yahoo:srchmi}Url").Value,
ThumbHeight = int.Parse(image.Element(
"{urn:yahoo:srchmi}Thumbnail")
.Element("{urn:yahoo:srchmi}Height").Value),
ThumbWidth = int.Parse(
image.Element("{urn:yahoo:srchmi}Thumbnail")
.Element("{urn:yahoo:srchmi}Width").Value),
Summary = image.Element(
"{urn:yahoo:srchmi}Summary").Value
};
Look at the XLINQ. It uses the result elements of the document and select
s a new Yahoo Image. But no constructor is used, the properties are set between the braces. Quite a nice, readable syntax. Watch out for the namespace - it can refuse to find the elements if the namespace is omitted!
I then added each thumbnail image to a button, and placed it in the scrollable StackPanel
as shown.
stackPanel1.Children.Clear();
foreach (var im in imageArray)
{
Image image = new Image();
ImageSourceConverter isc = new ImageSourceConverter();
image.Source = (ImageSource)isc.ConvertFromString(im.Thumbnail);
Button b = new Button();
b.Content = image;
b.Width = im.ThumbWidth;
b.Height = im.ThumbHeight;
b.Tag = im.LargeUrl;
b.ToolTip = im.Title + "\n\n" + im.Summary;
b.Click += delegate(object clicked_button, RoutedEventArgs evt_args)
{
Object missing = System.Reflection.Missing.Value;
try
{
Globals.ThisAddIn.Application.Selection.InlineShapes.AddPicture(
(clicked_button as Button).Tag.ToString(),
ref missing, ref missing, ref missing);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, ex.GetType().Name);
Globals.ThisAddIn.CustomTaskPanes.RemoveAt(0);
}
};
stackPanel1.Children.Add(b);
}
Unfortunately, the thumbnail images hosted by Yahoo have faulty headers, which cause errors which stop the image displaying. To allow the images to be shown even with protocol violations, I created an App.config file and put the following inside:
<system.net>
<settings>
<httpWebRequest useUnsafeHeaderParsing="true" />
</settings>
</system.net>
When a result is clicked, the large image is added to the document using Globals.ThisAddIn.Application.Selection.InlineShapes.AddPicture((clicked_button as Button).Tag.ToString(), ref missing, ref missing, ref missing)
. This method allows default parameters, but C# does not support default parameters. To send the default value, we use the value of the Missing object. This is needed in many Office functions.
Right-click the project in Solution Explorer and click Add, New Item, Ribbon (Visual Designer). This will display the ribbon designer. You can add ribbon items to either new ribbons or existing ribbons (e.g. Home, Insert, Page Layout, References). To add ribbon items to a new ribbon, click on the ribbon tab, and in properties, set the label property, and change the ControlIdType property to custom.
However, we want to add our first button to the Insert tab since we will be inserting images into the document. To do this we need to click on the tab and in properties, make sure that ControlIdType shows Office, and type the OfficeId of the tab we want to add. This can be one of the following:
-
TabHome
-
TabInsert
-
TabPageLayoutWord
-
TabReferences
-
TabMailings
-
TabReviewWord
-
TabView
-
TabDeveloper
-
TabAddIns
-
TabOutlining
-
TabPrintPreview
-
TabBlogInsert
-
TabBlogPost
-
TabSmartArtToolsDesign
-
TabSmartArtToolsFormat
-
TabChartToolsDesign
-
TabChartToolsLayout
-
TabChartToolsFormat
-
TabPictureToolsFormat
-
TabDrawingToolsFormatClassic
-
TabWordArtToolsFormat
-
TabDiagramToolsFormatClassic
-
TabOrganizationChartToolsFormat
-
TabTextBoxToolsFormat
-
TabTableToolsDesign
-
TabTableToolsLayout
-
TabHeaderAndFooterToolsDesign
-
TabEquationToolsDesign
-
TabPictureToolsFormatClassic
-
TabInkToolsPens
So, to add to the insert tab, we use TabInsert. A group has already been added (more can be added from the toolbox). Controls must be placed inside groups. From the control toolbox, add a button. Since there is only one button, I will set its ControlSize
to RibbonControlSizeLarge
so that it looks better. The other property of note is OfficeImageId. A button can hold either no image, an image from a resource, or a built in Office image. To use an Office image, I needed to find the image's Id which can be done by downloading the Office 2007 Icons Gallery from http://www.microsoft.com/downloads/details.aspx?familyid=12b99325-93e8-4ed4-8385-74d0f7661318&displaylang=en . I chose OmsSlideInsert. Double-clicking the button brings up the click event handler, and inside it we need code to create the custom task pane and display it. An ElementHost
is added to the Windows Forms user control, to host the WPF control. For this, we need WindowsFormsIntegration.dll to be referenced. An event handler is also added for when the task pane is closed, so that it can be removed from memory.
private void button1_Click(object sender, RibbonControlEventArgs e)
{
ElementHost _ElementHost;
_ElementHost
= new ElementHost();
_ElementHost.Child
= new ImageSearchWpfUserControl();
_ElementHost.Dock = System.Windows.Forms.DockStyle.Fill;
WinFormsUserControl _UserControl;
_UserControl = new WinFormsUserControl();
_UserControl.Controls.Add(_ElementHost);
Microsoft.Office.Tools.CustomTaskPane m_CustomTaskPane
= Globals.ThisAddIn.CustomTaskPanes.Add(_UserControl, "Yahoo Images Searcher");
m_CustomTaskPane.DockPosition =
Microsoft.Office.Core.MsoCTPDockPosition.msoCTPDockPositionLeft;
m_CustomTaskPane.Visible = true;
m_CustomTaskPane.VisibleChanged +=
new EventHandler(m_CustomTaskPane_VisibleChanged);
}
void m_CustomTaskPane_VisibleChanged(object sender, EventArgs e)
{
Microsoft.Office.Tools.CustomTaskPane ctp =
(sender as Microsoft.Office.Tools.CustomTaskPane);
if (ctp.Visible == false)
{
Globals.ThisAddIn.CustomTaskPanes.Remove(ctp);
}
}
Here we, again, used the wonderful Globals
class, which gives easy access to your add-in and Office integration from any code file.
See the Yahoo Images button on the far right, and the WPF search results.
The next task is to add a button to the Review tab (TabReviewWord) labelled Keyword Analysis, which will send the document to the Yahoo Content Analysis web service (after trying it it's not that good but oh well) to get keywords for the document. I added a new tab to the ribbon, and added a group with a button. The button's OfficeImageId was set to FunctionsRecentlyUsedtInsertGallery. On clicking the button, we need to post the data to the web service, and then iterate through the results before displaying them in a standard MessageBox. To get the document text I used Globals.ThisAddIn.Application.ActiveDocument.Content.Text
. Many other properties and functions can be found by browsing the intellisense. Below, note the Keyword Analysis button and see that the web service has identified relevant keywords to the active document.
private void button2_Click(object sender, RibbonControlEventArgs e)
{
System.Net.WebClient wc = new System.Net.WebClient();
System.Collections.Specialized.NameValueCollection nvc =
new System.Collections.Specialized.NameValueCollection();
nvc.Add("appid", Properties.Settings.Default.AppId);
nvc.Add("context",
Globals.ThisAddIn.Application.ActiveDocument.Content.Text);
XDocument xdoc = XDocument.Parse(Encoding.UTF8.GetString(
wc.UploadValues("http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction",
"POST", nvc)));
StringBuilder keywords = new StringBuilder();
foreach (var xresult in xdoc.Elements().First().Elements())
{
keywords.AppendLine(xresult.Value);
}
System.Windows.Forms.MessageBox.Show(
keywords.ToString(), "Keywords");
}
This was a quick intro to, among other things, the new Ribbon Designer in Visual Studio. The Ribbon Designer also allows you to add new menu items to the Office Button as well as allowing you to (irreversibly) export the ribbon to XML for some more advanced functions such as repurposing commands. For example, if you repurpose FileSave, whenever a user presses Ctrl+S, or clicks Save instead of the normal save dialog opening, your custom code can run. To try this, export the XML from the ribbon designer, follow instructions in the comments in the code and add the following XML and C# code, which will ask a user if he/she really wants to save before saving:
="1.0" ="UTF-8"
<customUI onLoad="Ribbon_Load" xmlns="http://schemas.microsoft.com/office/2006/01/customui">
-->
<commands>
<command idMso="FileSave" onAction="doYouWantToSave" />
</commands>
-->
<ribbon>
<tabs>
<tab idMso="TabInsert">
<group id="group1" label="Web">
<button id="button1" imageMso="OmsSlideInsert"
onAction="button1_Click" screentip="Yahoo! Images"
supertip="Search Yahoo! Images for images to add to your document." label="Yahoo Images"
size="large" />
</group>
</tab>
<tab idMso="TabReviewWord">
<group id="group2" label="Analysis">
<button id="button2" imageMso="FunctionsRecentlyUsedtInsertGallery"
onAction="button2_Click"
screentip="Keyword Analysis" supertip="Find keywords in this document using Yahoo! web service"
label="Keyword Analysis" size="large" />
</group>
</tab>
</ribbon>
</customUI>
private void doYouWantToSave(Microsoft.Office.Core.IRibbonControl control, bool cancel)
{
cancel = (System.Windows.Forms.MessageBox.Show("Do you really want to do FileSave?",
"???", System.Windows.Forms.MessageBoxButtons.YesNo)
== System.Windows.Forms.DialogResult.OK) ? false : true;
}
The attached code also contains a ribbon tab called mess. This has visible = false set by default, but after changing that, you can see how other controls can be added to the ribbon (besides buttons) such as galleries, button groups, toggle buttons. Notice how it appears before the Mailings tab � this is due to the Position
property being set.
The code Download WordAddIn1.zip - 132.9 KB is functional, and it fulfils its purpose of showing basic integration with Office using VS 2008, and some XLINQ. Enjoy. A brief description of how to use the article or code. The class names, the methods and properties, any tricks or tips. Blocks of code should be set as style "Formatted" like this:
Points of Interest
Annoying: The thumbnail images being hosted on a bad server which gives protocol violations
Nice: There is a function for practically anything you can do in Office
Nice: To install, go to the debug folder and just doubleclick the WordAddIn1.vsto! Easy.
Important: There are loads of codes/OfficeIds for things but they can be found on downloadable spreadsheets/documents.
Nice: It actually works quite well.
History
28 November 2007 - Added original article
30 November 2007, 5 December 2007 - Minor update