Create ComboBox in Excel cells - vba

I want to create a Select or Drop-down List or ComboBox (whatever you want to call it).
I do not want to do it with UserForm because it displays a window on the cells and I want it in the cells. Example (See image)
I want when selecting PERRO in the combobox the cell shows the number 1, if it is GATO the cell shows the number 2, if it is VACA the cell shows the number 3.

You have three options
Use Data Validation (DATA>DATA TOOLS>DATA VALIDATION) as you showed in the image, but you'll have to combine it with VLOOKUP function (kinda painful)
Attach a Combobox Form Control (DEVELOPER>CONTROLS>INSERT) right click and go to format control. From here, you can assign a source list and a result cell in numeric format. I mean if you select Perro, since it is the first element, the result cell give the value 1. It is the best way for me.
Finally, you can use a Combobox ActiveX Control (DEVELOPER>CONTROLS>INSERT), you can add the elements from the properties windows or VBA script by using the method AddItem
For example:
With ComboBox
.AddItem "perro"
.AddItem "gato"
.AddItem "vaca"
.AddItem "cerdo"
End With
and you can create a result cell with a conditional
Select Case Combobox.value
Case "perro": range("a1")=1
Case "gato": range("a1")=2
Case "vaca": range("a1")=3
Case "cerdo": range("a1")=4
End Select
That would be easy for a beginner.

other then the solution by the link provided by QHarr in the comments, you could try a totally VBA solution, with the code making the validation list appear every time the user selects the wanted cell
to do so you have to:
delete and placing back the validation rules at every cell selection
substitute list item select with its index in the list
so you need to deal with both Worksheet_SelectionChange() and Worksheet_Change() event handlers
place the following code in the worksheet code pane
Option Explicit
Dim animals As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$B$2" Then ' if selected cell is the wanted one (change "$B$2" to any wanted address
Dim element As Variant
Dim position As Long
For Each element In animals 'loop through validation list
position = position + 1 'update index position
If element = Target.Value Then 'if current loop element matches cell content
Application.EnableEvents = False 'prevent subsequent sheet change (deleting and writing cell content) fire this event again and start an infinite loop
Target.Validation.Delete 'remove data validation
Target.Value = position ' write the element index position
Application.EnableEvents = True 'restore proper event handling
Exit For
End If
Next
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Address = "$B$2" Then ' if selected cell is the wanted one (change "$B$2" to any wanted address
With Target
If hasValidation(.Cells) Then
.ClearContents 'clear any previous cell content
Else
animals = Array("Perro", "Gato", "Vaca", "Cerdo") 'set the validation list
.ClearContents 'clear any previous cell content
With .Validation 'set validation rules
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Join(animals, ",")
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End If
End With
End If
End Sub
Function hasValidation(rng As Range) As Boolean
On Error Resume Next
hasValidation = rng.SpecialCells(xlCellTypeSameValidation).Cells.Count = 1
End Function

Related

How to build a dropdown list in Excel from a comma separated list

I have one cell that contains a comma separated list of values, and I need to be able to use those values as a validation list. Is there any way to do this?
The list of values can be anywhere from 0 to 50 values long.
It is possible to split a comma separated string out of one cell into a column of cells without VBA:
Copy the cell and in Row1 enter:
=CHOOSE(ROW(),
then Paste, ), Enter and copy down to suit.
This should work:
Assuming the name of your sheet you have to work on is "task", the source is in cell A1 and you have to add validation list in A2 for the same sheet.
Activate VBA
Goto VBA editor
Create the following code for Workbook module
Private Sub Workbook_Open()
AddCSVListValidation "Task", "A1", "A2"
End Sub
Sub AddCSVListValidation(sheet, cellSource, cellTarget)
txt = ActiveWorkbook.Worksheets(sheet).Range(cellSource).Value
ActiveWorkbook.Worksheets(sheet).Range(cellTarget) = "Select your values here"
With ActiveWorkbook.Worksheets(sheet).Range(cellTarget).Validation
.Delete
.Add Type:=xlValidateList, Formula1:="a,b,c"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End Sub
The result is shown below - whenever you open the workbook it will be auto-populated. Hope this helps.
You can also trap the event for the task sheet change and run the code on condition of A1 range updates to update the validation list by calling
AddCSVListValidation "Task", "A1", "A2"
This also allows you to expand the operation to multiple source and target cells. You will need to save the book with .xlsm extension and enable macros in the more recent of excel versions.

Using Option Buttons on a UserForm to place text in a range from the sub calling the UserForm

I apologize if this is posted elsewhere, but I am having issues understanding the relationship between the sub calling the user form and the user form controls. I have a sub that will populate data into a worksheet from another worksheet. One of the cells that needs to be filled out is an explanation for the change in quantities of the project. I generated a user form with option buttons that the user can select the appropriate "reason" from. Once the OK button is clicked it will place the selected reason in the cell of the worksheet.
In the sub, I am using For Each cell in the range to populate the data across the row for each value above a specific criteria and it should show the form for each cell meeting the criteria. Can I pass the cell I am using as a row reference into the user form so that it can use that cell as an offset to enter in the chosen "reason" then unload the form?
Private Sub okbutton1_Click(bc As Range) 'bc should be the range from the sub calling this form
Select Case True
Case OptionButton1
If bc.Offset(0, 3).Value = "A" Then
Set bc.Offset(0, 6).Value = "Actual amount required is more than plan quantity."
Else
Set bc.Offset(0, 6).Value = "Actual amount required is less than plan quantity."
End If
Case OptionButton2
Set bc.Offset(0, 6).Value = "This items was not constructed/used/required."
Case OptionButton3
Set bc.Offset(0, 6).Value = "Only a portion of the contingency was required for items not in original plan."
Case OptionButton4
Set bc.Offset(0, 6).Value = "Deficiency levied against Contractor per IDOT Section 105.03."
Case OptionButton5
Set bc.Offset(0, 6).Value = "Damages levied against Contractor per IDOT Section 108.09."
Case OptionButton6
Set bc.Offset(0, 6).Value = InputBox("Please enter your reasoning below.", "Other")
End Select
Unload AuthReason2
End Sub
Then this is a portion of the sub that I am working with to populate the worksheet.
Line5: 'Populates the BLR13210A from the data entered on the BLR13210
Application.ScreenUpdating = False
Dim bws As Worksheet
Set bws = Worksheets("BLR 13210A")
Dim Arange As Range
Set Arange = aws.Range("AZ34:AZ198")
Dim Bcell As Range ' First cell in AttachA form
Set Bcell = bws.Range("B11")
For Each ACell In Arange
If ACell.Value > 1999.99 Then
Bcell.Value = ACell.Offset(0, -47).Value
Bcell.Offset(0, 1).Value = ACell.Value
Bcell.Offset(0, 2).Value = ACell.Offset(0, -37).Value
Bcell.Offset(0, 3).Value = ACell.Offset(0, -22).Value
AuthReason2(Bcell).Show
End If
Bcell = Bcell.Offset(1, 0)
Application.ScreenUpdating = True
Thank you in advance for your assistance.
User forms should be used to retrieve user inputs and pass them to processing sub that will treat data accordingly
so you'd better act the opposite way, i.e.:
your main sub
should "launch" the user form and retrieve data from it, treat those data to act on other data and then close the user form
it could be like follows:
Option Explicit
Sub main()
Dim bws As Worksheet
Set bws = Worksheets("BLR 13210A")
Dim Bcell As Range ' First cell in AttachA form
Set Bcell = bws.Range("B11")
With UserForm1 '<--| change "UserForm1" to your actual userform name
.Tag = Bcell.Offset(0, 3).Value '<--| store the value you want to share with Userform in its 'Tag' property
.Show '<-- show the userform and have it process user inputs
Bcell.Offset(0, 6).Value = .Tag '<--| retrieve the value that userform has left in its 'Tag' property accordingly to user inputs
End With
Unload UserForm1 '<--| change "UserForm1" to your actual userform name
End Sub
of course you will change Bcell.Offset(0, 3).Value to whatever range value should fit your needs
your user form
will handle user inputs and pass them back to the sub
there are many ways it can be done, but the following could suit your needs
put in its code pane something like follows:
Option Explicit
Private Sub okbutton1_Click()
Dim txt As String
With Me '<--| reference the userform
Select Case True
Case .OptionButton1 '<--| with the dot (.) access the referenced object members (in this case its controls)
If .Tag = "A" Then '<--| query the value that the calling sub has left in the useform 'Tag' property
txt = "Actual amount required is more than plan quantity."
Else
txt = "Actual amount required is less than plan quantity."
End If
Case .OptionButton2
txt = "This items was not constructed/used/required."
Case .OptionButton3
txt = "Only a portion of the contingency was required for items not in original plan."
Case .OptionButton4
txt = "Deficiency levied against Contractor per IDOT Section 105.03."
Case .OptionButton5
txt = "Damages levied against Contractor per IDOT Section 108.09."
Case .OptionButton6
txt = InputBox("Please enter your reasoning below.", "Other")
Case Else '<--| you may want to handle the case when the user dosen't select any option
txt = "some text" '<--| change it to your needs
End Select
.Tag = txt '<--| use 'Tag' property to store the value you want to pass back to the calling sub
.Hide '<--| hide the userform
End With
End Sub

