Accessing and Using a Mapping Web Service from a Custom Task Pane in Word 2007

Summary: Create a Microsoft Office Word 2007 add-in that includes a custom task pane which is launched by a ribbon button. The task pane connects to the Microsoft TerraService mapping Web service and allows you to insert the map into a Word document. (24 printed pages)

Frank Rice, Microsoft Corporation

October 2008

Applies to: Microsoft Office Word 2007, Microsoft Visual Studio 2008, Microsoft Visual Studio Tools for the Office System, Second Edition

Contents

  • Overview

  • Background

  • About the TerraService Add-In

  • Creating the Add-In Project

  • Adding the Custom Task Pane

  • Modifying the Ribbon

  • Connecting to the Web Service

  • Testing the Add-In Project

  • Conclusion

  • Additional Resources

Overview

The versatility of custom task panes in Microsoft Office Word 2007 allows for many types of communication with outside sources of data. Custom task panes used in conjunction with Web services provide the document user with many opportunities to retrieve and work with data. For example, the user can type the name of a company or individual into a task pane and retrieve information relating to that item such as address or sales data from an external directory service. This information can then be inserted into the document with the click of a button. Or the user could pull in the definition of a word or the translation of a term from one language to another.

In this article, you will create a Word 2007 .NET add-in in Microsoft Visual Basic .NET or Microsoft Visual C# that retrieves a map for a location that you type into a custom task pane. You are then able to insert the map into a document. The custom task pane will be displayed from a custom tab on the 2007 Office Fluent user interface also known as the Ribbon.

Background

Web service refers to a modular application that can be invoked through the Internet. The consumers of Web services are other computer applications that communicate, usually over HTTP, using XML standards including SOAP, WSDL, and UDDI. Each Web service publishes its interface in a Web Services Definition Language (WSDL) document that completely specifies the service’s request and response interface so that client applications can automatically connect to the Web service. Discussing these standards is beyond the scope of this article. You can find sources of information on these standards in the Additional Resources section at the end of the article.

The Microsoft TerraServer Web site stores aerial, satellite, and topographic images of the earth in a Microsoft SQL Server database that is available on the Internet. The imagery is available as a seamless mosaic of the earth. It is the world’s largest online atlas, combining fifteen terabytes of aerial imagery data and 1.5 terabytes of topographic maps from the United States Geological Survey (USGS) hence its name. The TerraService Web service provides a programmatic interface to the TerraServer database.

The mosaic is stored as sets of uniformly sized, JPEG or GIF compressed images called tiles. Each tile has a scaled resolution and is available in different formats (also called themes) including aerial imagery (photographs), and topographic maps (scanned from paper maps). All TerraServer tiles are 200 x 200 pixel images. The TerraService implements several methods to retrieve the individual tiles to construct a larger image, and then crop, re-size, and layer other graphical data on-top of the image. For example, in the example in this article you use the GetTile method to retrieve an individual tile. There are also methods available to retrieve tiles by latitude and longitude as well as by place name and landmarks.

About the TerraService Add-In

In this article, you create a Word 2007 add-in application by using Visual Studio Tools for the Office System in Microsoft Visual Studio using either Visual Basic .NET or C#. The add-in uses the GetTile method of the TerraService class to retrieve a single tile given the city, state, and country for a location. You also specify a theme (photo or topo) and a scale for the tile. Returning a single tile does not show off the real power of the TerraService and is not necessarily useful. However, the real purpose of the add-in is to illustrate calling a Web service from a custom task pane and then inserting the results of the call into a document.

The add-in also adds a custom tab to the Ribbon that contains a button. Clicking the button displays the custom task pane. The user then types in the location information into text boxes and clicks a button to retrieve the map tile. The user can then click another button to insert the tile into the document.

Visual Studio Tools for the Office System makes creating a custom task pane and adding a Ribbon tab and controls very easy. Additionally, Visual Studio makes connecting to a Web service as simple as linking to the Web service's WSDL file. So let's get started.

Creating the Add-In Project

In the following steps, you create the add-in project.

