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.
Related
I have been wondering about this one for a while now.
Let's say I have a formula in A1, Worksheet("Main")
=IF(B2="English";"Good morning";"Guten Morgan")
Then I have userform with code:
Private Sub TextBox1_Change()
ThisWorkbook.Worksheets("Main").Range("A1").Value = Me.TextBox1.Text
End Sub
Private Sub UserForm_Initialize()
Me.TextBox1.Text = ThisWorkbook.Worksheets("Main").Range("A1").Value
End Sub
How can I make it work so, that if I don't input anything into textbox, it will keep displaying functions result. If I will start to type text into textbox it will input my typed text to A1. Now if I open the userform it will overwrite A1 with the text in textbox and there will be no formula anymore. So if I change language in B2 result will no longer be interfaced into textbox.
Can be also some other approach with VBA. Everything is acceptable as long as logic will work.
I have tried to use textbox properly, something like linkedsource or similar, but it is crashing excel workbook sometimes. That's why I am trying to avoid it.
EDIT:
Thank you for suggestions! I have tried to implement this somehow but still don't get it. I am creating variable where I want to store result from ThisWorkbook.Worksheets("Other Data").Range("L49").Value then I would like to use it in Userform Me.TextBox14.Text to be displayed. Then once it is changed in Me.TextBox14.Text and Enter button has been pressed it should change also in ThisWorkbook.Worksheets("Other Data").Range("L49").Value.
Here is my current code I am trying to play with:
Private ProjectClass As String
Private Sub TextBox14_Enter()
ThisWorkbook.Worksheets("Other Data").Range("L49").Value = ProjectClass
End Sub
Private Sub UserForm_Initialize()
Me.TextBox14.Text = ProjectClass
End Sub
The TextBox.Enter event isn't fired when the user presses Enter, but when the control is entered - that is, when it gets the focus and a caret/cursor starts blinking inside it. You'll want to update the backing variable when the value is modified:
Private Sub TextBox14_Enter()
'runs when the control gets focus
End Sub
Private Sub TextBox14_Exit()
'runs when the control loses focus
End Sub
Private Sub TextBox14_Change()
'runs whenever the value changes (real-time)
End Sub
So in this case I'd go with the TextBox.Change event handler, and make it update the variable (not the worksheet):
Private ProjectClass As String
Private Sub TextBox14_Change()
ProjectClass = TextBox14.Text
End Sub
Now the problem is that the ProjectClass value needs to be accessible from outside the form, so that the caller can set an initial value. One way to do this could be to expose it as a property - one property (get+let) for each field you want to seed a value for:
Public Property Get ProjClass() As String
ProjClass = ProjectClass
End Property
Public Property Let ProjClass(ByVal value As String)
ProjectClass = value
ApplyModelProperties
End Property
Private Sub ApplyModelProperties()
TextBox14.Text = ProjectClass
'...
End Sub
Now from outside the form, at the call site (the code that's showing this dialog), you can seed the value from the worksheet, and the form never needs to know or care that a worksheet was involved:
With New UserForm1
.ProjClass = ThisWorkbook.Worksheets("Other Data").Range("L49").Value
.Show
MsgBox .ProjClass
End With
Note that because the value is exposed as a property, the calling code doesn't need to know about TextBox14 anymore.
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
I created a userform in Excel 2016 with two ListBoxes, using the Tools menu. I double clicked them to create subs and inserted code to check whenever one is selected.
Here is the code:
Private sub SaleType_Click ()
If SaleType.Value ="Core" then
'make sale label visible
QTDV.visible =true
' show core option btn
Core.Visible = true
End if
End sub
When I have a ListBox created from the toolbox this works, but every other time the form is run the saletype ListBox will be value null and this is a problem because I have a check to make sure the ListBox is not empty. Code follows:
If saletype = "" then
Dim msg as string Msg = " please select sale type"
Msgbox msg, and vbcritical
End if
If the ListBox presents value null it will not see it as empty and skip the check if I try saletype = null it still skips it.
I searched and it seems creating ListBoxes on the tool box is weird because Excel does not know what kind of control it is. I opted for creating the ListBoxes in VBA.
Private sub userform_initialize()
Dim saletype as msforms.Listbox
Set saletype = me.Controls.Add("Forms.ListBox.1", "SaleType")
But when running the form and selecting any option on the ListBox the SaleType_Click sub does not trigger.
If you want to implement event handling (like SaleType_Click) you need to declare the object with the WithEvents keyword:
Dim WithEvents saletype as msforms.Listbox
And if a variable/property is not set then its value doesn't exist, so instead of empty string ("") you need to validate for NULL - the IsNull function can do that (= NULL doesn't work as = only works with values):
If IsNull (saletype.Value) then
I just had a problem with my listbox_Click not being fired "every other time". Maybe it was when the same selection was desired twice in a row. Anyway, put this in the sheet code for the sheet that is "Show"ing the userform:
userformXXX.listboxYYY.ListIndex = -1
userformXXX.Show
This doesn't work if it is in the userform code.
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!
I have 3 combo boxes which are dependent with each other. List on cmb2 depends on the value on cmb1. cmb3 list depends on cmb2 value.
My problem is, when I remove/delete the values on all combo boxes, when I click cmb2, it still shows the list of the last value chosen on cmb1 & same goes with cmb3.
What's the code to show it empty if the combo box where it's dependent to doesn't have any value?
EDIT: I think based on added information in the comment that you should look to encapsulate the logic in sub procedures and functions. To best do so, there should be some logic between named ranges and the values in the combobox.
In the below example, I created a function to handle conversions from the value in the independent combobox and the named ranges. You can supply your own connector, but I assumed that any space will be changed to an underscore (so, 'Account Information' will be replaced with 'Account_Information').
Next a subprocedure checks to confirm that the named range exists in the workbook that matches the converted value in the independent combobox. If so, the dependent box takes on that named range as its List property. If not, the dependent combobox is cleared.
The benefit to using this type of a system is that it could be repeated for other comboboxes without rewriting code. See below code and let us know if you need added help.
Private Sub UserForm_Initialize()
With Me.ComboBox1
.AddItem "Account Information"
.AddItem "Other Stuff"
End With
End Sub
Private Sub ComboBox1_Change()
With Me
LoadDependentCBO Me.ComboBox1, Me.ComboBox2
End With
End Sub
Private Function ConvertValueToNamedRange(sValue As String, sConnector As String) As String
'This function takes values in the combobox and converts them to named ranges
'The spaces are replaced with whichever connector is supplied to the function
'NOTE: I created a separate function even though this only uses a built-in function
'in case more implementation code is needed
ConvertValueToNamedRange = Replace(sValue, " ", sConnector)
End Function
Private Sub LoadDependentCBO(cboIndependent As ComboBox, cboDependent As ComboBox)
'This code looks to see if there is a match between the value of the independent combobox
'and a named range in the workbook. If not, the dependent combobox is set to be empty
Dim sTemp As String
Dim rNamedRange As Range
sTemp = ""
On Error Resume Next
sTemp = Names.Item(ConvertValueToNamedRange(cboIndependent.Value, "_")).Name
On Error GoTo 0
If sTemp = "" Then
'There is no matching named range so clear the dependent combobox
cboDependent.Clear
Else
cboDependent.List = Range(sTemp).Value
End If
End Sub
OLD POST:
The code ComboBox1.ListIndex = -1 will set the combo box to empty (change the name of the box to suit). The logic to do so depends on more details. Let us know if you need help with implementation.