Excel VBA - Insert Username ONLY when cell is changed

Here's my problem: I have working code to insert a username and timestamp when a user makes a change anywhere in a row. Great! So my code works and I answered my own question, right? Nope! There's a tiny issue which, while it doesn't break the code, does lead to a user having their username input as having made a change when a change was not made.
Here's my code:
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
ThisRow = Target.Row
'protect Header row from any changes
If (ThisRow = 1) Then
Application.EnableEvents = False
Application.Undo
Application.EnableEvents = True
MsgBox "Header Row is Protected."
Exit Sub
End If
For i = 1 To 61
If Target.Column = i Then
' time stamp corresponding to cell's last update
Range("BK" & ThisRow).Value = Now
' Windows level UserName | Application level UserName
Range("BJ" & ThisRow).Value = Environ("username")
Range("BJ:BK").EntireColumn.AutoFit
End If
Next i
End Sub
Here's how it happens: A user decides they want to make a change to a cell, so they double click the cell. Now, if they push the escape key, nothing happens and everything is hunky dory. But, if they double click the cell, then click outside of the cell to another cell to leave that cell, the system logs that as a change even though no change was made and the user's username is put into column 62. This is no bueno, because someone could be held responsible for a mistake that another individual has made if they're incorrectly put down as the last person to change something in that row.
Conversely - it might be worthwhile to create a comment in a cell which is changed by a user, but I reckon I'd have the same issue with double-clicking a cell, so I'd still have to account for it.
Thoughts?
Edit: Full disclosure, I found this code elsewhere and adapted it to my purposes.
You can test to see if the old value and the new value are the same. I use "new" loosely, meaning excel things that the cell was edited so it's a "new" value in terms of the Worksheet_Change event understanding.
I also got rid of your For loop as it seemed very unnecessary. If I am mistaken, I apologize.
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
Dim ThisRow As Long ' make sure to declare all the variables and appropiate types
ThisRow = Target.Row
'protect Header row from any changes
If (ThisRow = 1) Then
Application.EnableEvents = False
Application.Undo
Application.EnableEvents = True
MsgBox "Header Row is Protected."
Exit Sub
End If
If Target.Column >= 1 And Target.Column <= 61 Then
Dim sOld As String, sNew As String
sNew = Target.Value 'capture new value
With Application
.EnableEvents = False
.Undo
End With
sOld = Target.Value 'capture old value
Target.Value = sNew 'reset new value
If sOld <> sNew Then
' time stamp corresponding to cell's last update
Range("BK" & ThisRow).Value = Now
' Windows level UserName | Application level UserName
Range("BJ" & ThisRow).Value = Environ("username")
Range("BJ:BK").EntireColumn.AutoFit
End If
Application.EnableEvents = True
End If
End Sub

Auto-hiding columns based on cell criteria in another worksheet

