I am attempting to write a fairly intelligent macro for Excel. In this instance, I have two seperate workbooks that I am transferring information between. The first workbook has a listing of dates in column A followed by corresponding values in the following columns. Like so:
As you can see, the dates and values are not consistently entered. They are only available certain days. The current entries stop at the end of 2012. The point of this macro is to add new numbers each month.
an excerpt from Workbook 2 looks like this:
My current macro currently locates the correct values in workbook 2 and copies them to Workbook 1.
Which is great except for the possibility of inconsistent dates. Where there could be 15 days worth of values in the "CMP (3 YEARS)"section, but only 14 days worth in the "CMP (4 YEARS)" section. If I were to blindly copy those values over to sheet one they would not line up correctly. I would rather there be a calculated average if a date is missing
So to remedy this problem, I am attempting the current solution:
'Grab 1 Month
Call Import("CMA/FIXED (4 WEEKS)", "B1", DestinationWorkbook, ExportedWorkbook, 3, 2, False, DestinationDateRange)
The above method is called for each column of numbers to be located as well as for extracting the dates.
Allow me to explain what is supposed to be happening below. First the String to search for in the second sheet is passed in. this is used in conjunction with the x and y offset integers to locate the correct values. The DestinationSheetName is Workbook1 and ExportedDataSheetName is Workbook2. the isDateImport boolean is there to tell the method that this is the first import and all we are grabbing is the first set of dates that will be added to column A of Workbook1. The ByRef DateRange is a pointer to those dates after they have been copied and pasted to column A of Workbook1. So obviously when this is used for dates initially this is null.
from there we check to see what kind of import we are doing. If it is for dates we just do a simple locate, copy and past. If not, we attempt an inteligent import of the located values. The idea is to create a pointer to both sets of dates, The dates in Column A in workbook1 and the dates that correspond to the values we are copying. We want the paste location to be in the correct row for each date. I create both pointers and point them at the start of both date columns. In place of the copy paste code I am just printing to the debug window. if the pointers values match each other. After comparing the dates I attempt to increment the pointers to the next values in the columns of dates.
This works great for 2 dates and then I get an error when I try to increment the pointers.
What should I be doing to fix this error? or is there an easier way to do this?... I know pointers are a little overkill for Excel spreadsheets...
Sub Import(SearchString As String, PasteLocation As String _
, DestinationSheetName As Variant _
, ExportedDataSheetName As Variant, xOffset As Integer, yOffset As Integer _
, isDateImport As Boolean, ByRef DateRange)
Windows(DestinationSheetName).Activate
Set newSpot = Range(PasteLocation).End(xlDown)
newSpot.Select 'remove
Set newSpot = newSpot.Item(2, 1)
Windows(ExportedDataSheetName).Activate
Cells.Find(What:=SearchString, After:=ActiveCell, LookIn:=xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False).Activate
ActiveCell.Select
If Not isDateImport Then
'intelligent import
Set datesColumn = ActiveCell.Item(xOffset, yOffset)
datesColumn.Select 'remove
Set valuesColumn = datesColumn.Item(1, 2)
valuesColumn.Select 'remove
Set datesColumn = Range(datesColumn, datesColumn.End(xlDown))
datesColumn.Select 'remove
Set valuesColumn = Range(valuesColumn, valuesColumn.End(xlDown))
valuesColumn.Select 'remove
Set DateColumnPointer = datesColumn.Item(1, 1)
DateColumnPointer.Select 'remove
Set DateRangePointer = DateRange.Item(1, 1)
Windows(DestinationSheetName).Activate
DateRangePointer.Select 'remove
For Each cell In valuesColumn
If (DateColumnPointer = DateRangePointer) Then
Debug.Print "Same"
Else
Debug.Print "Different"
End If
'increment Pointers
Windows(ExportedDataSheetName).Activate
DateColumnPointer = DateColumnPointer.Item(2, 1)
Windows(DestinationSheetName).Activate
DateRangePointer = DateRangePointer.Item(2, 1)
Next
Else
'primitive import
Set cell1 = ActiveCell.Item(xOffset, yOffset)
If isDateImport Then
Set cell2 = cell1.End(xlDown)
Else
Set cell2 = cell1.End(xlDown).End(xlToRight)
End If
Set rng = Range(cell1, cell2)
rng.Select
rng.Copy
Windows(DestinationSheetName).Activate
If isDateImport Then
Range("W1").Select
Else
Range("V1").Select
End If
ActiveSheet.Paste
'Add grabbed values
Set numbers = Range(Range("W1"), Range("W1").End(xlDown))
numbers.Copy
newSpot.PasteSpecial xlValues
End If
Windows(ExportedDataSheetName).Activate
Range("A1").Select
End Sub
You'll need to use set in your increment pointers section to increase the range incrementally otherwise your only copying the values from the next cell up to the current one.
'increment Pointers
Windows(ExportedDataSheetName).Activate
Set DateColumnPointer = DateColumnPointer.Item(2, 1)
Windows(DestinationSheetName).Activate
Set DateRangePointer = DateRangePointer.Item(2, 1)
Unless I've missed the point of your question?
Related
Am trying to make a VBA validation sheet on Excel to find all the cells that do not match a predefined pattern and copy it to another sheet
My pattern is "4 numbers/5 numbers"
Ex: 1234/12345 is accepted
2062/67943 is accepted
372/13333 is not accepted
1234/1234 is not accepted etc...
I tried to put the following in the conditions sheet : <>****/***** and <>????/????? and both did not work (am not sure about the correctness of the approach as am still a beginner in VBA)
For the code itself, this is what I wrote :
Sub GuaranteeElig()
Sheets.Add After:=ActiveSheet
ActiveSheet.Name = SheetName
Sheets("MainSheet").UsedRange.AdvancedFilter Action:= _
xlFilterCopy,
CriteriaRange:=Sheets("ConditionsSheet").Range("B1:B2"), _
CopyToRange:=Range("A1"), Unique:=False
End Sub
Any tips on how I can do it ?
Thanks in advance :)
As long as the values of the numbers are independent and do not matter, and it is only the Length of the numerical strings that count, you could use a for loop on the cells from the "search" sheet (I assume this is the MainSheet as shown in your code?) where your values are contained.
From there, I'll give you a couple ways to place the data in the validation sheet (assuming this is your ConditionsSheet as shown in your code?) where you are trying to pinpoint the values.
(You may need to change part of your approach depending on how you want the incorrect set of values laid out on your secondary sheet - but this should get you started.) I added a TON of comments as you say you're new to VBA - these will help you understand what is being done.
Sub GuaranteeElig()
'Adding this to help with performance:
Application.ScreenUpdating = False
'Assuming you are adding a sheet here to work with your found criteria.
Sheets.Add After:=ActiveSheet
ActiveSheet.Name = "ConditionsSheet"
'Using the naming bits below I am assuming the data you are searching for is on MainSheet
'Get used range (most accurate and efficient way I have found yet, others on S.O.
'may have better ways for this - research it if this does not work for you)
'I have had problems using the Sheets().UsedRange method.
Dim c as Long 'This may not be necessary for you if you are looping through only column "A"
Dim r as Long
'Cells(y,x) method uses numerical values for each row (y) or column (x).
c = Cells(1, Columns.Count).End(xlToLeft).Column 'May not be necessary depending on your needs.
'Using this because you have "UsedRange" in your
'code.
'.End(xlToLeft) signifies we are going to the end of the available cell range of
'Row 1 and then performing a "Ctrl+Left Arrow" to skip all blank cells until we hit
'the first non-blank cell.
r = Cells(Rows.Count, 1).End(xlUp).Row
'.End(xlUp) method is similar - we go to the end of the available cell range for the
'column ("A" in this case), then performing a "Ctrl+Up Arrow" to skip all blank cells.
'If you have a header row which spans across the sheet, this is your best option,
'unless you have 'helper' cells which extend beyond the final column of this header
'row. I am assuming Row 1 is a header in this case - change to your needs.
'For your Rows - choose the column which contains congruent data to the bottom of
'your used range - I will assume column 1 in this case - change to suit your needs.
Dim i as long
Dim j as integer
Dim cel as Range
Dim working_Str() as String 'String Array to use later
Dim string1 as String
Dim string2 as String
Dim badString as Boolean
For i = 2 to r Step 1 'Step down from row 2 to the end of data 1 Row at a time
'Row 1 is header.
set cel=Cells(i, 1) 'Sets the cell to check - assuming data is in Column "A"
'i will change from for loop so 'cel' changes from "A2555"
'to "A2554" to "A2553" etc.
working_Str=Split(cel.Value, "/", -1) 'Splits the value based on "/" inside of cel
string1=working_Str(0) 'what we hope will always be 4 digits
string2=working_Str(1) 'what we hope will always be 5 digits
If Len(string1)<>4 Then 'string1 _(xxxx)_(/)(don't care) does not equal 4 digits in length
badString = True
Elseif Len(string2)<>5 Then ''string1 (don't care)(/)_(xxxxx)_ does not equal 5 digits in length
badString = True
End If
If badString Then 'If either strings above were not correct length, then
'We will copy cell value over to the new sheet "ConditionsSheet"
'Comment the next 2 commands to change from going to one row at a time to
'Matching same row/Cell on the 2nd sheet. Change to suit your needs.
j = j + 1 'Counter to move through the cells as you go, only moving one cell
'at a time as you find incorrect values.
Sheets("ConditionsSheet").Range("A" & j).Value=cel.Value 'sets the value on other sheet
'UNComment the next command to change from going to one row at a time to
'matching same row/cell on the 2nd sheet. Change to suit your needs.
'Sheets("ConditionsSheet").Range("A" & i).Value=cel.Value
End if
badString = False 'resets your boolean so it will not fail next check if strings are correct
Next i
'Returning ScreenUpdating back to True to prevent Excel from suppressing screen updates
Application.ScreenUpdating = True
End Sub
UPDATE
Check the beginning and ending lines I just added into the subroutine. Application.ScreenUpdating will suppress or show the changes as they happen - suppressing them makes it go MUCH quicker. You also do not want to leave this setting disabled, as it will prevent Excel from showing updates as you try to work in the cell (like editing cell values, scrolling etc. . . Learned the hard way. . .)
Also, if you have a lot of records in the given row, you could try putting the data into an array first. There is a great example here at this StackOverflow Article.
Accessing the values of a range across multiple rows takes a LOT of bandwidth, so porting the range into an Array first will make this go much quicker, but it still may take a bit. Additionally, how you access the array information will be a little different, but it'll make sense as you research it a little more.
Alternative To VBA
If you want to try using a formula instead, you can use this - just modify for the range you are looking to search. This will potentially take longer depending on processing speed. I am entering the formula on 'Sheet2' and accessing 'Sheet1'
=IF(COUNTIF(Sheet1!A1,"????/?????"),1,0)
You are spot on with the search pattern you want to use, you just need to use a function which uses wildcard characters within an "if" function. What you do with the "If value is true" vs "If value is false" bits are up to you. COUNTIF will parse wildcards, so if it is able to "count" the cell matching this string combination, it will result in a "True" value for your if statement.
Regex method, this will dump the mismatched value in a worksheet named Result, change the input range and worksheet name accordingly.
In my testing, 72k cells in UsedRange takes about 4seconds~:
Option Explicit
Sub GuaranteeElig()
Const outputSheetName As String = "Result"
Dim testValues As Variant
testValues = ThisWorkbook.Worksheets("MainSheet").UsedRange.Value 'Input Range, change accordingly
Const numPattern As String = "[\d]{4}\/[\d]{5}"
Dim regex As Object
Set regex = CreateObject("VBScript.Regexp")
regex.Pattern = numPattern
Dim i As Long
Dim n As Long
Dim failValues As Collection
Set failValues = New Collection
'Loop through all the values and test if it fits the regex pattern - 4 digits + / + 5 digits
'Add the value to failValues collection if it fails the test.
For i = LBound(testValues, 1) To UBound(testValues, 1)
For n = LBound(testValues, 2) To UBound(testValues, 2)
If Not regex.Test(testValues(i, n)) Then failValues.Add testValues(i, n)
Next n
Next i
Erase testValues
Set regex = Nothing
If failValues.Count <> 0 Then
'If there are mismatched value(s) found
'Tranfer the values to an array for easy output later
Dim outputArr() As String
ReDim outputArr(1 To failValues.Count, 1 To 1) As String
For i = 1 To failValues.Count
outputArr(i, 1) = failValues(i)
Next i
'Test if output worksheet exist
Dim outputWS As Worksheet
On Error Resume Next
Set outputWS = ThisWorkbook.Worksheets(outputSheetName)
On Error GoTo 0
'If output worksheet doesn't exist, create a new sheet else clear the first column for array dump
If outputWS Is Nothing Then
Set outputWS = ThisWorkbook.Worksheets.Add
outputWS.Name = outputSheetName
Else
outputWS.Columns(1).Clear
End If
'Dump the array starting from cell A1
outputWS.Cells(1, 1).Resize(UBound(outputArr, 1)).Value = outputArr
Else
MsgBox "No mismatched value found in range"
End If
Set failValues = Nothing
End Sub
If you do not need duplicate values in the list of mismatched (i.e. unique values) then sound out in the comment.
Iv'e been breaking my head over this.
My first sheet contains these buttons:
ImageButtons
With this being the transportFile:
transportFile
So I'm trying to make it so that just the rows that contain (in this case) January and february dates get pasted to the "2016" sheet.
This is the code that I'm using right now:
If CheckBoxJanuary.Value = True Then
Worksheets("2016").Range(Worksheets("2016").Cells(2, 1), Worksheets("2016").Cells(janCount, 13)).Value = Worksheets("transportFile").Range(Worksheets("transportFile").Cells(2, 1), Worksheets("transportFile").Cells(janCount, 13)).Value
End If
If CheckBoxFebruary.Value = True Then
Worksheets("2016").Range(Worksheets("2016").Cells(janCount + 1, 1), Worksheets("2016").Cells(janCount + febCount, 13)).Value = Worksheets("transportFile").Range(Worksheets("transportFile").Cells(janCount + 1, 1), Worksheets("transportFile").Cells(janCount + febCount, 13)).Value
End If
"janCount" and "febrCount" represent the numbers of rows that contain January and february dates. This is being calculated in the transportFile with
"=SUMPRODUCT(--(MONTH($A$2:$A$150)=1))"
and
"=SUMPRODUCT(--(MONTH($A$2:$A$1500)=2))"
Afterwards, I run a loop that deletes the empty rows in the 2016 sheet.
Now I have 2 questions:
In the sumproduct formula of January I had to reduce the range because excel counts every empty cell as a January one. It's almost October, so that's not a problem now. But in 2017, when there's no data yet, there will be 150 January dates. How can I solve that?
If someone (by mistake) puts a March in between the Februaries, my ranges get all messed up. How can I avoid this?
If your column with dates is formatted properly as date, then why don't check for value of month(cell)?
You could do check for each combobox while looping through all cells in column A
like
If combo box "January" selected Then
'month = 1 and non empty
If (Month(Cells(i, 1).Value) = 1) And (Cells(i, 1) <> "") Then
'copy your rows to new sheet
End if
End if
If combo box "Feb" selected Then
'month = 2 and non empty
....
As for 1. " excel counts every empty cell as a January one" probably they can be excluded somehow, a crude way would be to do exact same sumproduct for all empty cells in a column and subtract them :)
=SUMPRODUCT(--(MONTH($A$2:$A$150)=1))-SUMPRODUCT(--(($A$2:$A$150)=""))
EDIT
Ok I had to check the sumproduct, correct way is to use second array to check for cells that are non empty:
=SUMPRODUCT(--(MONTH($A$2:$A$37)=1);--(($A$2:$A$37)<>""))
This will return count of cells that have month(cell)=1 AND cell.value <> empty so you don't get false count for January when empty cell returns month=1
As for 2 if you would make the loop using VBA to go through all your data then it doesn't matter if they are in order or not as each cell month value will be read, irrespectively of order.
EDIT 2
I will not propose the solution for this option but maybe the Pivot table could be the good solution for that task? VBA code could be use to modify displayed data in the pivot table depending on the selected checkboxes.
This code will look at each checkbox on the sheet to decide which has been ticket (assuming the only checkboxes you have are for months and they're all named CheckBoxMMMMM).
It then filters by those months and copies the filtered rows to the final sheet.
Sub CopyFiltered()
Dim wrkSht As Worksheet
Dim shp As Shape
Dim FilterMonths As Collection
Dim vItem As Variant
Dim rLastCell As Range
Dim rFilterRange As Range
Dim vFilterString() As Variant
Dim x As Long
Set wrkSht = ThisWorkbook.Worksheets("TickBoxSheet")
Set FilterMonths = New Collection
'Get a collection of ticked dates.
'This works by looking at each checkbox on the sheet.
'It assumes they're all called 'CheckBoxMMMM' so it can build a real date from the name.
For Each shp In wrkSht.Shapes
If shp.Type = msoFormControl Then
If shp.FormControlType = xlCheckBox Then
If shp.ControlFormat.Value = 1 Then
FilterMonths.Add DateValue("1 " & Replace(shp.Name, "CheckBox", ""))
End If
End If
End If
Next shp
'Create an array of "1 ,<date>,1 ,<2nd date>"
x = 1
ReDim vFilterString(1 To FilterMonths.Count * 2)
For Each vItem In FilterMonths
vFilterString(x) = 1
vFilterString(x + 1) = Format(vItem, "m/d/yyyy")
x = x + 2
Next vItem
'Apply the filter - the commented line works but is hardcoded.
'The other filter line appears to be the same as the commented line, but isn't working....
With ThisWorkbook.Worksheets("2016")
If .AutoFilterMode Then .AutoFilterMode = False
Set rLastCell = Sheet2.Cells.Find(What:="*", After:=.Cells(1, 1), SearchDirection:=xlPrevious)
Set rFilterRange = .Range(.Cells(1, 1), rLastCell)
rFilterRange.AutoFilter Field:=1, Operator:=xlFilterValues, Criteria2:=vFilterString
'Copy the visible filtered cells to the transportfile sheet.
.Range(.Cells(1, 1), rLastCell).SpecialCells(xlVisible).Copy Destination:=ThisWorkbook.Worksheets("transportfile").Range("A1")
End With
End Sub
From what I can find on the internet the numerical value given to the array (1) returns all values in that month. Other values available are:
0 year
1 month
2 day
3 hour
4 minute
5 second
I have the following code that I've written to take some names and use them to populate a timesheet.
Sub InitNames()
Dim i As Integer
i = 0
Dim name As String
Windows("Employee Data.xlsx").Activate
Sheets("Employees").Select
Range("A2").Select
Do Until IsEmpty(ActiveCell)
name = ActiveCell.Value
Workbooks("Timesheet").Sheets("ST").Range("A9").Offset(i * 9).Value = name
ActiveCell.Offset(1, 0).Select
Loop
End Sub
Basically, the cells in the target sheet are spaced 9 rows away from each other, so the first name would go in cell A9, the second in A18, the third in A27, and so on. I'm sure I'm missing something incredibly simple, but I'm not getting any feedback from Excel whatsoever (no error messages). The cells in the timesheet are merged cells, but I cannot change them (locked by the owner), but I don't think that has anything to do with it.
EDIT: I added a line: OriginalValue = Workbooks("Timesheet").Sheets("ST").Range("A10").Offset((x - 2) * 9, 0).Value so I could watch to see what values were being overwritten in my Timesheet and I noticed something interesting: OriginalValue only grabs the first cell's text (A9), thereafter, for every cell (A18, A27, etc.) the debugger indicates that OriginalValue = "" even though those cells also contain names. However, when I open another worksheet and reference A9, A18, etc., I AM pulling the names.
EDIT 2: I modified the test line to read Workbooks("Timesheet").Sheets("ST").Range("A" & ((x - 1) * 9)).Value = "Test" which does change the values in all the target cells. Why would VBA allow me to assign "Test" to a cell value but not the names in the other worksheet?
Try something like this. It will accomplish the task you are requesting without using .Select or .Activate
Sub InitNames()
Dim i As Integer
Dim Wksht as Worksheet
i = 0
Set Wksht = Workbooks("Employee Data.xlsx").Sheets("Employees")
For i = 2 to Wksht.Range("A" & Wksht.Rows.Count).End(xlUp).Row
Workbooks("Timesheet").Sheets("ST").Range("A9").Offset((i-2) * 9,0).Value = Wksht.Range("A" & i).Value
Next i
End Sub
I run a small retail store and I've been tasked with creating inventory labels for our items. I export a list of items from an inventory management software (AMan Pro) into Excel taking the items' Description, Quantity, Condition, SKU#, and Platform (video game platform). I've got two Macros currently. One will trim up the SKU to a usable format (gets rid of leading letters) and the other separates multiple quantity items out to separate rows. (i.e. item #1 with a Quantity of 5 will be copied to 5 rows all with a quantity of 1) That stuff works, but I think I'm doing some unnecessary steps that could be handled with a macro.
My AMan program spits out an Excel workbook with the items. I then copy those items into my 'macro-enabled' workbook on sheet one and then run my macros. Sheet two has the properly formatted data for the labels. For example; it has formulas that trim the Description field to only use the first 60 characters. (some of the descriptions are pretty long)
I'm feeling like that second sheet isn't really necessary. I would like to copy the items into Sheet 1 and run a macro that does all of that formatting for me on sheet 1.
Separate items with multiple quanities onto separate rows (I do have
a working macro for this.)
Remove leading letters from SKU (working macro) and then put SKU in seven digit number format (#######)
Reduce the Description to only the first 60 characters.
Reduce the Condition to only the first 2 characters.
Reduce the Platform to only the first 15 characters.
Here's my current macro code and a link to my spreadsheet. Thanks in advance, guys.
SKU_LABEL_FINAL.xlsm
Sub ExpandRows()
Dim dat As Variant
Dim i As Long
Dim rw As Range
Dim rng As Range
Set rng = Sheets(1).UsedRange
dat = rng
' Loop thru your data, starting at the last row
For i = UBound(dat, 1) To 2 Step -1
' If Quantity > 1
If dat(i, 2) > 1 Then
' Insert rows to make space
Set rw = rng.Rows(i).EntireRow
rw.Offset(1, 0).Resize(dat(i, 2) - 1).Insert
' copy row data down
rw.Copy rw.Offset(1, 0).Resize(dat(i, 2) - 1)
' set Quantity to 1
rw.Cells(1, 2).Resize(dat(i, 2), 1) = 1
End If
Next
Columns("D:D").Select
Selection.Replace What:="AManPro-", Replacement:="", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
End Sub
To format your SKUs into a 7 digit number format is pretty simple with what you have. You would just need to insert this line of code before the End Sub line
Selection.NumberFormat = "0000000"
You could trim the length of each cell when you parse through the data.
First, I would use some Const statements to note which columns are what, and what length they need to be. You could just use the numbers in the code, but this makes it easier to update things in the future if they change. Add below the final Dim
Const DESCRIPTIONLENGTH = 60
Const DESCRIPTIONCOLUMN = 2
Then as you loop through each row, you would then update the value of the cell - add below the For i =...
'Format each column's data
dat(i, DESCRIPTIONCOLUMN).Value = Left(dat(i, DESCRIPTIONCOLUMN), DESCRIPTIONLENGTH)
I'm hoping the code for the description column will help you to build what you need for the other columns as well.
I have a list in Excel and I need to format rows based on the value in the cell 2 of that row. This is how data looks like
No. | Name | Other data | Other data 2 | Date | Date 2 |
For example, if Name=John Tery => color row as Red, if Name=Mary Jane => color row as Pink, etc.
I tried using conditional formatting, but I did not know how to make this work. I have very little experience with such tasks in Excel.
Can anyone help?
PS. all name are two-word names
if there are only a few names to handle, each conditional-format formula would look like this
=$B2="John Tery"
you need to have selected the affected rows from the top row down (so current active cell is in the 2nd row, not in the last row)
absolute reference to column $B means that for all cells in different columns, column B will be tested
relative reference to row 2 means that for cell in different rows, its own row will be tested (e.g. for cell A42, the formula will test value of $B42)
equality operator = will return either TRUE or FALSE (or an error if any of the arguments are errors) and it has the same use as inside IF conditions...
Edit Rereading the question, I saw that the entire row is to be coloured not just the name. I also decided that if a recognised name is replaced by an unrecognised name, the colour should be removed from the row. The original code has been replaced to address these issues.
I decided I did not care about the answers to my questions because the solution below seems the easiest for any scenerio I could identify.
First you need some method of identifying that "John Tery" is to be coloured red and "Mary Jane" is to be coloured pink. I decided the easiest approach was to have a worksheet NameColour which listed the names coloured as required. So the routine knows "John Tery" is to be red because it is red in this list. I have added a few more names to your list. The routine does not care how many words are in a name.
The code below must go in ThisWorkbook. This routine is triggered whenever a cell is changed. The variables MonitorColNum and MonitorSheetName tell the routine which sheet and column to monitor. Any other cell changes are ignored. If it finds a match, it copies the standard form of the name from NameColour (delete this statement from the code if not required) and colours the cell as required. If it does not find a match, it adds the name to NameColour for later specification of its colour.
Hope this helps.
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Changed As Range)
Dim CellCrnt As Variant
Dim ColLast As Long
Dim Found As Boolean
Dim MonitorColNum As Long
Dim MonitorSheetName As String
Dim RowNCCrnt As Long
MonitorSheetName = "Sheet2"
MonitorColNum = 2
' So changes to monitored cells do not trigger this routine
Application.EnableEvents = False
If Sh.Name = MonitorSheetName Then
' Use last value in heading row to determine range to colour
ColLast = Sh.Cells(1, Columns.Count).End(xlToLeft).Column
For Each CellCrnt In Changed
If CellCrnt.Column = MonitorColNum Then
With Worksheets("NameColour")
RowNCCrnt = 1
Found = False
Do While .Cells(RowNCCrnt, 1).Value <> ""
If LCase(.Cells(RowNCCrnt, 1).Value) = LCase(CellCrnt.Value) Then
' Ensure standard case
CellCrnt.Value = .Cells(RowNCCrnt, 1).Value
' Set required colour to name
'CellCrnt.Interior.Color = .Cells(RowNCCrnt, 1).Interior.Color
' Set required colour to row
Sh.Range(Sh.Cells(CellCrnt.Row, 1), _
Sh.Cells(CellCrnt.Row, ColLast)).Interior.Color = _
.Cells(RowNCCrnt, 1).Interior.Color
Found = True
Exit Do
End If
RowNCCrnt = RowNCCrnt + 1
Loop
If Not Found Then
' Name not found. Add to list so its colour can be specified later
.Cells(RowNCCrnt, 1).Value = CellCrnt.Value
' Clear any existing colour
Sh.Range(Sh.Cells(CellCrnt.Row, 1), _
Sh.Cells(CellCrnt.Row, ColLast)).Interior.ColorIndex = xlNone
End If
End With
End If
Next
End If
Application.EnableEvents = True
End Sub