Reputation: 547
I'm trying to use a variable that was initialized in a UserForm. But the simple example below just generates an empty MessageBox, regardless of what I write in the UserForm as the input.
Public X As String
Private Sub Module1()
InputForm.Show
MsgBox(X)
End Sub
InputForm:
Private Sub CommandButton_Done_Click()
X = InputForm.ListBox1.Value
Unload InputForm
End Sub
I'm new to VBA and I'm not sure how to debug the problem
Upvotes: 2
Views: 2403
Reputation: 1
I've got the same problem. I found that context is different between "where is stored the main code" and "where is stored the form code"
so, I ended up in this way (I'll apply to your code) and IT WORKED BOTH DIRECTIONS
Private Sub CommandButton_Done_Click()
ThisWorkbook.X = InputForm.ListBox1.Value
Unload InputForm
End Sub
Upvotes: 0
Reputation: 71167
There are many problems waiting to bite you in the rear end down that road. This article describes everything in details - here's the crux of it: flip the dependencies around, don't let the form run the show.
Create a new class module - that will be your model:
'class ExampleModel (class module)
Option Explicit
Private Type TModel
Foo As String
'other instance members
End Type
Private this as TModel
Public Property Get Foo() As String
Foo = this.Foo
End Property
Public Property Let Foo(ByVal value As String)
this.Foo = value
End Property
'other properties...
The members of this class will be everything the form needs to know. IOW that's where your X
belongs.
The form is your view - it needs to know about the model, so you expose it to the outside world:
'class InputForm (UserForm module)
Option Explicit
Private m As ExampleModel ' at module-level, because you'll eventually want it to be WithEvents at one point
Public Property Get Model() As ExampleModel
Set Model = m
End Property
Public Property Set Model(ByVal value As ExampleModel)
Set m = value
End Property
All the form controls do, is manipulate the Model
properties. So if you want to have m.Foo = ListBox1.Value
, then you handle ListBox1.Change
and do exactly that:
Private Sub ListBox1_Change()
m.Foo = ListBox1.Value
End Sub
Now all the "OK" button needs to do, is close the form:
Private Sub CommandButton_Done_Click()
Me.Hide
End Sub
Notice it's hiding the form, not Unload
ing anything. The form is an object, which means it was created by something, somewhere - and that something will not be expecting the object it created to spontaneously self-destruct. So you never Unload Me
, and even more never (if that's a thing) do Unload TheFormClassName
, because that would unload the form's default instance, and there's nothing anywhere that can guarantee that your form is always going to be shown off the default instance: it's a class like any other, it wants to be instantiated.
In order to avoid self-destructing the form, you need to handle the QueryClose
event:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
Here, we cancel the closing/destruction of the form when the user clicks the red [X] button in the upper-right corner, and hide the current instance (the Me
keyword always refers to the current instance of the class).
So what does the calling code look like now?
'module Module1 (standard module)
Option Explicit
Private Sub Test()
Dim m As ExampleModel
Set m = New ExampleModel
Dim view As InputForm
Set view = New InputForm
Set view.Model = m
view.Show
MsgBox m.Foo
End Sub
Now if you want you can add logic in the Model
property setter to pre-populate your form controls with values controlled by the calling procedure. Or you can have some IsCancelled
property on the model, that's set when some CancelButton
is clicked, and then the calling code can determine whether the form was cancelled, and conditionally show that MsgBox
.
Global variables needed: 0.
Upvotes: 2
Reputation: 14580
Your first sub opens the userform, thus your input box will be blank so you should expect a blank msgbox (Unless you have the box auto-populating).
A good practice (in my experience) is to work with your variables before Unloading the Userform, thus, you should handle them in your CommandButton_Done_Click()
1) Sub to Open Userform
2) Seperate Sub (Initialize) to control what happens when the userform is open (Control entry type, auto-populate fields, etc)
3) Final sub (Done_Click) to control what happens before the unload... (Validate input is string/int/etc, place inputs in cells, etc.)
Private Sub Module1()
InputForm.Show
End Sub
Private Sub CommandButton_Done_Click()
Msgbox InputForm.ListBox1.Value
Unload InputForm
End Sub
Upvotes: 1
Reputation: 578
You need specify that X
is in the context of the module in which X
exists. For example, if X
exists as a module variable in module Module1
you could change the CommandButton_Done_Click()
subroutine to be
Private Sub CommandButton_Done_Click()
Module1.X = InputForm.ListBox1.Value
Unload InputForm
End Sub
It is important to note that you cannot declare the module which holds X
as private if you want this to work.
Upvotes: 3