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ᴇʜ
Related
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.
I'm having this staggering problem in creating a reference at runtime.
In short: I have an addin referenced in another addins, all created by myself and all working fine (except for some annoying popup which appears on loading Excel). As a workaround to this annoyance, I removed the existing reference to create it later, at runtime. But when I try creating the reference, it returns an "Name conflicts with existing module, project, or object library" error popup, and the reference is not created--which makes no sense, because if I create the reference at design time it works perfectly. There is no module or project with repeated name.
Now explaining a little further.
I have four kinda-complex Excel applications I'd had developed for my job. Each one is a VBA project, distributed as Excel Addins (.xlam).
Additionally, I have a fifth project with functions common to the other four. This fifth project is referenced in the other ones (via Tools -> References). So now, all I have to do is to call them as any API outside referenced function (commonProject.Function (arg1, arg2, arg3 etc)).
Unfortunately, I had some problems with a popup message (here) and the workaround seems to be referencing the fifth project in the other ones at runtime.
In order to do this, I uninstalled three of the other four Addins, remaining only one, and used the following code in its Workbook_Open event:
Private Sub Workbook_Open()
Dim strNamePath As String
Dim bolAddinIsRefered as Boolean
Dim oRef As Variant
strNamePath = "c:\etc\etc\etc\Filename.xlam"
For Each oRef In ThisWorkbook.VBProject.References
If oRef.FullPath = strNamePath Then bolAddinIsRefered = True
Next oRef
If bolAddinIsRefered = False Then ThisWorkbook.VBProject.References.AddFromFile strNamePath
End Sub
The problem is, when I used to create the reference at design time by hand, it worked mostly fine (except for the annoying popup I'm now trying to circumvent). But when I try creating the Reference at runtime, it returns an "Name conflicts with existing module, project, or object library" error popup, and the reference is not created.
I have verified the module and worksheet names and there is no duplicated one.
Does anyone have any idea how to prevent this error?
it seems after one and a half day struggling with a problem, when you ask the question on StackOverflow the solution just pops in your mind.
I'd already tried to change both projects names, even internal functions or subs resembling the same name. But I had not looked upon the FILE NAME!
As my solution is called Sisyphus, the filenames of all addins started with "Sisyphus"--"Sisyphus Common Functions.xlam", "Sisyphus DocMerge.xlam" etc.
The problem was VBA was comparing the first word in filename. I removed spaces and it worked well. Now my filenames are "SisyphusCommonFunctions.xlam", "SisyphusDocMerge.xlam" etc. and the referencing in runtime works all right.
Thank you for your time, I'll let this Question and Answer here, because it can be usefull to someone.
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.
I have reviewed other questions related to calling a module within a sub but the solutions do not seem to be applicable to my case. I am trying to run code within a module when there is a change within a userform textbox which I have named filepath1.
Below is a copy of the code I am trying to run. Each time I get a compile error "Expected variable or procedure, not module." To confirm the name of my module is not the same as any other name in the sub or userform. Any advice is appreciated!
Private Sub Filepath1_Change()
Call ChangeFilepath
End Sub
For the most part, the module can be safely ignored. You're not trying to run a module, you're trying to run a subroutine that's stored in a module, and as long as it's not set to private you shouldn't need to specify the module name.
If you had a macro called "ChangePath" in a module called "UpdateFilepath", your call would be Call ChangePath.
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: