Calling a Sub or Function that sometimes doesn't exist - vba

I'm trying to create a macro (in PERSONAL.XLSB) that every time a workbook is opened, it checks a condition and in if it's true (this means the workbook opened contains an specific Sub), it calls this Sub.
Option Explicit
Private WithEvents App As Application
Private Sub Workbook_Open()
Set App = Application
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
If condition Then Call Specific_Sub
End Sub
It runs fine when I open a file that contains that Sub, however, if the Sub is not in the file, the compiler returns the error “Sub or Function not defined”, naturally.
I’m trying very hard to find a way to do this and deal with the error, but On error GoTo doesn’t work because the compiler error is before the run time, so it’s not executed.
I guess I have to do this in a different way but I can’t picture how to do it, any help or ideas?
Thanks a lot!

Thanks to the answers I've discovered that the best way is to use Application.Run. To keep the code as simple as possible, I just changed the last part to look like this:
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
On Error Resume Next
If condition Then
Application.Run ("'" & ActiveWorkbook.FullName & "'!" & "Specific_Sub")
End If
End Sub
Thank you all.

I cobbled this together from a few web sites. The key is that your sub routines name is in a variable and application. run uses the variable. This gets past the compiler error you are running into
Sub SubExists()
Dim ByModule As Object
Dim ByModuleName As String
Dim BySub As String
Dim ByLine As Long
'
'Module and sub names
ByModuleName = "Module1"
BySub = "Specific_Sub"
On Error Resume Next
Set ByModule = ActiveWorkbook.VBProject.vbComponents(ByModuleName).CodeModule
ByLine = ByModule.ProcStartLine(BySub, vbext_pk_Proc)
If Err.Number = 0 Then
Application.Run BySub
End If
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
SubExists
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.

Runtime 91 Variable not set - Sharing Variable between Forms and Modules

I think I'm misunderstanding something simple here, as usual, some fundamental principle I have yet to learn.
Anyway, I am trying to set up a combobox for the purpose of selecting from the active workbooks and passing the workbook name to a variable for use in future code.
However, I could only get this to work by setting said variable type as Variant, and after the code finishes executing I am still getting an error... so what's going on there then?
Private Sub cmdPopulate_Click()
If cmbWorkbooks.Value <> "" Then
selectedWorkbook = cmbWorkbooks.Value
Call populateChecklist
End If
Unload Me
End Sub
Private Sub UserForm_Initialize()
For Each wb In Workbooks
cmbWorkbooks.AddItem wb.Name
Next wb
Me.Show
End Sub
Public wb As Workbook
Public ws As Worksheet
Public selectedWorkbook As Variant
Sub populateChecklist()
MsgBox "hello " & selectedWorkbook
End Sub
Edit:
To clarify the error I receive upon finished execution is:
Run-time error '91':
Object variable or With block variable not set
Edit2:
ok I think I've figured it out...
I commented out everything by degrees until I found the culprit.
It seems to be caused by Me.Show in the userform_Initialize sub, which is apparently redundant code anyway!

Private Sub App_SheetChange Doesnt work if an error occurs in a previous macro

I have the following code to activate a macro when a change is made to cell A1
Class Module
Option Explicit
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Name = "S" Then
Dim rngKeyCells As Range
Set rngKeyCells = Sh.Range("A1")
If Intersect(rngKeyCells, Target) Is Nothing Then
Exit Sub
End If
Application.Run "a"
End If
End Sub
This_Workbook Code
Private OurEventHandler As EventHandler
Private Sub Workbook_Open()
'Initiates the data change when the filter is changed
Set OurEventHandler = New EventHandler
End Sub
This works absolutely fine usually, however an issue occurs if i try making a change in A1 after i open VBA.
It will work fine 90% of the time but if during one of the previous macro's that i run, there is an error, it won't work.
Example - I run a macro that deletes the Worksheet to the left of the active one. If there is no worksheet to the left of the active one it will error. I press end and that's fine. Now if i try to change the cells A1 and expect the macro above to run, nothing happens.
Is this the kind of thing that is solvable without showing the entire macro? Or could it be something that is inbuilt into the rest of the macro that is causing the issue?
Thanks
In the programming there is something named Design Patterns. In your case, it would be really useful to make a Singleton for the App variable.
Here is a good example of it for VBA:
How to create common/shared instance in vba
As already mentioned in the comments: When an error happens and end is pressed, the whole VBA-Context is reset, the content of global Vars is lost (so your variable OurEventHandler is nothing).
If you can't catch all errors to ensure that this reset isn't happening (and I think you never really can), maybe it is easiest to implement the event handler in the worksheet itself:
Private Sub Worksheet_Change(ByVal Target As Range)
' Ne need to check Worksheet as the Hander is valid only for the Worksheet
if target.row = 1 and target.column = 1 then
Application.Run "AIMS_and_eFEAS_Report.AIMS_Criteria"
end if
End Sub

How to Call Subs from an Add-In Directly

So it's obviously easy to write code that will call a sub/function that is in an add-in library via VBA code, simply by doing
call myFunctionOrSub
However, is there a way to allow users to directly call public subs in an add-in? For example, when the user goes to Tools -> Macros and pulls up this screen:
I would like to add to the list of macros in that box all Subs which are included in add-ins that are linked to for the file. That is, I have a library (library.xlam) that is referenced by this current workbook. In this library.xlam file, I have Subs (such as copyToResults). I want copyToResults to appear as a runnable macro in this list. Is there a way to do that?
The only solution I could come up with was to create a Sub in my test file for each Sub in library.xlam. This Sub in the test file would do nothing by call library's Sub. However, this is terrible for the purpose of having external libraries and terrible for scalability, so we definitely don't want to go this route.
Make a form in your xlam with a list box.
Use the script from this post to populate your form. You will have to change some excel settings.
Get a list of the macros of a module in excel, and then call all those macros
Here is my code from my form:
Private Sub btnCancel_Click()
Unload Me
End Sub
Private Sub btnExecute_Click()
Application.Run "macros.xlam!" & lstMacros.Value
Unload Me
End Sub
Private Sub UserForm_Initialize()
Dim pj As VBProject
Dim vbcomp As VBComponent
Dim curMacro As String, newMacro As String
Dim x As String
Dim y As String
Dim macros As String
On Error Resume Next
curMacro = ""
Documents.Add
For Each pj In Application.VBE.VBProjects
For Each vbcomp In pj.VBComponents
If Not vbcomp Is Nothing Then
If Not vbcomp.CodeModule = "Utilities" Then
For i = 1 To vbcomp.CodeModule.CountOfLines
newMacro = vbcomp.CodeModule.ProcOfLine(Line:=i, _
prockind:=vbext_pk_Proc)
If curMacro <> newMacro Then
curMacro = newMacro
If curMacro <> "" And curMacro <> "app_NewDocument" Then
frmMacros.lstMacros.AddItem curMacro
End If
End If
Next
End If
End If
Next
Next
End Sub
In the end mine looked like this:
Macros Form

Run Macros if Sheet Does Not Exist

I am trying to only run a set of macros if a sheet doesn't already exist. I have a macro that creates a sheet and combines data from two sheets into it, and another that formats the new sheet. Since it needs to run on workbook open, I can't have it recreating the sheet again and again. I have been trying the following, but it gives the error: "sub or Function not defined":
Private Sub Workbook_Open()
If SheetExist("MyNewSheet") Then
End Sub
Else
Combine
Format
End Sub
You aren't doing anything if the sheet exists, so change your test.
Private Sub Workbook_Open()
If Not SheetExist("MyNewSheet") Then
Combine
Format
End If
End Sub
Function SheetExist(sheetname As String) As Boolean
SheetExist = True ' replace this with code from link below
End Function
Use the answers here: Excel VBA If WorkSheet("wsName") Exists for examples of functions that determine whether the sheet exists.
Yea, the problem is "End Sub" should be "Exit Sub" You can also use the solution above/below.
Your fixed code would be:
Private Sub Workbook_Open()
If SheetExists("MyNewSheet") Then
Exit Sub
Else
Combine
Format
End If
End Sub
Also:
Public Function SheetExists(ByVal WorksheetName As String) As Boolean
On Error Resume Next
WorksheetExists = (Sheets(WorksheetName).Name <> "")
On Error GoTo 0
End Function