Let's take a deeper look behind the scenes at what is going on when you customize a worksheet or document. Create a new Excel C# project, create a named range, and take a look at the code for Sheet1.cs.
Listing 13-1. The Developer's Customized Worksheet Class
namespace ExcelWorkbook1 { public partial class Sheet1 { private void Sheet1_Startup(object sender, System.EventArgs e) { this.MyRange.Value2 = "Hello"; } private void Sheet1_Shutdown(object sender, System.EventArgs e) { } #region VSTO Designer generated code ///
/// Required method for Designer support - do not modify /// the contents of this method with the code editor. ///
private void InternalStartup() { this.Startup += new System.EventHandler(Sheet1_Startup); this.Shutdown += new System.EventHandler(Sheet1_Shutdown); } #endregion } }
Upon closer inspection, a few questions might come to mind. What does that partial mean in the class declaration? Where is the MyRange property declared and initialized? Didn't we say earlier that the customized worksheet class extends a base class? Where is the base class declaration?
It's the partial that is the key. C# and Visual Basic support a new syntax that allows a class declaration to be split up among several files. The portion that you see before you is the home of all your developer-customized code; the automatically generated code is hidden in another portion of the class not displayed by default.
Click the Show All Files button in the Solution Explorer and you will see that a number of normally hidden files make up the class, as shown in Figure 13-3.
Figure 13-3. Using the Show All Files button to examine hidden code.
First, notice that behind every worksheet there is an XML file for the worksheet. If you look at the first few lines of the XML, you will see that it contains a description of the contents of the worksheet and how to represent it as a class. This "blueprint" contains information about what namespace the class should live in, what the name of the class should be, what controls are exposed on the class, how Excel identifies those controls, and so on.
Behind this language-independent representation of the class there is another C# file that contains the other half of the partial class, generated from the XML blueprint. It begins something like this:
namespace ExcelWorkbook1 { [Microsoft.VisualStudio.Tools.Applications.Runtime. StartupObjectAttribute(1)] [System.Security.Permissions.PermissionSetAttribute( System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")] public sealed partial class Sheet1 : Microsoft.Office.Tools.Excel.Worksheet, Microsoft.VisualStudio.Tools.Applications.Runtime.IStartup { internal Microsoft.Office.Tools.Excel.NamedRange MyRange;
As you can see, here is where the base classes are specified and the member variables declared. The class also specifies that it is one of the startup classes in your customization assembly, and that code that calls members of this class must be fully trusted.
There is plenty more code in the hidden portion of the partial class, most of which is devoted to initializing controls, starting up data binding, and handling data caching; Chapter 17 discusses data binding in more detail. The constructor, in particular, should look familiar:
public Sheet1(IRuntimeServiceProvider RuntimeCallback) : base(((IHostItemProvider)(RuntimeCallback.GetService( typeof(IHostItemProvider)))), RuntimeCallback, "Sheet1", null, "Sheet1") { this.RuntimeCallback = RuntimeCallback; }
This is functionally the same code as just discussed in the previous section on creating custom host items by calling the aggregate base class constructor.
If you ever want to debug through this code, ensure that Just My Code Debugging is turned off (via the Tools > Options > Debugging > General dialog); you can then put breakpoints on any portion of the hidden code, just like any other code.
Do not attempt to edit the hidden code. Every time you make a change in the designer that would result in a new control being added, or even change a control property, the hidden half of the partial class is completely regenerated. Any changes you have made to the hidden half will be lost; that is why it is hidden by default!
The Startup and Shutdown Sequences
You have probably noticed by now that we have been putting custom initialization code in an event handler:
private void Sheet1_Startup(object sender, System.EventArgs e) { this.MyRange.Value2 = "Hello"; }
But exactly what happens, in what order, as the startup classes are created and initialized? Excel customizations typically have many startup classes, one for each sheet and one for the workbook itself; which ones load first?
You already saw a clue that answers the latter question. In the hidden half of the partial class, each class declaration has an attribute:
[Microsoft.VisualStudio.Tools.Applications.Runtime. StartupObjectAttribute(1)]
The Workbook class has 0 for the argument, Sheet1 has 1, Sheet2 has 2, and so on. The workbook aggregate always has ordinal 0, and each worksheet is given its ordinal based on what order Excel enumerates its sheets. The startup sequence happens in four phases, and each phase is executed on each startup class in order of the given ordinal before the next phase begins.
In the first phase, each class is constructed using the constructor mentioned above. This simply constructs the classes and stores away the information that will be needed later to fetch the unmanaged aggregated objects from Excel or Word.
In the second phase, the Initialize method of each startup class is calledagain, in multiclass customizations, starting with the workbook and then each worksheet by ordinal. If you look at the hidden half of the partial class, you will see the Initialize method:
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.ComponentModel.EditorBrowsableAttribute( System.ComponentModel.EditorBrowsableState.Never)] public void Initialize() { this.HostItemHost = ((IHostItemProvider) (this.RuntimeCallback.GetService(typeof(IHostItemProvider)))); this.DataHost = ((ICachedDataProvider) (this.RuntimeCallback.GetService(typeof(ICachedDataProvider)))); Globals.Sheet1 = this; System.Windows.Forms.Application.EnableVisualStyles(); this.InitializeCachedData(); this.InitializeControls(); this.InitializeComponents(); this.InitializeData(); this.BeginInitialization(); }
The attributes prevent the Initialize method from showing up in IntelliSense drop-downs and mark the method as being "not my code" for the Debug Just My Code feature. The initializer then fetches services from the host needed to initialize the view and data elements, sets up the global class (discussed in more detail later in this chapter), loads cached data, and initializes all the controls.
In the third phase, data binding code is activated. Data bindings must be activated after all the classes are initialized because a control on Sheet2 might be bound to a dataset on Sheet1.
Finally, in the fourth phase, after everything is constructed, initialized, and data bound, each startup class raises its Startup event, and the code in the developer's half of the partial class runs.
This multiphase startup sequence ensures that you can write handlers for the Startup event that can assume not just that the class itself is ready to go, but that every startup class in the customization is ready to go.
Ideally, it would be a good idea to write Startup event handlers for each class that do not depend on the order in which they are executed. If you must, however, you can always look at the startup attributes to see in what order the events will be executed.
The shutdown sequence is similar but simpler. As the host application, Word or Excel, shuts down, each host item class raises the Shutdown event. Shutdown events are raised in the same order as each phase in the startup sequence.
The Globals Class in Excel
Suppose you're writing code in the Sheet1 class that needs to set a property on a control hosted by Sheet2. You are probably going to need to obtain the instance of the aggregated Sheet2 class somehow. Instead of aggregating properties representing all the other sheets and the workbook aggregates onto each startup class, VSTO exposes all the sheets and the workbook as static members of the Globals class:
private void Sheet1_Startup(object sender, System.EventArgs e) { Globals.Sheet2.MyRange.Value2 = "Hello"; }
Because at least the first three phases of the startup sequence have finished at this point, you know that the Globals class and Sheet2 have been initialized, although Sheet2's Startup event has probably not fired yet.
Notice that by default, controls aggregated onto the worksheet classes are given the internal visibility modifier. You can change the visibility modifier generated for a control by selecting the control in the designer and then selecting the Modifiers property in the Properties window. However, if you change the visibility of the control to private, you will be unable to access the control's field from the Globals class.
The Globals class is also constructed using partial classes, although by default there is no visible portion. Rather, each generated code file defines a portion of the Globals class. You can see this code at the bottom of the hidden file for each class. Should you for some reason want to add your own custom members to the Globals class, you can always create your own portion of the partial class.
Part One. An Introduction to VSTO
An Introduction to Office Programming
Introduction to Office Solutions
Part Two. Office Programming in .NET
Programming Excel
Working with Excel Events
Working with Excel Objects
Programming Word
Working with Word Events
Working with Word Objects
Programming Outlook
Working with Outlook Events
Working with Outlook Objects
Introduction to InfoPath
Part Three. Office Programming in VSTO
The VSTO Programming Model
Using Windows Forms in VSTO
Working with Actions Pane
Working with Smart Tags in VSTO
VSTO Data Programming
Server Data Scenarios
.NET Code Security
Deployment
Part Four. Advanced Office Programming
Working with XML in Excel
Working with XML in Word
Developing COM Add-Ins for Word and Excel
Creating Outlook Add-Ins with VSTO