Building a Status Report Application with Visual Studio 2005 Tools for Office

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

J. Jason De Lorme, Simplesheet, Inc.

Revised: November 2005

Applies to: Microsoft Visual Studio 2005 Tools for the Microsoft Office System, Microsoft Office Word 2003

Summary: Describes the process of creating a Status Report solution in Microsoft Office Word 2003 that supports schema-based programming and the actions pane using Microsoft Visual Studio 2005 Tools for the Microsoft Office System, compared with the Microsoft Smart Document API. (23 printed pages)

Download OfficeVSTOWordStatusReport.msi.

Contents

  • Overview

  • Setup Requirements

  • Scenario

  • Deployment

  • Code Security

  • Conclusion

  • Additional Resources

  • About the Author

Overview

If your organization is anything like those that I've worked with, you've more than likely been tasked with moving at least one, if not all internal applications to the ubiquitous Web browser platform the corporate intranet runs on. The re-invention of the thin-client revolution has taken organizations by storm. It has reduced the cost of application maintenance by expediting the development, deployment, update management, security, configuration, and debugging of countless applications traditionally hosted on each user desktop.

Unfortunately, for solutions traditionally built with document authoring and formatting tools, such as proposals, contracts or status reports, this revolution has come at the price of many features users were accustomed to. I can't tell you how many times I've been asked to build a spell check, rich text editor, or the ability to cut and paste Word tables and other rich formatting into a simple HTML Web form. Many IT departments have done the math and figured that deploying Web applications just made more sense, even if the application was traditionally a document authoring solution.

There is a need for applications that are internet/intranet based, that can execute business logic, consume or produce XML and use the native features of a word processor such as Microsoft Office Word 2003. Why try and build the "undo" function, a spelling checker, bulleted lists, formatting styles or tables of a word processor all over again when the user really wants to work in Word? Also, don't discount the training factor associated with any new application.

Conceding to the user's desire of using Word in the past may have meant writing a disconnected COM add-in with an extensibility interface, or non-compiled, unsecured macro scripts with Microsoft Visual Basic for Applications (VBA). Don't get me wrong, VBA is a useful tool and as I'll point out later, a great way to learn the Word object model, but reliable, scalable, enterprise applications have tended to be those that can communicate with business systems, use existing class libraries, enforce typed data and are compiled, among other characteristics.

Using Microsoft Visual Studio 2005 Tools for the Microsoft Office System, you can build a Microsoft Office Word 2003 or Microsoft Office Excel 2003 solution written in Microsoft Visual C# or Microsoft Visual Basic, using the Microsoft .NET Framework version 2.0, bootstrapped and executed for you by the Office application itself without sacrificing the ease of deployment of the Web.

Note

I should emphasize that Visual Studio 2005 Tools for Office is not a replacement for VBA. VBA has its place with smaller utilities and common repeatable tasks. Developers and power users benefit from the deftness and relatively low learning curve of VBA.

This article demonstrates how to implement a managed code solution using Word 2003 and Visual Studio 2005 Tools for Office. The article will also discuss possible solutions for a few technical challenges along the way. While the topic is building a Word solution, much of what we discuss applies to building an application with Excel 2003, such as financial reporting, expense reports or business intelligence.

Setup Requirements

To use the Status Report solution, you must have the following software installed:

  • Microsoft Visual Studio 2005 Tools for the Microsoft Office System

  • Microsoft Office Word 2003 or Microsoft Office Professional Edition 2003 (complete installation)

The order of product installation is not important. Installing Microsoft Office 2003 is a prerequisite for using Visual Studio 2005 Tools for Office. When you install Visual Studio 2005 Tools for Office, the installer installs the Office Primary Interop Assemblies (PIAs), if they are not already installed.

To compile and run the Status Report solution sample file

  1. Perform a complete installation of Office Professional Edition 2003 or Word 2003.

    Note

    Only a complete installation includes the PIAs.

  2. Install Visual Studio 2005 Tools for Office.

  3. Download the package associated with this article, which contains the source code, Word 2003 document, and XML data files.

  4. Decompress those files to a directory on your local computer.

  5. Open the Visual C# or Visual Basic project in Visual Studio 2005 Tools for Office by double-clicking the StatusReport.sln file.

  6. Press F5 to compile and run the sample file.

Scenario

