How to activate a wookbook named in a different module - excel

I call module2 from module1 where I name a workbook "x" in module2. But later when I try "x.Activate" in module1 I get an error "Run-time error '424': Object required"
I have a rather lengthy module that I would like to organize by breaking it up into multiple modules. So far I have created a module called "INPUTS" in this module I have a "Sub RT_CMM_DATA_COMPILER_INPUTS()" presumably in the future I will have other Subs in this module "Sub RT_Some_Other_Project_INPUTS()" I name a workbook in "Sub RT_CMM_DATA_COMPILER_INPUTS()" and try to activate that workbook by name in a separate module called sandbox. But it displays an error.
'RT_Sandbox Module
Sub sandbox()
Call RT_CMM_DATA_COMPILER_INPUTS
wkbwatchFolders_table.Activate
lastShtRow = LASTSHEETROW(ActiveSheet)
MsgBox lastShtRow
End Sub
'Inputs module
Sub RT_CMM_DATA_COMPILER_INPUTS()
watchFolders_filePath = "D:\RT_CMM_Data_File_Paths.xlsx"
Set wkbwatchFolders_table = Workbooks.Open(Filename:=watchFolders_filePath)
End Sub
Am I going about this attempt to organize my code completely wrong? Should I be using class modules for this instead? Or is it just some syntax I am missing?

The critical part you're missing is Option Explicit at the top of every module.
With that option, code will refuse to compile until all variables are explicitly declared.
Without it, watchFolders_filePath is an undeclared variable in both procedures, and in the scope where it is read but not assigned, its data type is Variant/Empty.
Rubberduck (free, open-source VBIDE add-in project that I manage) can help locate and fix these issues (and others) in your code:
OptionExplicit inspection
UnassignedVariableUsage inspection
UndeclaredVariable inspection
VariableNotAssigned inspection
VariableNotUsed inspection
As for your code, you don't need any global variables. Avoid global variables whenever possible. Use functions (and parameters) instead:
Function RT_CMM_DATA_COMPILER_INPUTS() As Workbook
Dim watchFolders_filePath As String
watchFolders_filePath = "D:\RT_CMM_Data_File_Paths.xlsx"
Set RT_CMM_DATA_COMPILER_INPUTS = Workbooks.Open(Filename:=watchFolders_filePath)
End Function
Sub sandbox()
Dim wb As Workbook
Set wb = RT_CMM_DATA_COMPILER_INPUTS
wb.Activate
lastShtRow = LASTSHEETROW(wb.ActiveSheet)
MsgBox lastShtRow
End Sub

Using Public statement will work here:
Public x As Workbook
Public your_var As Object
You need to declare these outside your procedure, at the top of the module. After declaring these you can access them anywhere in any module.
Read More here: Declaring Variables

Related

How do I Late Bind a Class Module from my Project?

