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
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
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
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
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!