Userform - Return to public sub from private sub

北城余情 提交于 2019-12-24 00:44:10

问题


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 Unloading 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 inside UserForm3 (use Me 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!