Using for loops to identify rows - excel

I tried here, here, and here.
I'm trying to highlight a row based on the string contents of a cell in the first column.
For example, if a cell in the first column contains the string "Total", then highlight the row a darker color.
Sub tryrow()
Dim Years
Dim rownum As String
Years = Array("2007", "2008", "2009") ' short example
For i = 0 To UBound(Years)
Set rownum = Range("A:A").Find(Years(i) & " Total", LookIn:=xlValues).Address
Range(rownum, Range(rownum).End(xlToRight)).Interior.ColorIndex = 1
Next i
End Sub
I get this error message:
Compile error: Object required
The editor highlights rownum = , as if this object hadn't been initialized with Dim rownum As String.

You've got a couple issues here, indicated below alongside the fix:
Sub tryrow()
Dim Years() As String 'Best practice is to dim all variables with types. This makes catching errors early much easier
Dim rownum As Range 'Find function returns a range, not a string
Years = Array("2007", "2008", "2009") ' short example
For i = 0 To UBound(Years)
Set rownum = Range("A:A").Find(Years(i) & " Total", LookIn:=xlValues) 'Return the actual range, not just the address of the range (which is a string)
If Not rownum Is Nothing Then 'Make sure an actual value was found
rownum.EntireRow.Interior.ColorIndex = 15 'Instead of trying to build row range, just use the built-in EntireRow function. Also, ColorIndex for gray is 15 (1 is black, which makes it unreadable)
End If
Next i
End Sub

You can avoid loop by using autofilter which will work much faster. The code assumes that table starts from A1 cell:
Sub HighlightRows()
Dim rng As Range, rngData As Range, rngVisible As Range
'//All table
Set rng = Range("A1").CurrentRegion
'//Table without header
With rng
Set rngData = .Offset(1).Resize(.Rows.Count - 1)
End With
rng.AutoFilter Field:=1, Criteria1:="*Total*"
'// Need error handling 'cause if there are no values, error will occur
On Error Resume Next
Set rngVisible = rngData.SpecialCells(xlCellTypeVisible)
If Err = 0 Then
rngVisible.EntireRow.Interior.ColorIndex = 1
End If
On Error GoTo 0
End Sub

Related

Find range of cells, when given 2 Dates

