As mentioned in previous sections, the example code for this chapter includes an application called WordDocEditTimer, which maintains a list of edit times for Word documents. In this section, we
examine the code for this application in detail, as it illustrates everything you’ve read about so far and includes some useful tips.
The general operation of this application is that whenever a document is created or loaded, a timer is started, linked to the document name. If you close a document, then the timer for that document pauses. If you open a document that has previously been timed, then the timer resumes. Also, if you use Save As to save a document with a different filename, then the timer is updated to use the new filename.
This application is a Word application-level add-in, and uses a custom task pane and a ribbon menu. The ribbon menu contains a button that you can use to turn the task pane on and off and a check box that enables you to pause the timer for the currently active document. The group containing these controls is appended to the Home ribbon tab. The task pane displays a list of active timers.
This user interface is shown in Figure 40-13 .
This keeps a reference to a Microsoft. Office. Interop. Word. Document object as well as the total edit time, whether the timer is active, and the time it last became active. The ThisAddln class maintains a collection of these objects, associated with document names:
These event handlers are used to monitor documents as they are opened, created, and closed, and also to ensure that the Pause check box is kept up-to-date on the ribbon. This latter functionality is achieved by keeping track of window activations with the WindowActivate event.
The last task performed in this event handler is to start monitoring the current document and add the custom task panel to the window containing the document:
// Start monitoring active document
The MonitorDocument() utility method adds a timer for a document:
internal void MonitorDocument(Word.Document Doc)
This method simply creates a new DocumentTimer for the document. The DocumentTimer references the document, has zero edit time, is active, and was made active at the current time. It then
adds this timer to the documentEditTimes collection and associates it with the document name.
The AddTaskPaneToWindow() method adds the custom task pane to a window. This method starts by checking the existing task panes to ensure that there isn’t one in the window already. Also, one other
strange feature of Word is that if you immediately open an old document after loading the application, the default Documentl document vanishes, without raising a close event. This can lead to an exception being raised when the window for the task pane that was in the document is accessed, so the method also checks for the ArgumentNullException that indicates this.
If an exception is thrown, then.the offending task pane is removed from the collection:
// Remove pane if necessary
If no task pane was found for the window, then the method finishes by adding one:
The added task pane is an instance of the TimerDisplayPane class. You will look at this class shortly. It is added with the name Document Edit Timer. Also, an event handler is added for the VisibleChanged event of the CustomTaskPane that you obtain after calling the CustomTaskPanes. Add() method. This enables you to refresh the display when it first appears:
The TimerDisplayPane class exposes a RefreshDisplay() method that is called in the preceding code. This method, as its name suggests, refreshes the display of the timerControl object.
Next, there is the code that ensures that all documents are monitored. First, when a new document is created, the eventlnterface_NewDocument() event handler is called, and the document is monitored by calling the MonitorDocument() and AddTaskPaneToWindow() methods, which you’ve already seen.
This method also clears the Pausecheck box in the ribbon menu as new documents start with the time running. This is achieved through a util!ty method, SetPauseStatus( ), which is defined on the ribbon:
// Set checkbox
Just before a document is closed, the eventlnterface_DocumentBeforeClose () event handler is called. This method freezes the timer for the document, updates the total edit time, clears the Document reference, and removes the task pane from the document window (with RemoveTaskPaneFromWindow( ), detailed shortly) before the document is closed.
When a document is opened, the eventlnterface_DocumentOpen() method is called. There is a little more work to be done here, as before monitoring the document, the method must determine whether a timer already exists for the document by looking at its name.
If the document isn’t already being monitored, then a new monitor is configured as for a new document.
The,RemoveTaskPaneFromwindow() method is used to remove the task pane from a window. The code for this method first checks that a task pane exists for the specified window:
If a task window is found, then it is removed by calling the CustomTaskPanes. Remove() method. It is also removed from the local collection of task pane references.
The last event handler in this class is eventlnterface_WindowActivate(), called when a window is activated. This method gets the timer for the active document and sets the check box on the ribbon menu so that the check box is kept updated for the document:
The code for ThisAddln also includes two utilitymethods. The first of these ToggleTaskPaneDisplay(), isused to show or hide the display of the task pane for the currently active document by setting the CustomTaskPane. Visible property.
The ToggletaskPaneDisplay() method shown in the preceding code is called by event handlers on the ribbon control,as you will see shortly.
Finally,the class has another method that is called from the ribbon menu, which enables ribbon controls to pause or resume the timer for a document:
The only other code in this class definition is an empty event handler for Shutdown, and the VSTO generated code to hook up the Startup and Shutdown event handlers.
Next, the ribbon in the project, TimerRibbon, is laid out, as shown in Figure 40-14.
This ribbon contains a RibbonButton, a RibbonSeparator, a RibbonCheckBox, and a DialogBoxLauncher. The button uses the large display style, and has an Officelmageld of StartAfterPrevious which displays the clock face shown in Figure 40-13. (These images are not visible at design time.) The ribbon uses the TabHome tab type, which causes its contents to be appended to the Home tab.
The ribbon has three event handlers, each of which calls on one of the utility methods in ThisAddln described earlier:
The ribbon also includes its own utility method, SetPauseStatus(), which as you saw earlier is called by code in ThisAddln to select or clear the check box:
The other component in this solution is the TimerDisplayPane user control that is used in the task pane. The layout of this control is shown in Figure 40-15.
This control includes a button, a label, and a list box – not the most exciting of displays, although would be simple enough to replace it with, for example, a prettier WPF control.
The code for the control keeps a local reference to the document timers, which is set in the constructor:
The button event handler calls the RefreshDisplay() method to refresh the timer display:
The RefreshDisplay() method is also called from ThisAddln, as you ‘saw earlier. It is a surprisingly complicated method considering what it does. It also checks the list of monitored documents against the list of loaded documents and corrects any problems. This sort of code is often necessary in VSTO applications, as the interface with the COM Office object model occasionally doesn’t work quite as it
should. The rule of thumb here is to code defensively,
The method starts by clearing the current list of timers in the timerList list box:
Next, the monitors are checked. The method iterates through each document inthe Globals .ThisAddln Application Documents collection and determines if the document is monitored, unmonitored, or monitored but has had a name change since the last refresh.
Finding monitored documents simply involves checking the document name against the document names in the documentEdit Times collection of keys:
If the names don’t match, then the document references are compared, which enables you to detect name changes to documents, as shown in the following code:
For unmonitored documents, a new monitor is created:
Whereas documents with name changes are re-associated with the monitor used for the old named document:
After reconciling the document edit timers, a list is generated. This code also detects whether referenced documents are still loaded, and pauses the timer for documents that aren’t by setting the Is Acti ve property to false. Again, this is defensive programming.
For each monitor, a list item is added to the list box that includes the document name and its total edit time:
This completes the code in this example. This example has shown you how to use ribbon and task pane controls and how to maintain task panes in multiple Word documents. It has also illustrated many of the techniques covered earlier in the chapter.