In our practical example for this chapter, we add an application-level event handling class to our PETRAS timesheet application that will make two significant changes. First, it will enable us to convert the time-entry workbook into an Excel template. This will simplify creation of new time-entry workbooks for new purposes as well as allow multiple time entry workbooks to be open at the same time. Second, the event handler will automatically detect whether a time entry workbook is active and enable or disable our toolbar buttons accordingly. Chapter 7
Module
Procedure
Change
PetrasTemplate.xlt
Changed the normal workbook into a template workbook
CAppEventHandler
Added an application-level event handling class to the add-in
MEntryPoints
NewTimeSheet
New procedure to create timesheets from the template workbook
MOpenClose
Auto_Open
Removed timesheet initialization logic and delegated it to the event handling class
MSystemCode
Moved all time-entry workbook management code into the event handling class
A template workbook reacts differently than a normal workbook when opened using the Excel Workbooks.Open method. A normal workbook will simply be opened. When a template workbook is opened a new, unsaved copy of the template workbook will be created. To create a template workbook from a normal workbook, choose File > Save As from the Excel menu and select the Template entry from the Save as type drop-down. As soon as you select the Template option, Excel will unhelpfully modify the directory where you are saving your workbook to the Office Templates directory, so don't forget to change this to the location where you are storing your application files.
After we begin using a template workbook, the user has complete control over the workbook filename. We will determine whether a given workbook belongs to us by checking for the unique named constant setIsTimeSheet that we have added to our template workbook for this purpose.
A template workbook combined with an application-level event handler enables us to support multiple instances of the time entry workbook being open simultaneously. This might be needed, for example, if there is a requirement to have a separate time sheet for each client or project.
Moving to a template user interface workbook also requires that we give the user a way to create new time sheet workbooks, because it is no longer a simple matter of opening and reusing the same fixed timesheet workbook over and over. In Figure 7-2, note the new toolbar button labeled New Time Sheet. This button enables the user to create new instances of our template.
As shown in Listing 7-21, the code run by this new button is very simple.
Public Sub NewTimeSheet() Application.ScreenUpdating = False InitGlobals Application.Workbooks.Add gsAppDir & gsFILE_TIME_ENTRY Application.ScreenUpdating = True End Sub
We turn off screen updating and call InitGlobals to ensure that our global variables are properly initialized. We then simply open the template workbook and turn screen updating back on. When you open a template workbook from VBA, it is treated differently than a normal workbook. Rather than opening PetrasTemplate.xlt, a new copy of PetrasTemplate.xlt, called PetrasTemplate1, is created. Each time the user clicks the New Time Sheet button, he gets a completely new, independent copy of PetrasTemplate.xlt.
The act of opening the template triggers the NewWorkbook event in our event handing class. This event performs all the necessary actions required to initialize the template. This event procedure is shown in the next section.
Within our application-level event handling class, we encapsulate many of the tasks that were previously accomplished by procedures in standard modules. For example, the MakeWorksheetSettings procedure and the bIsTimeEntryBookActive function that we encountered in Chapter 5 Function, General and Application-Specific Add-ins are now both private procedures of the class. We will describe the layout of the class module in Listing 7-22, then explain what the pieces do, instead of showing all of the code here. You can examine the code yourself in the PetrasAddin.xla workbook of the sample application for this chapter on the CD, and are strongly encouraged to do so.
Module-Level Variables
Private WithEvents mxlApp As Excel.Application
Class Event Procedures
Class_Initialize Class_Terminate mxlApp_NewWorkbook mxlApp_WorkbookOpen mxlApp_WindowActivate mxlApp_WindowDeactivate
Class Method Procedures
SetInitialStatus
Class Private Procedures
EnableDisableToolbar MakeWorksheetSettings bIsTimeEntryBookActive bIsTimeEntryWorkbook
Because the variable that holds a reference to the instance of the CAppEventHandler class that we use in our application is a public variable, we use the InitGlobals procedure to manage it. The code required to do this is shown below.
In the declarations section of the MGlobals module:
Public gclsEventHandler As CAppEventHandler
In the InitGlobals procedure:
' Instantiate the Application event handler If gclsEventHandler Is Nothing Then Set gclsEventHandler = New CAppEventHandler End If
The InitGlobals code checks to see whether the public gclsEventHandler variable is initialized and initializes it if it isn't. InitGlobals is called at the beginning of every nontrivial entry-point procedure in our application, so if anything causes our class variable to lose state, it will be instantiated again as soon as the next entry-point procedure is called. This is a good safety mechanism.
When the public gclsEventHandler variable is initialized, it causes the Class_Initialize event procedure to execute. Inside this event procedure, we initialize the event handling mechanism by setting the class module-level WithEvents variable to refer to the current instance of the Excel Application, as follows:
Set mxlApp = Excel.Application
Similarly, when our application is exiting and we destroy our gclsEventHandler variable, it causes the Class_Terminate event procedure to execute. Within this event procedure we destroy the class reference to the Excel Application object by setting the mxlApp variable to Nothing.
All the rest of the class event procedures, which are those belonging to the mxlApp WithEvents variable, serve the same purpose. They "watch" the Excel environment and enable or disable our toolbar buttons as appropriate when conditions change.
Disabling toolbar buttons when they can't be used is a much better user interface technique than displaying an error message when the user clicks one in the wrong circumstances. You don't want to punish the user (that is, display an error message in response to an action) when he can't be expected to know he has done something wrong. Note that we always leave the New Time Sheet and Exit PETRAS toolbar buttons enabled. The user should always be able to create a new timesheet or exit the application.
In addition to enabling and disabling the toolbar buttons, the mxlApp_NewWorkbook and mxlApp_WorkbookOpen event procedures detect when a time entry workbook is being created or opened for the first time, respectively. At this point they run the private MakeWorksheetSettings procedure to initialize that time entry workbook. All of the mxlApp event procedures are shown in Listing 7-23. As you can see, the individual procedures are very simple, but the cumulative effect is very powerful.
Private Sub mxlApp_NewWorkbook(ByVal Wb As Workbook) If bIsTimeEntryWorkbook(Wb) Then EnableDisableToolbar True MakeWorksheetSettings Wb Else EnableDisableToolbar False End If End Sub Private Sub mxlApp_WorkbookOpen(ByVal Wb As Excel.Workbook) If bIsTimeEntryWorkbook(Wb) Then EnableDisableToolbar True MakeWorksheetSettings Wb Else EnableDisableToolbar False End If End Sub Private Sub mxlApp_WindowActivate(ByVal Wb As Workbook, _ ByVal Wn As Window) ' When a window is activated, check to see if it belongs ' to one of our workbooks. Enable all our toolbar controls ' if it does. EnableDisableToolbar bIsTimeEntryBookActive() End Sub Private Sub mxlApp_WindowDeactivate(ByVal Wb As Workbook, _ ByVal Wn As Window) ' When a window is deactivated, disable our toolbar ' controls by default. They will be re-enables by the ' WindowActivate event procedure if required. EnableDisableToolbar False End Sub
The full power of having an event handling class in your application is difficult to convey on paper. We urge you to experiment with the sample application for this chapter to see for yourself how it works in a live setting. Double-click the PetrasAddin.xla file to open Excel and see how the application toolbar behaves. Create new timesheet workbooks, open non-timesheet workbooks and switch back and forth between them. The state of the toolbar will follow your every action.
It is also educational to see exactly how much preparation the application does when you create a new instance of the timesheet workbook. Without the PetrasAddin.xla running, open the PetrasTemplate.xlt workbook and compare how it looks and behaves in its raw state with the way it looks and behaves as an instance of the timesheet within the running application.
The PETRAS reporting application has been modified in much the same way, and for the same reasons, as the PETRAS timesheet add-in. By adding a class module to handle application-level events, we can enable the user to have multiple consolidation workbooks open at the same time and switch between them using the new Window menu, as shown in Figure 7-3.
Module
Procedure
Change
CAppEventHandler
Added an application-level event handling class to the application to manage multiple consolidation workbooks.
MCommandBars
SetUpMenus
Added code to create the Window menu.
MSystemCode
Added procedures to add, remove and place a checkmark against an item in the Window menu.
MEntryPoints
MenuWindowSelect
New procedure to handle selecting an item within the Window menu. All Window menu items call this routine.