I have a VBA excel userform that is misbehaving. Basically I want my code to pause until a command button on a userform is pressed. Any ideas?
In a module:
Public okayclicked as boolean
Sub MyThing
UserForm1.Show
Do Until okayclicked
DoEvents
Loop
UserForm1.Hide
End Sub
In my userform:
Sub CommandButton1_Click()
okayclicked = True
End Sub
Not sure what else to try to make this work. Any help is appreciated! Thanks!
'module
Sub MyThing1
UserForm1.Show
End Sub
Public Sub MyThing2
'....rest of code
End Sub
'userform
Sub CommandButton1_Click()
MyThing2
Me.Hide
End Sub
Either use a MsgBox to ask for the user input, or set your form to be Modal (MSDN link).
A Modal form will allow you to continue to work in the form, and any code underlying the form will continue to run, but the main code that calls the form will be 'blocked'.
Should the underlying Excel sheets be modified during the period of pause, you can add code when the button press is detected (i.e. when the Form is closed or then the MsgBox OK button is pressed) to update the relevant parts of the Form.
But we have digressed from the original simple question and many other factors (such as how the underlying sheets will be modified when the form is open) come into play.
Consider if a change in the underlying sheet has to change the form immediately or if it can occur when the user resumes the form/code.
Consider if the functions you want can be done natively in the spreadsheet and minimise the use/functionality of the form.
Consider other event driven options where the code is broken into natural sections, functions, subroutines or modules and each section only runs when triggered by the right event (sheet change, user input, subroutine finished, form exit etc.)
Related
I have a requirement for VBA script in Microsoft Word to pause so that the user can select text that will be copied to the clipboard so that it can exported to an Excel file. The user will make a number of selections and finally indicate he/she is done when the contents of the clipboard will be copied to a template Excel file.
I have the code working to copy each selection to the clipboard and then all rows to the Excel file. But I need assistence in figuring out how to pause the code to allow the user to make the selection and then restart the code to copy the selection to the clipboard. I am able to get the userform with toggle switch to switch states and labels when pressed. But have not figured out how to pause the VBA code to allow the user to navigate to the next section of the Word document for the next selection.
The Stakeoverflow question/answer below appears to address this requirement but I have not been able to get it to work. It appears that the code is incomplete.
Pause VBA macro, allow user to make a selection, and restart where it left off
Can someone provide example VBA code that accomplishes this?
Your assistence is much appreciated as I have been beating my head against the wall and it is starting to hurt!
There's no way in VBA to "pause" a macro. Code must run to completion... Unless there's a command for user input.
Input can be requested via the InputBox and MsgBox methods, but those block access to the document because they're modal. A UserForm, however, can be set to display as non-modal, meaning it stays on top, but doesn't block access to the document or the application features. Since you're already working with a UserForm, this can be implemented relatively easily.
In the small example below, the Continue button runs the code to perform an action on the user selection. When Done is clicked the entire code is exited and the form unloaded.
Code behind the user form
Option Explicit
Private Sub cmdContinue_Click()
Debug.Print Selection.Range.Text
End Sub
Private Sub cmdDone_Click()
Me.Hide
End Sub
Private Sub UserForm_Activate()
'Position the form near the top-left of the window
'So that the user can work with the document
Me.Top = Application.ActiveWindow.Top + 50
Me.Left = Application.ActiveWindow.Left + 50
End Sub
Code in a regular module
Option Explicit
Sub DisplayModeless()
Dim frm As frmModelessForInput
Set frm = New frmModelessForInput
frm.Show False 'Display as non-modal
Set frm = Nothing
End Sub
I have created a Word 2010 VBA Macro Sub with a UserForm. The Sub searches for ==Codes== in a form document, places the found ==code== as a label into the Userform and then allows the user to replace the ==code== with his or her input in the Combobox (part of the same UserForm).
Each string of inputted data is then saved to the Combobox list in the UserForm for later selection if needed.
This works fine until this Macro/Userform expires because a searched document is completed (or cancelled).
I would then like to open the next form document, and in the new launch of this same Macro/Sub retain the former combobox list of data (as options to fill this next opened document - for instance, the code ==Client Name== will come up frequently, and I'd rather select a combobox list entry rather than having to type the client name over and over)
But I can't seem to keep the combobox list in the new launch of this Macro Sub populated with the previous combobox data - even if I isolate this routine as a separate module and pre-define the variables with "Public" dimensions.
So, before I knock myself out trying to figure this out ... just a simple question:
Once a Macro terminates are all of the Public variables "dropped"? When I used to program in DOS WP.51 Macros you could keep data strings in the RAM endlessly (until you "killed" them, or closed WP)
If the Public variable are not "dropped", could someone give me a sample of code by which Public variables could be retained and populated into a duplicately launched combobox userform.
Any ideas, howsoever brief, would help
Thanks much in advance. . .
Mike
Not entirely sure what you're trying to do, but what I'd recommend is the following (Assuming that the form is named, "UserForm1" and then "UserForm2":
1) Create a Module that you use to open the form using:
Sub test()
UserForm1.Show
'Rest of things that you want to do...you will be able to access the values in your combobox in userform1
UserForm2.Show
End Sub
2) In your UserForm1, include a button that will close the form and include the following code:
Sub btn_Exit_Click()
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = 0 Then
Cancel = True
MsgBox "The X is disabled, please use a button on the form.", vbCritical
End If
End Sub
This will allow you to maintain control of the UserForm and ensure that you can keep the values. It may be possible to disable the Closing button, but I wasn't able to figure it out (my experience with forms is mostly in Access and those have different properties).
Hiding the form keeps the values so that you can look at them whereas when the user closes the form you lose the values that were in it.
*Note: Code to disable the X button taken from VBA Express
My question is: using VBA in Excel 2013 how can I gracefully close an entire instance of Excel when the user decides they don't want to fill out a UserForm and clicks quit or cancel?
Currently, if the user clicks quit or cancel, I check to see if my instance is the only one open. If it is not, I can use ThisWorkbook.Close and I think I will be okay. However, if it is, I do not want the application to still be present, so I used Application.Quit. This, though, tries to finish running the macro, throws errors (originally "type mismatch" because I unload the form), and only closes after I click "Debug" or "End" (which it does so fast for either I cannot actually debug). I'm ignoring the first case for now and just trying to exit the entire application. It's a very long macro with a lot of subroutines and functions, so for debugging and posting here, I have shortened it. The type mismatch error no longer occurs, but I believe that was a consequence of the actual error: code running after the command to close the application is called.
First, here's the code that starts everything:
Private Sub CommandButton1_Click()
Call form_variables
frm_REQUEST.Show
Call a_REQUEST_main
End Sub
The subroutine
form_variables
is a subroutine that creates public variables so I can store the data from the UserForm.
frm_REQUEST.Show
initializes (including calling a function that finds another workbook, extracts a list, does some formatting, closes the workbook and enters the list into the userforms drop down box) and shows the form, and finally
a_REQUEST_main
uses the public variables (where UserForm data is stored) and does its thing (but shouldn't do anything if the UserForm is closed).
The code that is executed when .Show is called is:
Private Sub UserForm_Initialize()
' Get job numbers from other workbook
Dim job_selection_list As Variant
job_selection_list = get_job_list()
With frm_REQUEST.Job_Number_ComboBox
.List = job_selection_list
End With
' set focus on Job Numbers
JN_combobox.SetFocus
End Sub
Private Sub cancel_button_Click()
Set job_selection_list = Nothing
Unload Me
Application.Quit
End Sub
Private Sub submit_button_Click()
' Values from userform saved as global (?) variables so other subroutines can access.
End Sub
I stepped through the program and saw that, once Application.Quit is called in the UserForm, the macro, in the main subroutine, steps to
Call a_REQUEST_main
but it should really just close everything out. I tried doing "save" commands, and changing the order of things, and read about objects needing to be set to nothing (hence the setting of the job_selection_list which is created when the drop down list is initialized), but I cannot seem to get this to work, or find anything online. Can anyone provide some guidance or let me know of a better way to close an excel instance? Help me Stack-Overflow Kenobi, you're my only hope!
Thanks.
Just add a variable to account for when the user closes the form
in the form
'hold flag if users cancels form
Public btnCancel As Boolean
Private Sub CommandButton1_Click()
Unload Me
btnCancel = True
End Sub
'set the flag each time the form active
Private Sub UserForm_Activate()
btnCancel = False
End Sub
then in your code
Call form_variables
frm_REQUEST.Show
If frm_REQUEST.btnCancel Then
Application.Quit
Else
Call a_REQUEST_main
End If
Put Application.Quit in the form's Terminate event handler instead of in the button's Click event handler.
The reason is that clearly the procedure will keep running even if the form has unloaded. So use the events to your advantage.
Putting it in the Click event will unload the form, but the procedure will keep running which of course may raise errors or other undesired effects.
Note: You may be prompted to save/discard changes (if any) to the workbook.
Is there anyway to have a userform that acts modeless, while still pausing code execution like a modal form?
I'd like the userform to show, but still allow interaction with the parent program. Modal forms block interaction with the parent program. A modeless form would work, but I would like the code execution to pause while the form is up.
I've worked around this by creating an infinite loop that checks if the form is visible, but that seems a bit hacky.
Public Sub GetFormInfoAndDoStuff
ufForm.show vbModeless
Do while ufForm.Visible
DoEvents
Loop
' Do other stuff dependent on form
End Sub
EDITED to clarify that code after .show exists which must execute after the user form is done
You should be able display the form as vbModeless and only execute code when specifically requested, i.e., from a CommandButton or other control.
You then leave the form visible/shown until it is specifically closed, via the "X" button or via another control which calls the UserForm_Terminate event.
In order to achieve this, you may need to move some of your executable code in to another subroutine and/or module, and call this subroutine for example from a CommandButton_Click event.
You already have a subroutine somewhere that contains a line like:
Sub ShowTheForm()
UserForm1.Show vbModeless
End Sub
So the form is displayed properly to allow user-input to the parent application.
You don't really need to put any other code in the above module. We will put the other code in other modules/subs, and then call it from user controls like command buttons.
Example:
Take all of your executable code, and put it in another subroutine (and if it suits your organizational preference, another module), like:
Sub MyMacro(msg$)
MsgBox msg
End Sub
On the UserForm, add a command button and assign it the following code:
Sub CommandButton1_Click()
MyMacro "hello"
End Sub
Now, the form will display until the user clicks the "X" button. Code will only run when called from the command button.
EDIT FOR CLARIFICATION
You don't need to "pause" the execution using this method. Execution ends once the form is displayed modelessly, and the form persists. The object has some events which you may use to trigger further execution of code.
Here's what I do.
This example is for a form I called "Find Header". The code tries to find several column headers, but the markers for a few of them may be missing (and the header text may have been overwritten with something random), so I may need to pause and ask the user to locate (click on) some of the headers for me:
First, put this declaration in a standard module:
Public bDlgFindHeaderIsShowingModeless As Boolean
Then, put this in the event procedure for any button or other control that dismisses the modeless dialog, such as the Click events for the form's OK and Cancel buttons:
bDlgFindHeaderIsShowingModeless = False
Then, put this wherever in your code you want to show the modeless form while paused for user interactivity:
bDlgFindHeaderIsShowingModeless = True 'init
frmFindHeader.Show vbModeless
Do
If Not bDlgFindHeaderIsShowingModeless Then Exit Do
DoEvents
Loop
Yes, it churns the CPU, so you might not want to do it if you're on a single-core processor and there are critically important background processes running. But it works; the user is able to easily and smoothly interact with Excel while the modeless form displays. The user doesn't feel like they are fighting an endless loop.
The Best method would be to use two different subs. I was able to solve this problem without splitting my sub as follows:
Public Mode as Boolean
Sub Stuff()
If Mode Then
Goto Continue
End If
'Code before Userform
Mode = True
Userform.Show vbModeless
Exit Sub
Continue:
Mode = False
'Rest of your code
End Sub
I made "Mode" a global variable because I use this userform for multiple subs. If you are using a single sub you can use it locally. I also made "Mode" false when opening this workbook by going under "ThisWorkbook" Tab and adding the following code:
Private Sub Workbook_Open()
Mode = False
End Sub
This again will only be needed if you use your userform for more than one sub.
Last add this code under your Userform code when your proceed button is pressed.
Private Sub Confirm_Click()
Userform.hide
if Mode Then
Call Stuff
End If
End Sub
If you are only are using the one sub method skip the if statement and just call the sub.
My module code calling the userform:
PreInfo.Show
My userform code:
Public Sub PreInfo_Initialize()
Dim Invoice, Name, Model, Crank, MyValue1, StrokeL As Variant
'Dim ListBox1 As ListBox
Dim c As Range
Dim oneControl As Object
'Empty Text Boxes and Set Focus
For Each oneControl In PreInfo.Controls
Select Case TypeName(oneControl)
Case "TextBox"
oneControl.Text = vbNullString
'Case "ListBox"
'oneControl.AddItem "Test"
End Select
Next oneControl
With lbTest
.AddItem Item:="2 Cylinders" '3 different syntax used as test to isolate issue
.AddItem "3 Cylinders"
.AddItem ("5 Cylinders")
End With
Invoice.TextBox.SetFocus 'Activate?
End Sub
My module code does not trigger my userform initialize sub, therefore nothing in that sub runs. I cannot figure out why this is happening. I would greatly appreciate any help!
When this code runs, userform pops up, however none of the listbox items are added
Userform_Initialize event is triggered by a line like this called in a module:
Load Userform1
In order for it to be triggered again, you'll need to unload the userform (not simply hide it). This can be done either after the load call within the module:
Unload Userform1
Or anywhere within the Userform's code:
Unload Me
Notice that the events Initialize and QueryClose will be triggered by the Unload call as well (QueryClose is also triggered when the close button on the top right corner is pressed), so I really recommend you refrain from using Initialize. Instead, After the Load call, add the initialize code within the same module (or create a separate sub if it'll be called from multiple places).
Sub LoadThatUserform
Load Preinfo
'All textboxes are loaded with their value set to vbnullstring, _
unless you specified otherwise in the Properties box.
With ThatUserform.lbTest
'Answering this test
.AddItem Item:="2 Cylinders" 'Here you used the parameter name. _
It's entirely optional, which is why the one below _
also works. It's necessary, however, if you wanna skip _
an optional parameter on a procedure call.
.AddItem "3 Cylinders"
.AddItem ("5 Cylinders") 'This will theoretically create a _
run-time error: a procedure call either outside of a Call _
statement or not setting a value to a variable or property _
doesn't require parentheses.
End With
'After loading, show the form
Preinfo.Show
'Showing, if not as modeless, stops code execution for the user _
to make changes to the form. Once he presses a button _
or whatever, and the form is hidden, code will resume. _
After you grab every form data you need, just call Unload.
Unload Preinfo
End Sub
Last but not least, if you're running a Modeless form (let's code run in the background while showing), you'll need to use the Activate event for the code to run. The event sequence is:
Userform_Initialize, after Load Userform
Userform_Activate, after Userform.Show
Userform_QueryClose, after Unload Userform, pressing the closing "X" or terminating via closing Excel/Task Manager
Userform_Terminate, when it's really gonna end (though I have no clue how this is used).
I had the same problem, and found a very simple solution.
In your case, instead of using
Public Sub PreInfo_Initialize()
use
Public Sub UserForm_Initialize()
I have figured it out. Long story short, my Module needed the following code:
Userform.Userform_Activate 'THIS IS THE NEW CODE
Userform.Show 'existing code, unchanged
which signals the userform to activate before it is open (calling "initialize", then showing the userform for the user to alter).
Userform.Show SHOULD cue this activation sub to run, however mine was not for whatever reason. This fixes the issue until I determine why Userform.Userform_Activate was not called like it should have been.
I use userform.hide when the user clicks the "continue" button on the userform, which closes the userform and prints the userform inputs into a worksheet
What is happenening is that your userform is never unloaded from memory. Hide only removes it from view.
This means that it is only initialized the first time you run the userform within that Excel instance.
You can prevent this by using
unload me
or
End
instead of UserForm.Hide depending on your other code. You could also potentially use the UserForm_Activate method instead of UserForm_Initialize method.
To populate the ListBox, use:
lbTest.AddItem "3 Cylinders"
etc outside the With statement.
you have to keep syntax UserForm_Initialize() to make it happen
Cheers