Getting the actual usedrange - excel

I have a Excel worksheet that has a button.
When I call the usedRange() function, the range it returns includes the button part.
Is there anyway I can just get actual used range that contains data?

What sort of button, neither a Forms Control nor an ActiveX control should affect the used range.
It is a known problem that excel does not keep track of the used range very well. Any reference to the used range via VBA will reset the value to the current used range. So try running this sub procedure:
Sub ResetUsedRng()
Application.ActiveSheet.UsedRange
End Sub
Failing that you may well have some formatting hanging round. Try clearing/deleting all the cells after your last row.
Regarding the above also see:
Excel Developer Tip
Another method to find the last used cell:
Dim rLastCell As Range
Set rLastCell = ActiveSheet.Cells.Find(What:="*", After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False)
Change the search direction to find the first used cell.

Readify made a very complete answer. Yet, I wanted to add the End statement, you can use:
Find the last used cell, before a blank in a Column:
Sub LastCellBeforeBlankInColumn()
Range("A1").End(xldown).Select
End Sub
Find the very last used cell in a Column:
Sub LastCellInColumn()
Range("A" & Rows.Count).End(xlup).Select
End Sub
Find the last cell, before a blank in a Row:
Sub LastCellBeforeBlankInRow()
Range("A1").End(xlToRight).Select
End Sub
Find the very last used cell in a Row:
Sub LastCellInRow()
Range("IV1").End(xlToLeft).Select
End Sub
See here for more information (and the explanation why xlCellTypeLastCell is not very reliable).

Here's a pair of functions to return the last row and col of a worksheet, based on Reafidy's solution above.
Function LastRow(ws As Object) As Long
Dim rLastCell As Object
On Error GoTo ErrHan
Set rLastCell = ws.Cells.Find("*", ws.Cells(1, 1), , , xlByRows, _
xlPrevious)
LastRow = rLastCell.Row
ErrExit:
Exit Function
ErrHan:
MsgBox "Error " & Err.Number & ": " & Err.Description, _
vbExclamation, "LastRow()"
Resume ErrExit
End Function
Function LastCol(ws As Object) As Long
Dim rLastCell As Object
On Error GoTo ErrHan
Set rLastCell = ws.Cells.Find("*", ws.Cells(1, 1), , , xlByColumns, _
xlPrevious)
LastCol = rLastCell.Column
ErrExit:
Exit Function
ErrHan:
MsgBox "Error " & Err.Number & ": " & Err.Description, _
vbExclamation, "LastRow()"
Resume ErrExit
End Function

