问题
I have the below code which is a private sub which is initialized from Userform3.show in a public sub, as far as I am aware the code below works however it does not return to the public sub on completion.
Please note when I change the value of sheet8.range(I16) another private sub is called. However I believe the below code is the issue.
Can anyone advise how I can get the private sub to return to the public sub on completion?
Private Sub UserForm_Initialize()
'populate "Combo-Box with Boards
With Me.ComboBox1
.Clear ' clear previous items (not to have "doubles")
.AddItem "BISSB"
.AddItem "MORIB"
.AddItem "RMIB"
End With
End Sub
Private Sub CommandButton1_Click()
If Me.ComboBox1.ListIndex = -1 Then
UserForm3.Hide
MsgBox "No board was selected, please re-run macro and select appropriate board"
Exit Sub
Else
Sheet8.Range("I16").Value = ComboBox1.Text
End If
End Sub
Private Sub CommandButton2_Click()
UserForm3.Hide
MsgBox "No board was selected, please re-run macro and select appropriate board"
End
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = 0 Then
MsgBox "No board was selected, please re-run macro and select appropriate board"
End
End If
End Sub
Start of Public Sub
Sub newResumeAssessment_Click()
Dim answer As Variant
Dim Proceed As Boolean
Dim Uname As String
If UCase(Sheets("Main Menu").Range("A1")) = "YES" Then
answer = 6
Else
answer = MsgBox("Click Yes to start a Business Case." & _
vbCrLf & "Click No to resume the Business Case." & vbCrLf & _
"Click Cancel to go back to the main menu." & vbNewLine & _
vbNewLine & "Please note, you will need to load the board submission " & _
"tracker before you start a new business case.", 35, "Business Case")
End If
If answer = 6 Then
UserForm3.Show
回答1:
First thing, remove End
, everywhere. End
is a big red NUKE 'EM ALL button that ends execution right there and then - after End
has executed, it doesn't matter where you were in the call stack, for there is no call stack anymore; your code isn't running anymore, there's nowhere to "return" to.
Second thing, don't keep state in the default instance of a form. Treat it like the object it is, and New
up a new instance when you need one - that way you don't need to be bothered with Unload
ing it and/or resetting the state between calls: the _Initialize
handler will run every time, and there won't be a need to Clear
the items from the previous call, since you'll be working with a fresh instance every time. You do that like this:
With New UserForm3 'UserForm_Initialize handler runs here
.Show 'UserForm_Activate handler runs here
'anything after .Show will only run after the form is closed
If Not .Cancelled Then
Sheet8.Range("I16").Value = .ComboBox1.Text
End If
End With 'UserForm_Terminate handler runs here
Notice the worksheet is NOT being written to by the form - it's not its job! So how do we make that Cancelled
member legal?
First you name things and make CommandButton1
be OkButton
and CommandButton2
be CancelButton
- or whatever - just not Button1 and Button2.
I like that you're hiding the form instance instead of nuking it with Unload Me
, however you're explicitly working off the default instance again, which means the above New UserForm3
code will not be hiding the same instance that's being shown. NEVER qualify member calls with a default instance when you mean to work with Me
.
In other words:
UserForm3.Hide 'hides the default instance of UserForm3
Me.Hide 'hides whatever the current instance is
Hide 'same as Me.Hide
So. Add a Private isCancelled As Boolean
private field (module-level variable), and then expose a Public Property Get Cancelled() As Boolean
public property getter that returns it:
Option Explicit
Private isCancelled As Boolean
Public Property Get Cancelled() As Boolean
Cancelled = isCancelled
End Property
Next, make your cancel button set the flag:
Private Sub CancelButton_Click()
isCancelled = True
Me.Hide
End Sub
Then make the QueryClose
handler set it too - and use the existing named constants wherever possible:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
isCancelled = True
Me.Hide
End If
End Sub
Then implement your "happy-path" logic in the OK button's handler:
Private Sub OkButton_Click()
Me.Hide
End Sub
I would disable the OK button until the user makes a selection - that way they can either cancel, x-out, or make a valid selection!
Public Property Get SelectedBoard() As String
SelectedBoard = IIf(Me.ComboBox1.ListIndex = -1, vbNullString, Me.ComboBox1.Text)
End Property
Private Sub ComboBox1_Change()
ValidateForm
End Sub
Private Sub ValidateForm()
Me.OkButton.Enabled = (SelectedBoard <> vbNullString)
End Sub
Private Sub UserForm_Activate()
ValidateForm
End Sub
And now the caller can look like this:
With New UserForm3
.Show
If Not .Cancelled Then
Sheet8.Range("I16").Value = .SelectedBoard
Else
MsgBox "No board was selected, please re-run macro and select appropriate board"
End If
End With
And now you have a form that's nothing more than an I/O device for your code, as it should be. And you're using objects instead of global state.
TL;DR:
- Remove
End
instruction wherever they are. - Don't reference
UserForm3
insideUserForm3
(useMe
instead). - Work with a fresh
New
instance every time. - Expose
Property Get
members for the calling code to access, to abstract away controls that the caller doesn't need to care about. - Don't allow the form to be okayed in an invalid state.
回答2:
Unless you're showing a form non-modally, the opening code always halts until the form is closed
来源:https://stackoverflow.com/questions/46452296/userform-return-to-public-sub-from-private-sub