Obtaining a single variable from a userform in Visual Basic - excel

I have little VB knowledge but was hoping to figure this out without any help, but alas!
I have a spread sheet that shows the layout of HP blades inside a chassis. Currently I have set-up a macro (I right-clicked view code on the sheet) whereby you click on a specific 'blade' (really a few merged cells) and an inputbox appears, asking the user which IP they'd like - this then looks for exact matches (e.g. O - OAM, I - iLO) and depending on the option selected - assigns a value in a case statement. For example: O - OAM IP would assign the number 2 - which performs a VLOOKUP and looks in column 2 to return an IP address to the users clipboard.
I'm trying to change the inputbox to a userform. I've created one and have a simple dropdown with the available options.
I'm struggling to export the users selection into a variable to be used later on.
All of my efforts so far result in a variable being assigned following completion of the 'userform' and pressing the 'OK' button. But that variable is then not assigned in the sheet's code area. I assume it's something to do with scope and assignment.
Code for the worksheet
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Intersect(Target, Range("D57:H80")) Is Nothing Then
Dim HostName As String
Dim ColIndex As Integer
Dim NodeIP As String
HostName = ActiveCell.Value
ufrmIPSelection.Show
'uOption = InputBox("Option Required" & vbCrLf & "o = OAM IP" & vbCrLf & "i = iLO IP", "User selection required")
'uOption
MsgBox "Option captured is " & uOption
Select Case uOption
Case "OAM IP"
ColIndex = 2
Case "iLO IP"
ColIndex = 3
End Select
MsgBox "Column referenced is " & ColIndex
NodeIP = Application.VLookup(HostName, Sheet7.Range("A1:C503"), ColIndex, False)
MsgBox "Hostname is " & HostName
MsgBox "Node IP is " & NodeIP
End If
End Sub
and code for the userform OK button
Public Sub cmdOK_Click()
uOption = cboIP.Value
MsgBox "Combo Box Value " & uOption
'Above line used to see if variable has been assigned
Unload Me
End Sub
Thanks,

Unload Me essentially says "destroy this object". But a form's default instance isn't just any other object - it's a global instance that's automatically re-created by VBA whenever it's referenced again, if it's ever destroyed. But the re-created instance can't magically remember what the previous state was: the re-created instance therefore has whatever the inital / design-time state was.
Another thing that destroys a form and its state, is the little [X] button in the top-right corner.
A modal form is essentially a dialog - and a dialog can either be accepted, or cancelled. When the user dismisses your dialog by clicking that little [X] button, they're telling your program that they wish to cancel whatever that dialog was intended to be doing.
For this reason, it's important that a dialog does nothing other than collect data from the user. The basic code-behind boilerplate for a UserForm with an Ok and a Cancel button, might look like this:
Option Explicit
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub OkButton_Click()
Hide
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
cancelled = True
Hide
End Sub
Handling the QueryClose event allows us to cancel the destruction of the form and the resetting of its state when the user clicks that [X] button.
Now the calling code can do this:
ufrmIPSelection.Show
If ufrmIPSelection.IsCancelled Then Exit Sub
But since that's storing the state in the form's global/default instance, it's a much better idea to do this:
With New ufrmIPSelection
.Show
If .IsCancelled Then Exit Sub
End With
That way every usage of the form is nicely insulated, and the form instance and its state only live as long as it needs to (i.e. it's gone at End With).
You could go ahead and query the individual controls' state from the calling code, but a much cleaner approach would be to encapsulate that state, using properties (a bit like suggested in 3.1415's answer); these properties only need to be read from the calling code, so only a Propery Get member is needed:
Public Property Get SelectedIPType() As String
SelectedIPType = cboIP.Value
End Property
And now the calling code can do this:
With New ufrmIPSelection
.Show
If .IsCancelled Then Exit Sub
MsgBox .SelectedIPType
End With
The next step would be to extract an actual stateful class to encapsulate the model, so as to completely decouple the form from the data.
Read more about forms' default instance in UserForm1.Show, an article I wrote a few months ago (the basic code-behind boilerplate snippet is taken from there).

You need to add a public property to your form ...
Private m_OptionChoice as String
Property Let OptionChoice(value As String)
m_OptionChoice = value
End Property
Property Get OptionChoice() As String
OptionChoice = m_OptionChoice
End Property
And then change your OK button code to ...
Public Sub cmdOK_Click()
OptionChoice = cboIP.Value
MsgBox "Combo Box Value " & uOption
'Above line used to see if variable has been assigned
Unload Me
End Sub
Now when your form closes, you can access the OptionChoice via ufrmIPSelection.OptionChoice. Eg change ...
MsgBox "Option captured is " & uOption
to ...
MsgBox "Option captured is " & ufrmIPSelection.OptionChoice
Finally, as suggested in Mathieu Guindon's comment below, you need to replace Unload Me with Me.Hide. Thanks Mathieu!

