Excel VBA Function to determine row number - excel

I built a function to determine the first row in which data exists. When I call the data i keep getting an error stating object required. How do I get around this error and is this the best way to accomplish my goal? TYIA!
Sub rename()
Dim strOldType As String
Dim correctrow As Long
Dim a As Range
Set a = startrow(correctrow)
Range("s" & a).Select
strOldType = Selection.Value
End Sub
Function startrow(firstroww)
Dim strRow As String
Dim firstrow As Range
Range("ab1").Select
strRow = Selection.Value
If strRow <> "" Then
firstroww = 1
Else
Range("ab1").Activate
Selection.End(xlDown).Select
firstroww = ActiveCell.Row()
End If
End Function

You can try a function like this. You will need to pass a range into the function as seen below in Sub Test().
The custom function can find the first used cell below from any starting point.
Option Explicit
Function FR(Start As Range) As Long
Select Case Start
Case <> ""
FR = Start.Row
Case Else
FR = Start.End(xlDown).Row
End Select
End Function
Sub Test()
MsgBox FR(Range("A1"))
End Sub

Don't try to use Select inside a function.
Use Set to assign a range object. Do not use Set to assign a number to a variable.
Make up your mind whether you want a row number or a range object. You bounce back and forth between the two with no regard for result.
Corrected code:
Sub rename()
Dim strOldType As String
Dim correctrow As Long
Dim a As LONG '<~~ correction
correctrow = 1 '<~~ correction
a = startrow(correctrow) '<~~ correction
strOldType = Range("s" & a).Value
End Sub
Function startrow(firstroww)
if Range("ab" & firstrow) <> "" then '<~~ correction
startrow = firstrow
else
startrow = Range("ab" & firstrow).end(xldown).row
end if
End Function

First Cell in Column Function
It is assumed that you're looking for a VBA function to use in Excel to calculate the first non-empty row of a column (specified by a range).
Features
The Volatile method marks a user-defined function as volatile. A
volatile function must be recalculated whenever calculation occurs in
any cells on the worksheet. A nonvolatile function is recalculated
only when the input variables change (VBA Help).
At least for the sake of correctness, you have to use IsEmpty
instead of "" for the reason e.g. if the cell in the resulting row
contains a formula that evaluates to "", it will be ignored.
The Find Method Version uses the Find method to calculate the First Row, which is safer than
the End Version e.g. if you input a value into the first cell of the
column i.e. the result is 1 and you hide the first row, the result of
the End Version will not be 1.
The formula can be inserted in the same column as SelectRange
Column. In some cases the End Version would not show the correct
result or create a circular reference. Therefore ThisCell is used
in the End version and 0 is returned if no value was found in SelectRange column.
Find Method Version
Function FirstRowFind(SelectRange As Range) As Long
Application.Volatile
Dim FirstCell As Range
With Columns(SelectRange.Column)
Set FirstCell = .Find("*", .Cells(.Cells.Count), -4123, 1, 2, 1)
End With
If Not FirstCell Is Nothing Then
FirstRowFind = FirstCell.Row
End If
End Function
Find Method
Instead of
Set FirstCell = .Find("*", .Cells(.Cells.Count), -4123, 1, 2, 1)
you can use
Set FirstCell = .Find("*", .Cells(.Cells.Count), _
xlFormulas, xlWhole, xlByColumns, xlNext)
or
Set FirstCell = .Find(What:="*", After:=.Cells(.Cells.Count), _
LookIn:=xlFormulas, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext)
The parameters for the arguments LookAt(unimportant in this case) and SearchDirection(Default is Next) can be omitted, but since I couldn't find any difference in efficiency, I didn't.
Usage in Excel
For Column AB:
=FirstRowFind(AB1)
=FirstRowFind(AB20)
=FirstRowFind(AB17:AH234)
End Version (Not recommended)
Function FirstRowEnd(SelectRange As Range) As Long
Application.Volatile
Dim FirstCell As Range
If Application.ThisCell.Column = SelectRange.Column Then Exit Function
If Not IsEmpty(SelectRange.Cells(1)) Then
FirstRowEnd = 1
Else
Set FirstCell = Cells(1, SelectRange.Column).End(xlDown)
FirstRowEnd = FirstCell.Row
If FirstRowEnd = Rows.Count And IsEmpty(FirstCell) Then
FirstRowEnd = 0
End If
End If
End Function
Usage in Excel
For Column AB:
=FirstRowEnd(AB1)
=FirstRowEnd(AB20)
=FirstRowEnd(AB17:AH234)

You can use this:
ActiveSheet.UsedRange.Row
ActiveSheet.UsedRange.Column
The UsedRange object is the largest rectangle covering all nonempty cells.

Related

Define an absolute column as lookup_value

First of all, I am completely new to VBA besides recording simple macros, so bear over with me.
I am creating a VBA macro to import and sort results from a poll, based on names from the answers.
The import will take place from a Data sheet and will be imported into a Results sheet, for this I am using the XLOOKUP function.
The poll will run every other week and each time, the results sheet will expand with one column showing the newest results.
If I were to use the function directly in the spreadsheet this would work:
=XLOOKUP(A3&"*";Data!$D$2:$D$20;Data!$F$2:$F$20;"F";2)
where A3&"*" is my lookup_value.
I made it work with the offset function, but since my lookup_value has to be absolute I need another solution which I can not figure out.
Sub RES_Farver()
Dim Farve_Cell As Range
For Each Farve_Cell In Range("B3:B50")
Farve_Cell.Value = _
Application.WorksheetFunction.XLookup(Farve_Cell.Offset(0, -1) & "*", _
Sheets("Data").Range("D2:D50"), Sheets("Data").Range("F2:F50"), "F", 2)
Next Farve_Cell
End Sub
Not that Range("B3:B50") will later be based on user input, so it can be dynamic and change over time, like I described earlier.
What would I have to use instead of Offset?
Thanks!
as suggested by #Siddharth Rout
Sub lookup()
Dim vRange As String
vRange = InputBox("Please enter the range.", "Range:", "B3:B50")
If vRange = "" Then Exit Sub
Range(vRange).Formula = "=XLOOKUP(A3&""*"",Data!$D$2:$D$20;Data!$F$2:$F$20,""F"",2)"
End Sub
I went with this
result= _
Application.WorksheetFunction.XLookup(Cells(Farve_Cell.Row, "A") & "*", _
Sheets("Data").Range("D2:D50"), Sheets("Data").Range("F2:F50"), "F", 2)
Then it will use the row Farve_Cell is at in column A.
I also modified the range to only rows containing values and to the current week as column, where I have stated the weeks of the year in row 11
In total it looks like this (with danish messages)
Sub RES_Farver()
Dim currentWeek As Integer
currentWeek = DatePart("WW", Date, , vbFirstFullWeek)
Dim weekColumn As Integer
Set weekCell = Range("11:11").Find(currentWeek)
If weekCell Is Nothing Then
MsgBox "Ugenummer ikke fundet"
Exit Sub
Else
weekColumn = weekCell.Column
End If
Dim lastRow As Integer
lastRow = Range("A" & Rows.Count).End(xlUp).Row
Dim rng As Range
Set rng = Range(Cells(13, weekColumn), Cells(lastRow, weekColumn))
For Each Farve_Cell In rng
Dim result As String
result = _
Application.WorksheetFunction.XLookup(Cells(Farve_Cell.Row, "A") & "*", _
Sheets("Data").Range("D2:D50"), Sheets("Data").Range("F2:F50"), "F", 2)
If result = "Grøn" Then
Farve_Cell.Value = "GN"
Else
If result = "Gul" Then
Farve_Cell.Value = "GL"
Else
If result = "Rød" Then
Farve_Cell.Value = "RD"
Else
Farve_Cell.Value = ""
End If
End If
End If
Next Farve_Cell
End Sub

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!.

VBA, how to obtain last column as range in a given range?

