Clas variable declaration not working to with dynamic class loading in VBA, - excel

Any help with dynamically loading of classes /variables in VBA?
I have an excel sheet and other few dependent sheets, depending on the requirement, on click of a button I am loading other sheets.
Say Main workbook -> Loads sheet1.xlam and related classes ( cls_one.cls) on click of button.
I am using below code to load sheet1.xlam and cls_one.cls
Step 1: Load Main.xlsm
Step 2 load - sheet1.xlsm
Set addedWs = ActiveWorkbook.Sheets.Add(Type:=path, after:=ActiveWorkbook.Sheets(ActiveWorkbook.Sheets.count))
Step 3: Load Cls_one.cls dynamically using the below method.
Public Function InsertClass(ByRef oWB As Workbook, ByVal className As String, ByVal filePath As String, Optional bUAT As Boolean = False) As Boolean
On Error GoTo Errhandler
oWB.VBProject.VBComponents.Import filePath
InsertClass = True
Exit Function
Errhandler:
If err <> 0 Then
If Not bUAT Then
MsgBox (err.Description)
End If
err.Clear
End If
InsertClass = False
End Function
Above code works fine, however, I have a reference to cls_one in sheet1.xlsm which never works, on loading or give error Undefined object error.
Public Myclassone As cls_one
this variable declaration is in sheet1.xlsm.
I tried flipping steps loading class first and sheet1.xlam next, but still getting the same error with Excel 2013, this piece of code works fine with 2010.
Trying to understand what is the best way to reference dynamically loaded classes in forms or other classes?
Also, I tried changing error preferences in tools -> Options -> Break on Unhandledexception.

You can't early-bind to something that's only going to exist at run-time, by definition - there's no way that could have worked:
Public Myclassone As cls_one
If cls_one doesn't exist at compile-time, then the module can't be compiled.
this piece of code works fine with 2010
No. This piece of code works fine if it's not in any execution path that involves the module declaring a public variable of a type that doesn't exist... regardless of what version the host application is (this behavior is purely VBA, nothing to do with Excel).
That's kind of a hack though: a VBA project will not compile (through Debug -> Compile), but will happily run anyway if the entry point doesn't involve loading the module: that's because of the somewhat-interpreted nature of VBA.
Proof:
Module1
Option Explicit
Public foo As Something '<~ undefined, won't compile
Module2
Option Explicit
Public Sub Test()
Debug.Print "I can run even if the project doesn't compile!"
End Sub
You can run Module2.Test regardless of whether the project compiles, because Module1 isn't in the picture at all. Now change Module2.Test to this:
Public Sub Test()
foo.DoStuff '<~ expect fireworks
End Sub
When I ran this, Excel just outright crashed.
So the bottom line is this: you can reference a non-existing class in a module. The project won't be compilable, but if no code references the non-existing class then the project will be executable anyway, and the non-compilable module can then be executed in another execution context (i.e. from a separate entry point), after the class is added.
I would not recommend using non-compilable code for anything remotely important though, since doing that takes away the only compile-time validation you have in the VBE for your code - consider reviewing Rubberduck inspections in that case (several inspections flag run-time error situations statically).
A better, more viable solution would be to reconsider the dependency chain and the overall approach.

Related

Err.Raise only works in module, but not a sheet or workbook