Status reports can be tedious to compile on a regular basis and generally consist of data that is already captured, such as project milestones, timesheets, and bug reports by other business systems. Most of the time uses tend to cut and paste this data between these disparate systems or to simply retype and reformat the data for presentation in Word. Users pull aggregate data, such as total hours, by running reports from the timesheet system. Users pull milestones and deliverables from the project management system or cut and paste them from previous reports. Not only can this be a laborious process, but also it is also prone to user error.

Many modern business systems today are built using Web service standards that enable these systems to interact and share data. For demonstration purposes, the Status Report sample application uses local XML files to represent data that could be consumed as though it were returned by a Web service call.

Background

Contoso is a fictitious professional services company created to demonstrate our Status Report solution. Contoso bills customers for time spent by their consultants. They must send status reports to their clients on a weekly basis describing the work performed and time recorded by the consultants. Contoso consultants record all of their time in a Timesheet application that exports a report in XML format through a Web service.

Status reports are delivered to the client in Word format, as shown in Figure 1.

Figure 1. Status Report document

Contoso project managers want to reduce the amount of copy and paste they currently do each week from the Timesheet application to Word when creating these reports.

Business Rules

The following business rules must be enforced by the application:

  • Status Reports are always reported on a weekly basis. Weeks begin on Monday and the report is dated with the Sunday of the week being reported on.

  • The Contoso project manager can make adjustments to which hours the client should be billed for, the number of hours and the addition or removal of Timesheet entries.

  • Some tasks performed by the consultants are not billable to the client.

  • Some Contoso clients like to see a tallied total of billable hours and total hours.

  • The Contoso Resource (Consultant) and whether they were onsite or if they consider their Timesheet entry billable are needed by the project manager when constructing the report, but should do not need to be shown to the client.

  • The Contoso project manager should be able to edit the descriptions typed by the consultants to sanitize for client viewing.

  • The data updated in the Status Report must be submitted back to the Timesheet sever to reflect any updates the project manager may have made.

  • Valid Status Report XML documents must adhere to the StatusReport1.xsd schema.

Solution

The Status Report application saves the project manager time by pre-filling boilerplate client and project information and enables the project manager to import time entries recorded by consultants. The project manager can then complete the status report by modifying entry details if necessary and completing the remaining sections such as next week's planning.

Note

To follow along with the solution, download the sample code accompanying this article and extract the files to your local computer.

Using the Solution

When a user opens the Status Report Word document, the application solution .NET assembly automatically loads and the code begins to execute. Visual Studio 2005 Tools for Office embeds metadata in the document that describes the code associated with the document. Word is able to detect this metadata and launch the Visual Studio 2005 Tools for Office runtime, which in turn loads the custom solution assembly.

As you can see from Figure 1, the user may logically select the date to begin. The business requirement for the Status Report solution is that the date should always be the Sunday of the week to which the report refers, assuming the calendar week begins on Monday. To help the user select Sunday's date, we use the managed MonthCalendar control, as shown in Figure 2. For example, when the user selects Thursday May 6, 2004, the MonthCalendar control will automatically highlight Sunday, May 9, 2004. This control is loaded into the actions pane when the user selects the Week Ending region of the document.

Note

Very limited sample data is included with the Status Report solution. Data is included only for the weeks of May 8, 2005 and May 15, 2005.

Figure 2. Actions pane with the managed MonthCalendar control

When the application loads, it retrieves a list of clients and projects from an XML file and then loads them into a DataSet. When the user moves the cursor into the region of the document that contains client and project information, two list boxes appear in the actions pane (as shown in Figure 3) allowing the user to select a client and project.

Figure 3. Actions pane with Client and Project drop-down list boxes

When a client is selected, such as Contoso, Ltd., the list of projects in the Project ListBox is updated. When the user selects a project, such as VSTO Development, and then clicks Insert in the actions pane, the document populates with the client and project information, as shown in Figure 4. Based on the client, project and date, time entries are queried from the system (available in this sample as XML files) and loaded into another DataSet. For each time entry available, a row is inserted in the Accomplished Tasks table.

Figure 4. Document populated with client and project information

By default, only the total number of hours is shown in the document. For some clients, Contoso needs to display the number of billable hours separately. By moving the cursor to that region, the actions pane is updated with a CheckBox control. Selecting the Show Billable Hours box displays a description and the calculated number of billable hours, as shown in Figure 5.

Figure 5. Show billable hours

If you click inside any of the Accomplishments table entries, your actions pane load the control shown in Figure 6.

Figure 6. Task Entry Details control

