Professional Excel Development [Electronic resources] : The Definitive Guide to Developing Applications Using Microsoft® Excel and VBA® نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Professional Excel Development [Electronic resources] : The Definitive Guide to Developing Applications Using Microsoft® Excel and VBA® - نسخه متنی

Stephen Bullen, Rob Bovey, John Green

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید











Managed Workbooks


Concept


Managed workbooks are at the core of the VSTO design. The basic principle is to completely separate code from dataa principle that has been stressed many times in this book. In VBA, our code is always embedded within a workbook and we achieve code/data separation by using multiple workbooksone for the code and one for the data. In a VSTO solution, our VB.NET or C# code is compiled into a .NET assembly and the workbook is linked to the code by the two custom document properties _AssemblyLocation0 and _AssemblyName0, giving the directory and filename of the assembly respectively.

To deploy the application, the assembly is copied to a network share (with the appropriate security permissions setsee later) and the _AssemblyLocation0 property is updated with the URL of the share, in \\server\share form. The workbook is then distributed to the end users. When they open it, Excel checks the custom properties, downloads the assembly, checks the user's security settings and runs it (via the .NET runtime) if the security settings are configured to allow it to run.

This concept brings a number of benefits:

We can update everyone's code by copying a new version of the assembly to the network sharewe no longer need to track who the document has been sent to in order to distribute updates.

As the assembly is always downloaded before being run, it doesn't matter whether someone has the workbook open when we update the assemblythey'll automatically start using the new assembly the next time they open the workbook.

We never have to distribute the source code, so there is better protection for our intellectual property.

If each user has his .NET security settings configured to allow VSTO solutions to run only if they have come from a specific URL, opening managed workbooks from other (untrusted) sources will not run the code, thereby preventing viruses and improving security.


Unfortunately, it also introduces a few issues:

It's much harder to have different groups of people running different versions of the application, such as during a phased rollout or region-specific updateswe would have to distribute new versions of the document that point to different assemblies, negating the benefit of the "automatic" updates.

Everyone must be able to access the network share in order to download and run the assembly, which makes it much harder to use the document outside the corporate network, such as taking it home to work on, or sharing it with partner companies.

Every computer has to have its .NET security permissions set to allow VSTO solutions to run from the network share. In a corporate environment, this could be administered centrally by including the configuration with a login script. A small company or home user would have to configure the settings manually, requiring detailed knowledge of the .NET and VSTO security model (see later).


A Hello World Managed Workbook


Let's start by creating a simple Hello World VSTO solution. Fire up Visual Studio.NET 2003, select New Project and choose a new Excel workbook, as shown in Figure 22-1.

Figure 22-1. The Visual Studio.NET NewProject Dialog

After clicking OK through the dialogs, we end up with a VSTO OfficeCodeBehind class template, including a collapsed region called Generated initialization code and some stubs for the Workbook_Open and Workbook_BeforeClose events. Adding a MsgBox call to each stub gives us our Hello World VSTO solution, shown in Listing 22-1, where the code we've added has been highlighted and the collapsed section expanded to show its contents.

Listing 22-1. VSTO Solution



Imports System.Windows.Forms
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Imports MSForms = Microsoft.Vbe.Interop.Forms
' Office integration attribute. Identifies the startup
' class for the workbook. Do not modify.
<Assembly: System.ComponentModel.DescriptionAttribute( _
"OfficeStartupClass, Version=1.0, " & _
"Class=ExcelProject1.OfficeCodeBehind")>
Public Class OfficeCodeBehind
Friend WithEvents ThisWorkbook As Excel.Workbook
Friend WithEvents ThisApplication As Excel.Application
#Region "Generated initialization code"
' Default constructor.
Public Sub New()
End Sub
' Required procedure. Do not modify.
Public Sub _Startup(ByVal application As Object, _
ByVal workbook As Object)
ThisApplication = CType(application, Excel.Application)
ThisWorkbook = CType(workbook, Excel.Workbook)
End Sub
' Required procedure. Do not modify.
Public Sub _Shutdown()
ThisApplication = Nothing
ThisWorkbook = Nothing
End Sub
' Returns the control with the specified name on
' ThisWorkbook's active worksheet.
Overloads Function FindControl(ByVal name As String) _
As Object
Return FindControl(name, CType(ThisWorkbook.ActiveSheet, _
Excel.Worksheet))
End Function
' Returns the control with the specified name on the _
' specified worksheet.
Overloads Function FindControl(ByVal name As String, _
ByVal sheet As Excel.Worksheet) As Object
Dim theObject As Excel.OLEObject
Try
theObject = CType(sheet.OLEObjects(name), _
Excel.OLEObject)
Return theObject.Object
Catch Ex As Exception
' Returns Nothing if the control is not found.
End Try
Return Nothing
End Function
#End Region
' Called when the workbook is opened.
Private Sub ThisWorkbook_Open() Handles ThisWorkbook.Open
MsgBox("Hello World")
End Sub
' Called before the workbook is closed. Note that this
' method might be called multiple times and the value
' assigned to Cancel might be ignored if other code or
' the user intervenes. Cancel is False when the event
' occurs. If the event procedure sets this to True, the
' document does not close when the procedure is finished.
Private Sub ThisWorkbook_BeforeClose( _
ByRef Cancel As Boolean) _
Handles ThisWorkbook.BeforeClose
MsgBox("Goodbye World")
Cancel = False
End Sub
End Class

When you press F5 to run the project, Visual Studio builds the assembly and starts Excel, passing in the workbook to open. Excel opens the workbook, checks the _AssemblyLocation0 and _AssemblyName0 custom properties, loads the assembly, reads the OfficeStartupClass assembly attribute to find the class to start and calls the _Startup procedure in that class. Excel passes in a reference to itself and a reference to the workbook it opened, which the _Startup procedure assigns to two module-level WithEvent variables, ThisApplication and ThisWorkbook. That gives us both application-level and workbook-level events by default. Excel then raises the workbook's Open event, which we handle in the ThisWorkbook_Open procedure to show our "Hello World" message.

The Default VSTO Template


The default Excel workbook VSTO class shown in Listing 22-1 includes procedure and variable names that have been chosen to mimic those found in Excelsuch as ThisWorkbookto make it a little easier for those with some experience of VBA to get started. Note that these are nothing more than normal variable and procedure names and do not have the intrinsic meaning they do in VBA.

For some reason, the template also includes two versions of a FindControl function, used to locate MSForms controls on worksheets. Presumably, this is because the designers thought most VSTO solutions would include MSForms controls in the worksheet, which would need to have event hooks configured.

In practice, the default template really doesn't work well for people used to working in VBA. We're used to having global objects such as Application and ThisWorkbook and being able to refer to worksheets by the code name. It also mixes up the procedures required for the communication with Excel, the event handling code for the workbook and some standard functions. Our best practice recommendation would be to separate out these functional areas into their own modules.

The ProExcel VSTO Template


When we create VSTO projects, the first thing we do is remove the default ThisWorkbook module and use our own template instead. The template files are located on the CD in the \Concepts\Ch22Using VB.NET and the Visual Studio Tools for Office\ProExcelTemplate folder and comprise the following:

VSTOHooks.vb contains the procedures that Excel calls to start up and shut down the VSTO project.

CExcelApp.vb contains procedures to set up and handle the Excel Application's events

CThisWorkbook.vb contains procedures to set up and handle the workbook's events.

CSheet1.vb, CSheet2.vb and CSheet3.vb contain procedures to set up and handle the worksheet events for each worksheet in a default three-sheet workbook.

MGlobals contains global variable definitions to refer to the application class, the workbook class and each of the three sheet classes.

MStandardCode contains standard functions to identify a worksheet from its CodeName property and the FindControl function to find an ActiveX control on a worksheet.


MGlobals


Listing 22-2 shows the code contained in the MGlobals.vb file, which just defines some global variables to refer to the Excel Application object, the workbook event handler class and the worksheet event handler classes.

Listing 22-2. The MGlobals Code



Option Explicit On
Module MGlobals
'The Excel Application event-handler class
Friend ExcelApp As CExcelApp
'The workbook event-handler class
Friend ThisWorkbook As CThisWorkbook
'The worksheet event-handler classes
Friend Sheet1 As CSheet1
Friend Sheet2 As CSheet2
Friend Sheet3 As CSheet3
End Module

VSTOHooks


Listing 22-3 shows the code contained in the VSTOHooks.vb file.

Listing 22-3. The VSTOHooks Code



Option Explicit On
'Define aliases for commonly-used libraries
Imports Excel = Microsoft.Office.Interop.Excel
Imports Forms = System.Windows.Forms
' Office integration attribute. Identifies the startup class
' for the workbook. Do not modify.
<Assembly: System.ComponentModel.DescriptionAttribute( _
"OfficeStartupClass, Version=1.0, " & _
"Class=" & VSTOHooks.msAssemblyName & ".VSTOHooks")>
Public Class VSTOHooks
'TODO: Change this to be the name of the Assembly
Friend Const msAssemblyName As String = "ProExcelTemplate"
' Default constructor. Do not remove
Public Sub New()
End Sub
' Required procedure. Do not remove.
' Called by Excel when it loads the workbook.
' Used to check the environment and set up event hooks.
' DO NOT DO ANYTHING WITH THE EXCEL OBJECT MODEL IN HERE!
Public Sub _Startup(ByVal application As Object, _
ByVal workbook As Object)
'Initialise global variables to refer to classes that
'handle the events for the Excel application class...
ExcelApp = New CExcelApp(CType(application, _
Excel.Application))
'... and the workbook that this assembly is linked to
ThisWorkbook = New CThisWorkbook(CType(workbook, _
Excel.Workbook))
End Sub
' Required procedure. Do not remove.
' Called when Excel closes the workbook
'(after any prompts/confirmation etc.)
Public Sub _Shutdown()
'Tell the CThisWorkbook class to shut down
ThisWorkbook.ShutDown
'Tear down the global variables
ExcelApp = Nothing
ThisWorkbook = Nothing
Sheet1 = Nothing
Sheet2 = Nothing
Sheet3 = Nothing
End Sub
End Class

The line that starts <Assembly: tells Excel which class in the assembly contains the _Startup and _Shutdown procedures for it to call. Modifying this line is likely to stop Excel being able to load and start the assembly. It uses the constant msAssemblyName to identify the class; the definition of that constant must be changed to match the name of the assembly created by the VSTO New Project Wizard.

The _Startup procedure is called by Excel when the workbook and assembly are first loaded. It is used to set up event handlers for the application and workbook events, with the worksheet events being set up within the Workbook_Open event in the CThisWorkbook class (shown later). If Excel is started from the command line with a VSTO workbook to open, the assembly's _Startup procedure is called before Excel fully initializes its object model. If you add anything to the _Startup procedure that uses the object model, Excel may become unpredictable. We recommend only using the _Startup procedure to set up the plumbing for the application and workbook event hooks and perhaps including .NET-only code. All other initialization tasks should be done within the Workbook_Open event, which occurs after Excel has loaded the object model.

After checking it's okay to start the code, we create new instances of each of our event handling classes, passing in the Excel Application or Workbook. In .NET, every class has a Sub New() procedure, which can be modified to include extra parameters used in initializing the class.

The _Shutdown procedure is called immediately prior to Excel unloading the workbook. Note that this occurs after the user has had an opportunity to cancel the close, so can be safely used for cleanup routines, such as tearing down command bars and so forth. To improve encapsulation, we've included a public ShutDown procedure in the CThisWorkbook class (shown later), where we can place our cleanup code, rather than include it here.

CExcelApp


Listing 22-4 shows the code contained in the CExcelApp class:

Listing 22-4. The CExcelApp Code



'Class to handle events for the Excel Application object
Option Explicit On
'Define aliases for commonly-used libraries
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Public Class CExcelApp
Friend WithEvents Application As Excel.Application
'Called when we create an instance of the class
Public Sub New(ByVal appExcel As Excel.Application)
Application = appExcel
End Sub
End Class

The CExcelApp class contains the code required to set up the plumbing to handle application events. We first declare a variable to handle the application-level events, then use the Sub New() procedure to set it to the application object Excel gives us in the VSTOHooks _Startup procedure. We can then add procedures to handle any of the Excel application events by selecting from the object and event dropdowns in the normal way, as shown in Figure 22-2.

Figure 22-2. The Object and Event Drop-Downs, Used to Add New Application Event Procedures

[View full size image]

Note that the ExcelApp global variable holds a reference to the instance of this class, not to the Excel Application object itself. The Excel Application object is exposed by declaring the Application variable as Friend, which makes it appear as a property of the class. All our other classes and modules can access the Excel Application object using ExcelApp.Application. By using this mechanism, the ExcelApp variable will also expose any properties we add to the class, thereby enabling us to encapsulate the Application object, its event handling and any custom properties we might add to control those events. The same mechanism is used for the workbook and worksheet classes below, so we can use ThisWorkbook.Workbook to refer to the Excel workbook we're linked to and Sheet1.Worksheet to refer to the worksheet handled by the Sheet1 variable.

CThisWorkbook


The CThisWorkbook class shown in Listing 22-5 contains code very similar to the CExcelApp class, with the only difference being the variable names and object types. The Workbook_Open event should be used for any startup checks, command bar configuration and so on.

Listing 22-5. The CThisWorkbook Code



'Class to handle events for the Workbook
Option Explicit On
'Define aliases for commonly-used libraries
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Public Class CThisWorkbook
Friend WithEvents Workbook As Excel.Workbook
'Called when we create an instance of the class
Public Sub New(ByVal wkbWorkbook As Excel.Workbook)
Workbook = wkbWorkbook
End Sub
'The standard Workbook_Open event, used to set up the
'worksheet event-handler classes, command bars etc.
Private Sub Workbook_Open() Handles Workbook.Open
'Make sure we can read the code names in the VBProject,
'so we can identify each worksheet accurately.
Try
Dim bSaved As Boolean = workbook.VBProject.Saved
Catch
MsgBox("Access to the Visual Basic Project must " & _
"be trusted for this workbook." & vbLf & _
"Please tick the 'Trust access to Visual " & _
"Basic Project' box in the " & vbLf & _
"Tools > Macro > Security dialog, then " & _
"close and reopen this workbook.")
Exit Sub
End Try
'Create event handlers for each sheet in the workbook.
Sheet1 = New CSheet1(FindWorksheetByCodeName("Sheet1"))
Sheet2 = New CSheet2(FindWorksheetByCodeName("Sheet2"))
Sheet3 = New CSheet3(FindWorksheetByCodeName("Sheet3"))
End Sub
'Called by the _Shutdown procedure in VSTOHooks
'Used to destroy commandbars etc, akin to an
'After_Close event
Public Sub ShutDown()
End Sub
End Class

Within the Workbook_Open event, we set up the worksheet event handler classes, using the FindWorksheetByCodeName function (from the MStandardCode module) to identify a worksheet based on its VBA code name. Fortunately, the code name is still available to us, as long as we have access to the VBProject and force it to be initialized. Checking the Workbook.VBProject.Saved property both forces Excel to initialize the VBProject and tests whether we have access to it. If the test causes an error, we ask the user to allow access to the VBProject and reopen the workbook. Note that trying to do this code within the Startup processing (as opposed to Workbook_Open) corrupts the VBProject! Most VSTO solutions would also include code in the Workbook_Open procedure to set up command bars and so forth.

The ShutDown procedure is called from the _Shutdown procedure in the VSTOHooks class when Excel shuts down the workbook and is a good place to clean up command bars and so forth because it occurs after the user has had a chance to cancel the close.

CSheet1


The template code modules for each of the worksheets follow the same structure as the CExcelApp and CThisWorkbook classes, so won't be repeated here. When setting up the VSTO project, you will need to add a worksheet class for each of the worksheets in your solution, adding global variables to refer to them and adding code to Workbook_Open to initialize them. If the worksheet has any ActiveX controls (including controls from the Control Toolbox), the Sub New() procedure should be used to set up their event hooks, as shown in Listing 22-6, for a worksheet that contains a command button btnShow.

Listing 22-6. A Worksheet Class with a CommandButton Event Handler



'Class to handle events for a worksheet
Option Explicit On
'Define aliases for commonly-used libraries
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Imports MSForms = Microsoft.Vbe.Interop.Forms
Public Class CSheet1
Friend WithEvents Worksheet As Excel.Worksheet
'An event handler for the button
Dim WithEvents btnShow As MSForms.CommandButton
'Called when we create an instance of the class
'Use this procedure to set up event hooks for any
'controls on the sheet
Public Sub New(ByVal wks As Excel.Worksheet)
Worksheet = wks
'Hook up the button's event handler
btnShow = FindControl("btnShow", Worksheet)
End Sub
'The Click event for the button
Private Sub btnShow_Click() Handles btnShow.Click
MsgBox("Clicked me!")
End Sub
End Class

MStandardCode


The MStandardCode module shown in Listing 22-7 contains two simple functions to locate a control on a worksheet (so we can set up event hooks for it) and to locate a worksheet in the workbook, from its VBA code name.

Listing 22-7. The MStandardCode Module



'Module containing standard procedures, copied between projects
Option Explicit On
'Define aliases for commonly-used libraries
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Module MStandardCode
' Returns the control with the specified name on the
' specified worksheet.
Function FindControl(ByVal name As String, _
ByVal sheet As Excel.Worksheet) As Object
Dim theObject As Excel.OLEObject
Try
theObject = CType(sheet.OLEObjects(name), _
Excel.OLEObject)
Return theObject.Object
Catch Ex As Exception
' Returns Nothing if the control is not found.
End Try
Return Nothing
End Function
'Identify a worksheet in the workbook, by matching the
'CodeName
Function FindWorksheetByCodeName( _
ByVal sCodeName As String) As Excel.Worksheet
Dim wksSheet As Excel.Worksheet
'Find the sheet with the matching codename
For Each wksSheet In ThisWorkbook.Workbook.Worksheets
If wksSheet.CodeName = sCodeName Then
Return wksSheet
End If
Next
End Function
End Module

Sharing Command Bars


Like VB6 COM Add-ins, VSTO solutions use event hooks to trap the Click event of command bar buttons and the Change event of command bar combo boxes. Instead of repeating here how command bar events are handled, you should review the relevant section of Chapter 8 Advanced Command Bar Handling, but to summarize it we need to do the following:

Give all our menu items the same Tag property, to uniquely identify them as belonging to our add-in.

Give each menu item a unique Parameter property, to identify them in code.

Have a class module containing a With Events declaration for a CommandBarButton (and/or CommandBarCombobox).

In the CommandBarButton_Click event procedure, confirm that the Tag is set to ours then call the procedure appropriate to the Parameter value.

When setting up our menus, create a new instance of the class for each combination of ID and Tag that we use. If we're not using any built-in menu items, we would only need a single instance of the class.


We show some examples of this later in this section.

The basic operation of a VSTO workbook involves Excel loading and running the linked VSTO assembly, which sets up event hooks for application, workbook, worksheet and control events and responds to the events being raised. The principle is that each VSTO assembly is self-contained, works only with the workbook it's linked to and doesn't interact with any other VSTO workbooks that might be open. How, then, should we handle the situation of a workbook having some custom menu items and the user having multiple instances of the workbook open? The easiest solution is to design our VSTO workbook to neatly collaborate with any other instances that might be open.

When starting up, we should first test for the existence of our menu items and only add them if they don't already exist (because they might have been added by another copy of the workbook). Either way, we set up event hooks for them. This means we will have one set of menu items shared by all open copies of the workbook, which each have their own instance of the VSTO assembly. When a menu item is clicked, the Click event is raised in all the assemblies, so within the Click event handler, we should only respond if that assembly's workbook is the active workbook. When closing down, we should see whether there are any other workbooks open which use the shared menu items. The easiest way to do this is to check whether there are any other workbooks open that link to the same VSTO assembly. If we find one, we leave the menus for it to use; if we don't find one, we delete the menus. Example code for collaborative menu sharing between VSTO projects is shown in Listing 22-8 and can be found on the CD in the \Concepts\Ch22Using VB.NET and the Visual Studio Tools for Office/SharedMenus folder.

Listing 22-8. Collaborative Use of Command Bars