I have a table with numbers from 1 to 10. (Starting from D2 to M2)
Suppose in A1 there is 03/09/2019
AND in B1 there is 06/09/2019
AND in C1 there is Hello
In COLUMN A I have a multiple series of words starting from A3 to A10
Here is an Example of the Excel Table
What I would like to do is: Search for the word Student in Column A, when I find it, get the numbers from A1 --> 3
and A2 --> 6 and write the word Hello that is in C1 in the cells that go to 3 to 6 in the row of the finded word Student
So my output would be like:
This is my code so far:
Dim Cell As Range
Columns("A:A").Select
Set Cell = Selection.Find(What:="Student", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Cell Is Nothing Then
MsgBox "Word not found"
Else
MsgBox "Word found"
End If
Basically I can find the word Student but don't know how to write the word Hello in the cells between 3 to 6
A few notes regarding the code below (not tested!).
1) Always try use worksheet qualifiers when working with VBA. This will allow for cleaner code with less room for unnecessary errors
2) When using .Find method I use LookAt:=xlWhole because if you do not explicitly define this your code will use the last known method that you would have used in Excel. Again, explicit definition leaves less room for error.
3) Try include error handling when you code. This provides “break points” for easier debugging in the future.
4) You can make the below much more dynamic that it currently is. But I'll leave that up to you to learn how to do!
Option Explicit
Sub SearchAndBuild()
Dim rSearch As Range
Dim lDayOne As Long, lDayTwo As Long
Dim lColOne As Long, lColTwo As Long
Dim sHello As String
Dim wsS1 As Worksheet
Dim i As Long
'set the worksheet object
Set wsS1 = ThisWorkbook.Sheets("Sheet1")
'store variables
lDayOne = Day(wsS1.Range("A1").Value)
lDayTwo = Day(wsS1.Range("B1").Value)
sHello = wsS1.Range("C1").Value
'find the student first
Set rSearch = wsS1.Range("A:A").Find(What:="Student", LookAt:=xlWhole)
'error handling
If rSearch Is Nothing Then
MsgBox "Error, could not find Student."
Exit Sub
End If
'now loop forwards to find first date and second date - store column naumbers
'adjust these limits where necessary - can make dynamic
For i = 4 To 13
If wsS1.Cells(2, i).Value = lDayOne Then
lColOne = i
End If
If wsS1.Cells(2, i).Value = lDayTwo Then
lColTwo = i
Exit For
End If
Next i
'now merge the range
wsS1.Range(wsS1.Cells(rSearch.Row, lColOne), wsS1.Cells(rSearch.Row, lColTwo)).Merge
'set the vvalue
wsS1.Cells(rSearch.Row, lColOne).Value = sHello
End Sub
This is just one way to approach the problem. Hopefully this helps your understanding!
No need for a loop here - just find your value and parse the dates. Assuming your value to be found exists in Column A and your table starts in Column D, there is clear relationship between the columns which is Day(date) + 3.
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim lr As Long, Found As Range
Dim date_a As Long, date_b As Long
lr = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set Found = ws.Range("A2:A" & lr).Find("Student", LookIn:=xlValues)
If Not Found Is Nothing Then
date_a = Day(Range("A1")) + 3
date_b = Day(Range("B1")) + 3
With ws.Range(ws.Cells(Found.Row, date_a), ws.Cells(Found.Row, date_b))
.Merge
.Value = ws.Range("C1")
End With
Else
MsgBox "Value 'Student' Not Found"
End If
End Sub
I've tried this:
Dim ThisRow As Long
Dim FindWhat As String
FindWhat = "Student"
Dim MyStart As Byte
Dim MyEnd As Byte
MyStart = Day(Range("A1").Value) + 3 'we add +3 because starting 1 is in the fourth column
MyEnd = Day(Range("B1").Value) + 3 'we add +3 because starting 1 is in the fourth column
Dim SearchRange As Range
Set SearchRange = Range("A3:A10") 'range of values
With Application.WorksheetFunction
'we first if the value exists with a count.
If .CountIf(SearchRange, FindWhat) > 0 Then 'it means findwhat exists
ThisRow = .Match(FindWhat, Range("A:A"), 0) 'we find row number of value
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Value = Range("C1").Value
Application.DisplayAlerts = False
Range(Cells(ThisRow, MyStart), Cells(ThisRow, MyEnd)).Merge
Application.DisplayAlerts = True
Else
MsgBox "Value 'Student' Not Found"
End If
End With
Note I've used worksheets function COUNTIF and MATCH. MATCH will find the position of an element in a range, so if you check the whole column, it will tell you the row number. But if it finds nothing, it will rise an error. Easy way to avoid that is, first, counting if the value exists in that range with COUNTIF, and if it does, then you can use MATCH safely
Also, note that because we are using MATCH, this function only finds first coincidence, so if your list of values in column A got duplicates, this method won't work for you!.

Stack different columns into one column on a different worksheet

I want to copy all filled cells starting from C5 to column F of a different worksheet.
I referred to another post: Excel - Combine multiple columns into one column
Modified the code based on my needs.
Sub CombineColumns()
Dim Range1 As Range, iCol As Long, Range2 As Range, Check As Range, wks As Worksheets
Set Range1 = wks("T(M)").Range(Cells(5, 3), Cells(Cells(5, 3).End(xlDown).Row, Cells(5, 3).End(xlToRight).Column))
Set Check = wks("csv").Range("F1")
If IsEmpty(Check.Value) = True Then
Set Range2 = Check
Else
LastRow = wks("csv").Range("F" & Rows.Count).End(xlUp).Row
Set Range2 = wks("csv").Cells(LastRow, 6).Offset(1, 0)
End If
For iCol = 3 To Range1.Columns.Count
wks("T(M)").Range(Cells(5, iCol), Cells(Range1.Columns(iCol).Rows.Count, iCol)).Copy
wks("csv").Range2.PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Next iCol
End Sub
But I kept getting the error message
"object doesn't support this method or property"
at the step of pasting. After I tried to qualify all the ranges, It says I didn't set the object variable.
Thank you so much for the help!
How about this?
Sub Transposes()
' Example just for hardcoded data
Dim inputRange As Range
Set inputRange = Sheets("Sheet1").Range("C5:F10").SpecialCells(xlCellTypeConstants)
Dim outputCell As Range
Set outputCell = Sheets("Sheet2").Range("A1")
Dim cell As Range
For Each cell In inputRange
Dim offset As Long
outputCell.offset(offset).Value = cell.Value
offset = offset + 1
Next cell
End Sub
Set the last row in ColumnF to be whatever you want, and if that changes dynamically, just use any one of the multiple techniques out there to find the last cell you need to copy/paste.