Related

How can I run a userform from a macro?

I developed many UDFs and macros in VBA for Excel 2016. One of my macros uses an Inputbox to get data used subsequently by the macro. I want to replace the Inputbox with a user form. I have created the user form, with one text box. I want to activate the user form, populating the text box with the default data, and return the text box data to the macro when OK is selected. I have searched extensively for an end-to-end example for all the the code needed to do this, with no luck. Does an example for this simple problem exist?
Add a Property to your user form. For this answer, let us use the following code within the user form.
Public Property Get MyResult() As String
' You may want to do any manipulation here
' including converting to a number, in which case the return type should be changed (*)
MyResult = TextBox1.Text
End Property
(*) If you are doing conversion, you can have another function in your user form to disable the "OK" button until they have valid convertible data in the text box.
You also want to know if they have hit "Cancel"
Public Property Get Cancelled() As Boolean
Cancelled = pCancelled ' Declare pCancelled as a Boolean in the scope of the form
End Property
Public Sub CancelButton_Click() ' Standard click event for the button
pCancelled = True
Me.Hide
End Sub
Public Sub OKButton_Click() ' Standard click event for the button
pCancelled = False
Me.Hide
End Sub
In your calling macro
MyForm.Show ' This is modal, so will block execution until a response is provided
If Not MyForm.Cancelled Then
Debug.Print MyForm.MyResult
'Do something with MyForm.MyResult
End If
UnLoad MyForm ' assuming you do not want to re-use this form as part of your logic.
There is an example of how you can pass the value to a form and get the result back. The approach uses Scripting.Dictionary object created within standard module scope and passed to userform to allow values to be changed. So it makes possible to send the default values to userform, and keep the result values in the dictionary even after the userform is closed and unloaded. You may have multiple values, just add the necessary quantity of keys to the dictionary, e. g. oData("property1"), oData("property2"), etc.
Add a standard module to the project and put the below code into it:
Option Explicit
Sub Test()
Dim oData
' Set default value and show form
Set oData = CreateObject("Scripting.Dictionary")
oData("") = "Some default text"
UserForm1.ShowForm oData
' Wait until user close form
Do While IsUserFormLoaded("UserForm1")
DoEvents
Loop
' Output returned value
MsgBox oData("")
End Sub
Function IsUserFormLoaded(UserFormName As String) As Boolean
Dim oUF As Object
For Each oUF In UserForms
If LCase(oUF.Name) = LCase(UserFormName) Then
IsUserFormLoaded = True
Exit Function
End If
Next
End Function
Add a userform module named UserForm1 to the project, place controls as shown:
And put the below code into the userform module :
Private opData
Public Sub ShowForm(oData)
Set opData = oData
Me.TextBox1.Value = opData("")
Me.Show
End Sub
Private Sub UserForm_Initialize()
If TypeName(opData) <> "Dictionary" Then Set opData = CreateObject("Scripting.Dictionary")
End Sub
Private Sub CommandButton1_Click()
Unload Me
End Sub
Private Sub CommandButton2_Click()
opData("") = Me.TextBox1.Value
Unload Me
End Sub

How to reference variables from listboxes in the main code of a userform?