I have two class modules in my project, "ClassAlfa" and "ClassBeta". I have separated the code between the two classes so that if an end-user does not have ClassBeta included in their project, then any of ClassAlfa's late bound references to it gracefully fail. Where I'm having trouble is making the script work if the end-user DOES have ClassBeta available.
Within ClassAlfa, I have the following code:
Option Explicit
Public myClass as Object
Private Sub Class_Initialize()
On Error Resume Next
Set myClass = Application.Run("ClassBeta")
If Err = 0 Then 'End user has this class available, go ahead and use it.
'Do code with ClassBeta
Else
'Do code without ClassBeta
End If
On Error GoTo 0
End Sub
This throws the following error on the Set line:
Run-time error '1004':
Cannot run the macro 'ClassBeta'. The macro may not be available in this workbook or all macros may be disabled.
I have also tried replacing the Set line with this:
Set myClass = CreateObject("ClassBeta")
which instead throws the error
Run-time error '429':
ActiveX component can't create object
Also does not work:
Set myClass = CreateObject("'" & ThisWorkbook.Name & "'!" & "ClassBeta")
What is the proper way to late bind a custom class from my own project?
There is no mechanism in VBA that would allow you to check if a class exists by it's name and to create a new instance if it is.
However, it is possible to achieve. Let's dissect the problems and see how we can work around them.
Problem 1
You need to create an instance of a class that you are not sure it exists in the current project. How?
As you already tried, Application.Run and CreateObject do not work.
Application.Run is only capable of running methods in standard .bas modules (not class modules). It does not create instances of classes.
CreateObject does a few things behind the scenes. First, it calls CLSIDFromProgIDEx using the ProgID (the string) you are passing. Then, it calls CoCreateInstance. The problem is that VBA classes do not have their ProgIDs in the registry so CreateObject simply doesn't work. You would need to have your class in a registered .dll file of ActiveX.exe to make this work.
That leaves us with the New keyword to instantiate a new ClassBeta. That obviously works when the class is present but gives a 'User-defined type not defined' compiler error when it's not. There are only 2 ways to supress this compiler error:
Have a compiler directive
Not have the New ClassBeta at all
A compiler directive would look like this in ClassAlfa:
Option Explicit
#Const BETA_EXISTS = False
Sub Test()
Dim myBeta As Object
#If BETA_EXISTS Then
Set myBeta = New ClassAlpha
#End If
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
This compiles fine without having the ClassBeta in the project but you could never make the BETA_EXISTS conditional compiler constant to turn True because:
Only conditional compiler constants and literals can be used in expression
So, the last option is not to have the New ClassBeta anywhere in the project. Except, we do. We can put it in the ClassBeta itself as a factory:
Option Explicit
Public Function Factory() As ClassBeta
Set Factory = New ClassBeta
End Function
When the class is missing, the factory is missing and the New ClassBeta will not throw a compiler error.
Problem 2
How do we call the .Factory method on the ClassBeta?
Well, we obviously cannot create a new instance of ClassBeta because that is our first problem.
What we can do is to make sure that ClassBeta always has a default global instance (like userforms have). To do that we need to export the class to a .cls text file and edit it with a text editor (like Notepad). The text should look like this:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ClassBeta"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Public Function Factory() As ClassBeta
Set Factory = New ClassBeta
End Function
Notice that I've changed the VB_PredeclaredId attribute to True (manually, in the text editor). Now we can import the class back. We can check if it worked by typing ?Typename(ClassBeta.Factory) in the Immediate window and then pressing Enter. We should see:
The code in ClassAlfa can now be written as:
Option Explicit
Sub Test()
Dim myBeta As Object
On Error Resume Next
Set myBeta = ClassBeta.Factory
On Error GoTo 0
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
Problem 3
If we remove ClassBeta from the project, the following line does not compile:
Set myBeta = ClassBeta.Factory
But, compared to problem 1, this time the compiler error is 'Variable not defined'.
The only way that I could think of, to get rid of this new compiler error, is to turn off Option Explicit. Yeah! That bad!
ClassAlfa:
'Option Explicit 'needs to be off to be able to compile without the ClassBeta class
Sub Test()
Dim myBeta As Object
On Error Resume Next
Set myBeta = ClassBeta.Factory
On Error GoTo 0
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
Final Thoughts
You could do the development while Option Explicit is on and turn it off for your users.
If your users will use the ClassAlfa with Option Explicit off, the the code should work fine. But if they want ClassBeta as well, then the only way they could get it would be via importing the .cls file. Copy-pasting code won't work because they would lose the global instance of ClassBeta (the one we set in the VB_PredeclaredId hidden attribute).
I must say that I do not recommend removing Option Explicit because that could lead to other issues. I do not think there is a way to achieve what you want in a 'clean' way.

Excel VBA Global Range variable out of scope after sub procedure completes

I am perplexed why my global variable within a module fall out of scope at the conclusion of a sub procedure.
I declare the range at the top of the module out side of all subproc and functions
as below
Option Explicit
Dim TIMEDATA As Range
Dim FREQDATA As Range
Const StartLoc = "B4"
Const flowLoc = "F4"
Const dtLoc = "J8"
In my subproc I define one of the ranges.
Public Sub PortandConvertData()
<SNIP>
Set TIMEDATA = calcSheet.Range(Cells(2, 2).Address, Cells(2 + dataSize, 2).Address)
End Sub
After the sub completes in the watch window I see the variable TIMEDATA go from
Range/Range to Range and the value go from correct to simply out of context.
I want to store data in the module rather than pasting in a sheet or something.
Any help is much appreciated
Make sure that the Context in the Watch properties includes the Procedure/Module you are actually watching. You can make sure by setting the context to All Modules:
From the Watches panel: Right Click the Expression --> Edit Watch --> From the Context group set Procedure/Module to All.
If this is not the actual issue, then you are having the same issue I'm having from Access VBA.
This thread explains the same:
ThisWorkbook not holding global variable value to cancel ontime()

how to I use functions in vba from my personal macro workbook?