Public Sub FindTrueUsedRange(RowLast As Long, ColLast As Long)
Application.EnableEvents = False
Application.ScreenUpdating = False
RowLast = 0
ColLast = 0
ActiveSheet.UsedRange.Select
Cells(1, 1).Activate
Selection.End(xlDown).Select
Selection.End(xlDown).Select
On Error GoTo -1: On Error GoTo Quit
Cells.Find(What:="*", LookIn:=xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Activate
On Error GoTo -1: On Error GoTo 0
RowLast = Selection.Row
Cells(1, 1).Activate
Selection.End(xlToRight).Select
Selection.End(xlToRight).Select
Cells.Find(What:="*", LookIn:=xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Activate
ColLast = Selection.Column
Quit:
Application.ScreenUpdating = True
Application.EnableEvents = True
On Error GoTo -1: On Error GoTo 0
End Sub

This function returns the actual used range to the lower right limit. It returns "Nothing" if the sheet is empty.
'2020-01-26
Function fUsedRange() As Range
Dim lngLastRow As Long
Dim lngLastCol As Long
Dim rngLastCell As Range
On Error Resume Next
Set rngLastCell = ActiveSheet.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious)
If rngLastCell Is Nothing Then 'look for data backwards in rows
Set fUsedRange = Nothing
Exit Function
Else
lngLastRow = rngLastCell.Row
End If
Set rngLastCell = ActiveSheet.Cells.Find("*", searchorder:=xlByColumns, searchdirection:=xlPrevious)
If rngLastCell Is Nothing Then 'look for data backwards in columns
Set fUsedRange = Nothing
Exit Function
Else
lngLastCol = rngLastCell.Column
End If
Set fUsedRange = ActiveSheet.Range(Cells(1, 1), Cells(lngLastRow, lngLastCol)) 'set up range
End Function

I use the following vba code to determine the entire used rows range for the worksheet to then shorten the selected range of a column:
Set rUsedRowRange = Selection.Worksheet.UsedRange.Columns( _
Selection.Column - Selection.Worksheet.UsedRange.Column + 1)
Also works the other way around:
Set rUsedColumnRange = Selection.Worksheet.UsedRange.Rows( _
Selection.Row - Selection.Worksheet.UsedRange.Row + 1)

This function gives all 4 limits of the used range:
Function FindUsedRangeLimits()
Set Sheet = ActiveSheet
Sheet.UsedRange.Select
' Display the range's rows and columns.
row_min = Sheet.UsedRange.Row
row_max = row_min + Sheet.UsedRange.Rows.Count - 1
col_min = Sheet.UsedRange.Column
col_max = col_min + Sheet.UsedRange.Columns.Count - 1
MsgBox "Rows " & row_min & " - " & row_max & vbCrLf & _
"Columns: " & col_min & " - " & col_max
LastCellBeforeBlankInColumn = True
End Function

Timings on Excel 2013 fairly slow machine with a big bad used range million rows:
26ms Cells.Find xlPrevious method (as above)
0.4ms Sheet.UsedRange (just call it)
0.14ms Counta binary search + 0.4ms Used Range to start search (12 CountA calls)
So the Find xlPrevious is quite slow if that is of concern.
The CountA binary search approach is to first do a Used Range. Then chop the range in half and see if there are any non-empty cells in the bottom half, and then halve again as needed. It is tricky to get right.

Here's another one. It looks for the first and last non empty cell and builds are range from those. This also handles cases where your data is not rectangular and does not start in A1. Furthermore it handles merged cells as well, which .Find skips when executed from a macro, used on .Cells on a worksheet.
Function getUsedRange(ByRef sheet As Worksheet) As Range
' finds used range by looking for non empty cells
' works around bug in .Find that skips merged cells
' by starting at with the UsedRange (that may be too big)
' credit to https://contexturesblog.com/archives/2012/03/01/select-actual-used-range-in-excel-sheet/
' for the .Find commands
Dim excelsUsedRange As Range
Dim lastRow As Long
Dim lastCol As Long
Dim lastCell As Range
Dim firstRow As Long
Dim firstCol As Long
Dim firstCell As Range
Set excelsUsedRange = ActiveSheet.UsedRange
lastRow = excelsUsedRange.Find(What:="*", _
LookIn:=xlValues, SearchOrder:=xlRows, _
SearchDirection:=xlPrevious).Row
lastCol = excelsUsedRange.Find(What:="*", _
LookIn:=xlValues, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Set lastCell = sheet.Cells(lastRow, lastCol)
firstRow = excelsUsedRange.Find(What:="*", After:=lastCell, _
LookIn:=xlValues, SearchOrder:=xlRows, _
SearchDirection:=xlNext).Row
firstCol = excelsUsedRange.Find(What:="*", After:=lastCell, _
LookIn:=xlValues, SearchOrder:=xlByColumns, _
SearchDirection:=xlNext).Row
Set firstCell = sheet.Cells(firstRow, firstCol)
Set getUsedRange = sheet.Range(firstCell, lastCell)
End Function

This is a different approach to the other answers, which will give you all the regions with data - a Region is something enclosed by an empty row and column and or the the edge of the worksheet. Basically it gives all the rectangles of data:
Public Function ContentRange(ByVal ws As Worksheet) As Range
'First, identify any cells with data, whose neighbourhood we will inspect
' to identify contiguous regions of content
'For efficiency, restrict our search to only the UsedRange
' NB. This may be pointless if .SpecialCells does this internally already, it probably does...
With ws.UsedRange 'includes data and cells that have been formatted
Dim cellsWithContent As Range
On Error Resume Next '.specialCells will error if nothing found, we can ignore it though
Set cellsWithContent = .SpecialCells(xlCellTypeConstants)
Set cellsWithContent = Union(cellsWithContent, .SpecialCells(xlCellTypeFormulas))
On Error GoTo 0
End With
'Early exit; return Nothing if there is no Data
If cellsWithContent Is Nothing Then Exit Function
'Next, loop over all the content cells and group their currentRegions
' This allows us to include some blank cells which are interspersed amongst the data
' It is faster to loop over areas rather than cell by cell since we merge all the CurrentRegions either way
Dim item As Range
Dim usedRegions As Range
For Each item In cellsWithContent.Areas
'Debug.Print "adding: "; item.Address, item.CurrentRegion.Address
If usedRegions Is Nothing Then
Set usedRegions = item.CurrentRegion 'expands "item" to include any surrounding non-blank data
Else
Set usedRegions = Union(usedRegions, item.CurrentRegion)
End If
Next item
'Debug.Print cellsWithContent.Address; "->"; usedRegions.Address
Set ContentRange = usedRegions
End Function
Used like:
Debug.Print ContentRange(Sheet1).Address '$A$1:$F$22
Debug.Print ContentRange(Sheet2).Address '$A$1:$F$22,$N$5:$M$7
The result is a Range object containing 1 or more Areas, each of it which will represent a data/formula containing region on the sheet.
It is the same technique as clicking in all the cells in your sheet and pressing Ctrl+T, merging all those areas. I'm using it to find potential tables of data

Related

Reliably get Last Column in Excel with or without Merged Cells

I recently ran into an issue where my get_lcol function returned A1 as the cells in A1:D1 were merged. I adapted my function to account for this, but then I had some other data with cells merged in A1:D1 but another column in G and my function returned D1 so I adjusted it again. The problem is I don't trust it still to work with all data types as its only checking merged cells in row 1.
Take a look at the below data, how can I reliably get the function to return D or 4 regardless of where I move the merged row and/or any other issues I haven't foreseen?
Current Function:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
Dim sEmpty As Boolean
On Error Resume Next
sEmpty = IsWorksheetEmpty(Worksheets(WS.Name))
If sEmpty = False Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
If IsMerged(Cells(1, Get_lCol)) = True Then
If Get_lCol < Cells(1, Get_lCol).MergeArea.Columns.Count Then
Get_lCol = Cells(1, Get_lCol).MergeArea.Columns.Count
End If
End If
Else
Get_lCol = 1
End If
End Function
Update:
Try this data w/ function:
This is a twist on the classic "Find Last Cell" problem
To state the aim:
find the column number of the right most cell containing data
consider merged cell areas that extend beyond other cells containing data. Return the right most column of a merged area should that extend beyond other data.
exclude formatted but empty cells and merged areas
The approach:
Use Range.Find to locate the last data cell
If the last column of the Used Range = Found last data cell column, return that
Else, loop from the last column of the Used Range back to the found data cell column
test for data in that column (.Count > 0), if true return that
test for merged cells in that column (IsNull(.MergeCells))
if found, loop to find the merged area
test the left most cell of the merged area for data
if found return the search column
Note
this may still be vulnerable to other "Last data" issues, eg Autofilter, Hidden rows/columns etc. I haven't tested those cases.
Has the advantage of limiting the search for merged cells to the relavent right most columns
Function MyLastCol(ws As Worksheet) As Long
Dim ur As Range
Dim lastcell As Range
Dim col As Long
Dim urCol As Range
Dim urCell As Range
Set ur = ws.UsedRange
Set lastcell = ws.Cells.Find("*", ws.Cells(1, 1), xlFormulas, , xlByColumns, xlPrevious)
For col = ur.Columns.Count To lastcell.Column - ur.Column + 2 Step -1
Set urCol = ur.Columns(col)
If Application.CountA(urCol) > 0 Then
MyLastCol = urCol.Column
Exit Function
End If
If IsNull(urCol.MergeCells) Then
For Each urCell In urCol.Cells
If urCell.MergeCells Then
If Not IsEmpty(urCell.MergeArea.Cells(1, 1)) Then
MyLastCol = urCol.Column
Exit Function
End If
End If
Next
End If
Next
MyLastCol = lastcell.Column
End Function
#Toddleson got me on the right track, here is what I ended with:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
On Error Resume Next
If Not IsWorksheetEmpty(WS) Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim Cell As Range
For Each Cell In WS.UsedRange
If Cell.MergeCells Then
With Cell.MergeArea
If .Cells(.Cells.Count).Column > Get_lCol Then Get_lCol = .Cells(.Cells.Count).Column
End With
End If
Next Cell
Else
Get_lCol = 1
End If
End Function
The Find Method Backed Up by the UsedRange Property: What?
Talking about wasting time...
Option Explicit
Function GetLastColumn( _
ByVal ws As Worksheet) _
As Long
If ws Is Nothing Then Exit Function
' Using the 'Find' method:
'If ws.AutoFilterMode Then ws.AutoFilterMode = False ' (total paranoia)
Dim lcCell As Range
Set lcCell = ws.Cells.Find("*", , xlFormulas, , xlByColumns, xlPrevious)
If Not lcCell Is Nothing Then
GetLastColumn = lcCell.Column
End If
Debug.Print "After 'Find': " & GetLastColumn
' Using the 'UsedRange' property (paranoia):
Dim rg As Range: Set rg = ws.UsedRange
Dim clColumn As Long: clColumn = rg.Columns.Count + rg.Column - 1
If clColumn > GetLastColumn Then
If rg.Address(0, 0) = "A1" Then
If IsEmpty(rg) Then
Exit Function
End If
End If
GetLastColumn = clColumn
'Else ' clColumn is not gt GetLastColumn
End If
Debug.Print "Final (if not 0): " & GetLastColumn
End Function
Sub GetLastColumnTEST()
Debug.Print "Sub Result: " & GetLastColumn(Sheet1)
Debug.Print Sheet1.UsedRange.Address(0, 0)
End Sub
' It works for a few (?) cells, otherwise it returns 'Null'.
Sub TestMergeCells() ' Useless?! Could someone confirm.
Debug.Print Sheet1.Cells.MergeCells ' Null for sure
Debug.Print Sheet1.UsedRange.MergeCells
End Sub

Excel / VBA - How to select the top row (absolute) of whichever column (relative) the currently selected cell is in?

I spent 11 hours a few days ago ripping my brain apart to do this. Everyone either answers using sample ranges with absolute values, or with the entirely relative .offset function. Or they mention it's not good to select in vba, or they provide a workaround of some sort that I can't adapt, or .select doesn't work with R1C1...etc, etc. My script is done now, and fully functioning with the following loop code, but it's SLOW because it uses this loop about 2000-3000 times each time the macro runs:
Do Until Selection.Row = 1
If Selection.Row <> 1 Then
Selection.Offset(-1, 0).Select
End If
Loop
I just want to know, for whichever cell is currently selected, wherever it is, is there a faster way in vba to .Select the top row (row #1, absolute reference) of that (any) column (relative reference)?
For do it faster you can optimize like this:
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Do Until Selection.Row = 1
If Selection.Row <> 1 Then
Selection.Offset(-1, 0).Select
End If
Loop
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
One question, you can not use Cells(x,y).row instead of Selection metod ? This is a faster way.
In other way, the column normaly have a name in top, you can search this name and get this position an select the row below.
Something like this:
Private Sub CommandButton1_Click()
Dim intColumn As Integer
intColumn = ObtainColumn(Range("A1:F1"), "NameColum")
intRow = ObtainRow(Range("A1:A10"), "NameColum")
Cells(intRow, intColumn).Select
End Sub
Function ObtainColumn(rng As Range, strValue As String) As Long
Dim lCol As Long
'Set rng = ActiveSheet.Cells
On Error Resume Next
lCol = 0
lCol = rng.Find(What:=strValue, _
After:=rng.Cells(1), _
LookAt:=xlWhole, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
ObtainColumn = lCol
On Error GoTo 0
End Function
Function ObtainRow(rng As Range, strValue As String) As Long
Dim lRow As Long
'Set rng = ActiveSheet.Cells
lRow = 0
On Error Resume Next
lRow = rng.Find(What:=strValue, _
After:=rng.Cells(1), _
LookAt:=xlWhole, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
ObtainRow = lRow
On Error GoTo 0
On Error GoTo 0
End Function
There is no need to iterate to find the top of the current column:
Selection.End(xlUp).Activate
The .End(xlUp) member of a Range finds the end of the contiguous data set. More info here.
or
Cells(1, Selection.Column).Activate
This method uses the Column member of a Range to return the number of that column, then uses the Cells function to call the first row of that column. More info here.
or (as mentioned)
Selection.Offset(1 - Selection.Row).Select
This method uses the Offset member of a Range. This function (more info here) has two optional parameters. The first is RowOffset, so this formula will offset a cell in A21 by -20 rows, thus giving A1.
Edited for more information and references

Using Range.Find in VBA to find only the previous value x?

I am trying to use the Range.Find method in VBA to locate the closest previous row number that has a "true" value.
For example, in Column X, there will be a "true" value (row 35), 10 rows with "false," and then "true" again (row 46).
When I get to row 46 in my loop, I need to do a range.find and return row 35.
The code I am using is this:
Worksheets("Data").Cells.Find(True, searchorder:=xlByColumns, searchdirection:=xlNext).Row
What is happening is that I am only finding either the very first "true" value (in this case, row 2), or the very last "true" value (row 24,xxx), as I vary search direction.
What can I do to find only the previous-most "true" value?
You can find the previous row with True by using the After argument in the Find method combined with xlPrevious as the SearchDirection. I have updated the code to add it into a loop, based on your comments.
Since you posted your code, I have edited my answer into your code.
Sub Main()
Dim iCurRow As Long
Dim iCounter As Long
Dim iLastRow As Long
Dim iTempRow As Long
Dim iPreviousRow As Long
Dim iChangeCol As Long
Dim ws As Worksheet
Set ws = Worksheets("Data")
With ws
iChangeCol = .Cells.Find(what:="Change Over?", searchorder:=xlByColumns, searchdirection:=xlNext).Column
iLastRow = .Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
iPreviousRow = 2
For iCounter = 3 To iLastRow
If .Cells(iCounter, iChangeCol).Value = True Then
iTempRow = .Cells.Find(what:=True, After:=.Cells(iCounter, iChangeCol), searchorder:=xlByColumns, searchdirection:=xlPrevious).Row
iPreviousRow = iTempRow
End If
Next iCounter
End With
End Sub
This short snippet uses both the Range.Find method and Range.FindNext method to cycle through all matching cells in column X.
Sub rings_true()
Dim fnd As Range
With Worksheets("Sheet1") `<~~ set this worksheet reference properly
With .Columns(24)
Set fnd = .Find(What:="TRUE", after:=.Cells(.Rows.Count), _
LookIn:=xlValues, LookAt:=xlWhole, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Do While Not fnd Is Nothing
If MsgBox("Currently at " & fnd.Address(0, 0) & Chr(10) & "exit now...?", vbYesNo + vbQuestion) = vbYes Then
Exit Do
Else
Set fnd = .FindNext(after:=fnd)
End If
Loop
End With
End With
End Sub
The current cell address is reported through a MsgBox function. The same MsgBox offers the user the opportunity to break the loop.
Additional error control might include confirming at least one matching value in column X before entering into the loop.
There are multiple arguments to put into the Find method, regarding what you told us, I suggest that you use :
After:=.Cells(.Rows.Count, 1) to start from the bottom of the column
LookIn:=xlValues
LookAt:=xlWhole
SearchOrder:=xlByRows to look row by row (instead of column by column)
SearchDirection:=xlPrevious to look "back", from bottom to top
MatchCase:=False
SearchFormat:=False
And furthermore, you can use the .Find method into a specific range, so rather than Worksheets("Data").Cells.Find(..., you should use Worksheets("Data").Range("X:X").Find(... to look only in the column X.
Here is your amended code :
Sub test_ilarson007()
Dim FirstAddress As String, PreviousMatch As Range, cF As Range
Worksheets("Data").Activate
With Worksheets("Data").Range("X:X")
'First, define properly the Find method
Set cF = .Find(What:=True, _
After:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False, _
SearchFormat:=False)
'If there is a result,
If Not cF Is Nothing Then
FirstAddress = cF.Address
MsgBox "The row containing the previous 'True' in Column X is : " & cF.Row
'keep looking with FindNext method : Not usefull for your example
Do
Set PreviousMatch = cF
Set cF = .FindNext(cF)
'-------------------------------------------------------------
'----Place instructions to execute on the matched cell/row/...
'First match (i.e. Row 46 in your example)
MsgBox PreviousMatch.Row 'Should display 46 (then 35, then ??)
'Second match (i.e. Row 35 in your example)
MsgBox cf.Row 'Should display 35 (then ??, then ??)
'-------------------------------------------------------------
'Look until you find again the first result
Loop While Not cF Is Nothing And cF.Address <> FirstAddress
End If
End With
End Sub

Excel VBA .Find Range Anomaly

'Found an interesting one - after 4 hours of tearing my hair out.
It seems that the Excel 2010 VBA won't find a date value in a range of merged-across cells if the first column's width is too narrow for the font size used. (This is similar to Excel VBA being unable to find a date value in a hidden row/column).
3 Possible Solutions: best first
Change the LookIn parameter to xlFormulas.
Widen the column until the macro works with LookIn:=xlValues.
Reduce the font-size until the macro works with LookIn:=xlValues.
Steps to reproduce:
Insert a date into A2 (eg. 7/3).
Merge Across 4 columns (A2:D2) - this is the field for the date to be found
Create a set of sequential dates in cells A4:A35 (eg. 1/3 to 31/3).
Merge Across 4 columns (A4:D35)
Run the following code:
Sub findDate()
Dim myRange As Range
Dim myDate As Date
Dim myFindDate As Date
Dim myRow As Integer
With ActiveSheet
Set myRange = .[A2]
myFindDate = .[A4:D35].Value
On Error Resume Next
myRow = myRange.Find( _
what:=myFindDate, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False).Row
On Error GoTo 0
If myRow <> 0 Then
MsgBox "The date is in row number = " & myRow
Else
MsgBox "Column A too narrow. Either use LookIn:=xlFormulas, widen Column A or reduce the font size."
End If
End With
End Sub
Notice that the message box presents the relevant row number.
Now reduce the width of Column A to 2.4 and run the code again.
Notice the message box produced: Excel VBA is no longer able to find the date!
Here's the code for solution 1, above:
Sub findDate()
Dim myRange As Range
Dim myDate As Date
Dim myFindDate As Date
Dim myRow As Integer
With ActiveSheet
Set myRange = .[A2]
myFindDate = .[A4:D35].Value
On Error Resume Next
myRow = myRange.Find( _
what:=myFindDate, _
LookIn:=xlFormulas, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False).Row
On Error GoTo 0
If myRow <> 0 Then
MsgBox "The date is in row number = " & myRow
Else
MsgBox "Column A too narrow. Either use LookIn:=xlFormulas, widen Column A or reduce the font size."
End If
End With
End Sub
(The only change is in the LookIn parameter: xlFormulas instead of xlValues)
If you run this second bit of code, the message box will present the row number again.
'Hope this saves someone else the pain it caused me!!
Gary
I followed your "Steps to reproduce" instructions and your example would not work for me.
Some things I have noticed though.
Dim myDate As Date
Dim myFindDate As Date
Dim myRow As Integer
The values may be dates but you are working with ranges.
So start the code correctly,
Dim myRange As Range, myFindDate As Range, myRow As Range
then set the ranges correctly.
Set myRange = [A2]
Set myFindDate = [A4:D35]
Set myRow = myFindDate.Find(what:=myRange, lookat:=xlWhole)
Using the code this way, it does not matter how wide the columns are.
Complete code.
Sub findDateB()
Dim myRange As Range, myFindDate As Range, myRow As Range
Set myRange = [A2]
Set myFindDate = [A4:D35]
Set myRow = myFindDate.Find(what:=myRange, lookat:=xlWhole)
If Not myRow Is Nothing Then
MsgBox "The date is in row number = " & myRow.Row
Else: MsgBox "Not Found"
Exit Sub
End If
End Sub

Set VBA Range with Variable End

I'm kind of new to VBA and am struggling to understand some of the syntax.
I have a range from a3:c13, for example, and I'd like to set it as a variable so I can pass it to vlookup later as a the table array. However, the range is defined by user input in terms of its size. It will always start in A3, it will always include columns A:C, but I don't know how far down it would go. In that case, I think I'd set it as:
With range("a3")
table_array = range(.cells(0,0), .End(xlDown).End(xlToRight)).Select
End With
However, that doesn't seem to work. I get a runtime error:
Run-time Error '1004': Method '_Default' of object 'Range' failed.
Assuming cols A, B, and C have the same number of rows:
Sub Macro1()
Set r = Range("A3")
Set table_array = Range(r, r.End(xlDown)).Resize(, 3)
End Sub
You can find the last row in Col A:C and then construct your range?
Sub Sample()
Dim ws As Worksheet
Dim LastRow As Long
Dim Rng As Range
'~~> Change this to the relevant sheet
Set ws = ThisWorkbook.Sheets("Sheet1")
With ws
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
LastRow = .Range("A:C").Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
LastRow = 1
End If
If Not LastRow < 3 Then
Set Rng = .Range("A3:C" & LastRow)
Debug.Print Rng.Address
Else
MsgBox "No Data found beyond A3"
End If
End With
End Sub

Resources