I have a very simple question, which unfortunately I can not resolve. Thus would appreciate your help. Here is the thing:
I should obtain last column from the range as a range. For example, if I have A26:D32, I should get D26:D32 as a result and input into the loop.
This is the code I have so far:
Function getSmth(CustomCol As Range)
Dim i As Double
For Each cell In CustomCol 'start of the loop. CustomCol here should return D26:D32 already
If cell.Value > i Then
i = cell.Value
End If
Next
....
End Function
What I have tried to do was writing CustomCol.Columns(6), as I know the last column, but it did not work out.
Would really be glad for your help!
You can try this:
Function GetLasRangeCol(rng As Range) As Range
Set GetLasRangeCol = rng.Columns(rng.Columns.Count).Cells
End Function
That you may use in your calling code as:
For Each cell In GetLasRangeCol(myRange)
Where ‘myRange’ is a valid range reference, i.e: either a variable of Range type or some Range object (like ‘Range(“A5:B21”)’) to get last column out of
There's probably a shorter way to it, but this does the trick:
Function lastColumn(rg As Range) As Range
Set lastColumn = Range(Cells(Range(Split(rg.Address(0, 0), ":")(0)).Row, _
Range(Split(rg.Address(0, 0), ":")(1)).Column).Address(0, 0) _
& ":" & Split(rg.Address(0, 0), ":")(1))
End Function
It take a Range object as a parameter and returns a Range object.
Example Usage:
Sub demo()
Dim rg As Range, rg2 As Range
Set rg = Range("A26:D32")
Set rg2 = lastColumn(rg)
Debug.Print "The last column is: " & rg2.Address
End Sub
...returns: The last column is: $D$26:$D$32
There's probably a "tidier" method (perhaps using INTERSECT) but this will work fine.
Explanation:
This is the same function as above, but broken down so it's easier to understand:
Function lastColumn(rg As Range) As Range
Dim firstRow, lastRow, firstCol, lastCol, leftPart, rightPart
leftPart = Split(rg.Address(0, 0), ":")(0)
rightPart = Split(rg.Address(0, 0), ":")(1)
firstRow = Range(leftPart).Row
firstCol = Range(leftPart).Column
lastCol = Range(rightPart).Column
Set lastColumn = Range(Cells(firstRow, lastCol), Range(rightPart))
End Function
More Information:
MSDN : Application.Range Property (Excel)
MSDN : Range.Cells Property (Excel)
Office Support : Split Function (VBA)
If you want the last column as a range, then
myRange.Columns(myRange.Columns.Count)
If you want the number of the last column then
myRange.Columns(myRange.Columns.Count).Column
To find last column in row 1:
Dim LastCol As Long
LastCol = Sheets("Sheet1").Cells(1, Columns.Count).End(xlToLeft).Column

find first completely empty row on a sheet