Resize non sequential column reference to the first row

Can someone tell me how I can resize a column reference to the first row in a worksheet?
I get errors (for different reasons on both) which is understandable but still frustrating:
Attempt 1:
Dim Columns_To_Export as String
Columns_To_Export ="$B:$C,$E:$E"
Range(Columns_To_Export).Resize(1).Select
Attempt 2:
Dim Columns_To_Export as String
Columns_To_Export ="$B:$C,$E:$E"
Colulumns(Columns_To_Export).Resize(1).Select
I'm afraid I wasn't very attentive with my above response. Here is what I believe is a better try.
Private Sub SelectCells()
On Error Resume Next
Debug.Print CellsForExport("$B:$C, $E:$E, G").Address
End Sub
Function CellsForExport(ByVal Desc As String) As Range
' 15 Apr 2017
Dim Fun As Range, Rng As Range
Dim Spi() As String, Spj() As String
Dim i As Integer, j As Integer
Dim Cstart As Long, Cend As Long
Desc = Replace(Desc, "$", "")
Spi = Split(Desc, ",")
For i = 0 To UBound(Spi)
Spj = Split(Trim(Spi(i)), ":")
Cstart = Columns(Trim(Spj(0))).Column
Cend = Cstart
If UBound(Spj) Then Cend = Columns(Trim(Spj(1))).Column
With ActiveSheet.Rows(1)
Set Rng = .Range(.Cells(Cstart), .Cells(Cend))
End With
If Fun Is Nothing Then
Set Fun = Rng
Else
Set Fun = Application.Union(Fun, Rng)
End If
Next i
Set CellsForExport = Fun
End Function
The function CellsForExport returns a range as specified by the calling procedure SelectCells. The calling procedure in this case doesn't select the range. Instead it prints its address. That is for testing purposes. CellsForExport("$B:$C, $E:$E, G").Select will select the cells. You could also paste this range somewhere or manipulate it in any other way you can manipulate a range.
Note that you can omit the $ signs when specifying the columns. You also don't have to specify E:E to define a single column, but if someone does all that the macro will sort it out. Blank spaces don't matter, commas are of the essence.
Basically, Column is a range, not a string. This code will do the job.
Dim Rng As Range
With ActiveSheet
Set Rng = Application.Union(.Columns("B"), .Columns("C"), .Columns("E"))
End With
Rng.Select

Copy & Paste values if cell value = "N/A"