Say I had a userform with 3 options:
Listbox1: Item 1
Item 2
Item 3
I want to reference them in the main code, like:
If Listbox1 = *Item 1*
do something
or
*Item 1* = 400
How do I reference Item 1 in the code? Would it be Listbox1.1? Listbox1 = 1? "Item 1"?
Here is one simple example illustrating the concept. As Mathieu Guindon pointed out in a comment, you can use ListBox.Text.
Suppose you have the following user form which is opened by clicking the button "Show User Form". I assume from your question that you would like to do something like selecting an item in the list and then do something in the code when you click the OK button. In my example, clicking OK launches the Message Box showing which item was selected, but you could of course do whatever you want. The code that generates the messsage box is located in a regular module, while the code for the OK button and Listbox is located in the user form.
The button "Show User Form" simply calls the following sub in the regular module:
Public Sub ShowForm1()
UserForm1.Show
End Sub
This displays the user form, which is initialized with the following code:
Private Sub Userform_initialize()
With Me.ListBox1
.AddItem "Item 1"
.AddItem "Item 2"
End With
End Sub
The code below for the OK button includes the reference Me.ListBox1.Text that captures the selected item and stores it in a variable which is then passed to the subroutine ShowMsgBox in the regular module. Note the use of Me. which is shorthand for referencing the user form where the listbox is located (i.e. the same form as the OK button):
Private Sub okButton_Click()
sSelectedItem = Me.ListBox1.Text
ShowMsgBox (sSelectedItem)
Unload Me
End Sub
The code above calls ShowMsgBox, which is in the regular module:
Public Sub ShowMsgBox(sInput As String)
MsgBox "You selected " & sInput & ".", vbOKOnly
' Other code to do something with the selected item goes here.
End Sub
Note that in ShowMsgBox() you no longer reference the listbox, but rather work with the value sInput passed as an argument.
Hope you find this useful.
EDIT:
Following Skye's comment, here is a suggestion on how to open other forms instead of a message box. You must replace one line in okButton_Click()
, like this:
Private Sub okButton_Click()
sSelectedItem = Me.ListBox1.Text
ShowOtherUserForm (sSelectedItem)
Unload Me
End Sub
Then, in the regular module, add the sub ShowOtherUserForm(). This sub uses Select Case to check which item was selected. You can do the same job with If, but I prefer the Select style for this scenario. My example requires that you have two userforms called "OtherForm1" and "OtherForm2".
Sub ShowOtherUserForm(sInput As String)
Dim x As Object ' Must be "Object", declare "As UserForm" won't work.
Select Case sInput
Case "Item 1"
Set x = OtherForm1
Case "Item 2"
Set x = OtherForm2
End Select
x.Show
End Sub
Screenshot of result:

How do objects get disconnected to its clients?

I made the code below from Excel 2016 VBA.
Private Sub useridno_AfterUpdate()
Dim strMsg As String
Dim ret_type As Integer
Dim strTitle As String
strTitle = "Wrong User ID Number!"
strMsg = " Would you like to try again?"
If Me.useridno.Value <> 1 Then
ret_type = MsgBox(strMsg, vbYesNo + vbCritical, strTitle)
Select Case ret_type
Case 6
Me.useridno.SetFocus
Me.useridno.Text = ""
Case 7
Unload Me
End Select
End If
End Sub
When I run it, it returns the following error:
This happens when I choose the "No" button in the message box. Why is this so?
Somewhere in your project you have a Show command which displays the userform. From that moment forward the userform is in control. The code following the Show command will resume when the userform is closed. The command to do so is Me.Hide as #Storax has pointed out.
Your code has Unload Me instead. The error occurs when the other procedure tries to refer to the userform after the Show command. Your code might try to read some of the data from the form or there might be an innocuous Set UserForm = Nothing. It is already unloaded and can't, therefore, be referred to anymore.
The correct way would be to unload the form in the calling procedure, following the Show command and after you have retrieved all data from the form that you might want to use. Then, if you wish to explicitly release the form's object variable from memory you can do so.

how to load the combobox and then how to use the selected value from it in vba?

