Calling a Sub within a Form - vba

I currently have a Form set in place within my workbook. This form contains a button. On the click of the button, I would like to call a Sub which is located within the "ThisWorkbook" section. How could I go along of doing this?
Button within form...
Sub CommandButton1_Click()
Call Main("Test")
End Sub
The Sub that needs to be called within "ThisWorkbook"
Sub Main(DPass As String)
msgbox Dpass
End Sub
This will give me a compile error of: Sub or Function not defined. Why does this happen?

There are essentially two types of modules:
"Standard/Procedural" modules
Class modules
"Document" modules (e.g. ThisWorkbook, Sheet1, etc.) are just special kinds of class modules. Same for "UserForm" modules, which are basically classes with a default instance and a designer.
Members of a class module don't exist at run-time; a class is nothing but a blueprint for an object - so you need to either create an object of that type (the class determines the type), or use an existing one.
ThisWorkbook is an instance of the Workbook class; Sheet1 is an instance of the Worksheet class. Chart1 is an instance of the Chart class; UserForm1 is an instance of the UserForm class. And so on.
If you make a new class module and call it Class1, and add a public procedure to it:
Public Sub DoSomething()
MsgBox "Something!"
End Sub
Then in order to call DoSomething you need an instance of Class1:
Dim foo As Class1
Set foo = New Class1
foo.DoSomething
If DoSomething is in the ThisWorkbook module, then you can call it by qualifying the method name with the object it exists on, as was mentioned in the comments and in the other answer:
ThisWorkbook.DoSomething
If DoSomething is implemented in a standard/procedural module, then there is no object, the procedure exists in global scope, and you can just do this:
DoSomething
However public members of procedural modules are also exposed as macros (Public Sub) and user-defined functions (Public Function), which you may not want to do.
If you need a procedural module with public members that you can only call from VBA code (and not by clicking a button on a worksheet, or by entering a formula in a cell), then you can specify Option Private Module at the top:
Option Private Module
Public Sub DoSomething()
' DoSomething is not exposed as a macro,
' but can be called from anywhere in the VBA project.
End Sub

Put your sub in a module and not in your ThisWorkbook unless you have to for some reason, if so use ThisWorkbook.Main "string".

Related

use Application_MAPILogonComplete and WithEvents in Module instead of ThisOutlookSession in Outlook VBA

Right now all of my VBA code is in ThisOutlookSession. I want to put everything in a module so I can export it and other folks can import it without having to muck around with their own ThisOutlookSession. I want it to be easy for the user -- the user just imports my module file.
My code depends on Application_MAPILogonComplete and WithEvents. Neither of these are available/work in a module.
I see that classes have a Class_Initialize but it only triggers when a class object is initialized so I'd still need some kind of Application_MAPILogonComplete event.
Is there anyway to do what I want? Keep everything in a module or class that can be exported and imported that has code run when Outlook opens and supports WithEvents so I can execute a function when new e-mails are added to a folder?
Only an object can handle events, so you need a class - ThisOutlookSession is one, but if you want to modularize your code you'll need to have a class module to do that work.
Option Explicit
Private WithEvents App As Outlook.Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub Class_Terminate()
Set App = Nothing
End Sub
'select "App" from the left-hand dropdown at the top of the code pane.
'then select the event(s) you want to handle from the right-hand dropdown;
'the VBE will automatically generate the handler(s) with the correct signature.
All that's left to do is to have an actual instance of that class (let's say it's called Class1) - you'll want to create that instance in ThisOutlookSession
Option Explicit
Private AppEvents As Class1
Private Sub Application_Quit()
Set AppEvents = Nothing
End Sub
Private Sub Application_Startup()
Set AppEvents = New Class1
End Sub

VBA - Call Excel Object in Function from Add-in

I have a Excel Object named ThisWorkbook and a Module named Module1. In Module1, I have a function called function1. ThisWorkbook has a private sub called sub1. When user call this function, I want Excel to do sub1 first and if it has no error, perform the rest of the function. However, I am unable to process sub1 when call function1.
Excel Object - ThisWorkbook
Private WithEvents App As Application
Public Sub sub1(some parameters)
...
If (condition) Then
Msgbox ()
End If
...
End Sub
Private Sub Workbook_Open()
Set App = Application
End Sub
Excel Module - Module1
Function function1(Add As String, some parameters) As String
ThisWorkbook.sub1(some parameters)
...
End Function
** updated frequently to show the current state of code
Since Sub1 is within the scope of ThisWorkbook and not in a global module you need to specify that in your call:
Call ThisWorkbook.Sub1
When you want to refer to a sub, which is in ThisWorkbook, you should refer to the ThisWorkbook as well like this:
ThisWorkbook.sub1 instead of Call sub1.

Passing a parameter to an event handler procedure