To create the add-in

  1. Start Microsoft Visual Studio 2008 (or Microsoft Visual Studio 2005).

  2. On the File menu, click New Project.

  3. If you are using Visual Basic, in the New Project dialog box, in the Project types pane, expand Visual Basic, Office, and then click 2007.

  4. If you are using C# , in the New Project dialog box, in the Project types pane, expand Other languages, Visual C#, Office, and then click 2007.

  5. In the Templates pane, select the Word 2007 Add-in icon.

  6. Specify the name of the project as TerraServiceAddIn, and then click OK.

Adding the Custom Task Pane

In the following steps, you create and populate the task pane with controls.

To create the custom task pane

  1. In the Solution Explorer, right-click the TerraServiceAddIn node, point to Add, and then click New Item.

  2. In the Add New Item dialog box, select the User Control, name it TerraServiceTaskPane.vb (TerraServiceTaskPane.cs) and click Add.

    Next, add the task pane controls.

  3. On the View menu, click Toolbox.

  4. From the toolbox, add the controls specified in Table 1 to the TerraServiceTaskPane [Designer] screen and set the properties as shown. The design should look similar to Figure 1.

    Table 1. Add these controls to the TerraServiceTaskPane control

    Control Type

    Property

    Setting

    Label

    Text

    City

    TextBox

    Name

    txtCity

    Label

    Text

    State

    TextBox

    Name

    txtState

    Label

    Text

    Country

    TextBox

    Name

    txtCountry

    Label

    Text

    Map Type

    ComboBox

    Name

    cboMapType

    Items/Collection

    Photo

    Topo

    Label

    Text

    Scale

    ComboBox

    Name

    cboScale

    Button

    Name

    btnRequestMap

    Text

    Get Map

    Button

    Name

    btnInsertMap

    Text

    Insert Map

    PictureBox

    Size

    260, 274

    SizeMode

    StretchImage

    Figure 1. The TerraService Task Pane Designer

    TerraService Task Pane Designer

  5. In the Solution Explorer, right-click the TerraServiceTaskPane.vb (TerraServiceTaskPane.cs) node, and click View Code.

  6. Add the following namespace declarations above the class, if they do not already exist.

    Imports System.Drawing
    Imports System.IO
    Imports System.Window.Forms
    Imports TerraServiceAddIn.TerraService
    Imports Word = Microsoft.Office.Interop.Word
    
    using System.Drawing;
    using System.IO;
    using System.Windows.Forms;
    using TerraServiceAddIn.TerraService;
    using Word = Microsoft.Office.Interop.Word;
    
  7. Just below the statement Public Class TerraServiceTaskPane (**C#:**public partial class TerraServiceTaskPane : UserControl), add the following code that creates an array of map scales for the photo types of map and then the topo type of map. As you can see, each map type has its own set of scales.

    NoteNote

    In Visual Basic, do not forget to add continuation characters (a space and underscore) at the end of each line of the array declaration.

    ' Scale values for a Photo map type.
    Private photoScales() As String = 
    {"Scale1m", "Scale2m", "Scale4m", "Scale8m", "Scale16m", "Scale32m", _
    "Scale64m", "Scale128m", "Scale256m", "Scale512m}
    
    ' Scales for Topo map.
    Private topoScales() As String = {"Scale2m", "Scale4m", "Scale8m", "Scale16m", _
     "Scale32m", "Scale64m", "Scale128m", "Scale256m", "Scale512m"}
    
    // Scale values for a Photo map type.
    string[] photoScales = 
    {"Scale1m", "Scale2m", "Scale4m", "Scale8m", "Scale16m", "Scale32m", 
    "Scale64m", "Scale128m", "Scale256m", "Scale512m};
    
    // Scales for Topo map.
    string[] topoScales = 
    {"Scale2m", "Scale4m", "Scale8m", "Scale16m", "Scale32m", "Scale64m",
     Scale128m", "Scale256m", "Scale512m"};
    
  8. In the TerraServiceTaskPane [Designer] screen, double-click the Get Map button. Replace the btnRequestMap_Click procedure with the following code.

    Private Sub btnRequestMap_Click(ByVal sender As Object, ByVal e As EventArgs)
       Dim ts As TerraServiceSoapClient = New TerraServiceSoapClient
       ' Specify a place.
       Dim place As Place = New Place
       place.City = txtCity.Text
       place.State = txtState.Text
       place.Country = txtCountry.Text
    
       ' Get the place facts.
       Dim pfacts As PlaceFacts = ts.GetPlaceFacts(place)
    
       ' Get the scale.
       Dim scale As Scale
       Dim cboSelection As String = CType(cboScale.SelectedItem,String)
       ' If the map type is Photo.
       If (mapType = 1) Then
          Select Case (cboSelection)
             Case "Scale1m"
                scale = CType(Integer.Parse("10"),Scale)
             Case "Scale2m"
                scale = CType(Integer.Parse("11"),Scale)
             Case "Scale4m"
                scale = CType(Integer.Parse("12"),Scale)
             Case "Scale8m"
                scale = CType(Integer.Parse("13"),Scale)
             Case "Scale16m"
                scale = CType(Integer.Parse("14"),Scale)
             Case "Scale32m"
                scale = CType(Integer.Parse("15"),Scale)
             Case "Scale64m"
                scale = CType(Integer.Parse("16"),Scale)
             Case "Scale128m"
                scale = CType(Integer.Parse("17"),Scale)
             Case "Scale256m"
                scale = CType(Integer.Parse("18"),Scale)
             Case "Scale512m"
                scale = CType(Integer.Parse("19"),Scale)
             Case Else
                scale = CType(Integer.Parse("10"),Scale)
          End Select
       Else
          Select Case (cboSelection)
             Case "Scale2m"
                scale = CType(Integer.Parse("11"),Scale)
             Case "Scale4m"
                scale = CType(Integer.Parse("12"),Scale)
             Case "Scale8m"
                scale = CType(Integer.Parse("13"),Scale)
             Case "Scale16m"
                scale = CType(Integer.Parse("14"),Scale)
             Case "Scale32m"
                scale = CType(Integer.Parse("15"),Scale)
             Case "Scale63m"
                scale = CType(Integer.Parse("16"),Scale)
             Case "Scale125m"
                scale = CType(Integer.Parse("17"),Scale)
             Case "Scale250m"
                scale = CType(Integer.Parse("18"),Scale)
             Case "Scale512m"
                scale = CType(Integer.Parse("18"),Scale)
             Case Else
                scale = CType(Integer.Parse("11"),Scale)
          End Select
       End If
       ' Get tile metadata for place, read them and the scale.
       Dim tm As TileMeta = ts.GetTileMetaFromLonLatPt(pfacts.Center, mapType, scale)
    
       ' Get the tile image.
       Dim imageBytes() As Byte = ts.GetTile(tm.Id)
       Dim ms As MemoryStream = New MemoryStream(imageBytes)
       pictureBox1.Image = New Bitmap(ms)
    End Sub
    
    private void btnRequestMap_Click(object sender, EventArgs e)
    {
        TerraServiceSoapClient ts = new TerraServiceSoapClient();
    
        // Specify a place.
        Place place = new Place();
        place.City = txtCity.Text;
        place.State = txtState.Text;
        place.Country = txtCountry.Text;
    
        // Get the place facts.
        PlaceFacts pfacts = ts.GetPlaceFacts(place);
    
        // Get the scale.
        Scale scale;
        String cboSelection = (String)cboScale.SelectedItem;
        // If the map type is Photo.
        if (mapType == 1)
        {
            switch (cboSelection)
            {
                case "Scale1m":
                    scale = (Scale)int.Parse("10");
                    break;
                case "Scale2m":
                    scale = (Scale)int.Parse("11");
                    break;
                case "Scale4m":
                    scale = (Scale)int.Parse("12");
                   break;
                case "Scale8m":
                    scale = (Scale)int.Parse("13");
                    break;
                case "Scale16m":
                    scale = (Scale)int.Parse("14");
                    break;
                case "Scale32m":
                    scale = (Scale)int.Parse("15");
                    break;
                case "Scale64m":
                    scale = (Scale)int.Parse("16");
                    break;
                case "Scale128m":
                    scale = (Scale)int.Parse("17");
                    break;
                case "Scale256m":
                   scale = (Scale)int.Parse("18");
                   break;
                case "Scale512m":
                    scale = (Scale)int.Parse("19");
                    break;
                default:
                    scale = (Scale)int.Parse("10");
                    break;
            }
        }
        else
        {
            switch (cboSelection)
            {
                case "Scale2m":
                    scale = (Scale)int.Parse("11");
                    break;
                case "Scale4m":
                    scale = (Scale)int.Parse("12");
                    break;
                case "Scale8m":
                    scale = (Scale)int.Parse("13");
                    break;
                case "Scale16m":
                    scale = (Scale)int.Parse("14");
                    break;
                case "Scale32m":
                    scale = (Scale)int.Parse("15");
                    break;
                case "Scale63m":
                     scale = (Scale)int.Parse("16");
                     break;
                case "Scale125m":
                     scale = (Scale)int.Parse("17");
                     break;
                case "Scale250m":
                    scale = (Scale)int.Parse("18");
                    break;
                case "Scale512m":
                    scale = (Scale)int.Parse("18");
                    break;
                default:
                     scale = (Scale)int.Parse("11");
                     break;
            }
        }
    
        // Get tile metadata for place, read them and the scale.
        TileMeta tm = ts.GetTileMetaFromLonLatPt(pfacts.Center, mapType, scale);
    
        // Get the tile image.
        Byte[] imageBytes = ts.GetTile(tm.Id);
        MemoryStream ms = new MemoryStream(imageBytes);
        pictureBox1.Image = new Bitmap(ms);
    }
    
    NoteNote

    At this point in the project, you have not added a reference to the TerraService Web service so you will see errors for members specific to that service. Shortly you will add that reference.

    In this procedure, the GetPlaceFacts method is called from the Web service with the location information the user enters in the task pane. The information returned is used later in the procedure to get the map tile corresponding to that location. Most of the remainder of the code are Case (switch) statements to set the desired scale of the map based on the user's choice in the scale combo box. The place facts metadata and the scale information are passed to the Web service's GetTileMetaFromLonLatPt method to return the place coordinates. These coordinates are the passed to the Web service GetTile method which returns the map tile. The tile image is then inserted into the picture box on the task pane.

  9. In the TerraServiceTaskPane [Designer] screen, double-click the Insert Map button and replace the btnInsertMap_Click procedure with the following code.

    Private Sub btnInsertMap_Click(ByVal sender As Object, ByVal e As EventArgs)
       Dim rng As Word.Range = Globals.ThisAddIn.Application.ActiveDocument.Content
       Dim img As Bitmap = New Bitmap(pictureBox1.Image)
       Clipboard.SetImage(img)
       rng.Paste
    End Sub
    
    private void btnInsertMap_Click(object sender, EventArgs e)
    {
        Word.Range rng = Globals.ThisAddIn.Application.ActiveDocument.Content;
    
        Bitmap img = new Bitmap(pictureBox1.Image);
    
        Clipboard.SetImage(img);
        rng.Paste(); 
    }
    

    This procedure selects a range in the document and inserts the image in the picture box into that range.

  10. In the TerraServiceTaskPane [Designer] screen, double-click the cboMapType combo box. Add the following declaration and replace the cboMapType_SelectedIndexChanged procedure with the following code.

    Dim mapType As Integer
    Private Sub cboMapType_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
       ' Get the map type.
       Select Case (cboMapType.SelectedIndex)
          Case 0
             mapType = 1
          Case 1
             mapType = 2
          Case Else
             mapType = 1
       End Select
       ' Populate the scale combo box based on the selection 
       ' in the map type combo box.
       If (mapType = 1) Then
          For Each s As String In photoScales
             cboScale.Items.Add(s)
             cboScale.Refresh
          Next
       ElseIf (mapType = 2) Then
          For Each s As String In topoScales
             cboScale.Items.Add(s)
             cboScale.Refresh
          Next
       End If
    End Sub
    
    int mapType;
    private void cboMapType_SelectedIndexChanged(object sender, EventArgs e)
    {
       // Get the map type.
       switch (cboMapType.SelectedIndex)
       {
          case 0:
              mapType = 1;
              break;
          case 1:
              mapType = 2;
              break;
          default:
              mapType = 1;
              break;
       }
    
       // Populate the scale combo box based on the selection in the 
       //map type combo box.
       if (mapType == 1)
       {
          foreach (string s in photoScales)
          {
            cboScale.Items.Add(s);
            cboScale.Refresh();
          }
       }
       else if (mapType == 2)
       {
          foreach (string s in topoScales)
          {
              cboScale.Items.Add(s);
              cboScale.Refresh();
          }
       }
    }
    

    This procedure is used to populate the scale combo box based on the user's selection in the map type combo box. Each map type has its own set of scales.

Modifying the Ribbon

In the following steps, you add a custom tab to the Word 2007 Ribbon and then add a group and button to the group.

To create the custom ribbon

  1. In the Solution Explorer, right-click the TerraServiceAddIn node, point to Add, and then click New Item.

  2. In the Add New Item dialog box, in the Templates pane, select Ribbon (XML), and then click Add. The Ribbon1.vb (Ribbon1.cs) and Ribbon1.xml nodes are added to the Solution Explorer.

  3. Double-click the Ribbon1.xml node to display the code screen. The XML you see provides a starting point for customizing the Ribbon.

  4. To define the components of the Ribbon, replace the XML with the following code.

    <?xml version="1.0" encoding="UTF-8"?>
    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
      <ribbon>
        <tabs>
          <tab id="MyTab"
               label="Terra Service">
            <group id="MyGroup"
                   label="Insert Map">
              <toggleButton id="toggleButton1"
                            size="large"
                            label="Display Task Pane"
                            screentip="My Button Screentip"
                            onAction="DisplayTaskPane"
                            imageMso="FrameCreateRight" />
            </group>
          </tab>
        </tabs>
      </ribbon>
    </customUI>
    
    NoteNote

    If you want more general information on Ribbon components and callback procedures, you can find this and more in the three part series of articles entitled Customizing the 2007 Office Fluent Ribbon for Developers (Part 1 of 3)

    Looking at the code, notice that it adds a tab, a group, and a toggleButton. When the user clicks the toggle button the task pane is displayed. The label attribute adds a caption to the button. The imageMso attribute assigns an image built into Microsoft Office to the button. In addition, the button has an onAction attribute that points to the procedure that is executed when you click the button. These procedures are also known as callback procedures. When the button is clicked, the onAction attribute calls back to Microsoft Office, which then executes the specified procedure.

    The net result of the XML is to create a tab on the Word 2007 Ribbon that looks like that seen in Figure 2.

    Figure 2. The TerraService tab in Word

    The TerraService Tab

    Now you need to add the callback procedure that is called when you click the toggle button.

  5. In the Solution Explorer, double-click the Ribbon1.vb (Ribbon1.cs) node.

  6. Expand the Ribbon Callbacks region and add the following procedure.

    Private taskpaneControlExists As Boolean
    Public Sub DisplayTaskPane(ByVal control As Office.IRibbonControl, ByVal pressed As Boolean)
       If Not taskpaneControlExists Then
          Globals.ThisAddIn.AddMapTaskPane
       Else
          Globals.ThisAddIn.RemoveMapTaskPane
       End If
       taskpaneControlExists = Not taskpaneControlExists
    End Sub
    
    // This field contains the current state of task pane.
    private bool taskpaneControlExists;
    public void DisplayTaskPane(Office.IRibbonControl control, bool pressed)
    {
       if (!taskpaneControlExists)
       {
          Globals.ThisAddIn.AddMapTaskPane();
       }
       else
       {
          Globals.ThisAddIn.RemoveMapTaskPane();
       }
       taskpaneControlExists = !taskpaneControlExists;
    }
    

    This procedure is called each time the toggle button is clicked. Toggle buttons can have one of two states: depressed or not depressed. The taskpaneControlExistsBoolean field stores the current state of the task pane. When the add-in is first loaded, the task pane isn't displayed and the taskpaneControlExists field is set to True, the default Boolean value. The first time the user clicks the toggle button, the inverse state of the taskpaneControlExists field is checked and since it resolves to False, the AddMapTaskPane procedure is called to display the task pane is displayed and then the taskpaneControlExists is set to False. When the user clicks the button a second time, the inverse of the taskpaneControlExists field is tested and since it equals False, the second part of the if statement is executed, the RemoveMapTaskPane procedure is called, and the taskpaneControlExists is set to True. Each time the user clicks the button this process is repeated.

    Now you need to add the AddMapTaskPane and RemoveMapTaskPane procedures to the project.

  7. In the Solution Explorer, under the Word node, double-click the ThisAddIn.vb (ThisAddIn.cs) node.

  8. Add the following namespace declaration above the class statement.

    Imports Office = Microsoft.Office.Core
    
    using Office = Microsoft.Office.Core;
    
  9. In the code screen, add the following declarations and procedures to the class.

    Private TerraServiceTP As Microsoft.Office.Tools.CustomTaskPane
    
    Public Sub AddMapTaskPane()
       TerraServiceTP = Me.CustomTaskPanes.Add(New TerraServiceTaskPane, "Display a map")
       TerraServiceTP.DockPosition = _
          Microsoft.Office.Core.MsoCTPDockPosition.msoCTPDockPositionRight
       TerraServiceTP.Visible = true
       TerraServiceTP.Width = 298
    End Sub
    
    Public Sub RemoveMapTaskPane()
       Me.CustomTaskPanes.Remove(TerraServiceTP)
    End Sub
    
    private Microsoft.Office.Tools.CustomTaskPane TerraServiceTP = null;
    
    public void AddMapTaskPane()
    {
       TerraServiceTP = this.CustomTaskPanes.Add(new TerraServiceTaskPane(), "Display a map");
    
       TerraServiceTP.DockPosition =
          Microsoft.Office.Core.
          MsoCTPDockPosition.msoCTPDockPositionRight;
       TerraServiceTP.Visible = true;
       TerraServiceTP.Width = 298;
    }
    
    public void RemoveMapTaskPane()
    {
       this.CustomTaskPanes.Remove(TerraServiceTP);
    }
    

    The AddMapTaskPane procedure set the orientation of the task on the right side of the document window and makes it visible.

Connecting to the Web Service

In the following steps, you connect the project to the TerraService Web service.

To connect to the Web service

  1. First, you add a reference to the TerraService Web service. Adding the reference is all you need to use the methods exposed by the service. On the Project menu, click Show All Files.

  2. In the Solution Explorer, right-click the TerraServiceAddIn node and then click Add Web Reference.

    NoteNote

    If Add Web Reference is not one of the choices, click Add Service Reference. In the Add Service Reference dialog box, click the Advanced button and then click the Add Web Reference button.

  3. In the Add Web Reference dialog box, in the Address box, insert the following address: http://terraserver-usa.com/TerraService2.asmx?WSDL.

  4. Click Go.

  5. Next, name the reference TerraService and then click OK. The reference appears in the Solution Explorer under the Web References (or Service References) node.

Testing the Add-In Project

In the following steps, you run the project.

To test the add-in project

  1. On the Debug menu, click Start Debugging. The project is built and Word 2007 is displayed.

  2. On the Terra Service tab, click the Display Task Pane button. The custom task pane is displayed on the right side of the screen.

  3. In the textboxes, type the city, state, and country.

  4. In the Map Type combo box, select Photo or Topo.

  5. In the Scale combo box, select one of the scale settings, and then click Get Map. After a few seconds, the map tile is displayed similar to the one shown in Figure 3.

    Figure 3. After clicking Get Map, the map tile is displayed

    A map retrieved from the Web service

  6. Next click Insert Map. The map tile is inserted into the document.

  7. Close the document.

Conclusion

As you have seen in this project, interoperating with a Web service from a custom task pane is pretty straight-forward. Visual Studio Tools for the Office System simplifies the task of creating and hooking up the add-in to Word 2007. Creating the custom task pane and modifying the Ribbon are equally easy. And this article has only given you a brief glance of the power of the TerraService Web service. To expand the usefulness of the project, you could take advantage of other methods in the TerraService or location tiles based on landmarks. You are only limited by your imagination.

Additional Resources

You can find additional information on the topics and concepts discussed in this article at the following locations: