VBA: Global Variable Cleared after 'Workbook_Open' sub - vba

I have set up an application level event class to monitor when new workbooks are created/ opened by following CPearson's guide. This works fine in isolation. However, it is intended as part of add-in I'm writing where several other subs are also called in the 'Workbook_Open' sub, see below code:
Private XLApp As baseCXlEvents
Private Sub Workbook_Open()
Set XLApp = New baseCXlEvents
test
AddLocalReferences
AddModules
AddClassModules
Debug.Print "testing"
End Sub
So the XLApp variable is called in the module scope as a baseCXlEvents class. I have added a Class_Terminate event to this class and this is triggered after the Debug.print "testing" is run, i.e. XLApp is terminated after the Workbook_Open sub has run. This does not happen when I quote out the subs AddLocalReferences, AddModules, and AddClassModules, which do exactly as their names would imply. The sub test only prints a messages in debug to test whether calling additional subs caused XLApp to be terminated.
My current 'hunch' is that adding references, modules, or class modules counts as "editing", causing it to be terminated, as explained in this MS Support document. But, if so, why doesn't XLApp get terminated until the end of the sub? As opposed to as soon as AddLocalReferences is run.
Any suggestions why the class is terminated? I need it to 'stay alive' and also need to load additional modules and references for the addin upon workbook_open. If needed more details of this code can be provided.
I've decided to add my baseCXlEvents class module's code:
Option Explicit
Private WithEvents App As Application
Private Sub App_NewWorkbook(ByVal Wb As Workbook)
MsgBox "New Workbook: " & Wb.Name
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
MsgBox "Workbook opened: " & Wb.Name
End Sub
Private Sub Class_Initialize()
Debug.Print "Initializing baseCXlEvents instance.."
Set App = Application
End Sub
Private Sub Class_Terminate()
Debug.Print "Terminating baseCXlEvents instance.."
End Sub

Well then try to separate the Workbook_Open event handler stuff (where you add references etc.) from the creation of the instance of the class baseCXlEvents using Auto_Open.
Workbook_Open runs first, Auto_Open runs then.
Note: baseCXlEvents Instancing must be set to Public.
ThisWorkbook class
Public XLApp As baseCXlEvents
Private Sub Workbook_Open()
Test
AddLocalReferences
AddModules
AddClassModules
End Sub
Standard module
Sub Auto_Open()
Set ThisWorkbook.XLApp = New baseCXlEvents
End Sub

Related

Call a macro from ThisOutlookSession

I have a problem with my macros on outlook.
I am currently trying via a batch to call outlook and pass it as a parameter the name of a macro that I get via an environment variable I've set in my batch. However I do get the name of my macro, but the process stops at the time of the Call function. Could someone tell me the right way to proceed?
VBA ThisOutlookSession
Private Sub Application_Startup()
Dim strMacroName As String
strMacroName = CreateObject("WScript.Shell").Environment("process").Item("MacroName")
'MsgBox strMacroName
'MsgBox VarType(strMacroName)
If strMacroName <> "" Then Call strMacroName
End Sub
VBA Modules
Option Explicit
Sub macro1()
MsgBox "macro1"
End Sub
Sub macro2()
MsgBox "macro2"
End Sub
Batch
Set WorkingPath=C:\Temp\Outlook
Set MacroName=%1
start OUTLOOK.EXE
Set MacroName=
Set WorkingPath=
the result
There are several aspects here... The first point is possible security issues when dealing with the Outlook. You can read more about that in the Security Behavior of the Outlook Object Model article.
Another point is that you can call VBA macros declared in the ThisOutlookSession module in the following way (for example, from any other Office application):
Sub test()
Dim OutApp As Object
Set OutApp = CreateObject("Outlook.Application")
OutApp.Session.Logon
OutApp.HelloWorld
End Sub
Where the HelloWorld sub is declared in the ThisOutlookSession module in following way:
Option Explicit
Public Sub HelloWorld()
MsgBox "Hello world !!"
End Sub
Note, you may call any module from the ThisOutlookSession module. There is no need to get access to other modules directly.

In VBA, can a workbook object reference a method written in one of its worksheets?

Edited to provide more information.
Base Question:
Is it possible for a workbook to have access to a method written in one of its worksheets in VBA?
Real World Reason:
I need to have a method run on workbook launch (Workbook_Open() event) however, that method has a few requirements which makes modules not viable:
Needs to maintain state
Needs to store data
Needs to be referenced from both the workbook, and the worksheets in said workbook
Psuedocode example:
myWorkbook
Private Sub Workbook_Open()
Call myWorkbook.Sheet("worksheet1").helloWorld
End Sub
worksheet1
Public Sub printHelloWorld()
'Store "Hello World" so we can use later, this method needs to maintain state
Dim helloWorld As String
Set helloWorld = "Hello World"
MsgBox(helloWorld)
End Sub
Yes, this can be done. Both of the below work fine.
Sub TestWithExplicitWorksheet()
wsTest.TestMessage
End Sub
Sub TestWithWorksheetsCollection()
ThisWorkbook.Worksheets("Sheet1").TestMessage
End Sub
wsTest Code:
Sub TestMessage()
Debug.Print "Success"
End Sub
A Worksheet is more or less a Predeclared Class Module. So if you can do it with a Class, you should be able to do it with a Worksheet. Since the Worksheets collection of the Workbook object returns (if found) a Worksheet object, you can call the method on this returned object (if it's the correct object and contains the sub being called).
I would put the code below in a module
Public Sub printHelloWorld()
MsgBox("Hello World")
End Sub
and the code in myWorkBook will look like
Private Sub Workbook_Open()
printHelloWorld
End Sub
Yes, you can use the sheet CodeName (you can also change it in the sheet object properties):
Private Sub Workbook_Open()
Sheet1.printHelloWorld() ' full name is VBAProject.Sheet1.printHelloWorld()
End Sub

VBA: Running Workbook_BeforeClose from general module and not ThisWorkbook of Active Workbook

So I have a macro I wrote that I want to run on close of the workbook.
Unfortunately, the only way to apparently do this is to place the macro in the ThisWorkbook module of the actual file as opposed to having it sit in the PERSONAL.XLSB.
This is not desirable for a few reasons:
The macro would have to be put into every workbook it needs to be run on--I have hundreds.
The workbooks would need to be saved as macro enabled which, in my experience, many email servers won't accept emails with macro enabled workbooks attached.
So ideally I would like to be able to run the macro from the PERSONAL.XLSB in just a general module.
Any suggestions about how this might be possible?
EDIT:
Per instructions at:
http://www.cpearson.com/excel/AppEvent.aspx
PERSONAL.XLSB
CExcelEvents class module
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
MsgBox "New Workbook: " & Wb.Name
End Sub
PERSONAL.XLSB
ThisWorkbook
Private XLApp As CExcelEvents
Private Sub Workbook_Open()
Set XLApp = New CExcelEvents
End Sub
Doesn't work if you try to open a different workbook. If you click on PERSONAL.XLSB in recent documents it will trigger the message.
Move this into another ThisWorkbook object for a specific workbook and it still only works on that workbook:
Private XLApp As CExcelEvents
Private Sub Workbook_Open()
Set XLApp = New CExcelEvents
End Sub
So even though the class module is in PERSONAL.XLSB, it appears you still have to put the above into the workbook you want it to run on it, which I think I would still require saving it as an .XLSM and would run into email filter issues.
For some reason this did start working with everything in PERSONAL.XLSB although I didn't change anything. Exciting, but would like to know why.
However, now that I am trying to change the example to actually work how I need it with BeforeClose. So I updated to the following:
PERSONAL.XLSB
CExcelEvents class module
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
MsgBox "Closing the workbook."
End Sub
PERSONAL.XLSB
ThisWorkbook
Private XLApp As CExcelEvents
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Set XLApp = New CExcelEvents
End Sub
Back to what happened yesterday... will only trigger when you go to close PERSONAL.XLSB. One would think that since the PERSONAL.XLSB opens with all workbooks, it would trigger regardless, but it isn't. Again, saving in the ThisWorkbook object of the target workbook works, but isn't a solution due to having to save as an .XLSM and email filters.
You can look at Application Events, which allow you to hook into events at the Application level, using code which can be in your Personal.xlsb
Here's a good starting point: http://www.cpearson.com/excel/AppEvent.aspx

Detect the event ActiveWorkbook.Save from personal.xlsm in excel

I have all macros stored in personal.xlsm and untill now I have used a standard filter to hide/show columns. The new feature I want to implement now is that each user can have their own filter if they would like to. So basically i look in a folder for a personal filter if it exist and if it does it use that filter instead of the standard one.
But my problem is that i want to load a personal filter on workbookOpen event and reset to standard filter on the beforeClose event. My question is if I can do this from personal.xlsm in a way? Or do I have to manually go through all 250 workbooks and add in thisworkbook module onOpen and beforeClose events to call my method createFilter and resetFilter?
Here is a link to personal.xlsm for those who are not familiar with that methodology
In personal.xlsm, create a class module named "Helper". Place the following code in it:
Option Explicit
Public WithEvents xlApp As Application
Private Sub Class_Initialize()
Set xlApp = Application
End Sub
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
'Your code here
End Sub
Your code for loading a filter should go in the 'Your code here bit.
Add a standard code module and add this code:
Public MyHelper As Helper
Finally, in the Workbook_Open event of personal.xlsm, place this code:
Private Sub Workbook_Open()
Set MyHelper = New Helper
End Sub
Save personal.xlsm and restart Excel.
Here is the code in helper if someones needs it:
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
Dim myPath As String
On error resume next
If InStr(UCase(ActiveWorkbook.Name), "PARTSLIST") And (InStr(UCase(ActiveWorkbook.Name), "FILTERPARTSLIST") = 0) Then
myPath = Left(ThisWorkbook.Path, InStrRev(ThisWorkbook.Path, "\") - 1)
If Dir(myPath & "\filterPartsList.xlsx") <> "" Then '
Call ColumnCreater.updateFilter
Else
Call ColumnCreater.filterCreationStandard
End If
End If
End Sub

Using Events with the Application Object in MS Word

Ms Word does not want to load my add-in. I want to call a userform on print event. Here is my code:
in module 1
Option Explicit
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
'Debug.Print Now & " " & "App_DocumentBeforePrint: " & Wb.FullName
Userform1.Show
End Sub
Sub InitializeApp()
Dim X As New EventClassModule
Set X.App = Word.Application
End Sub
in Document module
Private Sub Document_Open()
Call InitializeApp
End Sub
in EventClassModule
Public WithEvents App As Word.Application
in Userform1 Mode
Option Explicit
Private Sub UserForm1_Initialize()
End Sub
I used this 2 links to help me write this code
1) https://msdn.microsoft.com/en-us/library/bb221264%28v=office.12%29.aspx
2) https://msdn.microsoft.com/en-us/library/gg597509%28v=office.14%29.aspx
Can anyone tell why my code does not work?
As explained in the first link you show, the procedure App_DocumentBeforePrint needs to be in the CLASS module (EventClassModule, in your explanation), not in Module 1.
Other than that, it's not clear what you mean by "my add-in". Usually, I'd think of a template (or COM add-in) when this term is used that's being loaded as an add-in. I'm concerned whether Document_Open is actually being triggered to initialize your events. This event, in the ThisDocument module (in reality, it's a class) will only fire when the document containing this code is opened...
For anybody else who comes across this thread like I did, this is what worked for me:
in module 1
(your module shouldn't contain the event-based sub; also, X needs to be declared as a global variable rather than within the 'InitializeApp' sub)
Option Explicit
Dim X As New EventClassModule
Sub InitializeApp()
Set X.App = Word.Application
End Sub
in Document module
Private Sub Document_Open()
Call InitializeApp
End Sub
in EventClassModule
(your Class Module should contain the event-based sub)
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
'''Your procedure here
End Sub