Siddharth's answer is nice, but relies on globally-scoped variables. There's a better, more OOP-friendly way.
A UserForm is a class module like any other - the only difference is that it has a hidden VB_PredeclaredId attribute set to True, which makes VB create a global-scope object variable named after the class - that's how you can write UserForm1.Show without creating a new instance of the class.
Step away from this, and treat your form as an object instead - expose Property Get members and abstract away the form's controls - the calling code doesn't care about controls anyway:
Option Explicit Private cancelling As Boolean Public Property Get UserId() As String UserId = txtUserId.Text End Property Public Property Get Password() As String Password = txtPassword.Text End Property Public Property Get IsCancelled() As Boolean IsCancelled = cancelling End Property Private Sub OkButton_Click() Me.Hide End Sub Private Sub CancelButton_Click() cancelling = True Me.Hide End Sub Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) If CloseMode = VbQueryClose.vbFormControlMenu Then cancelling = True Cancel = True Me.Hide End If End Sub
Now the calling code can do this (assuming the UserForm was named LoginPrompt):
With New LoginPrompt .Show vbModal If .IsCancelled Then Exit Sub DoSomething .UserId, .Password End With
Where DoSomething would be some procedure that requires the two string parameters:
Private Sub DoSomething(ByVal uid As String, ByVal pwd As String) 'work with the parameter values, regardless of where they came from End Sub