I want to copy and paste values to a range of cells but only if their value = "N/A". I want to leave the formulas as they are in all the cells that do not = "N/A".
In context, I have hundreds of VLOOKUPs. Example:
=IFERROR(VLOOKUP("L0"&MID(G$4,1,1)&"A0"&MID(G$4,1,1)&MID(G$4,3,2)&"-0"&$B6,Sheet1!$C:$D,2,FALSE),"N/A")
Is this possible with VBA?
First of all, you should use real error values rather than string that only look like errors. Secondly, VLOOKUP returns the N/A error directly if the lookup value is not found, so the IFERROR wrapper can be dispenced with. So the formula
=VLOOKUP("L0"&MID(G$4,1,1)&"A0"&MID(G$4,1,1)&MID(G$4,3,2)&"-0"&$B6,Sheet1!$C:$D,2,FALSE)
is sufficient as is.
To replace N/A results with error values, you can use this
Sub Demo()
Dim ws As Worksheet
Dim rngSrc As Range
Dim datV As Variant, datF As Variant
Dim i As Long
' Get range to process by any means you choose
' For example
Set ws = ActiveSheet
With ws
Set rngSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
' Copy data to variant arrays for efficiency
datV = rngSrc.Value
datF = rngSrc.Formula
' replace erroring formulas
For i = 1 To UBound(datV, 1)
If IsError(datV(i, 1)) Then
If datV(i, 1) = CVErr(xlErrNA) Then
datF(i, 1) = CVErr(xlErrNA)
End If
End If
Next
' return data from variant arrays to sheet
rngSrc.Formula = datF
End Sub
If you really want to use strings rather than true error values, adapt the If lines to suit
Rather than loop through all cells in a range, you can use SpecialCells to shorten working with the =NA()cells
This also open up a non-VBA method (if the only error cells are NA, ie no Div#/0)
The first two methods below (manual and code) deal with the situation where you only gave NA cells
the third uses SpecialCells to focus on only the cells that need to be tested, before then running a check for NA before making updates
option1
Manual selection of formula cells that evaluate to errors
Select the range of interest
Press [F5].
Click Special
Select Formulas
check only Errors
option2
VBA updating formula cells that evaluate to errors
code
Sub Shorter()
Dim rng1 As Range
On Error Resume Next
' All error formulas in column A
Set rng1 = Columns("A").SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
'update with new value (could be value or formulae)
rng1.Value = "new value"
End Sub
option 3
Test for =NA()
Sub TestSpecificRegion()
Dim rng1 As Range
Dim rng2 As Range
Dim X
Dim lngRow As Long
On Error Resume Next
' All error formulas in column A
Set rng1 = Columns("A").SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
'update with new value (could be value or formulae)
For Each rng2 In rng1.Areas
If rng2.Cells.Count > 1 Then
X = rng2.Value2
For lngRow = 1 To UBound(X, 1)
If X(lngRow, 1) = CVErr(xlErrNA) Then X(lngRow, 1) = "new value"
Next
rng2.Value = X
Else
If rng2.Value2 = CVErr(xlErrNA) Then rng2.Value = "new value"
End If
Next
End Sub

How to find a cell value within the visible range

I have a long list of entries to go through very quickly, where one of the columns, Column F, changes fairly often. Since I'm going through each entry individually, I want to highlight the entire row red where the value in Column F changes so that it's easy to see. Here's what I've written so far:
Dim visRng As Range, rngTop As Long, rngBot As Long
rngTop = ActiveWindow.visiblerange.Row
rngBot = ActiveWindow.visiblerange.Row + ActiveWindow.visiblerange.Rows.Count - 2
visRng = Worksheets("Work").Range("F" & rngTop, "F" & rngBot)
For Each Cell In visRng
If Cell.Value = Cell.Offset(-1).Value _
And Cell.Interior.ColorIndex = 0 Then
Cell.EntireRow.Interior.ColorIndex = 3
End If
Next
I keep getting an error on the row where I define "visRng":
Runtime Error '91':
Object variable or With block variable not set
I am fairly new to VBA, and self-taught, so I can't guarantee I'll understand all the jargon. Any help is greatly appreciated!
See comments for explanation.
Sub FindAndColor()
Dim visRng As Range
Dim rngCell As Range
'/ Set to range object. Yiu can't simply asign to it.
'/ This will get rid of Error 91
'/ Just Use VisibleCells of the usedRange
Set visRng = Worksheets("Work").UsedRange.SpecialCells(XlCellType.xlCellTypeVisible)
'/ Loop through the range
For Each rngCell In visRng.Cells
If rngCell.Row <> 1 Then
If rngCell.Value = rngCell.Offset(-1).Value _
And rngCell.Interior.ColorIndex = -4142 Then '/ -4142 is the default interior colorindex
rngCell.EntireRow.Interior.ColorIndex = 3
End If
End If
Next
End Sub

Resources