need a function that returns the first completely empty row (no values, no formulas, no blanks) from a sheet with sparely populated cells. No one single column is required to be filled.
I tried this, but i can even get it to compile:
Public Donations As Worksheet
Set Donations = Sheets("Sheet2")
Function getEmptyRow() As Long
Dim lastCol As Long, lastRow As Long, maxRow As Long
Dim col As Long
Dim r As Variant
lastCol = Donations.Cells(1, Columns.Count).End(xlToLeft).Column
For col = 1 To lastCol Step 1
lastRow = Donations.Cells(Rows.Count, col).End(xlUp).row
maxRow = Application.WorksheetFunction.max(maxRow, lastRow)
Next col
getEmptyRow = maxRow + 1
End Function
Using EntireRow (which is so useful let me tell you) and counting row by row starting in A1 is one very basic way of doing this.
This will tell you in the immediate window:
Sub findemptyrow() '''Psuedo Code
Application.ScreenUpdating = False 'turns off annoying blinky
Range("a1").Activate 'start at beginning
While a <> 1 'keep going
If Application.CountA(ActiveCell.EntireRow) = 0 Then 'is it blank?
Debug.Print "Row " & (ActiveCell.Row) & " is blank." 'it is
a = 1 'stop going
End If
ActiveCell.Offset(1).Activate 'next cell
Wend 'do it all over again
Application.ScreenUpdating = True 'back to normal settings
End Sub
Making ScreenUpdating False will make this faster, even with 10k's of rows.
The Range.Find method is likely the most expedient method. Look for a wildcard (What:=Chr(42)), start in A1 (After:=.Cells(1, 1), search backwards (SearchDirection:=xlPrevious), search row-by-row (SearchOrder:=xlByRows).Row), look at the formulas (LookIn:=xlFormulas) since that will find the first value or formula; looking at xlValues may not be correct if a formula is returning an empty string ("").
Option Explicit
Public Donations As Worksheet
Sub test()
Set Donations = Worksheets("Sheet2")
Debug.Print getNextEmptyRow(Donations)
End Sub
Function getNextEmptyRow(ws As Worksheet)
With ws.Cells
getNextEmptyRow = .Find(What:=Chr(42), After:=.Cells(1, 1), LookIn:=xlFormulas, _
SearchDirection:=xlPrevious, SearchOrder:=xlByRows).Row + 1
End With
End Function
You cannot set Donations in the declarations area of a module code sheet. Declare the public variable in the declarations area (top) of the code sheet but Set the variable in a sub or function.
Don't forget to add 1 to the row returned if you want the 'next empty row'.
Just another alternative using the SpecialCells method of a `Range:
Option Explicit
Sub Test()
Debug.Print "Last row on Sheet1 is: " & FirstCompletelyEmptyRow(Sheet1)
End Sub
Function FirstCompletelyEmptyRow(ByRef wsTarget As Worksheet) As Long
FirstCompletelyEmptyRow = wsTarget.Range("A1").SpecialCells(xlCellTypeLastCell).Row + 1
End Function

How to search and replace using VBA in Excel?

I am new to VBA Excel programming. Consider an Excel sheet with nxn values. My task is to search for text called "TOOTHBRUSH BATT" from A column. A column consists of multiple "TOOTHBRUSH " value.
Once the value is found suppose in cell A11 then I need to change text in D11 ie corresponding D column to "BATTERY". D11 will already have some text, I need to replace that text with "BATTERY"
My code is
Sub replacement()
Dim S As String
Dim H As String
S = "TOOTHBRUSH BATT"
For i = 1 To Range("A1").End(xlDown).Row
If Range("A" & i) = S Then
Range("D" & i) = "BATTERY"
End If
Next i
End Sub
nRow = Worksheets(1).Range("A:A").Find(What:="*TOOTHBRUSH BATT*", after:=Range("A1"), LookIn:=xlFormulas, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Row
Worksheets(1).Cells(nRow,"D") = "BATTERY"
By using auto filter (below code not tested)
Worksheets(1).autofiltermode = false
Worksheets(1).Range("A:B").autofilter
Worksheets(1).AutoFilter.Range.AutoFilter Field:=1, Criteria1:="*TOOTHBRUSH BATT*"
dim nRng as range
If Worksheets(1).AutoFilter.Range.Offset(1,0).Columns(1).SpecialCells(xlCellTypeVisible).Count > 1 Then
set nRng = Worksheets(1).AutoFilter.Range.Offset(1,0).Columns(2).Resize(Worksheets(1).AutoFilter.Range.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible)
nRng.value = "BATTERY"
End If
This is Similar to Eric's Answer.
' Declare range to set to the first cell we find
Dim find as Range
set find = Range("A:A").Cells.Find(What:="TOOTHBRUSH BATT")
' This is the cell Address (in case it keeps looping back to beginning)
Dim addy as string
if not find is nothing then addy = find.address
' If we've found a cell then Keep Do something with it
Do while not find is nothing
find.Value = "BATTERY"
' Find the next Cell
set find = Range("A:A").Cells.Find(What:="TOOTHBRUSH BATT", After:= find)
' If the next found cell is the first one then exit sub/function
if find.address = addy then exit sub
Loop

Resources