a question is : how to load a or give value to a combo box list and then call it in my worksheet and get the selected value from it ?i have a module that i want to call my userform1 which include the combobox in it . but when i debug the program it is just a show of the combo box . i think it doesn't do anything ... thanks for your time ..this is the code for user form:
Private Sub UserForm_Initialize()
With ComboBox1
.AddItem "weibull"
.AddItem "log-normal"
.AddItem "gambel"
.Style = fmStyleDropDownList
End With
End Sub
and this is how i ask in my sub to show the combobox:
UserForm1.Show
If ComboBox1.ListIndex = -1 Then
MsgBox "There is no item currently selected.", _
vbInformation, _
"Combo Box Demo"
Exit Sub
End If
MsgBox "You have selected " & ComboBox1.List(ComboBox1.ListIndex) & "." & vbNewLine _
& "It has " & ComboBox1.ItemData(ComboBox1.ListIndex) ", _
vbInformation, _
"Combo Box Demo"
the second part is what i found in net , but it made the program at least to show the combo box !
I thought Siddharth's answer was pretty clear particularly as it was posted from a mobile! However, I had an email from the OP saying he did not understand the answer. I provided the following background which was apparently sufficient to allow him to understand Siddharth's answer and solve his problem. I post it here for the benefit of any other visitor who needs more background on forms than Siddharth provides.
If you select VBA Help and type “userform show” you will get a description of the Show command.
If your user form is named “UserForm1”, you can have statements:
UserForm1.Show
UserForm1.Show vbModal
UserForm1.Show vbModeless
Statements 1 and 2 are equivalent.
The choice of VbModal or vbModeless completely changes the way the user form is controlled.
If a form is shown modeless, the user can see it but cannot access it. If I have a macro that takes a long time, I will use a modeless form to show progress. If I am working down the rows of a large worksheet I might have a form containing:
I am working on row nnnn of mmmm
Each of the boxes is a label. I set the value of the label containing “mmmm” to the number of rows when I start the macro. I set the value of the label containing “nnnn” to the row number at the start of each repeat of my loop. The user sees:
I am working on row 1 of 5123
then I am working on row 2 of 5123
then I am working on row 3 of 5123
and so on.
If it takes the macro five minutes to process every row, this tells the user that something is happening. Without the form, the user might think the macro had failed. With the form, the user knows the macro is busy and they have time to get a fresh cup of coffee.
On the other hand, if the form is shown modal, the macro stops until the user does something that closes or unloads the form with a statement such as:
Unload Me
The positioning of this statement depends on your form. I normally have a Submit button that saves information before ending with this statement.
Once the Unload Me statement is executed, the macro restarts at the statement after the Show statement. When the macro restarts, the form has gone. This is why the form must save anything the macro needs in global variables.
You are trying to access a control when the userform is already closed. And I say closed becuase you are not using vbmodeless to show the form. So the only way the next line after that can run is when the form is closed. Here is what I recommend.
Declare public variables in a module which will hold the relevant values when the useform closes and then use that later. For example
Paste this code in the userform
Option Explicit
Private Sub UserForm_Initialize()
With ComboBox1
.AddItem "weibull"
.AddItem "log-normal"
.AddItem "gambel"
.Style = fmStyleDropDownList
End With
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If ComboBox1.ListIndex <> -1 Then
SelectItem = ComboBox1.List(ComboBox1.ListIndex)
pos = ComboBox1.ListIndex + 1
End If
End Sub
And paste this in a module
Option Explicit
Public SelectItem As String, pos As Long
Sub Sample()
'
'~~> Rest of your code
'
SelectItem = "": pos = 0
UserForm1.Show
If pos = 0 Then
MsgBox "There is no item currently selected.", _
vbInformation, "Combo Box Demo"
Exit Sub
End If
MsgBox "You have selected " & SelectItem & "." & vbNewLine & _
"It is at position " & pos, vbInformation, "Combo Box Demo"
'
'~~> Rest of your code
'
End Sub
Also
There is no .Itemdata property of the Combobox. It is available in VB6 but not in VBA. With .Itemdata property of the Combobox, I guess you were trying to get the position?

VBA - Get OLD Value of Combobox

I would like to get the old value of a ComboBox when the combobox value will change.
I have tried something like:
Private Sub ComboBox1_Change()
Application.EnableEvents = False
newVal = ComboBox1.Value
Application.Undo
oldVal = ComboBox1.Valu
End Sub
or
Private Sub ComboBox1_Change()
Application.EnableEvents = False
newVal = ComboBox1.Value
ComboBox1.Undo
oldVal = ComboBox1.Valu
End Sub
but it seems not to work...
Thanks
You could use a Static variable that holds its value between calls to the ComboBox1_Change event:
Private Sub ComboBox1_Change()
Static OldValue As String
With Me.ComboBox1
Debug.Print "Value: "; .Value; " Old Value: "; OldValue
OldValue = .Value
End With
End Sub
If you need to access OldValue outside of the Change event, use a module-level variable, as described by #Ifrandom.
This is a bit more work, but what I've done in the past is create a private variable which contains the value.
Basically on the change event, you do what you need to do with the old value, then you update the old value variable to the new value.
Private oldValueComboBox as String
Private Sub ComboBox1_Change()
' Do whatever you need to do with the old value, in this case msgbox
msgbox oldValueComboBox
' Set the old value variable to the new value
oldValueComboBox = ComboBox1
End Sub
You can also use a static variable as another post mentions. If you use a static variable, it is only usable within the scope of the combobox change, if you use a private variable it is visible to the entire form.
I use excel combo boxes alot, and have developed a number of useful features like:
* save & load combo box data from the registry or a hidden "APP_DATA" worksheet
* add a new permanent combo box item by entering a new value and pressing
* allowing editing of combo history by double-clicking on the box
* clearing all combo history by erasing any currently showing item and pressing
These are just some ideas to get you going, the code is fairly simple, I just wrote some simple subs for:
* load a combo box from a history string
* dedup a delimited string
* event to trap a for "new item" or "erase items" function
* event to trap a for "edit items" function
When a new item is added, I just append it to the history string, and dedup it just in case. History strings are saved or loaded from registry on initialize and terminate, or as they are changed, and initialize also populates the combos.
I always assumed there would be an easy way to do this, since I see so many combo boxes maintaining history (I limit to the latest 24 items), but I never found any code, so I just made my own.
In some apps, even double-clicking a worksheet cell value can populate or CSV-append to a combo box, or a command button can prompt for and load a series of cells into a CSV list into combo, very useful.

Resources