How to Call Subs from an Add-In Directly - vba

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

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.

Document_Open() does not work in other files

I am writing a VBA project containing 1 module (m1) and 1 userform (uf).
In "ThisDocument" I am calling a public sub from "m1" that initializes a collection I then refer to in the userform. This works perfectly fine until I deploy this project to other files.
I save the file as a .dotm in the %Appdata%/Microsoft/word/startup folder so I can use the project in all of my word files. But as soon as I try to use my project in other files the userform opens itself as designed but the collection is empty.
What could be the problem here?
Manually calling the initialization method from the userform works fine.
'----------------------------------------------ThisDocument
Private Sub Document_Open()
initBetaCollection
End Sub
'----------------------------------------------m1
Option Explicit
Public beta As Collection
Sub initBetaCollection()
Set beta = New Collection
beta.Add Array("0041", "A"), Key:="0041"
'...
End Sub
'----------------------------------------------uf
Option Explicit
Private Sub txtSearch_Change()
Dim arr As Variant
Dim search As String
'Defining the textinput as "search"
search = txtSearch.Value
For Each arr In beta
If search <> "" Then 'No empty search field
If arr(1) Like "*" & search & "*" Then 'Match found
lbResults.AddItem arr(0)
End If
End If
Next
End Sub
I get the: Run Time Error '424' object required
The problem with using Document_Open in the ThisDocument class or a macro named AutoOpen in a regular module is that both execute specifically for this document (or documents created from the template), only.
In order to have an application-level event, that fires for all documents opened, it's necessary to work with application-level events.
For this, first a class module is required. In the class module, the following code is needed:
'Class module named: Class1
Public WithEvents app As Word.Application
Private Sub app_DocumentOpen(ByVal Doc As Document)
'MsgBox Doc.FullName & ": on Open"
'Code here that should fire when a document is opened
'If something needs to be done with this document, use
'the Doc parameter passed to the event, don't try to use ActiveDocument
End Sub
Then, in a regular module, an AutoExec macro can be used to initialize the class with event handling. (AutoExec: fires when Word starts and loads a template with a macro of this name.)
Option Explicit
Dim theApp As New Class1
Sub AutoExec()
'MsgBox "AutoExec"
Set theApp.app = Word.Application
End Sub
'Sub AutoOpen()
' MsgBox "Open in AutoOpen" - fires only for this document
'End Sub

Calling a Sub or Function that sometimes doesn't exist

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

excel vba transfer of code from module to sheet newly created work sheet

Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$D$10" Then
Call mymacro
End If
End Sub
the question is:
i can store this code to a module?
if not, how can the code be the code transfer to the newly created worksheet
from module how can i transfer this code every time a sheet is added
Thanks in advance
You could use the Workbook_SheetChange Event. Put the code in the workbook module. Then there is no need to copy any code it all.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Target.Address = "$D$10" Then
Call mymacro
End If
End Sub
EDIT If you need to prevent the code from running on certain sheets you could add the follwoing function
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
On Error Resume Next ' Invalid Parameters passed, IsInArray will be defaulted to FALSE
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
and change the Workbook_SheetChange Event to
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim shArr() As Variant
'Example, mymacro will not run on the sheets "Overview" and "Total"
shArr = Array("Overview", "Total")
If Not IsInArray(Sh.Name, shArr) Then
If Target.Address = "$D$10" Then
Call mymacro
End If
End If
End Sub
An example adaption of #davesexcel as i have never tried this way before. This code goes in a standard module which you could call from your main sub. Requires a reference to be added to VBA Extensibility and access to vb model to be trusted.
Public Sub AddWorksheetEventCode()
'Tools > references > Microsoft Visual Basic for Applications Extensibility 5.3
'Trust access to VBA model
Dim wb As Workbook
Dim wsNew As Worksheet
Set wb = ThisWorkbook
Dim xPro As VBIDE.VBProject
Dim xCom As VBIDE.VBComponent
Dim xMod As VBIDE.CodeModule
Dim xLine As Long
wb.Worksheets.Add After:= wb.Worksheets(ActiveSheet.Index)
Set wsNew = ActiveSheet
With wsNew
Set xPro = wb.VBProject
Set xCom = xPro.VBComponents(wsNew.Name)
Set xMod = xCom.CodeModule
With xMod
xLine = .CreateEventProc("Change", "Worksheet")
xLine = xLine + 1
.InsertLines xLine, "If Target.Address = ""$D$10"" Then "
xLine = xLine + 1
.InsertLines xLine, "Call mymacro"
End With
End With
End Sub
Enable Trust access to the VBA project object model:
Click File and then Options.
In the navigation pane, select Trust Center.
Click Trust Center Settings....
In the navigation pane, select Macro Settings.
Ensure that Trust access to the VBA project object model is checked.
Click OK.
**Read up on trusting access to the vba project model to determine if appropriate for you.
do like this
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$D$10" Then
Call mymacro
Me.Copy after:=Sheets(Sheets.Count)
ActiveSheet.Cells.Clear
End If
End Sub
I have up-voted #Storax answer because I didn't know that. However, you should bear in mind that the Workbook_SheetChange event will fire for all sheets, possibly including some where you don't want your macro to run. Therefore you would need to add code to prevent the macro from taking action when the event is triggered where you don't want it.
As an alternative, look at the way you create your sheets. If you insert a new sheet the new addition will be totally blank, but if you use "Move or Copy / Create a copy" (or its VBA equivalent) you get a new sheet which is the copy of the original including its code. A further advantage is that you get a fully formatted sheet, and it is normally quite easy to clean out any data that were also copied in this process.

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