The controls shown in Figure 6 are dependant on data that is very much part of the document, but just not visible to the user. In fact, we used this data a moment ago when we calculated the number of billable hours. How else would we have known which time entries were billable? Of course, we could have just looked at the DataSet, but what if this document is to be edited later, when we don't have access to the Timesheet data source? We will describe how this is accomplished with the use of XML attributes in "Data Binding and XMLNodes". Visual Studio 2005 Tools for Office supports the notion of cached datasets embedded within a document to support the offline scenario, but we're not going to talk about that in this article. Using cached data sets would be another viable way of achieving a similar goal.

The Planned for Next Week and Open Issues and Risks sections of the document have no specific functionality associated with them, but they hold required data that is submitted with the status report document.

Schema Based Programming

Microsoft Visual Studio Tools for the Microsoft Office System, Version 2003, provided much of the infrastructure we needed to start building reliable Office solutions with Microsoft .NET. The latest version of this toolset brings us a better overall design-time experience, schema-based programming, fully fledged Windows Forms controls in the document and a new managed actions pane control.

I'm guessing that you are probably aware of the native XML support available in the Office 2003 Professional products. If not, let me enlighten you. The stand alone version of Word 2003 as well as Microsoft Office Professional Edition 2003 support full fidelity reading and writing of WordML, which is Word's own Markup Language. The schema allows Word to express everything you could possibly put in a document with an XML representation. Previous versions of Word could save as XML, but they could not save all Word formatting. For a complete list of new XML features in Word 2003, see this product comparison on the Microsoft Office Online site. A new feature that makes this solution possible is the ability to attach user defined schemas.

Attaching a Schema

User defined schemas allow us to add structure to free form documents. As you can see in Figure 7, I've taken a traditional free flowing Word Document and added XML Tag identifiers around all of the important fielded data in the document.

Figure 7. User-defined schema applied to document

To attach a schema in Word

  1. On the Tools menu, click Templates and Add-Ins.

  2. The Templates and Add-ins dialog box appears. Click the XML Schema tab.

  3. Click Add Schema.

  4. Browse to find the schema file. If you have downloaded the sample code that accompanies this article, you will find the file named StatusReport1.xsd located in the StatusReport directory where you extracted the files.

  5. Click Open.

  6. Click XML Options.

  7. Select the Ignore mixed content check box and the Allow saving as XML even if not valid check box, and then click OK.

  8. Click OK again to close the dialog box.

Word is a formatting and authoring tool, not a tabular forms editor and because we're using Word to apply format and style around well structured data, we're bound to have a spate of "mixed" content throughout the document. Mixed Content is basically everything except what is between the XML Tags. Allowing the XML to be saved is really a precaution, in this solution there are going to be times when you have not completed all the required attributes or you are missing a tag of some sort, but you want to just save the document and go home for the night or save and continue before lightning strikes.

Document Mark-Up

After you have the schema attached to the document, the XML Structure task pane appears with a hierarchical tree-view list of all nodes. To designate a region in the document that represents data, simply highlight that region and then click the applicable node in the task pane. Word will put XML tags around the document text specifying this is an XML node. These tags are meant for mark-up purposes only and do not print. You can hide the tags by un-checking Show XML Tags in the document at the bottom of the XML Structure task pane.

In addition to representing XML data in the viewable document, XML allows us to define attributes on an XML node. Right-click any XML tag in the document, click Attributes in the context menu, and you are presented with a dialog box that enables you to add, edit, and remove attributes. If your schema has well-defined attributes, you will see a dialog box similar to Figure 8.

Figure 8. XML attributes

XML attribute values are not visible in a Word document and this comes in very handy for us here. We are able to pass metadata in the attributes that drive the behavior of the application, without explicitly displaying that data. Of course, this isn't a secure way to embed secrets because the user can easily view these attributes, or Save As XML and see the attributes in plain text format. As we'll see in a moment, several features of our application are driven by attributes in the XML.

With XML tags marked up in the document, we now have the ability to treat each individual field in this document as a unique data element and share it with business applications despite its source being a free flowing, unstructured document. To see the Data Only of this document:

  1. On the File menu, click Save As.

  2. The Save As dialog box appears, and has some additional options. In the lower-right corner, check the Save data only check box.

  3. Choose a file name, you might want to suffix it with _Data or something similar to discern between the formatted document and the Data Only version.

  4. Click Save.

  5. With Windows Explorer, browse to the location you saved your *_Data.xml file and double-click.

Internet Explorer is normally the default XML viewer, but if you have your own custom XML editor installed, this application may appear. Either way you should see an XML file, without any formatting, that represents your document's data.

XMLNodes