I am new to VBA coding and have so far successfully managed to create a scoping sheet in a workbook which hides/unhides tabs based on workbooks users' responses to yes/no questions.
I need to further refine the workbook so that the yes/no responses provided in the scoping tab lead to the auto hiding of columns in other sheets. Using a previous thread on this website I used this code (obviously amended for my own cells refs) on one of the tabs:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$B$6" Then
Select Case Target.Value
Case Is = "Cast"
Columns("f").EntireColumn.Hidden = False
Columns("d").EntireColumn.Hidden = True
Columns("e").EntireColumn.Hidden = True
Case Is = "LDF"
Columns("f").EntireColumn.Hidden = True
Columns("d").EntireColumn.Hidden = False
Columns("e").EntireColumn.Hidden = False
Case Is = "Select ROV Type"
Columns("f").EntireColumn.Hidden = False
Columns("d").EntireColumn.Hidden = False
Columns("e").EntireColumn.Hidden = False
End Select
In B6, I have a formula (=Name) which pulls through from the scoping tab. While the above code works, it only does so where I manually enter the cell to re-pull through data... any hints on:
- linking through to the original scoping tab in my macro, bypassing the cell reference; and
- automating the column hides?
The easiest thing to do seems to edit your code like this, where needed:
Sheet2.Columns("f").EntireColumn.Hidden = False
Sheet2 is the sheet, where the columns should be hidden.
If I correctly interpreted your needs go like follows
In "ThisWorkbook" code pane place the following code:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Application.EnableEvents = False
With Sheets("scoping sheet") '<== here you set which sheet you want to monitor
If .Range("B6") <> .Range("A1") Then '<== check if the "formula" cell changed its previous value, stored in the "echo" cell ("A1")
Select Case .Range("B6").Value
Case Is = "Cast"
.Columns("f").EntireColumn.Hidden = False
.Columns("d").EntireColumn.Hidden = True
.Columns("e").EntireColumn.Hidden = True
Case Is = "LDF"
.Columns("f").EntireColumn.Hidden = True
.Columns("d").EntireColumn.Hidden = False
.Columns("e").EntireColumn.Hidden = False
Case Is = "Select ROV Type"
.Columns("f").EntireColumn.Hidden = False
.Columns("d").EntireColumn.Hidden = False
.Columns("e").EntireColumn.Hidden = False
End Select
.Range("a1") = .Range("b6") '<== update the "echo" cell value for subsequent checking
End If
End With
Application.EnableEvents = True
End Sub
As you see, you must choose an "echo" cell in the "scoping" sheet, which will be used to store the previous value of its "B6" cell.
In my code I chose cell "A1" as the "echo" cell in "scoping" sheet, but you can choose whatever address you need provided it's a "free" cell (i.e.: your code and the user won't use it to write in) and change code accordingly (i.e. the "A1" address in If .Range("B6") <> .Range("A1") Then statement).

Show/Hide Rows Per Dropdown Selection

I found code online as an example that I have tweaked to show or hide specific rows depending on the selection I choose within a dropdown in my Excel file.
The macro is not working no matter what I try.
My code is as follows (also attached screenshot of rows under question 2 (2a - 2d) that are not showing/hiding)
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$F$13" Then
If Range("F13").Value = "Yes" Then
Rows("14:17").EntireRow.Hidden = False
End If
If Range("F13").Value = "No" Then
Rows("14:17").EntireRow.Hidden = True
End If
If Range("F13").Value = " " Then
Rows("14:17").EntireRow.Hidden = True
End If
End Sub
This is a good example of properly intending your code helping you identify an issue. You're missing an End IF statement. Try this:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$F$13" Then
If Range("F13").Value = "Yes" Then
Rows("14:17").EntireRow.Hidden = False
End If
If Range("F13").Value = "No" Then
Rows("14:17").EntireRow.Hidden = True
End If
If Range("F13").Value = " " Then
Rows("14:17").EntireRow.Hidden = True
End If
End If
End Sub
You may also want to use:
If Range("F13").Value = ""
instead of
If Range("F13").Value = " "
There is an End If missing. I assume the value of the target cell (F13) needs to be tested for it's value. If the value is "Yes", it should unhide row 14:17, if it is " " (spacebar) it should hide them and if it is "No" is should hide them as well. Other values will not affect the hiding/unhiding of the rows.
There should be a second End If before End Sub, so that all the if-statements above are wrapped within the Address check.
Also note that this code should be placed in the worksheet itself, since you want to hook into the Worksheet_Change event.
Try this in a worksheet module:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$F$13" Then 'Check if the changed value is indeed in F13
If Target.Value = "Yes" Then
ActiveSheet.Rows("14:17").EntireRow.Hidden = False 'Show the rows if the value is Yes
ElseIf Target.Value = "No" Then
ActiveSheet.Rows("14:17").EntireRow.Hidden = True 'Hide them when it's No
ElseIf Target.Value = " " Then
ActiveSheet.Rows("14:17").EntireRow.Hidden = True 'Or space
End If
End If
End Sub
Other remarks:
Instead of ActiveSheet you can also use Me (Me.Rows...) In this scenario they probably do the same. However, if you change the value on a worksheet from another worksheet (e.g. formula that recalculates), Me will reference the changed worksheet that fires the event, whereas activeworksheet will affect the currently active sheet.
Use Target instead of referencing the Range again. Target is a range object that is already in memory. Hence execution will be faster compared to accessing the worksheet again.