'
' In Module MGlobals
'
'Command bar constants
Public Const gsMENU_NAME As String = "Shared Menus"
Public Const gsMENU_TAG As String = "tgSharedMenus"
Public Const gsMENU_TOGGLE As String = "ToggleCase"
'Command bar event handler
Public gclsControlEvents As CControlEvents
'
' In Class CThisWorkbook
'
'The standard Workbook_Open event, used to set up the
'worksheet event-handler classes, command bars etc.
Private Sub Workbook_Open() Handles Workbook.Open
'Check for and create the menus
SetUpMenus()
'Set up the command bar button hooks
gclsControlEvents = New CControlEvents
End Sub
'Restore the menus when shutting down
Public Sub ShutDown()
RestoreMenus()
End Sub
'
' In Module MCommandBars
'
'Check for and set up our menus
Public Sub SetUpMenus()
Dim cbBar As Office.CommandBar
Dim btnCaps As Office.CommandBarButton
Try
'Does the commandbar exist?
cbBar = ExcelApp.Application.CommandBars(gsMENU_NAME)
Catch ex As Exception
'No, so create it...
cbBar = ExcelApp.Application.CommandBars.Add( _
gsMENU_NAME, temporary:=True)
cbBar.Visible = True
'... And add our button to it
btnCaps = cbBar.Controls.Add( _
Office.MsoControlType.msoControlButton, _
temporary:=True)
With btnCaps
.Parameter = gsMENU_TOGGLE
.Tag = gsMENU_TAG
.Caption = "Toggle Case"
.Style = Office.MsoButtonStyle.msoButtonCaption
End With
End Try
End Sub
'Tidily clean up
Public Sub RestoreMenus()
Dim sThisAssembly As String
Dim wkbWorkbook As Excel.Workbook
'Get which assembly we link to
sThisAssembly = AssemblyLocation(ThisWorkbook.Workbook)
'Check if any other workbooks link to the same assembly
'we do and quit the routine if we find one
For Each wkbWorkbook In ExcelApp.Application.Workbooks
If Not wkbWorkbook Is ThisWorkbook.Workbook Then
If AssemblyLocation(wkbWorkbook) = sThisAssembly Then
Exit Sub
End If
End If
Next
'No others, so delete the commandbar
Try
ExcelApp.Application.CommandBars(gsMENU_NAME).Delete()
Catch ex As Exception
End Try
End Sub
'
' In Module MStandardCode
'
'Read the location of a linked VSTO assembly from a
'workbook's custom document properties
Function AssemblyLocation( _
ByVal wkbBook As Excel.Workbook) As String
Dim iProp As Integer
Dim sPath As String
Dim sName As String
'Read the assembly location and name, allowing for long
'entries to spill into multiple properties
For iProp = 0 To 3
Try
sPath = sPath & _
CType(wkbWorkbook.CustomDocumentProperties( _
"_AssemblyLocation" & iProp), _
Office.DocumentProperty).Value
Catch ex As Exception
End Try
Try
sName = sName & _
CType(wkbWorkbook.CustomDocumentProperties( _
"_AssemblyName" & iProp), _
Office.DocumentProperty).Value
Catch ex As Exception
End Try
Next
Return sPath & "\" & sName
End Function
'Class CControlEvents
'Class to handle command bar button events
Option Explicit On
'Define aliases for commonly-used libraries
Imports Office = Microsoft.Office.Core
Imports Excel = Microsoft.Office.Interop.Excel
Public Class CControlEvents
'Variable to hook the buttons' events
Dim WithEvents btnButton As Office.CommandBarButton
'Hook the events for our buttons
Public Sub New()
btnButton = ExcelApp.Application.CommandBars _
.FindControl(tag:=gsMENU_TAG)
End Sub
'Handle the Click event
Private Sub btnButton_Click( _
ByVal Ctrl As Office.CommandBarButton, _
ByRef CancelDefault As Boolean) _
Handles btnButton.Click
'Only process this event if it's our tag
If Ctrl.Tag = gsMENU_TAG Then
'Only process this event if our workbook is active
If ExcelApp.Application.ActiveWorkbook Is _
ThisWorkbook.Workbook Then
'What to do?
Select Case Ctrl.Parameter
Case gsMENU_TOGGLE
Try
'Toggle the case of the active cell
With ExcelApp.Application.ActiveCell
If .Value = UCase$(.Value) Then
.Value = LCase$(.Value)
Else
.Value = UCase$(.Value)
End If
End With
Catch ex As Exception
End Try
End Select
End If
End If
End Sub
End Class


/ 225