So what does schema-based programming give us? Well, specifically, Visual Studio 2005 Tools for Office gives us the ability to programmatically access these nodes through the XMLNode host control. By mapping the XML StatusReport node in the document, Visual Studio 2005 Tools for Office creates a control in the ThisDocument class for us:

Friend WithEvents StatusReportNode As Microsoft.Office.Tools.Word.XMLNode
internal Microsoft.Office.Tools.Word.XMLNode StatusReportNode;

Note

This is not a System.XML.XmlNode.

Hostcontrols are controls that extend existing Microsoft Office Word 2003 and Microsoft Office Excel 2003 objects; however, they have additional functionality, including events and some have data binding capabilities. This XMLNode object makes it possible to independently read and write the text of the node:

WeekEndingDateNode.Text = DateTime.Now.ToShortDateString()
WeekEndingDateNode.Text = DateTime.Now.ToShortDateString();

Programming the Document Actions Task Pane

You may have noticed that most of the control functionality we've added to our solution is done through the actions pane. The Document Actions task pane (actions pane) is a great unobtrusive way to offer rich features to the document because it doesn't contend for the WYSIWYG (what you see is what you get) document surface and it can be prominently displayed in the correct context. As we demonstrated in the walkthrough, the functionality available in the actions pane was context sensitive. When we clicked in the date area, a calendar control was displayed, then when we clicked in the client where project area dropdown boxes appeared, and so on.

Until developers were given access to the Document Actions task pane in Office 2003, there really wasn't another way of displaying such rich functionality in Office. Consider some of the options:

  • Menu Bar

    Menu bars are manipulated through the command bar object model that you can use to show, hide, and modify existing menus and create new ones. Menu bars are useful for solitary commands such as overriding the Save function.

  • Toolbar

    For a more interactive approach, developers can create command bar items that contain buttons, drop-down style lists, combo boxes, or text boxes.

  • Context Menus

    Context menus allow the user to right click somewhere in the document and take the context of what they have clicked to display specialized, context sensitive functionality. A good example of a context menu is the SmartTag.

Smart Documents Background

Much of the functionality described so far is reminiscent of a smart document. Smart documents are documents that are aptly designed for users to follow a specified process. They determine what users need to do and give them context-sensitive help and functionality during the process.

While smart documents offer us much of the functionality we needed for the Status Report solution, the programming interface leaves much to be desired. To program a smart document, you must start by implementing the ISmartDocument interface, which takes approximately 150 lines of code.

ActionsPane Object in Visual Studio 2005 Tools for Office

Enter Visual Studio 2005 Tools for Office, where Microsoft has provided us with the ActionsPane object, a managed implementation of the Document Actions task pane. To say that it is easier to program smart documents with the latest Visual Studio 2005 toolset just isn't enough and it is, by far, my favorite enhancement of the latest release. Let me demonstrate how easy it is.

' Create a button control and add to the ActionsPane.
Dim myButton As New Button
myButton.Text = "Click me" 
ActionsPane.Controls.Add(myButton)
// Create a button control and add to the ActionsPane.
Button myButton = new Button();
myButton.Text = "Click me"; 
ActionsPane.Controls.Add(myButton);

You can add buttons, labels, or absolutely any other Windows Forms control to the ActionsPane, but the best way to do this is to create your own custom user control. Creating a user control allows you to create one or more controls and visually specify the layout with the designer surface in Visual Studio .NET, as shown in Figure 9.

Figure 9. Visual Studio .NET control design surface

After you design your control, either as part of the same project or in a separate project, you can add it to the actions pane in the same fashion:

Dim clientProjectsPane As New ClientProjectsUserControl()
ActionsPane.Controls.Add(clientProjectsPane)
// Create the custom control and add to the ActionsPane.
ClientProjectsUserControl clientProjectsPane = new
    ClientProjectsUserControl();
ActionsPane.Controls.Add(clientProjectsPane);

When you add a control, the document's actions pane is automatically displayed, but you can explicitly show or hide it with the following code:

ActionsPane.Show()
ActionsPane.Hide()
ActionsPane.Show();
ActionsPane.Hide();

Conventional smart documents intrinsically rely on XML tags in the document to automatically determine which controls are displayed in the actions pane. XML mapping is not required with the actions pane, but you can still map XML elements and program the actions pane to show and hide controls according to user's selection.

You can code against the actions pane to respond to any event such as host control, application, and document events. Mapping XML schema elements to your document creates a host control for each node, which allows us to handle the events such as ContextEnter and load contextual controls in the actions pane.

