How to copy only the last value of a split cell into a new worksheet - excel

I am trying to copy a string of values into a single column on a new sheet. My code works when there is only one value in the active cell, but will copy every value in the cell once there are multiple values. I want it to copy only the most recent addition to the column on the new sheet. The input is selections from a drop-down menu that allows for multiple selections. I then have these selections being split and offset to a new cell 9 columns over (I also have other drop-downs so that is why there is so much space, but the larger loop should be able to handle the other drop-downs).
This is an image of the input:
This is what I am currently getting as an output:
This is my desired output:
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 = 1 To UBound(FullName)
ActiveCell.Offset(i, 9).Value = FullName(i)
ActiveCell.Offset(i, 9).Copy
Worksheets("Links").Range("A3").End(xlUp).Offset(2, 0).Insert
Next i
I have included only the loop of code that is problematic in order to simplify finding a solution.

My best guess is that on a detected change, you want to update a distinct list of values in 9 cells over?
Right now you are already managing a single distinct list. All that you would need to do is clear the values in the column 9 cells over then print the values in your drop down.
Private Sub Worksheet_Change(ByVal Target As Range)
'Code by Sumit Bansal from https://trumpexcel.com
' To allow multiple selections in a Drop Down List in Excel (without repetition)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Address = "$A$1" 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
Dim txt As String
Dim i As Integer
Dim FullName As Variant
txt = ActiveCell.Value
FullName = Split(txt, ";")
ActiveCell.Offset(, 9).EntireColumn.Clear
For i = 0 To UBound(FullName)
ActiveCell.Offset(i, 9) = Trim(FullName(i))
Next i
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
But what if I want a distinct list from more than one drop down or ; delimited array? The best way to manage a distinct list in is a Collection or a Dictionary object.
If that is what your looking for, I will update this answer with a way to use those objects.
Based on your feed back I have updated the code to below to use a collection object to manage your distinct list from more than one drop down.
Option Explicit
Private col As Collection
' ^ we are defining this to the module level. That means it will retain values
' and be able to be referenced from any other place in the project.
Private Sub Worksheet_Change(ByVal Target As Range)
'Code by Sumit Bansal from https://trumpexcel.com
' To allow multiple selections in a Drop Down List in Excel (without repetition)
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Not Intersect(Target, Range("$A$1:$B$1")) Is Nothing Then
' ^ this will make the area your looking more specific than just .row = 11
' you could also replace the address with a namedRange
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else:
If Target.Value = "" Then
'' My guess is that here you would want to make a call to a function that
'' removes values from the function. You should be able to loop over the collection
'' to find the value to remove.
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
ManageList Newvalue
' ^ you already have the newest value. You just need a easy way to check if it
' is in the list. To do this I made a sub that receives text, and checks
' if it is in the publicly scoped collection.
End If
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
Private Sub ManageList(txt As String)
' This Sub will receive a text value and try to put it in a collection.
If col Is Nothing Then Set col = New Collection
On Error Resume Next
col.Add Item:=txt, Key:=txt
' ^ this method will throw an error if the Key is already in the collection.
' all we need to do then is watch for errors, and handle if we found a new one.
' I have found that collections and dictionary objects can handle .5M keys without any issues.
' using a dictionary, would allow you to test for a key without defining an error handler.
' with the trade off being that you have to add an additional reference to your project.
If Err.Number = 0 Then
' we had a new value
PrintList col
End If
End Sub
Private Sub PrintList(col As Collection)
Dim printTo As Range
Dim i As Long
Set printTo = Range("e1")
' ^ change e1 to a fully qualified address of where you
' want you list to be printed.
printTo.EntireColumn.Clear
On Error GoTo eos:
For i = 0 To col.Count - 1
printTo.Offset(i) = col(i + 1)
Next
eos:
End Sub

Related

Converting Worksheet VBA to all Sheets

I am trying to take some code is functioning on a single sheet and convert it so that it applies to all worksheets in the book. I thought I could move the code a module and then use the Workbook_SheetChange "wrapper" described in Worksheet change event for every sheet in the workbook. However, the Multiple Select dropdown still only works on the original sheet, despite the "Named_Range" being defined on all sheets.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
With Sh
Dim OldVal As String
Dim NewVal As String
' If more than 1 cell is being changed
If Target.Count > 1 Then Exit Sub
If Target.Value = "" Then Exit Sub
If Not Intersect(Target, ActiveSheet.Range("Named_Range")) Is Nothing Then
' Turn off events so our changes don't trigger this event again
Application.EnableEvents = False
NewVal = Target.Value
' If there's nothing to undo this will cause an error
On Error Resume Next
Application.Undo
On Error GoTo 0
OldVal = Target.Value
' If selection is already in the cell we want to remove it
If InStr(OldVal, NewVal) Then
'If there's a comma in the cell, there's more than one word in the cell
If InStr(OldVal, ",") Then
If InStr(OldVal, ", " & NewVal) Then
Target.Value = Replace(OldVal, ", " & NewVal, "")
Else
Target.Value = Replace(OldVal, NewVal & ", ", "")
End If
Else
' If we get to here the selection was the only thing in the cell
Target.Value = ""
End If
Else
If OldVal = "" Then
Target.Value = NewVal
Else
' Delete cell contents
If NewVal = "" Then
Target.Value = ""
Else
' This IF prevents the same value appearing in the cell multiple times
' If you are happy to have the same value multiple times remove this IF
If InStr(Target.Value, NewVal) = 0 Then
Target.Value = OldVal & ", " & NewVal
End If
End If
End If
End If
Application.EnableEvents = True
Else
Exit Sub
End If
End With
End Sub
Regular modules don't have events. They are host agnostic. The SheetChange event is in the workbook object. So this means you must place your code into the ThisWorkBook object.
Now looking at your code I see a With Sh block that does nothing. You never use that object. The only place where it makes sense to use the sheet object is where you cite ActiveSheet. We always want to use Sh and we never want to assume that the correct sheet is active.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim OldVal As String
Dim NewVal As String
' If more than 1 cell is being changed
If Target.Count > 1 Then Exit Sub
If Target.Value = "" Then Exit Sub
If Not Intersect(Target, Sh.Range("Named_Range")) Is Nothing Then
' Turn off events so our changes don't trigger this event again
Application.EnableEvents = False
NewVal = Target.Value
' If there's nothing to undo this will cause an error
On Error Resume Next
Application.Undo
On Error GoTo 0
OldVal = Target.Value
' If selection is already in the cell we want to remove it
If InStr(OldVal, NewVal) Then
'If there's a comma in the cell, there's more than one word in the cell
If InStr(OldVal, ",") Then
If InStr(OldVal, ", " & NewVal) Then
Target.Value = Replace(OldVal, ", " & NewVal, "")
Else
Target.Value = Replace(OldVal, NewVal & ", ", "")
End If
Else
' If we get to here the selection was the only thing in the cell
Target.Value = ""
End If
Else
If OldVal = "" Then
Target.Value = NewVal
Else
' Delete cell contents
If NewVal = "" Then
Target.Value = NewVal
Else
' This IF prevents the same value appearing in the cell multiple times
' If you are happy to have the same value multiple times remove this IF
If InStr(Target.Value, NewVal) = 0 Then
Target.Value = OldVal & ", " & NewVal
End If
End If
End If
End If
Application.EnableEvents = True
Else
Exit Sub
End If
End Sub
Finally, there is a logic flaw at the bottom of your if tree. If oldval is not in newval, oldval is not blank and new val is not blank you end up in that final place where it optinally adds a comma. If the that isn't needed then you're in a state where you have run .Undo on the change and you haven't set Target.Value at all. This might make it so you can't change the contents of a cell to a new value. I'm not sure if you intended that behavior.

trying to add 2 events that happen in the same column in a worksheet

Good day
I am trying to make both of the macros work in the same range since I want the drop down it that is created there to be able to check if there is a value in the column next to it get that rows values and also still be able to run the first macro. see picture attached since I don't think I am explaining properly what I want.
So in the picture in column A is set number. Column B has the dropdown where the first macro was implemented which enables it to be able to get values from column A and also be able to add a value to show 2+3 , I need it then to be able to get the values of stream C and actually add them in column C
attached is my current code and a picture of a example that doesn't necessarily work with the code just an example show what I mean.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Dim nommer As Integer
Dim finder As Range
On Error GoTo Exitsub
If Target.Column = 3 Then <------- here is macro 1
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
Target.Value = Oldvalue & "+ " & Newvalue
End If
End If
End If
If Not Intersect(Target, Range("J9")) Is Nothing Then
Select Case Range("J9")
Case "A": toets_my_ws
End Select
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
If Target.Column = "3" Then <------- here is macro 2
nommer = ActiveCell.Value
Else: If Target.Value = "" Then GoTo Exitsub Else
Set finder = Range("B9:B40").Find(what:=ActiveCell.Value,LookIn:=xlValues, lookat:=xlWhole)
ActiveCell.Offset(0, 3).Value = finder.Offset(0, 4).Value
ActiveCell.Offset(0, 5).Value = finder.Offset(0, 6).Value
End If
End Sub
]1
You can only have one event of this type as it works for the entire worksheet. So if you have multiple criteria for what happens when the worksheet changes you need to include all of that logic in the event. I have combined your logic in to one big thing but there is no way for me to tell if this was done correctly. You never use the variable nommer so that does nothing. It's also not clear what toets_my_ws is.
I would advise against using Worksheet change events as is slows down the worksheet considerably.
The better way to tackle this would be to use User Defined Functions (UDF). This way you can embed VBA in to a single cell without bogging down the whole sheet with logic every time you press a key.
HERE IS THE COMBINED LOGIC:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Oldvalue As String
Dim Newvalue As String
Dim nommer As Integer
Dim finder As Range
If Target.Value = vbNullString Then GoTo Exitsub
On Error GoTo Exitsub
If Target.Column = 3 Then
nommer = ActiveCell.Value
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then
GoTo Exitsub
Else
Application.EnableEvents = False
Newvalue = Target.Value
Application.Undo
Oldvalue = Target.Value
If Oldvalue = "" Then
Target.Value = Newvalue
Else
Target.Value = Oldvalue & "+ " & Newvalue
End If
End If
Else
Set finder = Range("B9:B40").Find(what:=ActiveCell.Value, LookIn:=xlValues, lookat:=xlWhole)
ActiveCell.Offset(0, 3).Value = finder.Offset(0, 4).Value
ActiveCell.Offset(0, 5).Value = finder.Offset(0, 6).Value
End If
If Not Intersect(Target, Range("J9")) Is Nothing Then
Select Case Range("J9")
Case "A"
toets_my_ws
End Select
End If
Exitsub:
Application.EnableEvents = True
End Sub
HERE IS INFORMATION ABOUT UDFS:
https://excelchamps.com/excel-user-defined-function/

Copy paste rows meeting conditions stated from a multiple selection in a drop-down list (with no repetition)

I have:
1) one worksheet named "Data" for my products data base;
2) one worksheet named "Quotation ENG" for products quotations based on selected products from the data base;
3) one worksheet named "Manager" with dropdown lists to make the criteria selections.
Then, I have two pieces of code running fine independently.
One named Sub Quote is for copy-pasting part of the rows from my database to the quotation sheet when a criterion is met,
And one named Sub Worksheet_Change (credits: TrumpExcel) is for enabling multiple selections in a dropdown list.
I'm quite clueless on how to change the code of my Sub Quote module to make the copy-paste operation possible if the dropdown lists enable multiple criteria. ANy guidance is welcome :)
Sub Quote()
Dim Source As Worksheet
Dim Target As Worksheet
Dim Company As String
Dim InfoA As String
Dim Finalrow As Integer
Dim I As Integer
Set Source = Worksheets("Data")
Set Target = Worksheets("Quotation ENG")
Company = Worksheets("Manager").Range("E5").Value 'Where one dropdown list is located.
InfoA = Worksheets("Manager").Range("E7").Value 'Where one dropdown list is located.
Source.Select
Finalrow = Cells(Rows.Count, 1).End(xlUp).Row
For I = 2 To Finalrow
If Cells(I, 1) = Company And Cells(I, 2) = InfoA Then
Source.Range(Cells(I, 16), Cells(I, 23)).Copy Target.Range("A200").End(xlUp).Offset(1, 0).Resize(1, 8)
End If
Next I
Target.Select
Range("A1").Select
End Sub
=============================================================
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 = "$E$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
I refactored some parts of your code and turned the company variable into an array so it can store several values. Please read the comments inside the code.
As a suggestion, try to use Excel structured tables to store your data. It's gonna be easier to work with them in the future.
Replace your current Quote Sub for this:
Sub Quote()
Dim Source As Worksheet
Dim Target As Worksheet
Dim Company() As String ' Converted the company variable to an array
Dim InfoA As String
Dim Finalrow As Integer
Dim counter As Integer
Dim I As Integer
Set Source = Worksheets("Data")
Set Target = Worksheets("Quotation ENG")
Company = Split(Worksheets("Manager").Range("E5").Value, ",") 'Where one dropdown list is located.
InfoA = Worksheets("Manager").Range("E7").Value 'Where one dropdown list is located.
' Added the source sheet and removed the select as it slows down your code
Finalrow = Source.Cells(Rows.Count, 1).End(xlUp).Row
' Loop through each company contained in the array
For counter = 0 To UBound(Company)
' Loop through each data row
For I = 2 To Finalrow
' Added Company(counter) so you can access each array element and wrapped it with trim to delete extra spaces
If Source.Cells(I, 1) = Trim(Company(counter)) And Source.Cells(I, 2) = InfoA Then
Source.Range(Source.Cells(I, 16), Source.Cells(I, 23)).Copy Target.Range("A200").End(xlUp).Offset(1, 0).Resize(1, 8)
End If
Next I
Next counter
' Activate worksheet
Target.Activate
' Refer to the object full path
Target.Range("A1").Select
End Sub
Let me know if it works.

Ambiguous name detected: Worksheet_change

I'm attempting to add a second code to a single worksheet and keep getting the "Ambiguous name detected" error. Realise that I need to combine the two codes but having trouble doing so. here are the two codes, one below the other:
Private Sub Worksheet_Change(ByVal Target As Range)
'are changes made within answer range?
Set isect = Application.Intersect(Target, Range("Answers"))
If Not (isect Is Nothing) Then
For Each chng In Target.Cells
'Get row number
startY = Impact.Range("Answers").Row
targetY = chng.Row
row_offset = (targetY - startY) + 1
rating_type = Impact.Range("Impacts").Cells(row_offset, 1)
If rating_type = "Major / V.High" Then cols = 16711884
If rating_type = "Significant / High" Then cols = 255
If rating_type = "Important / Moderate" Then cols = 49407
If rating_type = "Minor / Low" Then cols = 5287936
If rating_type = "" Then cols = 16777215
Impact.Range("Ratings").Cells(row_offset, 1).Interior.Color = cols
Impact.Range("Impacts").Cells(row_offset, 1).Interior.Color = cols
Next chng
End If
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
' To Select Multiple Items from a Drop Down List in Excel
Dim Oldvalue As String
Dim Newvalue As String
Application.EnableEvents = True
On Error GoTo Exitsub
If Target.Address = "$C$2" 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
Was hoping someone knows how to combine the two in order to circumvent this error.
Thanks in advance!
Based on my comment, you can track changes in more than one range as shown in the below sample code.
Private Sub Worksheet_Change(ByVal Target As Range)
'Exit the sub if more than one cells are changed at the same time
If Target.CountLarge > 1 Then Exit Sub
'Disable the event so that if the code changes the cell content of any cell, the code is not triggered again
Application.EnableEvents = False
'Error handling to skip the code if an error occurs during the code execution and enable the events again
On Error GoTo ErrorHandling
'Change event code will be triggered if any cell in column A is changed
If Not Intersect(Target, Range("A:A")) Is Nothing Then
MsgBox "The content of a cell in colunm A has been changed."
'Change event code will be triggered if any cell in column C is changed
ElseIf Not Intersect(Target, Range("C:C")) Is Nothing Then
MsgBox "The content of a cell in colunm C has been changed."
'Change event code will be triggered if any cell in column E is changed
ElseIf Not Intersect(Target, Range("E:E")) Is Nothing Then
MsgBox "The content of a cell in colunm E has been changed."
End If
ErrorHandling:
Application.EnableEvents = True
End Sub

Target.address for multiple rows in Excel

I need to reference an entire column of Excel spreadsheet, with a drop-down list using VBA. The code i got online works only for a single cell which is "$M$2". How can i define a range for the entire column?
Private Sub Worksheet_Change(ByVal Target As Range)
' To Select Multiple Items from a Drop Down List in Excel
Dim Oldvalue As String
Dim Newvalue As String
On Error GoTo Exitsub
If Target.Address = "$M$2" 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
Target.Value = Oldvalue & ", " & Newvalue
End If
End If
End If
Application.EnableEvents = True
Exitsub:
Application.EnableEvents = True
End Sub
Firstly, Target may be a single cell or multiple cells, depending on what the user changed
To test if any cell in (and only in) column M changed, use
If Target.EntireColumn.Address = "$M:$M" Then
To test if any cell in Target is in column M use
Dim rng As Range
Set rng = Application.Intersect(Target, Me.Columns("M"))
If Not rng Is Nothing Then
Note: the rest of your code will need to be modified to allow for Target being more than one cell

Resources