I have implemented a data validation in-cell drop down list that I use to retain multiple values in a column of cells. Currently you can select from the dropdown list in any order and the cell will populate in that order. Is there a way to force the order to stay consistent with the list that is the source for my dropdown?
For example: My dropdown list is:
Jim
Tom
Bob
Aaron
The selections are made in this order:
Bob
Jim
Tom
I want the cell to display:
Jim, Tom, Bob
Below is my current VBA code for the data validation drop down list:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' To allow multiple selections in a Drop Down List
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Column = 13 Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else
If Target.Value = "" Then
GoTo Exitsub
Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & ", " & Newvalue
Else
Target.Value = Oldvalue
End If
End If
End If
End If
End If
Exitsub:
Application.EnableEvents = True
End Sub
So, below is a quick example screenshot:
Basically, the code above (given to me by a former coworker, not of my own invention) lets me keep multiple selections from the list in the cell, separated by a comma. That works great, but the selections from the list are presented in the cell in the order they were chosen.
I need them to show up in the order they are in in the list. From the example, if someone chooses Bob, then Tom, then Ryan, the current code displays Bob, Tom, Ryan. I need the code to re-sort the selections to display as Tom, Bob, Ryan.
Try this out - some changes from your original version, including that if you select something already selected it is removed from the selection.
Private Sub Worksheet_Change(ByVal Target As Range)
' To allow multiple selections in a Drop Down List
Dim Oldvalue As String
Dim Newvalue As String
Dim rng As Range, rngToCheck As Range, listVals
'run some checks
If rng.Cells.Count > 1 Then Exit Sub '<< this first!
Set rngToCheck = Me.Range("A1,C1,D1,M1").EntireColumn '<< checking columns A,C,D, M
Set rng = Application.Intersect(Target, _
rngToCheck.SpecialCells(xlCellTypeAllValidation))
If rng Is Nothing Then Exit Sub
If rng.Value <> "" Then
On Error GoTo Exitsub
Application.EnableEvents = False
Newvalue = rng.Value
Application.Undo
Oldvalue = rng.Value
If Oldvalue = "" Then
rng.Value = Newvalue
Else
listVals = Application.Evaluate(rng.Validation.Formula1).Value
rng.Value = SortItOut(listVals, Oldvalue, Newvalue) '<< call function
End If
End If
Exitsub:
If Err.Number > 0 Then Debug.Print Err.Description
Application.EnableEvents = True
End Sub
'Figure out what gets added (or removed) and keep
' it all in the same order as the validation source range
Private Function SortItOut(listVals, oldVal, newVal)
Const THE_SEP As String = ", "
Dim i As Long, arr, s, sep, t, listed, removeNewVal
s = ""
sep = ""
arr = Split(oldVal, THE_SEP)
'new value already listed?
removeNewVal = Not IsError(Application.Match(newVal, arr, 0))
For i = 1 To UBound(listVals, 1)
t = listVals(i, 1)
listed = Not IsError(Application.Match(t, arr, 0))
If listed Or newVal = t Then
If Not (removeNewVal And newVal = t) Then
s = s & sep & t
sep = THE_SEP
End If
End If
Next i
SortItOut = s
End Function
You can add this at the top:
Dim nameArray() As String
Dim sortedArray() As Variant: sortedArray = Array("Tom", "Bob", "Ryan") 'etc whatever order you need
Dim finalArray() As Variant
Dim spot1 As Integer
Dim spot2 As Integer: spot2 = 0
Dim name as String
And also include this right under Target.Value = Oldvalue & ", " & Newvalue :
Target.Value = Replace(Target.Value, ",", "")
nameArray = Split(Target.Value)
For spot1 = 0 To UBound(nameArray)
For Each name in nameArray
If name = sortedArray(spot1)
finalArray(spot2) = name
spot2 = spot2 + 1
End If
Next
Next
Target.Value = ""
For spot1 = 0 To UBound(finalArray)
If spot1 <> UBound(finalArray) Then
Target.Value = Target.Value & finalArray(spot1) & ", "
Else
Target.Value = finalArray(spot1)
End If
Next
Couldn't test it myself so make sure u save your file before testing.
Best of luck
Related
I am trying to figure out how to delete a value coming from a list in a cell.
What i have is a list where you can select multiple values. The problem with this is that i needed to open the list for each choice.
And the second problem is that if i want to delete one value, i have to delete them all and then choose again.
If someone has any ideas on how to do to improve what i have i will appreciate.
For reference : this is the VBA code that i add in my previous excel sheet :
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Column = 5 Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else: If Target.Value = "" Then GoTo Exitsub Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & ", " & Newvalue
Else:
Target.Value = Oldvalue
End If
End If
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
Please, test the next updated code. It checks if the new selected string already exists in the list and if so, asks for exclusion. Pressing Yes, it will be excluded:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String, Newvalue As String, ans As VbMsgBoxResult
If Target.cells.count > 1 Then Exit Sub 'if more than one cell changed (by copying, for example) code exists
If Not hasLValidation(Target) Then Exit Sub 'if no List validtion code exits
If Target.Value = "" Then Exit Sub
On Error GoTo Exitsub
If Target.Column = 5 Then
Application.EnableEvents = False
Newvalue = Target.Value: Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & ", " & Newvalue
Else
ans = MsgBox("Do you like excluding """ & Newvalue & """ from the list?" & vbCrLf & _
"For excluding, please press ""Yes""!", vbYesNo, "Exclusion confirmation")
If ans <> vbYes Then
Target.Value = Oldvalue
Else
Dim arr, mtch
arr = Split(Oldvalue, ", ") 'place the list in an array
mtch = Application.match(Newvalue, arr, 0) 'match the array element
If Not IsError(mtch) Then 'if a match exists:
arr(mtch - 1) = "##$%&" 'replace that element with a strange string different from all existing
arr = filter(arr, "##$%&", False) 'eliminate that specific element
Target.Value = Join(arr, ", ") 'place back the list by joining the array
End If
End If
End If
End If
End If
Exitsub:
Application.EnableEvents = True
End Sub
Function hasLValidation(T As Range) As Boolean
Dim vType As Long
On Error Resume Next
vType = T.Validation.Type
On Error GoTo 0
If vType = 3 Then hasLValidation = True 'only for LIST validation type!
End Function
Please, send some feedback after testing it.
I have 2 sheets in same workbook, one is data sheet and 2nd sheet contains the data validation values. I am facing problem while removing value from a cell (in data sheet) (which contains data validation). The issue is when I try to remove the value from validation list, the same value didn't remove from the cell. (see screen shot)
"e.g. if I want to remove volunteer name from the validation list, the value didn't delete from cell in data sheet (cell highlighted in screenshot)."
I have written a vba code to add multiple values in same cell separated by commas. I would appreciate if someone help me to solve this issue.
My VBA code is below:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngDV As Range
Dim oldVal As String
Dim newVal As String
Dim lUsed As Long
If Target.Count > 1 Then GoTo exitHandler
On Error Resume Next
Set rngDV = Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo exitHandler
If rngDV Is Nothing Then GoTo exitHandler
If Intersect(Target, rngDV) Is Nothing Then
'do nothing
Else
Application.EnableEvents = False
newVal = Target.Value
Application.Undo
oldVal = Target.Value
Target.Value = newVal
If Target.Column = 3 Or Target.Column = 4 Or Target.Column = 5 Or Target.Column = 6 Or
Target.Column = 7 Or Target.Column = 8 _
Or Target.Column = 9 Or Target.Column = 11 Then
If oldVal = "" Then
'do nothing
Else
If newVal = "" Then
'do nothing
Else
lUsed = InStr(1, oldVal, newVal)
If lUsed > 0 Then
If Right(oldVal, Len(newVal)) = newVal Then
Target.Value = Left(oldVal, Len(oldVal) - Len(newVal) - 2)
Else
Target.Value = Replace(oldVal, newVal & ", ", "")
End If
Else
Target.Value = oldVal _
& ", " & newVal
End If
End If
End If
End If
End If
exitHandler:
Application.EnableEvents = True
End Sub
You can find the worksheet from the link below: (show1 Sheet is data sheet and Validation Fields contains the drop-down values)
Excel Sheet
Thanks
This code works for me:
Private Sub Worksheet_Change(ByVal Target As Range)
Const SEP As String = ","
Dim c As Range, NewValue As String, OldValue As String, arr, v, lst, removed As Boolean
On Error GoTo Exitsub
If Target.CountLarge > 1 Then Exit Sub '<< only handling single-cell changes
Select Case Target.Column
Case 3, 4, 5, 6, 7, 8, 9, 11
Set c = Target
Case Else: Exit Sub
End Select
If Len(c.Value) > 0 And Not c.Validation Is Nothing Then
Application.EnableEvents = False
NewValue = c.Value
Application.Undo
OldValue = c.Value
If OldValue = "" Then
c.Value = NewValue
Else
arr = Split(OldValue, SEP)
'loop over previous list, removing newvalue if found
For Each v In arr
If Trim(CStr(v)) = NewValue Then
removed = True
Else
lst = lst & IIf(lst = "", "", SEP) & v
End If
Next v
'add the new value if we didn't just remove it
If Not removed Then lst = lst & IIf(lst = "", "", SEP) & NewValue
c.Value = lst
End If
End If 'has validation and non-empty
Exitsub:
If Err.Number <> 0 Then MsgBox Err.Description
Application.EnableEvents = True
End Sub
I have a dropdown in my excel sheet and I wanted to add multiple items from that dropdown into the cell. I was able to do that and now it functions as shown in the picture below:
I also wanted the users to only be allowed to choose two items from the entire list of items from the dropdown. The users can choose any two but also only two. I was not able to do that. Can someone help me? here is the code I used.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Address = "$C$8" Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else: If Target.Value = "" Then GoTo Exitsub Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & vbNewLine & Newvalue
Else:
Target.Value = Oldvalue
End If
End If
End If
End If
If Target.Address = ("$C$8") Then
If InStr(1, Target.Value, "5", vbTextCompare) > 0 Then
Rows("9").EntireRow.Hidden = False
Else
Rows("9").EntireRow.Hidden = True
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
I had a hard time to understand your question and also the code. It is a very uncommon behavior that you have a data validation dropdown that allows to add something into that cell. This results in a very strange user experience
When I select an entry from the list, there is no way to remove it again except clearing the cell.
When I select something and then press Ctrl+Z (undo), Excel cannot undo my change.
When I select more than the limit (2) number of entries, the change is not accepted, without further notice.
When I enter something manually in that cell and the limit of 2 entries is already reached, my changes will not be accepted.
For me as a user, that strange behaviour would drive me mad. As a clever user, I will find out sooner or later that I can Paste something into that cell. That will delete your data validation - If you want to go down that road, you must add logic into the trigger that prevents this.
That said, I cleaned up your code and added a check for a maximum number of entries - the change itself is rather easy, before adding the entry, it is checked how many NewLines are already in the value.
Private Sub Worksheet_Change(ByVal Target As Range)
Const MaxEntries = 2
Dim Oldvalue As String
Dim Newvalue As String
If Target.Address <> "$C$8" Then GoTo Exitsub
On Error GoTo Exitsub
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then GoTo Exitsub
If Target.Value = "" Then GoTo Exitsub
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
ElseIf InStr(Oldvalue, Newvalue) = 0 And countCharsInStr(Oldvalue, vbNewLine) + 1 < MaxEntries Then
Target.Value = Oldvalue & vbNewLine & Newvalue
Else
Target.Value = Oldvalue
End If
Rows("9").EntireRow.Hidden = InStr(Target.Value, "5") > 0
Exitsub:
Application.EnableEvents = True
End Sub
Public Function countCharsInStr(s As String, c As String) As Long
countCharsInStr = (Len(s) - Len(Replace(s, c, ""))) / Len(c)
End Function
The code structure doesn't make much sense to me, so I rearranged it.
My solution is a separate function anyway, so it should work either way.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Address = "$C$8" Then
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
ElseIf Target.Value = "" Then
GoTo Exitsub
Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
If InStr(1, Oldvalue, Newvalue) = 0 And Not CountLines(Target) > 1 Then
Target.Value = Oldvalue & vbNewLine & Newvalue
Else
Target.Value = Oldvalue
End If
End If
End If
If InStr(1, Target.Value, "5", vbTextCompare) > 0 Then
Rows("9").EntireRow.Hidden = False
Else
Rows("9").EntireRow.Hidden = True
End If
End If
Exitsub:
Application.EnableEvents = True
End Sub
Public Function CountLines(Target As Range) As Double
Dim H1 As Double, H2 As Double
With Target
.WrapText = False
H1 = .Height
.WrapText = True
H2 = .Height
End With
CountLines = H2 / H1
End Function
Since the added values are separated by a new line, counting how many values there are can be a bit tricky. One way would be to measure the height of the cell, which is what the function does.
We call this function to check the number of lines as we are adding new values. I merged it with the If checking for duplicates.
I currently have a code that loops through a dynamic drop down menu and prints the different selections in their own cell in a separate column. However, I want to adapt this code to work with multiple drop down menus. Currently, when the user selects options from an adjacent drop down menu, the value overwrites the previous selections. I am attempting to create a loop that places the selections from each drop down menu in their own column. Any help would be greatly appreciated as I am very new to VBA.
I have attempted to create an ActiveCell.Column function underneath the Target.Row if statement, but I receive an error that a variable is required.
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Row = 11 Then
'The following commented line causes variable error'
'For ActiveCell.Column = 1 To 8'
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else: If Target.Value = "" Then GoTo Exitsub Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Dim val As String
val = ActiveCell.Value
If j = 0 Then
Cells(11, j + 9).Value = val
End If
Else
If InStr(1, Oldvalue, Newvalue) = 0 Then
Target.Value = Oldvalue & ", " & Newvalue
Dim txt As String
Dim i As Integer
Dim FullName As Variant
txt = ActiveCell.Value
FullName = Split(txt, ",")
For i = 0 To UBound(FullName)
Cells(i + 11, 9).Value = FullName(i)
Next i
Else:
Target.Value = Oldvalue
I am hoping someone can help me (I have a very tight turn around time (48 hours). I have a multi-select drop-down in one of my columns in excel. The user can select and de-select values from a list of 12 values (They usually select no more than 2 at a time). What I want is then based on the values selected in that column, that it populates another multi-select drop down in a second column.
For example(not real example but I can't share values in real example):
Column A: Fruit, Vegetables, Meat, Dairy
(They can select any of the above, and are stored as (Fruit, Vegetables) in cell. They can come back and say they want to deselect Fruit and add Meat then it would be stored as (Vegetables, Meat).
Column B: Fruit options are (F1, F2, F3) Vegetables (V1, V2, V3) Meat (M1, M2, M3) and Dairy (D1, D2, D3) etc.
Data validation only works if an individual selects one option for Column A. What I want to work is that it recognizes there are 2 or more values in column A and then display the corresponding values in Column B in a drop-down for user to select that is also multi-select and also allows for edits.
Why am I doing this? I need to create a dashboard to display the number of times values in column A are selected and number of times values in column B are selected, and also which ones were not selected, having them all in one column I figured was easier rather than having a separate column for each value where user inputs an 'x' if applicable.
I am open to better ways to do this.
Any assistance would be greatly appreciated.
Thank you!
My code Thus far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngDV As Range
Dim oldVal As String
Dim newVal As String
Dim strVal As String
Dim i As Long
Dim lCount As Long
Dim Ar As Variant
On Error Resume Next
Dim lType As Long
If Target.Count > 1 Then GoTo exitHandler
lType = Target.Validation.Type
If lType = 3 Then
Application.EnableEvents = False
newVal = Target.Value
Application.Undo
oldVal = Target.Value
Target.Value = newVal
If Target.Column = 7 Or Target.Column = 8 Or Target.Column = 12 Or Target.Column = 13 Then
If oldVal = "" Then
'do nothing
Else
If newVal = "" Then
'do nothing
Else
On Error Resume Next
Ar = Split(oldVal, ", ")
strVal = ""
For i = LBound(Ar) To UBound(Ar)
Debug.Print strVal
Debug.Print CStr(Ar(i))
If newVal = CStr(Ar(i)) Then
'do not include this item
strVal = strVal
lCount = 1
Else
strVal = strVal & CStr(Ar(i)) & ", "
End If
Next i
If lCount > 0 Then
Target.Value = Left(strVal, Len(strVal) - 2)
Else
Target.Value = strVal & newVal
End If
End If
End If
End If
End If
exitHandler:
Application.EnableEvents = True
End Sub
So it involves a bit of arduous coding but you can set validation in specific cells based on other cell values as follows (they are currently hard-coded)
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call FillCombo(Target)
End Sub
Private Function FillCombo(ByVal Target As Range)
On Error GoTo ExitFunction
If Target.Cells.Count > 1 Then Exit Function
Dim ComboList As String, CLUpdate As Boolean: CLUpdate = False
Dim CLAll As String: CLAll = "Apples,Pears,Oranges"
Dim CLApp As String: CLApp = "Granny Smith,Pink Lady"
Dim CLPea As String: CLPea = "Bartlett,Comice"
Dim CLOra As String: CLOra = "Satsuma,Tangerine,Blood"
If WorksheetFunction.CountIf(Range("A2:A4"), "Apples") > 0 _
Then ComboList = ListJoin(ComboList, CLApp)
If WorksheetFunction.CountIf(Range("A2:A4"), "Pears") > 0 _
Then ComboList = ListJoin(ComboList, CLPea)
If WorksheetFunction.CountIf(Range("A2:A4"), "Oranges") > 0 _
Then ComboList = ListJoin(ComboList, CLOra)
If Not Application.Intersect(Target, Range("A2:A4")) Is Nothing Then
Call UpdateCombo(Target, CLAll)
End If
Call UpdateCombo(Range("A6"), ComboList)
ExitFunction:
End Function
Private Function UpdateCombo(ByVal Target As Range, ComboList As String)
With Target.Validation
.Delete
.Add Type:=xlValidateList, Formula1:=ComboList
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
If InStr(ComboList, Target.Value) = 0 Then Target.Value = ""
End Function
Private Function ListJoin(Str1 As String, Str2 As String) As String
If Str2 = "" Then ListJoin = Str1
If Str1 = "" And ListJoin = "" Then ListJoin = Str2
If ListJoin = "" Then ListJoin = Str1 & "," & Str2
End Function