I'm making a workbook with a whole lot of different subs, and in an effort to avoid a user accidentally activating a sub that erases the code of a sheet for example, I've tried making all the subs private instead.
My subs can now only be activated by clicking buttons on the worksheet, and it all works as intended. Untill a sub of mine tries to call a private sub in another module of course.
To get around this I used Application.Run rather than Call, which worked and also allows me to call in a variable "NextSub" from the previous sub, which gives me some flexibility that I need, and apparently cant get with the Call.
Eg.
Sub FirstSub()
*Something going on
Application.Run "SecondSub", SomeVariableSub
End sub
Sub SecondSub(Nextsub as String)
If something Then
*Do something
Application.Run NextSub
Else
Application.Run NextSub
I thought that the Application.Run had solved all my problems, but I used to have a line that called an errorhandler, which in turn called a sub. It seems that the program can no longer backtrack to the sub that contained the errorhandler as it could when I used Call.
Does Applciation.Run break this functionality? If yes, can I then use Call with a variable NextSub as I am doing it now? And if I can't use the Call that way, then is all this fixed by adding a On Error GoTo ErrorHandler in the affected subs?
I know that the whole thing about calling Private Subs across modules is probably pretty bad practice, but I was compltely new to this when I started out, and the project is too extensive to fix that without rewriting all of the code.
Instead of making all subs private, either put Option Private Module on top of each module, or add a dummy argument to each routine:
Sub SomeHiddenRoutine(bDummy As Boolean = False)
'Routine can be called as usual using:
SomeHiddenRoutine
End Sub
If I understand you are trying to call a function specified by a string.
The correct way is to use something like this, which allows you to call all the private subs (as long as it is in the same module as the private functions):
Sub CallFunction(FuncName As String)
Select Case FuncName
Case "Func1": Func1
Case "Func2": Func2
Case "Func3": Func3
End Select
End Sub
Related
I am trying to write a simple function (in a module FileIO) which would take an instance of a work book, and just close it. This function is invoked from another module Business.
Below is the code snippet.
Public Function CloseExcelFile(wkBook As Workbook)
If (wkBook Is Not Nothing) Then
wkBook.Save
wkBook.Close
End If
End Function
I invoke this method by using the command FileIO.CloseExcelFile(catWorkBook). Variable catWorkBook is the object reference to the workbook I created (in a step before).
When ever I try too invoke the custom function, I am getting the error
object does not support this method or property
The below command closes the work book with no errors.
catWorkBook.Close
But the same does not happen when I use the custom function. What is going wrong here?
You just have your Not in the wrong place. Try it like this:
Public Function CloseExcelFile(wkBook As Workbook)
If Not wkBook Is Nothing Then
wkBook.Save
wkBook.Close
End If
End Function
As braX pointed out, your Not isn't in the right place.
You also don't need a Function here. Change it to a Sub. In fact, you barely need the Sub when you reduce it to one line like this:
Public Sub CloseExcelFile(wkBook As Workbook)
If Not wkBook Is Nothing Then wkBook.Close(SaveChanges:=True)
End Sub
The error was due to the INCORRECT way I was invoking a sub routine. I invoked the sub routine as:-
FileIO.CloseExcelFile(catWorkBook)
The CORRECT way to invoke a function would have been.
FileIO.CloseExcelFile catWorkBook
I'm wondering where to place my function declarations in my project. For this project I am supposed to modularize the coding which currently lies under Button Calculate (previous project) but everytime I try to place it under button calculate it tells me it doesn't belong within the method body. So how would I go about doing this?
Hmm. I dont want to sound condescending at all in writing this, but I'm guessing that you're trying to put a function directly under a line of code that says something like..
private Sub ButtonCalculate()
This is the start of a subroutine definition. Try looking further down the code for the line that says..
End Sub
and put your function declaration under there - leaving a blank line for readability's sake e.g.
private sub ButtonCalculate()
'code for buttoncalculate
'more code for buttoncalculate
'lots more code for buttoncalculate
End Sub
private Function randomFunction(WhateverYourParametersare byval integer) as integer
'your function code
'more function code
End Function
Does anyone out there know how to do a stack trace in access-vba. I'm trying to do something like:
Public Sub a()
Call c
End Sub
Public Sub b()
Call c
End Sub
Public Sub c()
Debug.Print "Which sub has called me ?"
End Sub
What I want to do in Sub c is to show if that has been called by Sub a or Sub b without passing any arguments. In c I would simply show the stack but I have no idea if this is even possible in VBA - any thoughts ?
You can access the call stack during runtime under the menu View -> Call Stack
Alternatively you can use the keyboard shortcut CTRL+L during runtime.
You can use Mztools Addins which has an option to view the procedure caller.
Download Mztools
The only way you could tell would be to get the return address pointer of the calling subroutine from the stack. VBA wouldn't do that directly, you would have to have written your own library or have a library from someone else who needed to know these things. This might be worthwhile if the calling routines are from a program you can't update, however if this is only coming from your own code, it's far easier to rewrite your code to identify the caller.
The way I do this is by having a ProcedureEnter and ProcedureExit call at the beginning and end of each VBA routine I have. The ProcedureEnter routine has an argument for the Sub/Function name, which gets stored in a global call stack collection. ProcedureExit just pops the last entry off the stack.
So, to get the caller routine name, you would just get the item at the Stack Collection .count - 1
I personally use MZTools to set up a default VBA routine header/footer, so that I just type
Function fnname(arg as whatever) as boolean
End Function
and click the Add Error Handler button in MZTools and it adds the ProcedureEnter and ProcedureExit calls for me. That makes it a lot less cumbersome to add the stack trace code.
This is probably the dumbest question I've ever asked here, but it's hard to find answers to things like this.
I have a program with a bunch of modules/subs that each calculate a different variable. They're pretty complex, so I like to keep them separate. Now I want an earlier module to skip to another module based on user input. I thought I could use the call (sub name) method for this, but then the program returns to where the call line was and continues on that module from where it left off.
Example:
Module 1:
Sub NewPracticeSub()
Call otherpracticesub
MsgBox ("We've gone back to this sub... :(")
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I don't want it to return to Module 1. What can I do to have it switch control to Module 2 without it then returning to complete Module 1 upon completion of Module 2?
I feel like I just used the most confusing language possible to explain all of this, but thank you for your help anyways!!
Edit: I know I used the words module and sub interchangeably, and I know they're different. I like to keep each sub (which are each very large in my program) in their own modules because it's easier to keep track of them, and easier to explain/demonstrate the application flow to other people.
I think all you're looking for is the command Exit Sub which will make the program leave the subroutine without continuing any further, But the way you usually want to do this is, rather than calling a Sub, rather call a Function that returns a boolean value.
So, for example:
Public Function MyFunc() as Boolean
....
If [good] MyFunc = True
Else MyFunc = False
End Function
Then you could do something along the lines of:
Sub MyCallingSub()
...
If MyFunc = True then Exit Sub
Else ...
End Sub
It just adds in A LOT more felxibility and ability to choose whether you want to continue further in your sub or not.
Hope that makes sense.
Other than using the ugly End statement which I will describe below (and strongly recommend you to avoid), I'm not aware of any way to circumvent the call stack. Even John's response necessarily returns to the calling procedure, and evaluates another statement to determine whether to proceed or end.
This may yield undesirable outcomes, which is why I hesitate to recommend it, in favor of properly structuring your code, loops, etc., with respect to the call stack.
In any case, here is how you can use the End statement within your child subroutines, without needing any sort of public/global variables. This still allows you the flexibility to decide when & where to invoke the End statement, so it need not always be invoked.
Sub NewPracticeSub()
Call otherpracticesub, True
MsgBox ("We've gone back to this sub... :(")
End Sub
Sub otherpracticesub(Optional endAll as Boolean=False)
MsgBox ("We're in the other practice sub!")
If endAll then End '## Only invoke End when True is passed to this subroutine
End Sub
Why I say this method should be avoided, via MSDN:
"Note The End statement stops code execution abruptly, without
invoking the Unload, QueryUnload, or Terminate event, or any other
Visual Basic code. Code you have placed in the Unload, QueryUnload,
and Terminate events of forms and class modules is not executed.
Objects created from class modules are destroyed, files opened using
the Open statement are closed, and memory used by your program is
freed. Object references held by other programs are invalidated.
The End statement provides a way to force your program to halt. For
normal termination of a Visual Basic program, you should unload all
forms. Your program closes as soon as there are no other programs
holding references to objects created from your public class modules
and no code executing."
It will always return but that doesn't mean its a problem. I suggest you use Exit Sub as follows:
Sub NewPracticeSub()
Call otherpracticesub
**Exit Sub**
'Nothing more can execute here so its no longer a worry
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I would just like the program to end itself.
Application.Exit just keeps rolling me back in a loop.
EDITED to Include Code::
Module Module 1
Sub Main()
Sub1()
Sub2()
End Sub
Sub1()
EndSub
Sub2()
End Sub
End Module
EDIT: It seems to be looping back here to Sub ChooseDomain2.. I am including Sub 1 as well.
Sub ChooseDomain1()
Dim DomainName As Object
'Get List of all users on Domain using WinNT
DomainName = InputBox(messageOK, Title, defaultValue)
de.Path = "WinNT://****".Replace("****", DomainName)
If DomainName Is "" Then ChooseDomain2() Else StoreUserData1()
End Sub
Sub ChooseDomain2()
MsgBox("Welcome to the Domain Searcher. Click OK to Auto Search for Domain")
Dim MsgBoxResult As Object = ActiveDirectory.Domain.GetCurrentDomain.Name
MsgBoxResult = InputBox(messageCan, Title, MsgBoxResult)
de.Path = "WinNT://*****".Replace("*****", MsgBoxResult)
StoreUserData1()
End Sub
When it hits end Module it Just starts back from Square one.
Modules don’t execute at all – so it never “hits end module” and never starts “from square one”. Modules merely group methods that can be executed, and Main is a special method that serves as the start of your application.
That said, your code is guaranteed (!) not to execute repeatedly. Also, there is no Application.Exit anywhere in your code so it’s hard to see what you are actually executing. Not the code you showed, anyway.
Note that VB potentially executes code that you didn’t write (code can be auto-generated by the compiler, in particular the application framework) but this doesn’t seem to be happening in your case, and shouldn’t loop in any case. But again, this is impossible to say from the information you have given.
Application.Exit is not required as the console app will quit after it finishes executing the last line in Sub Main. As previously mentioned it is likely you have Sub1 calling Sub2 (or something similar), so set a breakpoint on the start of each sub to find which one is continually being called. Then you can do a search in your code to find where this sub is being called from.