I'm learning error handling and I think I'm getting the hang of it. But I've come across an interesting behavior. I'm handling my errors in my class modules, and I bubble the errors up the stack. I have traceability so when the error reaches the caller (or top of stack) an error is displayed telling me where it occurred at. It works just fine if the class is being called from a module. BUT if the class is being called from the sheet or workbook, then it generates an unhandled runtime error.
For example, take the following code:
sub testError
err.raise -1000,,"This is a custom error"
end sub
If I run this in a regular module I get the following:
But if I run that exact same code in a sheet or workbook, I get the following:
So some of my classes are being called from macros located on a sheet. Others are being called from workbook and/or sheet events. And if a handled error happens down the line, I get an unhandled runtime error without any useful information.
I can move my macros to a standard module. But I can't move my events (value changes, new caluclation, etc) to a standard module.
So I have two questions. Why can't I do err.raise in a sheet / workbook?
What should I do instead?
Thanks.
So digging into this further. There are two kinds of error popups VBA will give you:
One with a continue/end/debug/help button
One with an okay/help button.
It turns out you can only modify the description with the popup that has the end/debug button. (via the err.raise).
What determines which popup you see is where the top of your stack is. If its in a standard module, you'll get the "end/debug" popup. If its in a sheet/workbook object, then you'll get the "okay/help" popup.
Unhandled errors bubble up all the way to the top stack. If that stack is in a standard module, then you'll have the option of pressing the debug button, which will take you to the line that you're error occurred. If the top stack is in a sheet/workbook, you'll have no idea where it happened.
To illustrate my point, take the following code that will throw a standard runtime error:
sub testError
dim a as long
a = clng("X")
end sub
If this method is ran in a standard module you'll get this:
If you run this in a sheet / workbook you'll get this:
The later doesn't really work well with err.raise (you can't edit the description).
My problem is I have macros and events that are initiated in the sheets / workbooks. Those methods then call other methods in a module. But since the top stack is located in a sheet / workbook, the wheels come off of my error handling.
So my work around is to initiate my module level methods in a way that puts those methods at the top of the stack, and then it works:
'standard module
sub createClass
dim myObj as myClass
set myObj = new myClass
call myObj.raiseError
end sub
Instead of calling this method from the sheet / workbook as so:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
call createClass
End Sub
Do this:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
application.run "createClass"
End Sub
Doing it this way will place the createClass method at the top of the stack. and since this is in a standard module, the error handling will work as expected.

Passing in Parameters to Workbook_Activate from Command Line

I'm trying to keep alive an old Excel VBA program, and my client has asked me to run some existing code on startup (instead of having to click a button).
So, I'm following this tutorial here.
It kinda works ... ie when the excel files is open the MsgBox prompts appear but with blanks or 0s. However that only happens when I don't include the first line:
Declare Function GetCommandLineA Lib "Kernel32" () As String
when I include that, I get this error message:
Compile Error: Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules.
So - how do I do it?
The Sub Workbook_Activate is Private and I don't think I can change that, can I?
Posting for other confused people:
I was trying to run it from within the ThisWorkbook sub, and what Pᴇʜ rightly pointed out was that you have to create this code in a normal module, and then call that module's sub FROM the ThisWorkbook sub.
thank you Pᴇʜ

Cannot run common module in xlam file as macro is disabled

VBA / Excel - 2007
I want to create one (possibly many) .xlam file(s) to hold common modules accessible across projects. Along the way I have received a number of errors but through the creation of a "mickey-mouse" scenario I have boiled it down to one error condition (as shown in actual result):-
I have seen several problems reported here related to this which in the end have either not been answered or the solution has not quite hit the mark
As a precursor to this I was able to put my common module into another .xlsm project and successfully execute it by using a reference to that called project. However it would be preferable not to create a workbook for the sole purpose of housing common modules
'Caller VBAProject (Caller.xlsm):
Public Sub Caller()
Dim i As Integer
i = 0
'*** Error in line below***
Application.Run "C:\Users\IT\AppData\Roaming\Microsoft\AddIns\Common.xlam!Test", i
End Sub
'Called Test (Common.xlam) -- different project, obviously
Function Test(ByRef i As Integer) As Boolean
If i = 0 Then
Test = False
Else
Test = True
End If
End Function
Actual Result
Run-time error '1004':
Cannot run the macro
'C:\Users\IT\AppData\Roaming\Mirosoft\AddIns\Common.xlam!Test'
The macro may not be available in this workbook or all macros may
be disabled.
Steps Undertaken (in Excel Options)
Trust Center
a) Macro Settings
-- both i) and ii) (at different times)
i) Disabled all macros with notification
ii) Enabled all macros
iii) set Trust access to the VBA project object model
b) Add-ins -- left as default ie no option ticked
c) Trusted Locations -- have added the following
C:\Users\IT\AppData\Roaming\Microsoft\AddIns\
Add-Ins
As an Active Application Add-in I have
C:\Users\IT\AppData\Roaming\Microsoft\AddIns\Common.xlam
Can anybody please tell me what I might have missed?
So just to summarise I don't have a direct answer to my question thus far, that is how to avoid the 1004 error when calling a macro within a .xlam file. However, I do have a more than adequate alternative which is to import the common file into a different module within the same project. What I'm realising is that as I write this it isn't an import at runtime or late binding as Zac implied. I'm not sure I'm too worried though (at least at the moment).

VBA Recompilation required

I have an interface IReadable where I declare:
Public Function getFields() As Dictionary
End Function
Public Function getData()
End Function
The interface's instancing property is set to publicNotCreatable
I have an Excel sheet that implements this interface and these methods (details not important).
From module mApp, I apply the following code from a sub routine:
Dim oSheet As IReadable
Set oSheet = ThisWorkbook.Sheets("tbl_deals") 'Compile error
When the VBA gets compiled via the menu Debug - Compile VBAProject, everything runs fine. Strangely, the code only gets run once.
If I try to run the code a second time, I get a Type Mismatch error.
In order to make the code run again, I need to switch the status of the interface to private and back, recompile, and the code runs again a single time (the second time, the code returns a type mismatch error again).
Any ideas on how to solve this problem, to make the code run stable without needing to recompile every time?
Edit: This is a strange problem. I thought it might have been related to pollution of the compiled code, but when re-creating the case in a new Excel workbook, the identical problem persists.
Use code name of worksheet instead of displayed name:

Errors Raised within Class Debug As If Raised at Property Call

I am (unfortunately) developing an application in Excel 2000 VBA. I believe I have discovered that any error raised within a Custom Class property, function, or sub debugs as if the error were raised at the point in the VBA code where the property is called. That is, the VBE debugger does not take me to the point in the Class property where the error occurred, but instead where the property was first entered (from a Module Sub or Function, e.g.) This makes it frustrating to develop anything more than the most shallow OO Excel 2000 VBA code since I have to step line-by-line through every Class method to discover the instructions causing an error.
Am I missing something or is this a known bug I have to deal with in Excel 2000? Has this been fixed in 2003 or 2007?
Example code:
'''''''''''''''
'In Module1:
Public Sub TestSub1()
Dim testClass As Class1
Dim testVariant As Variant
Set testClass = New Class1
testVariant = testClass.Property1 'Debugger takes me here...
End Sub
''''''''''''''
' In Class1
Property Get Property1() As Variant
Err.Raise 666, , "Excel 2000 VBA Sux!" 'But error is actually thrown here.
End Property
For Office 2003 you will get this behaviour when the debugger is configured to break on unhandled errors (the default configuration).
If you want it to break on the Err.Raise line, you need to configure it to break on all errors (Tools/Options/General/Error Trapping/Break on All Errors).
I believe it's the same for Office 2000 but don't have a copy to check.
This page is a very good resource on error handling in VBA:
Error Handling and Debugging Tips and Techniques for Microsoft Access, VBA, and Visual Basic 6
This "feature" is the same in Excel 2003 and I'd be surprised if it's different in 2007.
The same still holds true in Excel 2010 - that's where I met this behaviour.
To quote Chip Pearson's site:
There is absolutely no reason to use an error trapping setting other than Break In Class Module.
His description of the difference between the error modes:
When you are testing and running your code, you have three error trapping modes. The first is Break On All Errors. This will cause the debugger to open if any error occurs, regardless of any On Error handling you might have in the code. The second option is Break On Unhandled Errors. This will cause the debugger to open if the error is not handled by an existing On Error directive. This is the most often used option and is the default setting. The third option, Break In Class Module is the most important and least used. It is not the default error trapping mode, so you have to set it manually.
The Break In Class Module is the most important because it will cause the debugger to break on the line of code within an object module that is actually causing the problem. The Break In Class Module setting is in the Options dialog accessible on the Tools menu. It is on the General tab of the Options dialog, as shown below.

Resources