Private clientProjectsPane As ClientProjectsUserControl

' Create the control during document initialization.
Private Sub ThisDocument_Startup(ByVal sender As Object, ByVal e As _
        System.EventArgs) Handles Me.Startup

    clientProjectsPane = New ClientProjectsUserControl()
    ActionsPane.Controls.Add(clientProjectsPane)

End Sub

' Fires when the XML Tag for ClientProjects is selected.
Private Sub StatusReportClientProjectNode_ContextEnter _
        (ByVal sender As System.Object, ByVal e As _
        Microsoft.Office.Tools.Word.ContextChangeEventArgs) Handles _
        StatusReportClientProjectNode.ContextEnter
    ' Assign the week ending XML node value to the control.
    clientProjectsPane.WeekEndingDate = _
        StatusReportWeekEndingDateNode.Text
    ' Display the control in the actions pane.
    ActionsPane.Controls(clientProjectsPane.Name).Visible = True
End Sub

' Fires when user leaves the ClientsProjects XML node.
Private Sub StatusReportClientProjectNode_ContextLeave(ByVal sender As _
        System.Object, ByVal e As _
        Microsoft.Office.Tools.Word.ContextChangeEventArgs) Handles _
        StatusReportClientProjectNode.ContextLeave
    ' Hide the control.
    ActionsPane.Controls(clientProjectsPane.Name).Visible = False
End Sub
private ClientProjectsUserControl clientProjectsPane;

// Create the control during document initialization.
private void ThisDocument_Startup(object sender, 
System.EventArgs e)
{

    clientProjectsPane = new ClientProjectsUserControl(); 
    ActionsPane.Controls.Add(clientProjectsPane);

}

// Fires when the XML Tag for ClientProjects is selected.
void ClientProjectNodes_ContextEnter(object sender,
Microsoft.Office.Tools.Word.ContextChangeEventArgs e)
{
// Assign the week ending XML node value to the control.
clientProjectsPane.WeekEndingDate =
StatusReportWeekEndingDateNode.Text;

// Display the control in the actions pane.
    ActionsPane.Controls[clientProjectsPane.Name].Visible = true;
}

// Fires when user leaves the ClientsProjects XML node.
void ClientProjectNodes_ContextLeave(object sender,
Microsoft.Office.Tools.Word.ContextChangeEventArgs e)
{
    // Hide the control.
    ActionsPane.Controls[clientProjectsPane.Name].Visible = false;
}

Programming XML Nodes

The Word.XMLNode host control supports most of what you would expect an XML object to support, but unfortunately Word.XMLNode is not derived from anything in the System.XML namespace. The Word.XMLNode host control is based on the old Office MSXML loader. This can be a problem if you want to transform an individual node using System.Xml.Xsl.XslTransform, for instance. The Word object model includes the XSLTransform object to transform the document, but that doesn't work for individual nodes. To use Word XMLNodes with System.Xml objects you can simply get the XML string representation back from the object and create a System.Xml object with the LoadXml() method.

The Word.XMLNode class has a few additional benefits including a handful of incredibly useful events. In addition to the individual XMLNode events, the Word application provides an XMLSelectionChange event which is going to prove very useful for us shortly. Table 1 describes these events.

Table 1. Interesting XML events

Event

Description

XMLNode.ContextEnter

Occurs when the user's selection point moves into the XML mapped region of that node.

XMLNode.ContextLeave

Occurs when the user's selection point leaves the XML mapped region of that node.

Word.Application.XMLSelectionChange

Occurs when the user selects any XML mapped node in the document.

Data Binding and XMLNodes

Data binding between a data source and a host control involves a two-way data update. Simple Data Binding exists when a single control property is bound to a single data source value. An example of simple data binding is the Text property of a TextBox control being bound to a single data value in a DataSet. If the DataSet is updated, the Text in the TextBox control is updated and vice versa. Complex data binding occurs when your control is bound to more than one data element, such is the case in a ListBox control list where the DisplayMember and ValueMember are bound to different columns in the datasource.

Despite the fact that every other host control supports data binding, the XMLNode does not. The data source for an XMLNode is the underlying data in the document. In the TaskEditorUserControl, depicted in Figure 6, we are using the XMLNode control created by Word which represents the following underlying XML Hours element:

<Hours billable="true" onsite="false">5</Hours>

In this application we want the value from the document to be populated in the text box when the control is loaded and values typed in the text box to be updated in the document. To do this we use the Text property of the hoursNode XMLNode object as the data source to the Text property of the hoursTextBox text box with the following code:

hoursTextBox.DataBindings.Add("Text", hoursNode, "Text")
hoursTextBox.DataBindings.Add("Text", hoursNode, "Text");

Data Binding to Attributes

By using attributes on the XML node we are able to carry context within the document that Word will not display. In the case of billable hours, we do not want to display this on every line, but we want the ability to calculate this value in order to display the total billable hour summary at the top.

The billable and onsite check boxes are bound to the respective Boolean values in the Hours element. I created a helper method to support this binding:

Private Sub BindBoolAttribute(ByVal attribute As String, _
        ByVal containingNode As Word.XMLNode, _
        ByVal checkBoxControl As CheckBox)

    Dim xPath As String = String.Format("./@{0}", attribute)
    Dim NameSpacePath As String = String.Format( _
             "xmlns:{0}='{1}'", "SR", _
             "http://tempuri.org/StatusReport.xsd")

    Dim attributeNode As Word.XMLNode = _
        containingNode.SelectSingleNode(xPath, _
        NameSpacePath, False)

    ' If the attributes don't exist we'll create them.  
    If (attributeNode Is Nothing) Then
        ' Set the default value to false for new rows.
        attributeNode = containingNode.Attributes.Add( _
                attribute, "")
        attributeNode.NodeValue = "false"
    End If

    Dim b As Binding = New Binding("Checked", attributeNode, _
        "NodeValue", False)
    checkBoxControl.DataBindings.Add(b)

End Sub
void BindBoolAttribute(string attribute, Word.XMLNode
        containingNode, CheckBox checkBoxControl)
{
    string xPath = String.Format("./@{0}", attribute);
    string NameSpacePath = String.Format( 
        "xmlns:{0}='{1}'", "SR", 
        "http://tempuri.org/StatusReport.xsd");

    Word.XMLNode attributeNode =
        containingNode.SelectSingleNode(xPath, 
        NameSpacePath, false);

    // If the attributes don't exist we'll create them.  
    if (attributeNode == null)
    {
        object objMissing = Type.Missing;

        // Set the default value to false for new rows.
        attributeNode = containingNode.Attributes.Add(
            attribute, "", ref objMissing);
        attributeNode.NodeValue = "false";
    }

    Binding b = new Binding("Checked", attributeNode, "NodeValue", 
        false);

    checkBoxControl.DataBindings.Add(b);
}

A couple words of caution are warranted here. When using many of the interop methods beware of ref object XXXX parameters. When we call the Attributes.Add method above, we're asked for a ref object Range parameter, which ideally we would just pass as null. Passing a null here will undoubtedly crash Word, provided you don't trap the error properly. The trick is to use a System.Type.Missing reference object as demonstrated above.

The second tip you can glean from the code above is the empty string being passed for the namespace parameter. When creating the attribute make sure to pass an empty string for the namespace or you will get unexpected results.

Selecting Document Data with XPath

The AccomplishedTask XML node actually contains more data than is required for display on the printed status report, but nonetheless is required for the context of the application. For example the Resource name is used by the project manager when constructing the document, but should not get printed on the actual status report.

      <AccomplishedTask id="2">
         <TaskDate>5/3/2004</TaskDate>
         <Hours billable="true" onsite="true">8</Hours>
         <Description>Smart Document development.</Description>
         <Resource name="Syed Abbas" id="3" />
      </AccomplishedTask>

Because the Resource name is read-only in the actions pane, there is no need to implement data binding here. As I mentioned before, the XMLNode supports most of what you would expect an XML object would, including the SelectSingleNode method which uses an XPath query to retrieve another XMLNode:

Dim resourceNode As Word.XMLNode = _
    hoursNode.SelectSingleNode("./@resourceName", _
    Globals.ThisDocument.NameSpacePath, False)

If (resourceNode IsNot Nothing) Then
    resourceNameLabel.Text = resourceNode.NodeValue
End If
Word.XMLNode resourceNode = 
    hoursNode.SelectSingleNode("./@resourceName", 
    Globals.ThisDocument.NameSpacePath, false);

if (resourceNode != null)
{
    resourceNameLabel.Text = resourceNode.NodeValue;
}

Contrary to creating attributes, make sure that you include the namespace in the SelectSingleNode otherwise you may get stuck thinking your XPath doesn't work when that is not the issue.

Repeating XML Elements