I have the following function in my personal workbook:
Public Function Get_Rows_Generic_personal(sheet_name As String) As Long
Get_Rows_Generic = Worksheets(sheet_name).UsedRange.Rows.Count
End Function
How do I call it from a module in another project?
This thread was supposed to have the answer but the link is dead.
You have two choices really:
You can use Application.Run; or
You can set a reference to the VBA project of your personal macro workbook ( but you'll need to change the name of the project from the default 'VBAProject' first). Once you have a reference set, you can call the function directly
To use Application.Run you pass the workbook and routine name as the first argument, then any arguments required by that routine as additional arguments to Run. If the routine name is the same as its parent module, or you have more than one routine in the workbook with the same name, you need to supply the module name too, and if the workbook name contains spaces, you need to enclose it in single quotes. So the basic syntax is either:
Application.Run "'workbook name.xlsm'!routine_name", parameter1
or:
Application.Run "'workbook name.xlsm'!module_name.routine_name", parameter1
For example:
dim lCounter as long
lCounter = Application.Run("'Personal.xlsb'!Get_Rows_Generic_personal", "some sheet")

How to declare Global Variables in Excel VBA to be visible across the Workbook

I have a question about global scope and have abstracted the problem into a simple example:
In an Excel Workbook:
In Sheet1 I have two(2) buttons.
The first is labeled SetMe and is linked to a subroutine in Sheet1's module:
Sheet1 code:
Option Explicit
Sub setMe()
Global1 = "Hello"
End Sub
The second is labeled ShowMe and is linked to a subroutine in ThisWorkbook's module:
ThisWorkbook code:
Option Explicit
Public Global1 As String
Debug.Print("Hello")
Sub showMe()
Debug.Print (Global1)
End Sub
Clicking on SetMe produces a compiler error: variable not defined.
When I create a separate module and move the declaration of Global1 into it everything works.
So my question is:
Everything I have read says that Global variables, declared at the top of a module, outside of any code should be visible to all modules in the project. Clearly this is not the case.
Unless my understanding of Module is not correct.
The objects Sheet1, Sheet2, ThisWorkbook,... that come with a workbook: are these not modules capable of declaring variables at global scope?
Or is the only place one can declare a global, in a separate module of type Modules.
Your question is:
are these not modules capable of declaring variables at global scope?
Answer: YES, they are "capable"
The only point is that references to global variables in ThisWorkbook or a Sheet module have to be fully qualified (i.e., referred to as ThisWorkbook.Global1, e.g.)
References to global variables in a standard module have to be fully qualified only in case of ambiguity (e.g., if there is more than one standard module defining a variable with name Global1, and you mean to use it in a third module).
For instance, place in Sheet1 code
Public glob_sh1 As String
Sub test_sh1()
Debug.Print (glob_mod)
Debug.Print (ThisWorkbook.glob_this)
Debug.Print (Sheet1.glob_sh1)
End Sub
place in ThisWorkbook code
Public glob_this As String
Sub test_this()
Debug.Print (glob_mod)
Debug.Print (ThisWorkbook.glob_this)
Debug.Print (Sheet1.glob_sh1)
End Sub
and in a Standard Module code
Public glob_mod As String
Sub test_mod()
glob_mod = "glob_mod"
ThisWorkbook.glob_this = "glob_this"
Sheet1.glob_sh1 = "glob_sh1"
Debug.Print (glob_mod)
Debug.Print (ThisWorkbook.glob_this)
Debug.Print (Sheet1.glob_sh1)
End Sub
All three subs work fine.
PS1: This answer is based essentially on info from here. It is much worth reading (from the great Chip Pearson).
PS2: Your line Debug.Print ("Hello") will give you the compile error Invalid outside procedure.
PS3: You could (partly) check your code with Debug -> Compile VBAProject in the VB editor. All compile errors will pop.
PS4: Check also Put Excel-VBA code in module or sheet?.
PS5: You might be not able to declare a global variable in, say, Sheet1, and use it in code from other workbook (reading http://msdn.microsoft.com/en-us/library/office/gg264241%28v=office.15%29.aspx#sectionSection0; I did not test this point, so this issue is yet to be confirmed as such). But you do not mean to do that in your example, anyway.
PS6: There are several cases that lead to ambiguity in case of not fully qualifying global variables. You may tinker a little to find them. They are compile errors.
You can do the following to learn/test the concept:
Open new Excel Workbook and in Excel VBA editor right-click on Modules->Insert->Module
In newly added Module1 add the declaration; Public Global1 As String
in Worksheet VBA Module Sheet1(Sheet1) put the code snippet:
Sub setMe()
Global1 = "Hello"
End Sub
in Worksheet VBA Module Sheet2(Sheet2) put the code snippet:
Sub showMe()
Debug.Print (Global1)
End Sub
Run in sequence Sub setMe() and then Sub showMe() to test the global visibility/accessibility of the var Global1
Hope this will help.

MS Access VBA: Reference Excel Application Object created in separate Module

This seems like it should be an easy one but I'm stuck.
I'm running a VBA script in Access that creates a 40+ page report in Excel.
I am creating an Excel Application Object using Early Binding:
Public obj_xl As New Excel.Application
Here is an example of how I am referencing the object:
With obj_xl
.Workbooks.Add
.Visible = True
.Sheets.Add
.blahblahblah
End With
The problem is that the procedure has become too large and I need to break the code up into separate modules.
If I try to reference the Excel Application Object from a different module than it was created in, it throws an error ("Ambiguous Name").
I'm sure I could do something with Win API but that seems like it would be overkill.
Any thoughts? Thanks
this is the type of situation that can cause the error "Ambiguous Name"
Function Split(s As String)
MsgBox s
End Function
Function Split(s As String)
MsgBox s
End Function
I know the example is trivial, but what you are looking for is a function , an object and/or a form control with the same names.
If you convert your declaration to Global, you can reference it in all your modules. For example, in one module, put this at the top:
Global obj_xl As Excel.Application
Then in an another module,
Sub xx()
Set obj_xl = New Excel.Application
Debug.Print obj_xl.Name
End Sub

Resources