VSTO extends the Word and Excel object models in several ways. Although it is possible to use these features without understanding what is actually happening "behind the scenes," it is helpful to take a look back there. This section explains by what mechanisms host items and host controls extend the Word and Excel programming models. Then the discussion focuses on exactly which new features are available.
Aggregation, Inheritance, and Implementation
If you create a Word project in Visual Studio and open the Object Browser window, you will see several assemblies listed. Two are of particular interest. You already know that the Microsoft.Office.Interop.Word assembly is the primary interop assembly (PIA), containing the definitions for the interfaces that allow managed code to call the unmanaged Word object model. Similarly, the Microsoft.Office.Interop.Excel assembly is the PIA for the unmanaged Excel object model.
You can find the VSTO extensions to the Word and Excel object models in the Microsoft.Office.Tools.Word and Microsoft.Office.Tools.Excel assemblies; each contains a namespace of the same name.
From a VSTO Word document project, open the Object Browser and take a look at the Document host item class in the Tools namespace, as shown in Figure 13-2.
Figure 13-2. Examining the Document host item class in the Object Browser.
Notice that the host item class implements the properties, methods, and events defined by the Document interface from the PIA, and extends the BindableComponent base class. Chapter 17 gets into the details of how data-bindable components work; for now, the fact that this class implements the properties, methods, and events from the PIA interface rather than extends a base class is important. It is important to notice that even though the Document host item class has all the methods, properties, and events of the Document interface from the PIA, the type definition does not actually say that it implements the Document interface itself. This is a subtle distinction that we will discuss in more detail later.
Conceptually, the difference between extending a base class and implementing the properties, methods, and events from an interface is that the former describes an "is a" relationship, whereas the latter describes a "can act like" relationship. A Microsoft.Office.Tools.Word.Document object really is a bindable component; it actually shares functionalitycodewith its base class. But it merely looks like and acts like a Word Document object; it is not a Word document object as far as Word is concerned.
For example, the Sheet1 class in Excel has your event handlers and host controls. It extends the Microsoft.Office.Tools.Excel.Worksheet base class and implements the properties, methods, and events defined by the Microsoft.Office.Interop.Excel.Worksheet interface.
Hooking Up the Aggregates
VSTO's host item and host control objects aggregate some of the underlying Word and Excel document objects (such as the Document and Bookmark objects in Word, or the Worksheet and NamedRange objects in Excel). You have already seen how you can call methods on the document object in a VSTO customization. Suppose, for instance, that you call the CheckGrammar method on the document. If this is not really a Word Document object but merely looks like one, how does it work?
The aggregating object's implementation of that method checks to see whether it has obtained the aggregated Document object already. If it has not, it makes a call into Word to obtain it (and caches away the object so that it will be available immediately when you make a second method call). After it has the reference to the aggregated object, the aggregating object calls CheckGrammar on the aggregated object. The great majority of the properties and methods on the aggregating objects do nothing more than just pass the arguments along to the PIA code, which then passes them along to the unmanaged object model.
Events work in the analogous way; if your code listens to an event exposed by an aggregating object, the aggregating object listens to the event on the aggregated object on your behalf. When the event is raised by the aggregated object, the aggregating object's delegate is called, which then raises the aggregating object's event and calls your event handling delegate.
All the host controls are hooked up in a similar manner as the host items. For instance, if you have a NamedRange host control member of a worksheet, the aggregating Worksheet object itself creates an aggregating NamedRange object. The first time you call a method on the host control, the aggregating class obtains the underlying "real" object from Excel and passes the call along.
This might seem like a whole lot of rigmarole to go through just to add new functionality to the Word and Excel object models. The key benefit that this system of aggregates affords is that each host item class in each project can be customized. One spreadsheet can have an InvoiceSheet class with a CustomerNameRange property, another can have a MedicalHistorySheet class with a CholesterolLevelChart property, and so on.
In short, VSTO extends the Word and Excel object models by aggregating the unmanaged object models with managed objects. VSTO enables developers to further customize and extend some of those objectsthose representing the workbook, worksheet, chart sheet, and documentthrough subclassing.
Obtaining the Aggregated Object
Much of the time, the foregoing details about how the aggregation model works are just that: implementation details. Whether the host item "is a" worksheet or merely "looks like" one seems to be an academic point. However, in some rare scenarios, it does matter.
Word's and Excel's object models were not written with the expectation that managed aggregates would implement their interfaces; when you call a method that takes a range, Excel expects that you are passing it a real range, not an aggregated range that acts like a range.
For instance, suppose you have a customized worksheet with two host controls: a NamedRange member called InvoiceTotals and a Chart object called InvoiceChart. You might want to write code something like this snippet:
This code will not compile because the SetSourceData method on the chart aggregate must be passed an object that implements the Range interface. It looks like at runtime the InvoiceChart aggregate will pass InvoiceTotals, an aggregated range, to the "real" aggregated chart. But Excel will expect that the object passed to SetSourceData is a range, whereas in fact it is the VSTO aggregate; it merely looks like an Excel range.
When just calling methods, reading or writing properties, and listening to events, the aggregate is more or less transparent; you can just use the object as though it really were the thing it is aggregating. If for any reason you need to pass the aggregate to an Excel object model method that requires the real Excel object, you can obtain the real Excel object via the InnerObject property. The code above will compile and work properly if you rewrite it to look like this:
Aggregation and Windows Forms Controls
If you drag and drop a Windows Forms button onto a worksheet or document, the button control is also aggregated. However, Windows Forms controls are aggregated slightly differently than the NamedRange, Bookmark, ListObject, and other controls built in to Word and Excel. There are two relevant differences between Windows Forms controls and Office's controls. First, Windows Forms controls are implemented by extensible managed classes, unlike the unmanaged Office controls, which only expose interfaces in their PIAs. Second, Word and Excel controls inherently know how they are situated in relation to their containing document or worksheet; non-Office controls on a worksheet do not know that they are in a worksheet.
Word and Excel overcome the second difference by aggregating an extender onto a control sited on a document or worksheet. Word's extender implements the properties, methods, and events of the _OLEControl interface that can be found in the Word PIA (but as with other aggregated VSTO controls, the type definition does not actually claim to implement the _OLEControl interface). It has five methods, all of which take no arguments and return no result: Activate, Copy, Cut, Delete, and Select. It also exposes floating-point read-write properties Top, Left, Height, and Width, string properties Name and AltHTML, and an Automation object. Excel's extender implements the properties, methods, and events of the _OLEObject interface that can be found in the Excel PIA.
When you drop a button onto a document or worksheet, the project system adds a new field to the host item class, but types it as Microsoft.Office.Tools.Word.-Controls.Button or Excel.Controls.Button, respectively. Because the underlying System.Windows.Forms.Button class is extensible, this time the aggregate actually is a subclass of the Windows Forms control. However, it still must aggregate the unmanaged extender interface provided by Word or Excel.
As a further convenience, the managed objects representing embedded Windows Forms controls also have read-only Right and Bottom properties aggregated onto them.
Improving C# Interoperability
The Word and Excel object models were originally designed with VBA in mind. Unfortunately, there are some language features which VBA and VB.NET support but C# does not, such as parameterized properties. In VBA, you could do something like this:
Set Up = ThisWorkbook.Names.Item("MyRange").RefersToRange.End(xlUp)
End is a read-only property that takes an argument, but C# does not support passing arguments to property getters; arguments can only be passed to methods and indexers in C#. Therefore, the PIA exposes the property getter as a function. You could talk to the PIA like this in C#:
Up = ThisWorkbook.Names.Item("MyRange", System.Type.Missing, System.Type.Missing).RefersToRange.get_End( Microsoft.Office.Interop.Excel.XlDirection.xlUp)
Note that the PIA interface calls out that this is a "getter" function; for writable properties there would be a corresponding set_ function that took the parameters and new value as arguments.
C# does, however, support something similar to parameterized property accessors: parameterized indexers. In a VSTO project with a host item or host item control that has been extended, you can accomplish the same task like this:
Up = MyRange.End[Excel.XlDirection.xlUp];
The get_End accessor function is implemented by the aggregate, so you can still use it if you want to. However, because it is no longer necessary and there is a more elegant solution, it is not displayed in the IntelliSense drop-down.
In several places in the VSTO object model, parameterized indexers have replaced parameterized properties; you will find a list of them all along with the rest of the changes to the object model at the end of this chapter.
The "Tag" Field
Every host item and host control now has a field called Tag, which can be set to any value. This field is entirely for you to use as you see fit; it is neither read nor written by any code other than your customization code. It is included because it is very common for developers to have auxiliary data associated with a particular control, but no field on the control itself in which to store the data. Having the object keep track of its own auxiliary data is, in many cases, more straightforward than building an external table mapping controls onto data.
Event Model Improvements
Like VBA, VSTO encourages an event-driven programming style. In traditional VBA programming, relatively few of the objects source events, which can make writing event-driven code cumbersome. For instance, in Word, the only way to detect when the user double-clicks a bookmark using the standard VBA object model is to declare an "events" class module with a member referring to the application:
Public WithEvents WordApp As Word.Application
Then sink the event and detect whether the clicked range overlaps the bookmark:
Private Sub App_WindowBeforeDoubleClick(ByVal Sel As Selection, _ Cancel As Boolean) If Sel.Range.InRange(ThisDocument.Bookmarks(1).Range) Then MsgBox "Customer Clicked" End If End Sub
And initialize the event module:
Dim WordEvents As New WordEventsModule Sub InitializeEventHandlers Set WordEvents.WordApp = Word.Application End Sub
And then add code that calls the initialization method. In short, this process requires a fair amount of work to detect when an application-level event refers to a specific document or control. The VSTO extensions to the Word and Excel object models were designed to mitigate difficulties in some tasks, such as sinking events on specific controls. In VSTO, the bookmark object itself sources events, so you can start listening to it as you would sink any other event:
MyBookmark.BeforeDoubleClick += new ClickEventHandler(OnDoubleClick);
In Chapter 2, you saw some of the new VSTO extensions to the view object model in action. You also read about events added by VSTO in Chapters 4, "Working with Excel Events," and 7, "Working with Word Events." At the end of this chapter, we describe all the additions to the event model in detail.