Ideally, I'd like to see the XMLNodes object support complex data binding whereby you could load a DataSet then call a method like SetDataBinding that would automatically populate a Word Table or bulleted list with the rows in the DataSet. Unfortunately, this is not possible, but all is not lost. With the help of some Word Table object interop, we can simulate the population of the table, which takes care of creating all the XML Tags automatically. Figure 10 shows the XML tags that are marked up in the Accomplished Tasks table.

Figure 10. XML Nodes in a table

If you select the right-most cell in the table and press the tab key you will notice that Word is intelligent enough to copy all of the XML tags to the second row. This little piece of magic makes it feasible to simulate data binding. It turns out that if we use the Table.Rows.Add method using Word interop, the XML tags are properly copied to the next line as hoped.

The code to add the row has one gotcha which we discussed earlier; the Add method is looking for a BeforeRow parameter. Because we want to append to the end of the table, we'll use the Type.Missing trick in C# again:

table.Rows.Add()
table.Rows.Add(Type.Missing);

When you add repeating elements to a Word document, an XMLNodes host control is created. The XMLNodes object is actually a collection of XMLNode objects. By iterating through our DataSet, we can pair indexes in the collection of XMLNode objects with the index of the rows we create in our table and populate the values:

Dim row As DataRow
For Each row In timesheetDataSet.Tables("Task").Rows
    ' Add rows only after the first template row.
    If (rowId > 1) Then table.Rows.Add()
    ' Insert the task entry and add the number of hours.
    totalHours += PopulateTaskEntry(rowId, row)
    ' Move to the next row in the table.
    rowId += 1
Next

Private Function PopulateTaskEntry(ByVal rowId As Integer, _
        ByVal row As DataRow) As Integer
    ' Hours recorded on this entry.
    Dim taskHours As Integer = 0
    Dim taskNode As Word.XMLNode = _
       Globals.ThisDocument.AccomplishedTasksAccomplishedTaskNodes(rowId)
. . .
foreach (DataRow row in timesheetDataSet.Tables["Task"].Rows)
{
    // Add rows only after the first template row.
    if (rowId > 1)
    {
        table.Rows.Add(Type.Missing);
    }

    // Insert the task entry and add the number of hours.
    totalHours += PopulateTaskEntry(rowId, row);

    // Move to the next row in the table.
    rowId++;
}

