When a customized document with data-bound controls starts up, the datasets have to be filled in somehow before the controls display the data. As you saw at the beginning of this chapter, if you use the Data Sources pane to create data-bound controls, Visual Studio automatically emits code to fill the datasets using custom-generated adapters:
private void ThisDocument_Startup(object sender, System.EventArgs e){ if (this.NeedsFill("northwindDataSet")) this.productsTableAdapter.Fill(this.northwindDataSet.Products);
But under what circumstances would the dataset ever not need to be filled at startup? Consider, for example, a spreadsheet with a dataset containing a single table. One worksheet has a single datum bound to a named range. If you save that spreadsheet, only that one datum is going to be saved; all the other information in the dataset is just a structure in memory at runtime that will be lost when the workbook is closed. The data is potentially going to have to be fetched anew every time the worksheet host control starts up.
One of the key benefits of Word and Excel documents is that they are useful even on machines that are not connected to networks. (Working on a spreadsheet or document on a laptop on an airplane is the canonical scenario.) It would be unfortunate indeed if a data-bound customized document required your users to always be connected.
Fortunately, VSTO solves this problem. Click the icon for the typed dataset in the component tray, and then look at the Properties pane for this component. A Cached property defaults to false. If you set it to TRue, when you save the document the VSTO runtime will turn the dataset into XML and store the XML in a "data island" inside the document.
The next time the document starts up, the VSTO runtime detects that the data island contains a cached dataset and fills in the dataset from the cache. The call to NeedsFill in the Startup event will then return false, and the startup code will not attempt to fill in the data from the adapter. Essentially, the NeedsFill method returns false if the object was loaded from the cache automatically, true otherwise.
Caching Your Own Data Types
You can cache almost any kind of data in the XML data island, not just datasets. To be cacheable by the VSTO runtime, you must meet the following criteria:
To tell Visual Studio that you would like to cache a member variable, just add the Cached attribute to its declaration. Make sure you check whether the member was already filled in from the cache; the first time the document is run there will be no data in the cache, so you have to fill in the data somehow. For example, you could use the code in Listing 17-5.
Listing 17-5. Auto-Generated Table-Filling Code
public partial class ThisDocument { [Cached] public string CustomerName; private void ThisDocument_Startup(object sender, System.EventArgs e) { if (this.NeedsFill("CustomerName")) this.CustomerName = "Unknown Customer" } }
Dynamically Adding and Removing Cached Members from the Data Island
Cached data can be large; what if you decide that at some point you want to stop caching a particular dataset in the data island? Or, conversely, what if you do not want to automatically fill in a dataset and store it in the cache on the first run of the document, but rather want to start caching a member based on some other criterion? It would be unfortunate if the only way to tell VSTO to cache a member in the data island was to tag it with the Cached attribute at design time.
Therefore, all customized view item classes generated by a VSTO project expose four handy functions that you can call to query and manipulate the caching semantics, as follows:
NeedsFill we have already seen; if the named member was initialized from the data island by the VSTO runtime when the customization started up, this returns false. Otherwise, it returns TRue.
IsCached might seem like it is just the opposite of NeedsFill, but it is not. NeedsFill tells you whether the item in question was loaded out of the data island; IsCached tells you whether the item will be saved to the data island when the user saves the document.
StartCaching and StopCaching dynamically add and remove members from the set of members that will be saved to the data island. It is illegal to call StartCaching on a member already in the cache or StopCaching on a member not in the cache; use IsCached to double-check if you need to. The same rules that apply to cached members added to the cache by the Cached attribute apply to dynamically added members; only call StartCaching on public fields or public readable/writable properties.
If a cached member is set to null at the time that the document is saved, the VSTO runtime assumes that you intended to call StopCaching on the member and it will be removed from the data island. |
Advanced Topic: Using ICachedType
Suppose you have a large cached dataset that you loaded out of the data island when the customization started up. Serializing a dataset into XML can be a time- and memory-consuming process; so if there have been no changes to the dataset when the document is saved, the VSTO runtime is pretty smart about skipping the serialization.
This is also important if the user closes Word or Excel without saving the document. The host application needs to know whether to create the "Do you want to save changes?" dialog box. If the dataset is clean, there are no changes to save and the dialog should not be created.
How can VSTO tell whether a custom class added to the cached members is dirty? The VSTO runtime can track the Change events on a dataset or data table to tell whether they are dirty, but in general, any other types simply have to be written out every time. To prevent the "Do you want to save?" dialog, the VSTO runtime must pessimistically serialize the object and compare it to the state that it loaded; this is again potentially time-consuming.
If you require more finely grained control over the caching process for a particular member, you can implement the ICachedType interface. This interface enables you to not only hint to the VSTO runtime whether the item needs to be re-serialized, it also allows you to dynamically abort a save or load, and receive notification when the save or load is done. Listing 17-6 shows its members.
Listing 17-6. The ICachedType Inteface
namespace Microsoft.VisualStudio.Tools.Applications.Runtime { public interface ICachedType { bool IsDirty { get; } bool BeforeLoad(); void AfterLoad(); bool BeforeSave(); void AfterSave(); } }
If you implement this interface on a particular class and then add a member containing an instance to the class, the VSTO runtime will do the following:
Manipulating the Serialized XML Directly
Chapter 18 discusses how to view and edit the contents of the data island, start and stop caching members, and so on, without actually starting Word or Excel.
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