I have a VBA sub that makes a call to a sub that was written by someone else. occasionally, the other sub opens a MsgBox with an OK button. The other sub takes a long time to run, and I am calling it hundreds of times, so I want to be able to run this overnight. Unfortunately, I can't figure out a way to automatically click OK on the MsgBox.
I have tried
Application.DisplayAlerts = False
but this doesn't suppress message boxes.
Is there any way to do this?
Thanks
One way to do this is slightly modifying the code of the original sub. You will need to have the necessary permissions tough...
Modify the header of the original sub by throwing in an extra optinal parameter at the end setting the default value to True. This will result in something like Sub OriginalSubName(original set of parameters, Optional ShowMessages = True)
At the point where the msgbox is called, modify the code this way:
If showMessages = True Then 'The = True part is important here - see below.
showMessages is a Variant type
'The original statement that calls the msgBox
End If
Leave the rest of the code of the original sub unchanged
Modify the line where you call the original sub by throwing in False as an extra parameter. This results in OriginalSubNameyour set of parameters, False. This way you don't suppress the dialog box by default, but you do when you use it in your sub.
Wonder why I use an optional Variant type parameter?
The optional part: this prevents other existing subs from crashing when they call the modified sub
The Variant type part: optional parameters are always Variant type. That's also why you need to use If showMessages = True Then instead of just If showMessages Then.
Related
I am learning how to create input boxes and I keep getting the same error. I have tried two different computers and have received the same error. The error I get is a "Compile Error: Wrong number of arguments or invalid property assignment"
Here is my code:
Option Explicit
Sub InputBox()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = InputBox("give me some input")
ss.Range("A1").Value = Link
With ss
If Link <> "" Then
MsgBox Link
End If
End With
End Sub
When I run the code, it highlights the word "inputbox"
And help would be greatly appreciated.
Thanks,
G
Three things
1) Call your sub something other than the reserved word InputBox as this may confuse things. *Edit... and this alone will resolve your error. See quote from #Mat's Mug.
2) A̶p̶p̶l̶i̶c̶a̶t̶i̶o̶n̶.̶I̶n̶p̶u̶t̶B̶o̶x̶(̶"̶g̶i̶v̶e̶ ̶m̶e̶ ̶s̶o̶m̶e̶ ̶i̶n̶p̶u̶t̶"̶)̶ Use VBA.Interaction.InputBox("give me some input"). You can do this in addition to the first point. Documentation here.
3) Compare with vbNullString rather than "" . See here. Essentially, you will generally want to do this as vbNullString is, as described in that link, faster to assign and process and it takes less memory.
Sub GetInput()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = VBA.Interaction.InputBox("give me some input")
ss.Range("A1").Value = Link
' With ss ''commented out as not sure how this was being used. It currently serves no purpose.
If Link <> vbNullString Then
MsgBox Link
End If
' End With
End Sub
EDIT: To quote #Mat's Mug:
[In the OP's code, what is actually being called is] VBA.Interaction.InputBox, but the call is shadowed by the procedure's identifier "InputBox", which is causing the error. Changing it to Application.InputBox "fixes" the problem, but doesn't invoke the same function at all. The solution is to either fully-qualify the call (i.e. VBA.Interaction.InputBox), or to rename the procedure (e.g. Sub DoSomething(), or both.
Sub InputBox()
That procedure is implicitly Public. Presumably being written in a standard module, that makes it globally scoped.
Link = InputBox("give me some input")
This means to invoke the VBA.Interaction.InputBox function, and would normally succeed. Except by naming your procedure InputBox, you've changed how VBA resolves this identifier: it no longer resolves to the global-scope VBA.Interaction.InputBox function; it resolves to your InputBox procedure, because VBAProject1.Module1.InputBox (assuming your VBA project and module name are respectively VBAProject1 and Module1) are always going to have priority over any other function defined in any other referenced type library - including the VBA standard library.
When VBA resolves member calls, it only looks at the identifier. If the parameters mismatch, it's not going to say "hmm ok then, not that one" and continue searching the global scope for more matches with a different signature - instead it blows up and says "I've found the procedure you're looking for, but I don't know what to do with these parameters".
If you change your signature to accept a String parameter, you get a recursive call:
Sub InputBox(ByVal msg As String)
That would compile and run... and soon blow up the call stack, because there's a hard limit on how deep the runtime call stack can go.
So one solution could be to properly qualify the InputBox call, so that the compiler knows exactly where to look for that member:
Link = VBA.Interaction.InputBox("give me some input")
Another solution could be to properly name your procedure so that its name starts with a verb, roughly describes what's going on, and doesn't collide with anything else in global scope:
Sub TestInputBox()
Another solution/work-around could be to use a similar function that happens to be available in the Excel object model, as QHarr suggested:
Link = Application.InputBox("give me some input")
This isn't the function you were calling before though, and that will only work in a VBA host that has an InputBox member on its Application class, whereas the VBA.Interaction.InputBox global function is defined in the VBA standard library and works in any VBA host.
A note about this:
If Link <> "" Then
This condition will be False, regardless of whether the user clicked OK or cancelled the dialog by "X-ing out". The InputBox function returns a null string pointer when it's cancelled, and an actual empty string when it's okayed with, well, an empty string input.
So if an empty string needs to be considered a valid input and you need to be able to tell it apart from a cancelled inputbox, you need to compare the string pointers:
If StrPtr(Link) <> 0 Then
This condition will only be False when the user explicitly cancelled, and will still evaluate to True if the user provided a legit empty string.
This question already has an answer here:
Array argument must be ByRef
(1 answer)
Closed 6 years ago.
Given the following code:
I can not seem to successfully pass a Range Object Variable from one sub-function to another. I spent an entire day researching, and experimenting before I swallowed pride and came here.
Please read the comments below, and reply with any ideas you have regarding why the LAST two lines will not behave.
Public Sub doSomethingToRows(ROI As Range)
*'do Something with the cell values within the supplied range*
End Sub
'
Public Sub testDoAltRows()
Dim RegionOfInterest As Range 'is this an object or not?
'*The following yields: Class doesn't support Automation (Error 430)*
'*Set RegionOfInterest = New Worksheet 'this just gives an error*
Set RegionOfInterest = Worksheets("Sheet1").Range("A1")
RegionOfInterest.Value = 1234.56 '*okay, updates cell A1*
Set RegionOfInterest = Worksheets("Sheet1").Range("B5:D15")
RegionOfInterest.Columns(2).Value = "~~~~~~" '*okay*
'doSomethingToRows (RegionOfInterest) 'why do I get "OBJECT IS REQUIRED" error?
doSomethingToRows (Worksheets("Sheet1").Range("B5:C15")) 'but this executes okay
End Sub
From the msdn documentation of the Call keyword statement,
Remarks
You are not required to use the Call keyword when calling a procedure.
However, if you use the Call keyword to call a procedure that requires
arguments, argumentlist must be enclosed in parentheses. If you omit
the Call keyword, you also must omit the parentheses around
argumentlist. If you use either Call syntax to call any intrinsic or
user-defined function, the function's return value is discarded.
To pass a whole array to a procedure, use the array name followed by
empty parentheses.
From a practical standpoint, even though Subs can be called with or without the "Call" keyword, it makes sense to pick one way and stick with it as part of your coding style. I agree with Comintern - it is my opinion, based on observation of modern VBA code, that using the "Call" keyword should be considered deprecated. Instead, invoke Subs without parenthesis around the argument list.
And now the answer to the important question:
Why does your code throw an error?
Take for example the following Subroutine:
Public Sub ShowSum(arg1 As Long, arg2 As Long)
MsgBox arg1 + arg2
End Sub
We have established that, if not using the Call keyword, Subs must be invoked like so:
ShowSum 45, 37
What happens if it were instead called like ShowSum(45, 37)? Well, you wouldn't even be able to compile as VBA immediately complains "Expected =". This is because the VBA parser sees the parenthesis and decides that this must be a Function call, and it therefore expects you to be handling the return value with an "=" assignment statement.
What about a Sub with only one argument? For example:
Public Sub ShowNum(arg1 As Long)
MsgBox arg1
End Sub
The correct way to call this Sub is ShowNum 45. But what if you typed this into the VBA IDE: ShowNum(45)? As soon as you move the cursor off of the line, you'll notice that VBA adds a space between the Sub name and the opening parenthesis, giving you a crucial clue as to how the line of code is actually being interpreted:
ShowNum (45)
VBA is not treating those parenthesis as if they surrounded the argument list - it is instead treating them as grouping parenthesis. MOST of the time, this wouldn't matter, but it does in the case of Objects which have a default member.
To see the problem this causes, try running the following:
Dim v As Variant
Set v = Range("A1")
Set v = (Range("A1")) '<--- type mismatch here
Notice that you get a "Type Mismatch" on the marked line. Now add those two statements to the watch window and look at the "Type" column:
+-------------+-----+--------------+
| Expression |Value| Type |
+-------------+-----+--------------+
|Range("A1") | |Object/Range |
|(Range("A1"))| |Variant/String|
+-------------+-----+--------------+
When you surround an Object with grouping parenthesis, its default property is evaluated - in the case of the Range object, it is the Value property.
So it's really just a coincidence that VBA allowed you to get away with "putting parenthesis around the argumentlist" - really, VBA just interprets this as grouping parenthesis and evaluates the value accordingly. You can see by trying the same thing on a Sub with multiple parameters that it is invalid in VBA to invoke a Sub with parenthesis around the argument list.
#PaulG
Try this:
Public Sub Main()
Debug.Print TypeName(Range("A1"))
Debug.Print TypeName((Range("A1")))
End Sub
okay, I knew after I posted this question I'd be struck by lighting and receive an answer.
When passing an object VARIABLE to a sub-function and wishing to use parentheses "()", one must use CALL! Thus the correction to my code sample is:
**CALL doSomethingToRows(RegionOfInterest)**
Thank you!
Maybe we're talking about different things, but here's an example to make it a bit clearer what I mean.
Option Explicit
Sub TestDisplay()
Dim r As Range
'Create some range object
Set r = Range("A1")
'Invoke with Call.
Call DisplaySomething(r)
'Invoke without Call.
DisplaySomething r
End Sub
Sub DisplaySomething(ByVal Data As Range)
Debug.Print "Hi my type is " & TypeName(Data)
End Sub
Both calls work perfectly. One with Call and the other without.
Edit:
#Conintern. Thanks for explaining that. I see what is meant now.
However, I still respectively disagree.
If I declare the following:
Function DisplaySomething(ByVal Data As String)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
and invoke it:
Debug.print DisplaySomething(Range("A1"))
I believe that Excel has been clever and converted to a string. It can do that by invoking the Default Parameter and can convert to a string.
However, as in the original parameter example, If I declare the following:
Function DisplaySomething(ByVal Data As Range)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
There is no call on the Default Parameter, however it is called, because Excel was able to resolve it to that type.
Function DisplaySomething(ByVal Data As Double)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
will return a double because it was able to coerce to a double.
Indeed in those examples the Default was called.
But in this example we are defining as Range. No Default called there however it is invoked - brackets or no brackets.
I believe this is more to do with Excel and data coercion.
Similar to the following:
Public Function Test(ByVal i As String) As Integer
Test = i
End Function
and invoking with:
Debug.print Test("1")
BTW, yes I know this isn't an object without a Default parmeter. Im pointing out data coercion. Excel does its best to resolve it.
Could be wrong mind you...
I am having an issue with some VBA code which seems so basic but yet is just not working. I am new to VBA so possible that I am missing something.
The code is supposed to check a cell (XFD3002) to see if it equals 0, if it does then display a message and stop the file from saving. For background, there is a check to see that certain cells are filled and if not it will return a 0.
The code is:
Private Sub Check_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim check As Integer
check = Sheets("Data").Range("XFD3002").Value
If check = 0 Then
MsgBox ("You have missed one or more required field")
Cancel = True
End If
End Sub
Can anyone see something wrong with the above?
The sub name should be Workbook_BeforeSave.
The hardest to find errors are always the simplest ones. :D
You're seeing if what is in the check cell is a string, since you've put quotation marks around it - "0", but check has been declared as an integer. Try changing it to just 0 without the quotation marks.
EDIT: Please also ensure that this code appears in "TheWorkbook" module and change the name to Workbook_BeforeSave (as pointed out by #Pierre)
I know this might be a more of a general question, but I've looked at my code a 100 times and am not sure what's wrong. I set a global flag so that it can be used in different forms of my application.
Dim OverPopulated as Boolean
I have a function where I first set it to FALSE, and after it goes through some validating it gets set to TRUE.
Now i do a bunch of stuff, go through different forms, and the value stays correct the entire time. Basically I need this value for another form, so when I check
if OverPopulated = false then
MsgBox "You Can't do this and that"
exit sub
End if
Then all of the sudden it gets set to FALSE. I've went through every instance of OVERPOPULATED in my application and put a break point to it to make sure that nowhere do i set it to FALSE, except for the one time I set it to FALSE before my validation.
I only have it in 2 other forms in my applications. There is one place where the flag is being run twice through the same statement (just like the one above). The first time it goes through, the value is correct, then it goes through again and the value is set to FALSE.
Any ideas? if this is too vague, please let me know, I'll try to edit it.
EDIT: I deleted a lot of the code but here's how it looks.....
Dim OverPopulated as Boolean
' modular level
Private Sub ValidatePopulation()
Dim admDate as date
OverPopulated = False
admDate = Format(Now(), "mm/dd/yyyy")
Select Case revPURP
Case 0, 1, 2
'Check make sure these fields
'some if statements, checking, validation
Case Else ' no need to do a
End Select
OverPopulated = True 'I MAKE SURE IT GETS HERE and is set to TRUE!!!
End sub
Now, in other functions I used it like this...
If OverPopulated = False Then
If optStat (0).Value = True Or optStat (1).Value = True Then
MsgBox "You are not able to do this”, vbCritical, "Incorrect review status...."
TabPop.Tab = 6
End If
Exit sub
Else
'Proceed with SAVE
End If
So I have this. and I have another statement just like this somewhere else. This one gets hit twice. The first time I go through it line by line and it's FALSE, then the 2nd time around the value is TRUE.
It looks like for some reason the value was getting reset to FALSE. I took out the Dim OverPopulated as Boolean from my form code, and placed it in a GLOBAL module where all the other globals are stored. This seems to have fixed the issue of the value remaining the same while I'm manuevering in the application. Thank you for all your comments.
Global OverPopulated as Boolean
I'm running a VBA macro from SolidWorks. The form doubles as an input for two types of document. In the UserForm.Initialize subroutine I'm changing the name of the UserForm's Caption depending on which document type is open. Whenever I do this though, the program reruns UserForm.Initialize, and when it's all done, it carries on from where it left of, effectively running twice.
Does anyone know a way around this bizarre behaviour? I tried putting the FormName.Caption command into its own Sub but the result is the same.
Many thanks.
I can't replicate the problem and I don't know what SolidWorks is, so that may have something to do with it. Perhaps you can post a made-up example that shows Initialize being called twice.
My guess would be that it's related to auto-instantiating variables. When you use UserForm1, you are instantiating an object variable called UserForm1 that points to an object, also called UserForm1. It's similar to using the New keyword in a Dim statement. You never defined UserForm1 (the variable), but VBA did and the first time you use it, it instantiates automatically.
You should try to use the Me keyword when working inside the userforms class module (userforms are classes just like other objects except that they have a user interface element). In the Initialize event, say
Me.Caption = "blah"
instead of
UserForm1.Caption = "blah"
It could be (just a theory that I wasn't able to prove) that the flag that gets set to say "I'm pointing to a real instance" isn't set by the time you change the Caption property, and that by using the auto-instantiating variable UserForm1, you are forcing another instantiation.
Even better, don't use auto-instantiating variables, convenient though they are (and don't use the New keyword in a Dim statement either). You can control when your variables are created and destroyed and it's a best practice. Something like this in a standard module
Sub uftst()
Dim uf As UserForm1
Set uf = New UserForm1 'you control instantiation here
'Now you can change properties before you show it
uf.Caption = "blech"
uf.Show
Set uf = Nothing 'overkill, but you control destruction here
End Sub
Note that if the ShowModal property is set to False that the code will continue to execute, so don't destroy the variable if running modeless.
As Dick suggested, you should be able to stop the behavior by making sure to use me.caption instead of Userform1.caption.
Here's a way you can replicate the issue for those who are curious:
Create a Userform (Userform1) make sure you set ShowModal to false or you won't be able to see this.
In a module add the following:
Option Explicit
Sub ShowUserForm()
Dim uf As UserForm1
Set uf = New UserForm1
End Sub
In UserForm1 list the following code:
Option Explicit
Private Sub UserForm_Initialize()
UserForm1.Caption = "I'm UserForm1!" 'This will call the Initialize method of Userform1 not Me.
Me.Caption = "I'm Me!"
Me.Show
End Sub
Run ShowUserForm. You now have two Userforms with different captions.
Incidentally, if you have an Initialize method like I displayed adding Set uf = Nothing to the ShowUserForm sub actually fails to close either form.