private int PopulateTaskEntry(int rowId, DataRow row)
{
    // Hours recorded on this entry.
    int taskHours = 0;

    Word.XMLNode taskNode =
        Globals.ThisDocument.AccomplishedTasksAccomplishedTaskNodes[rowId];

Events with Repeating XMLNodes

Another complication we've introduced with repeating elements in the document is having the correct events fire when the user moves between repeating elements. As you can see in Figure 4, we populate the TextBox and CheckBox controls with the values from the currently selected row in the table. In most controls, we're able to do this with the ContextEnter and ContextLeave events we discussed earlier. However, as far as Word is concerned, when the user moves from AccomplishedTask #1 to AccomplishedTask #2 in the table, they are still in the same type of node and it does not fire an event for that node. This obviously creates an issue when we want to change the values in the actions pane based on the user selection.

To solve this problem, we handle the Word.Application.XMLSelectionChange event at the application level. Thankfully, Word fires this event for every change in user XML selection. This could have some issues if you end up having many different types of repeating elements, but for our purposes it solves the problem.

Private Sub ThisApplication_XMLSelectionChange1( _
    ByVal Sel As Microsoft.Office.Interop.Word.Selection, _
    ByVal OldXMLNode As Microsoft.Office.Interop.Word.XMLNode, _
    ByVal NewXMLNode As Microsoft.Office.Interop.Word.XMLNode, _
    ByRef Reason As Integer) Handles ThisApplication.XMLSelectionChange
void ThisApplication_XMLSelectionChange(Word.Selection Sel,
    Word.XMLNode OldXMLNode,
    Word.XMLNode NewXMLNode, ref int Reason)

We check the old and new nodes to see if they are both AccomplishedTask nodes, then identify them as unique and handle the appearance in the actions pane accordingly.

' Only check for AccomplishedTask repeating nodes.
If (NewXMLNode.ParentNode.BaseName = "AccomplishedTask" And _
        OldXMLNode.ParentNode.BaseName = "AccomplishedTask") Then
    ' Grab the old TaskId node and compare it to the new.
    Dim oldTaskIdNode As Word.XMLNode = _
       OldXMLNode.ParentNode.SelectSingleNode("./@id", _
       NameSpacePath, False)

    Dim newTaskIdNode As Word.XMLNode = _
       NewXMLNode.ParentNode.SelectSingleNode("./@id", _
       NameSpacePath, False)

    If (oldTaskIdNode IsNot Nothing) Then
        If (newTaskIdNode Is Nothing) Then
            ' The user has added a new node, add the new ID.
            newTaskIdNode = _
                NewXMLNode.ParentNode.Attributes.Add("id", "")
            Dim oldTaskId As Integer = Integer.Parse( _
                oldTaskIdNode.NodeValue)
            newTaskIdNode.NodeValue = (++oldTaskId).ToString()
        End If
    End If

    ' Check to see if the IDs differ.
    If (oldTaskIdNode.NodeValue <> newTaskIdNode.NodeValue) Then
       ' Create a new instance of the taskEditorPane.
       AddTaskEditorPane(NewXMLNode.ParentNode)
    End If
End If
// Just check for AccomplishedTask repeating nodes.
if (NewXMLNode.ParentNode.BaseName == "AccomplishedTask" &&
    OldXMLNode.ParentNode.BaseName == "AccomplishedTask")
{
    // Grab the old TaskId node and compare it to the new.

    Word.XMLNode oldTaskIdNode =
        OldXMLNode.ParentNode.SelectSingleNode("./@id", ns, false);

    Word.XMLNode newTaskIdNode =
        NewXMLNode.ParentNode.SelectSingleNode("./@id", ns, false);

    if (oldTaskIdNode != null)
    {
        if (newTaskIdNode == null)
        {
            // The user has added a new node, add the new ID.
            newTaskIdNode =
                NewXMLNode.ParentNode.Attributes.Add("id", "", 
ref missing);

            int oldTaskId = int.Parse(oldTaskIdNode.NodeValue);
            newTaskIdNode.NodeValue = (++oldTaskId).ToString();
        }
    }

    // Check to see if the IDs differ.
    if (oldTaskIdNode.NodeValue != newTaskIdNode.NodeValue)
    {
        // Create a new instance of the taskEditorPane.
        AddTaskEditorPane(NewXMLNode.ParentNode);
    }
}

Deployment

The status report solution presented here can be deployed in several ways. There are three standard deployment options for Visual Studio 2005 Tools for Office solutions, depending on your requirements:

  • The Word document and the associated assembly are deployed to a user's local computer.

  • The Word document is deployed to a user's local computer. The associated assembly is deployed to a network share (UNC) or Web server (HTTP).

  • The Word document and the associated assembly are deployed to a network share (UNC) or Web server (HTTP).

For more information, see Deploying Office Solutions.

Code Security

No matter which option you choose for deployment, Visual Studio 2005 Tools for Office solutions require Full Trust .NET code access security to run. When running from your development machine, Visual Studio 2005 takes care of granting Full Trust so you don't need to worry about it. If you deploy the document to an end user computer, or run it from another location, you must ensure that location is trusted on the computer running the solution.

The sample code included with this article is intended for instructional purposes, and should not be used in deployed solutions without modifications. In particular, code security must be taken into greater consideration.

To illustrate the simplicity of this sample solution, a list of potential threats has been identified using the threat modeling process and tools described in the Threat Modeling section in the Microsoft Security Developer Center.

The following is an example of an identified threat that should be taken into consideration before expanding or deploying this solution.

Table 2. Examples of threats

Threat Effect

Entry Point

Known Mitigation

The XML data files have been compromised and contain invalid data.

DataSet.ReadXml

Mark the XML data files as read only.

For more information on code security, please visit the Microsoft Security Developer Center.

Conclusion

Visual Studio 2005 Tools for Office 2005 greatly reduces the amount of code necessary to implement context sensitive documents that function like smart documents. Programming the actions pane to respond to the rich events provided by the new host controls gives us real control over the Document Actions task pane. By creating user controls in Visual Studio and hosting them in the actions pane, we now have a design-time experience for programming the actions pane. Even though we don't have complex data binding with XMLNode host controls, we can at least use the event model that Word provides and the rich features of the host controls to work directly with the values in the document.

Additional Resources

See the following resources fore more information.

Visual Studio 2005 Tools for Office

Code Security

About the Author

J. Jason De Lorme is a principal consultant with Simplesheet, Inc. a software architecture and development firm specializing in solutions built with Microsoft .NET Framework and Microsoft Office 2003 Editions. As an early adopter, he enjoys the opportunity to work with new products, especially when it involves .NET Framework. When he's not meeting with clients or writing code, he's probably cycling, hiking, or skiing.