My question may be very foolish. but i would like to know is it possible and how?
from one of the function\sub can i create another sub\function.
Example:
Sub first()
x = 10
if x = 10 Then
'Add Another Sub based on the Condition in another module.
Sub Second()
End Sub
End If
End Sub
My real problem is i am trying add new sheet in a loop based on some condition. if i am able to add sheet, then i need to create some buttons in another sheet, as well as function for those buttons.
Please help on this.
By default, my approach would be:
Sub first()
x = 10
if x = 10 Then
'Call Another Sub based on the Condition.
Call Second()
End If
End Sub
Sub Second()
' do stuff
End Sub
If you are creating new sheets and adding buttons with code behind them, all of the code, button design, etc. should be done. Any customization would be through parameters.
This is more about the design of your code. Firstly, you can't put a sub inside another sub, that's just not how it works unfortunately.
What you can do is use scope and conditions to control the flow of your code and make sure that certain parts are only executed when you want them to be.
The easiest, and recommended, method is to just write the other sub anyway but only call it if a certain condition is met.
Public Sub Main_Sub()
Dim x As Integer
x = 11 '// Change x to 5 and the other sub won't run
If x > 10 Then
Other_Sub
'// Code execution will resume here once 'Other_Sub' has finished
MsgBox "Other Sub complete!"
End If
End Sub
Private Sub Other_Sub()
MsgBox "You're in the other sub now!"
End Sub
Another way is to simply exit the sub if your condition isn't met, although this is used more for error handling than conditional execution:
Public Sub Main_Sub()
Dim x As Integer
x = 11 '// Change x to 5 and the other sub won't run
If x > 10 Then
Other_Sub
'// Code execution will resume here once 'Other_Sub' has finished
MsgBox "Other Sub complete!"
Else
Exit Sub
End If
MsgBox "You will only see this message if x was greater than 10!"
End Sub
Private Sub Other_Sub()
MsgBox "You're in the other sub now!"
End Sub
One final method could be using labels, and although it's a viable solution I do advise against it because it severely affects the readability and logic of your code:
Public Sub Main_Sub()
Dim x As Integer
x = 11 '// Change x to 5 and the other sub won't run
If x < 11 Then GoTo Skip_Other_Sub:
Other_Sub
Skip_Other_Sub:
MsgBox "You will see this regardless of the other sub being run!"
End Sub
Private Sub Other_Sub()
MsgBox "You're in the other sub now!"
End Sub
The main point here is that it's better to write all of the code that may or may not be needed and call it conditionally rather than trying to create code "on the fly" which is messy at best and requires programmatic access to the VBE which can leave your machine vulnerable if you aren't 100% confident with what you're doing.
Related
Dear Readers
I am trying to make individual Serial numbers ( incrementing number) for some Forms in Microsoft Word, so we can track each one of them much simpler between people.
I used this link and it did work,
but it needs always running Macro and I couldn't figure out how to make it automatic with just a simple Ctrl+P shortcut, so I used this second link for that reason,
finally, it looked so great but there is a problem since the second link is a just "before Print" code, there is always one extra print at the end since the Printing process starts exactly after macro ended. any cancel print process code out there?
how can I overcome this one?
Codes under the document
Private Sub Document_Open()
Register_Event_Handler
End Sub
Codes under the Module
Dim X As New EventClassModule
Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Codes under the class
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
' Run code directly inside this Sub OR
MsgBox "Before Print"
' Call another Sub here, note, Sub and Module name can't match
Call FilePrint
' See https://www.freesoftwareservers.com/wiki/compile-error-expected-variable-or-procedure-not-module-macros-microsoft-office-29982732.html
End Sub
and finally Codes of FilePrint section as a Module
Sub FilePrint()
Dim i As Long, j As Long
With ActiveDocument
j = CLng(InputBox("How many copies to print?", "Print Copies"))
For i = 1 To j
With .CustomDocumentProperties("Counter")
.Value = .Value + 1
End With
.Fields.Update
ActiveDocument.PrintOut Copies:=1
Next
.Save
End With
End Sub
First time poster but long time reader. I finally have a question that is either not here or I simply can't phrase it correctly enough to return an answer.
What I'm trying to achieve is to have a single sub that I can call from other subs, that is 90% the same for everything and only changes the last 10% based on the sub that called it (possibly via an IF statement)
maybe something like:
Sub A()
Call Main
End Sub
Sub B()
Call MAIN
End Sub
Sub MAIN()
If Sub A called Sub MAIN Then
Answer = 1
elseif: Sub B called Sub MAIN Then
Answer = 2
Endif
End sub
I'm thinking this may not be possible but would be great to use something like this rather than writing a heap of almost identical subs.
Any advice is appreciated!
Just pass an optional parameter if it's called from a specific location:
Sub A()
MAIN
End Sub
Sub B()
MAIN True
End Sub
Sub MAIN(Optional fromB As Boolean = False)
If fromB Then
Answer = 2
Else
Answer = 1
End If
End Sub
In VBA for excel, I have a userform then I want this to show only for 1 instance. Even if the user re-open it, it won't open again. Is there any code for it? well, I'm also using this code for my login:
Private Sub UserForm_Initialize()
User.Caption = Environ("Username")
End Sub
I'm thinking if i can use this code in my problem. Hoping for a quick response. thanks guys, you're awesome!
Yes, it's possible.
You have to add new sheet. In a cell A1 type 0 (zero), then hide it. In a code which calls UserForm, use this:
Sub ShowMyForm()
If ThisWorkbook.Worksheets("HiddenSheet").Range("A1")=0 then MyUserForm.Show
End Sub
In a form:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
ThisWorkbook.Worksheets("HiddenSheet").Range("A1")=1
ThisWorkbook.Save()
DoEvents
End Sub
If you don't want to add an extra sheet just to store one bool, you can set a custom document property like this:
Private Sub Workbook_Open()
On Error Resume Next
Dim test As Boolean
test = Me.CustomDocumentProperties("UserFormShown").Value
If Err.Number = 0 Then Exit Sub
UserForm1.Show
Me.CustomDocumentProperties.Add "UserFormShown", False, msoPropertyTypeBoolean, True
End Sub
If the property hasn't been set yet it will throw an error, so trapping an error lets you know if you've set the property (and shown the form).
I have created a userform that has two buttons on it. One is called CmdCon6 and the other is called CmdLbs6. When clicked, they are suppose to close the current userform, pull up another userform, and pull values from the 4th column in sheet18 and add them to a combobox named x48 (both of the new userforms have a combobox named x48)in the new userform. The range of cell values to be added to the combobox x48 will flucuate, but never exceed 20 (hence why I added a loop). Everything works great and does what it is suppose to do when I click the CmdCon6 button, but when I click the CmdLbs button, it gives me a run-time error '70' Permission denied and highlights the 20th line of code (line between the If and end if in the Sub CmdLbs_Click()).
I have tried to change the name of the combobox x48 in the frmInputLbs6 userform and keep it as x48 for the frmInputCon6 userform, but I still received the same error.
Any suggestions to fix this issue? I'm stumped, and can't think of a way around it. Thanks in advance!
Private Sub CmdCon6_Click()
Unload Me
For x = 1 To 20
If Sheet18.Cells(x, 4).Value <> "" Then
frmInputCon6.x48.AddItem Sheet18.Cells(x, 4)
End If
Next x
frmInputCon6.Show
End Sub
Private Sub CmdLbs6_Click()
Unload Me
For x = 1 To 20
If Sheet18.Cells(x, 4).Value <> "" Then
frmInputLbs6.x48.AddItem Sheet18.Cells(x, 4)
End If
Next x
frmInputLbs6.Show
End Sub
Controls on UserForms are private by default. You need to access them through the Controls collection:
Private Sub CmdLbs6_Click()
Unload Me
For x = 1 To 20
If Sheet18.Cells(x, 4).Value <> "" Then
frmInputLbs6.Controls("x48").AddItem Sheet18.Cells(x, 4)
End If
Next x
frmInputLbs6.Show
End Sub
I'd also note that although you mention that "they are suppose to close the current userform", this isn't what happens. Your forms also aren't actually being fully unloaded until the other form is closed. The .Show method defaults to modal so in the code above frmInputCon6 doesn't fully unload until after frmInputLbs6 is closed.
Just something to keep in mind, because it really messes up your event stack. You can see the results by with this simple test code. Add UserForm1 and UserForm2, and put a button on each of them and the following code:
UserForm1:
Private Sub CommandButton1_Click()
Unload Me
UserForm2.Show
End Sub '<--Put a breakpoint here.
Private Sub UserForm_Terminate()
Debug.Print "UserForm1 closed"
End Sub
UserForm2:
Private Sub CommandButton1_Click()
Unload Me
UserForm1.Show
End Sub '<--Put a breakpoint here.
Private Sub UserForm_Terminate()
Debug.Print "UserForm2 closed"
End Sub
Put a breakpoint on the End Subs of each Click() event, fire up one of the forms and hit the buttons to hop back and forth a few times. Then close one of the forms and count how many times you hit the breakpoints before you actually exit.
Say I have a button embedded into my spreadsheet that launches some VBA function.
Private Sub CommandButton1_Click()
SomeVBASub
End Sub
Private Sub SomeVBASub
DoStuff
DoAnotherStuff
AndFinallyDothis
End Sub
I'd like to have an opportunity to have some sort of a "cancel" button that would stop SomeVBASub execution at an arbitrary moment, and I'm not into involving Ctrl+Break here, 'cause I'd like to do it silently.
I guess this should be quite common issue, any ideas?
Thanks.
Add another button called "CancelButton" that sets a flag, and then check for that flag.
If you have long loops in the "stuff" then check for it there too and exit if it's set. Use DoEvents inside long loops to ensure that the UI works.
Bool Cancel
Private Sub CancelButton_OnClick()
Cancel=True
End Sub
...
Private Sub SomeVBASub
Cancel=False
DoStuff
If Cancel Then Exit Sub
DoAnotherStuff
If Cancel Then Exit Sub
AndFinallyDothis
End Sub
How about Application.EnableCancelKey - Use the Esc button
On Error GoTo handleCancel
Application.EnableCancelKey = xlErrorHandler
MsgBox "This may take a long time: press ESC to cancel"
For x = 1 To 1000000 ' Do something 1,000,000 times (long!)
' do something here
Next x
handleCancel:
If Err = 18 Then
MsgBox "You cancelled"
End If
Snippet from http://msdn.microsoft.com/en-us/library/aa214566(office.11).aspx
Or, if you want to avoid the use of a global variable you could use the rarely used .Tag property of the userform:
Private Sub CommandButton1_Click()
Me.CommandButton1.Enabled = False 'Disabling button so user cannot push it
'multiple times
Me.CommandButton1.caption = "Wait..." 'Jamie's suggestion
Me.Tag = "Cancel"
End Sub
Private Sub SomeVBASub
If LCase(UserForm1.Tag) = "cancel" Then
GoTo StopProcess
Else
'DoStuff
End If
Exit Sub
StopProcess:
'Here you can do some steps to be able to cancel process adequately
'i.e. setting collections to "Nothing" deleting some files...
End Sub
what jamietre said, but
Private Sub SomeVBASub
Cancel=False
DoStuff
If not Cancel Then DoAnotherStuff
If not Cancel Then AndFinallyDothis
End Sub
I do this a lot. A lot. :-)
I have got used to using "DoEvents" more often, but still tend to set things running without really double checking a sure stop method.
Then, today, having done it again, I thought, "Well just wait for the end in 3 hours", and started paddling around in the ribbon. Earlier, I had noticed in the "View" section of the Ribbon a "Macros" pull down, and thought I have a look to see if I could see my interminable Macro running....
I now realise you can also get this up using Alt-F8.
Then I thought, well what if I "Step into" a different Macro, would that rescue me? It did :-)
It also works if you step into your running Macro (but you still lose where you're upto), unless you are a very lazy programmer like me and declare lots of "Global" variables, in which case the Global data is retained :-)
K
~ For those using custom input box
Private Sub CommandButton1_Click()
DoCmd.Close acForm, Me.Name
End
End Sub
This is an old post, but given the title of this question, the END option should be described in more detail. This can be used to stop ALL PROCEDURES (not just the subroutine running). It can also be used within a function to stop other Subroutines (which I find useful for some add-ins I work with).
As Microsoft states:
Terminates execution immediately. Never required by itself but may be placed anywhere in a procedure to end code execution, close files opened with the Open statement, and to clear variables*. I noticed that the END method is not described in much detail. This can be used to stop ALL PROCEDURES (not just the subroutine running).
Here is an illustrative example:
Sub RunSomeMacros()
Call FirstPart
Call SecondPart
'the below code will not be executed if user clicks yes during SecondPart.
Call ThirdPart
MsgBox "All of the macros have been run."
End Sub
Private Sub FirstPart()
MsgBox "This is the first macro"
End Sub
Private Sub SecondPart()
Dim answer As Long
answer = MsgBox("Do you want to stop the macros?", vbYesNo)
If answer = vbYes Then
'Stops All macros!
End
End If
MsgBox "You clicked ""NO"" so the macros are still rolling..."
End Sub
Private Sub ThirdPart()
MsgBox "Final Macro was run."
End Sub