I am generating a scripting dictionary using one button on a userform, using it to populate a listbox, and then need to use that same dictionary using a second button on the form. I have declared my dictionary either using early binding as so:
Dim ISINDict As New Scripting.Dictionary
or late binding as so
Dim ISINDict as Object
...
Set ISINDict = CreateObject("Scripting.Dictionary")
When I try to pass the dictionary to the other button like so:
Private Sub OKButton_Click(ISINDict as Scripting.Dictionary) 'if early binding
Private Sub OKButton_Click(ISINDict as Object) 'if late binding
I get the following error: "Procedure declaration does not match description of event or procedure having the same name" on that line.
Any ideas what I'm doing wrong?
An event handler has a specific signature, owned by a specific interface: you can't change the signature, otherwise the member won't match the interface-defined signature and that won't compile - as you've observed.
Why is that?
Say you have a CommandButton class, which handles native Win32 messages and dispatches them - might look something like this:
Public Event Click()
Private Sub HandleNativeWin32Click()
RaiseEvent Click
End Sub
Now somewhere else in the code, you want to use that class and handle its Click event:
Private WithEvents MyButton As CommandButton
Private Sub MyButton_Click()
'button was clicked
End Sub
Notice the handler method is named [EventSource]_[EventName] - that's something hard-wired in VBA, and you can't change that. And if you try to make an interface with public members that have underscores in their names, you'll run into problems. That's why everything is PascalCase (without underscores) no matter where you look in the standard libraries.
So the compiler knows you're handling the MyButton.Click event, because there's a method named MyButton_Click. Then it looks at the parameters - if there's a mismatch, something is wrong: that parameter isn't on the interface, so how is the event provider going to supply that parameter?. So it throws a compile-time error, telling you you need to either make the signature match, or rename the procedure so that it doesn't look like it's handling MyButton.Click anymore.
When you drop a control onto a form, you're basically getting a Public WithEvents Button1 As CommandButton module-level variable, for free: that's how you can use Button1 in your code to refer to that specific button, and also how its Click handler procedure is named Button1_Click. Note that if you rename the button but not the handler, the procedure will no longer handle the button's Click event. You can use Rubberduck's refactor/rename tool on the form designer to correctly rename a control without breaking the code.
Variables in VBA can be in one of three scopes: global, module, or procedure level.
When you do:
Sub DoSomething()
Dim foo
End Sub
You're declaring a local-scope variable.
Every module has a declarations section at the top, where you can declare module-scope variables (and other things).
Option Explicit
Private foo
Sub DoSomething()
End Sub
Here foo is a module-scope variable: every single procedure in that module can access it - read and write.
So if you have data you want to pass between procedures and you can't alter their signatures, your next best option is to declare a module-scope variable.
[ignores global scope on purpose]
About As New - consider this:
Public Sub Test()
Dim foo As Collection
Set foo = New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code blows up with run-time error 91 "object variable not set", because when foo.Add executes, foo's reference is Nothing, which means there's no valid object pointer to work with. Now consider this:
Public Sub Test()
Dim foo As New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code outputs 1, because As New keeps the object alive in a weird, unintuitive and confusing way. Avoid As New where possible.
Declare the dictionary at the module level and fill it in button-1-click event handler. Then it can be simply re-used in button-2-click event handler. So there is no need to pass the dictionary to event handlers which is not possible either. HTH
Form module
Option Explicit
' Declare dictionary at the user form module level
Private ISINDict As Scripting.Dictionary
Private Sub CommandButton1_Click()
FillDictionary
End Sub
Private Sub CommandButton2_Click()
' Use the dictionary filled in event handler of CommandButton-1
End Sub
Private Sub FillDictionary()
With ISINDict
.Add "Key-1", "Itm-1"
.Add "Key-2", "Itm-2"
.Add "Key-3", "Itm-3"
End With
End Sub
Private Sub UserForm_Initialize()
Set ISINDict = New Scripting.Dictionary
End Sub

Global reference to object on Click() sub (VBA)

I was wondering if there's a way to refer to the object of the "Click()" sub.
To make it clearer, let's say we have a button named foo1 and this button has a click sub "foo1_Click()". Does vba has a keyword to get the reference to foo1 that is global?
Something like:
Public Sub foo1_Click()
GlobalKeyword.Property
End Sub
P.s.: something like the word "this" from java refering to its own instance of class
Edit: In the example, the "GlobalKeyword" would refer to "foo1"
I think you're looking for the Application.Caller property found here.
In your case you would want to do something like....
Public Sub foo1_Click()
Dim button As Shape
Set button = ThisWorkbook.Sheets("sheetname").Shapes(Application.Caller)
End Sub
Of course after that you would want to do some error checking to make sure button is not nothing.
If you want to use the same code for a lot of buttons, then you may be better of using a separate subroutine.
Private Sub foo1_Click()
Call do_something
End Sub
Private Sub foo2_Click()
Call do_something
End Sub
Sub do_something() 'called by the foo _Click event
MsgBox Application.Caller
End Sub
This way, you it is easy to maintain the core functionality for all buttons simply by updating the do_something procedure.

Word VBA hide macro from macro list

I know you can use
Option Private Module
to hide macros from the macro list, but apparently this is not true for public functions in class modules that do not have an input parameter. Any ideas how I could hide them?
This is the public procedure in my class
Its appearing in the macro list:
If you want it to remain Public, you can pass to it an optional argument and do nothing with it, so it doesn't show up in the Macro list.
Public Sub RemoveDuplicates(Optional DoNothing As Variant)
'Macro code goes here
End Sub
Just use Private and not Public like:
Private Sub RemoveDuplicates()
' your code here
End Sub
Option Private only restrict inter-project visibility as explained in the link.
It does not hide it in the Macro List.