VBA: How to use a variable as an argument in range? - excel

I am trying to replace the argument in a Range with a variable so I can call a sub with different variable.
Example:
sub calc(i, j As String)
.range(i:j)
end sub
sub main()
calc A1, B23
end sub
I want the final result in this case to be
.Range("A1:B23")
But I keep getting errors.
Example code which gets an error:
subscript out of range in:
If DatePart("y", Date) > DatePart("y", Sheets(s).Range(x).Value) Then
s & x are declared as Strings
Please help, thanks!

Please, test and try understanding the next approach:
Sub checDateParts()
Dim sh As Worksheet, rng As Range
Set sh = ActiveSheet
Set rng = rngCalc(sh, "A1", "B23")
MsgBox rng.Address 'returned the created range address
Set rng = rngCalc(sh, "A1")
If IsDate(rng.Value) Then 'check if the value of "A1" cell is date
MsgBox DatePart("y", Date) > DatePart("y", rng.Value)
Else
MsgBox "The value of cell """ & rng.Address & """ is not a date..."
End If
End Sub
Function rngCalc(sh As Worksheet, i As String, Optional j As String) As Range
If j <> "" Then
Set rngCalc = sh.Range(i & ":" & j)
Else
Set rngCalc = sh.Range(i)
End If
End Function
You cannot directly compare a date (today date) with a range containing more cells. You can previously extract the maximum date of the range and make the comparison with this one:
Sub checDatePartsBis()
Dim sh As Worksheet, rng As Range, maxDate As Date
Set sh = ActiveSheet
Set rng = rngCalc(sh, "A1", "B11")
MsgBox rng.Address 'returned the created range address
maxDate = DateValue(Format(WorksheetFunction.Max(rng), "dd.mm.yyyy")) ': Stop
MsgBox "Maximum date in the processed range is """ & maxDate & """ and " & vbCrLf & _
"And today is """ & Date & """."
If DatePart("y", Date) > DatePart("y", maxDate) Then
MsgBox "Yes, it is..."
Else
MsgBox "No, it is not..."
End If
End Sub
You must understand that using DatePart("y", Date) returns the day of the year. If you need/want comparing the years, you should use DatePart("yyyy", Date).
Please, test the above solution and send some feedback.

Related

VBA UDF evaluates after every change

I have an issue, I thought would be a pretty simple one, but now can't handle it so I guess was wrong.
I have a UDF that calculates the average of exchange rates between 2 dates
Option Explicit
Public Function averageFromRange() As Double
Dim sh As Worksheet
Set sh = ThisWorkbook.Worksheets("Exchange Rates")
Dim dateStart As Date: dateStart = sh.range("G1").Value
Dim dateEnd As Date: dateEnd = sh.range("G2").Value
Dim myRange As String
Dim rangeStart As range
Dim rangeEnd As range
Set rangeStart = sh.range("A:A").Find(What:=CStr(dateStart), LookAt:=xlWhole, LookIn:=xlValues).Offset(0, 1)
Set rangeEnd = sh.range("A:A").Find(What:=CStr(dateEnd), LookAt:=xlWhole, LookIn:=xlValues).Offset(0, 1)
If rangeStart Is Nothing Then
MsgBox ("Date " & dateStart & " out of range")
End If
If rangeEnd Is Nothing Then
MsgBox ("Date " & dateEnd & " out of range")
End If
If Not (rangeStart Is Nothing Or rangeEnd Is Nothing) Then
myRange = rangeStart.Address & ":" & rangeEnd.Address
averageFromRange = Application.WorksheetFunction.Average(range(myRange))
End If
End Function
Any change in the entire workbook (apart from the sheet in which the function is called) re-evaluates the function to #VALUE!. I tried both parametrizing the UDF to have these dates as input params, and activating the sheet. I have no other ideas how to handle this issue. Could you help me out?
The Function returns #VALUE! when any of the dateStart or dateEnd is not found in column [A:A] because of these lines:
Set rangeStart = sh.range("A:A").Find(What:=CStr(dateStart), LookAt:=xlWhole, LookIn:=xlValues).Offset(0, 1)
Set rangeEnd = sh.range("A:A").Find(What:=CStr(dateEnd), LookAt:=xlWhole, LookIn:=xlValues).Offset(0, 1)
Those lines are trying to set the Offset(0, 1) of Nothing (i.e. Find returns Nothing and the lines are still trying to return the Offset)
Solution: First find the Cell containing the Dates then if the dates are found, set the Offset range.
Also you may want the UDF be Volatile if Column [A:A] or the Dates (start & end) are updated by formulas.
Try this code:
Public Function averageFromRange() As Double
Dim dDateIni As Date, dDateEnd As Date
Dim rINI As Range, rEND As Range
Application.Volatile 'Comment this line is VOLATILE is not required
With ThisWorkbook.Worksheets("Exchange Rates")
dDateIni = .Range("G1").Value
dDateEnd = .Range("G2").Value
With .Columns(1)
Set rINI = .Find(What:=CStr(dDateIni), LookAt:=xlWhole, LookIn:=xlValues)
Set rEND = .Find(What:=CStr(dDateEnd), LookAt:=xlWhole, LookIn:=xlValues)
End With
End With
If rINI Is Nothing Then MsgBox ("Date " & dDateIni & " out of range")
If rEND Is Nothing Then MsgBox ("Date " & dDateEnd & " out of range")
If Not (rINI Is Nothing And rEND Is Nothing) Then
averageFromRange = Application.Average(Range(rINI.Offset(0, 1), rEND.Offset(0, 1)))
End If
End Function
Resources used:
Worksheet.Range,
With statement

Convert formula to value once formula has calculated

I'm using the following VBA module to create a Timestamp UDF, which stamps the date once the referenced cell reads "Done":
Function Timestamp(Reference As Range)
If Reference.Value = "Done" Then
Timestamp = Format(Date, "ddd dd mmm")
Else
Timestamp = ""
End If
End Function
The date stays the same even after refreshing / closing and opening the workbook as long as the referenced cell still reads "Done"; however if someone accidentally changes the referenced cell then the date is reset.
I need a VBA code to convert the formula to value once it has calculated, so the date will always stay the same. The solution needs to be automatic rather than manual and I can't enable iterative formulas on this workbook because it's used by multiple users. Any help much appreciated!
You can use the Worksheet Change event for that:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim AffectedCells As Range
Set AffectedCells = Intersect(Target, Me.Range("A:A")) ' Range A:A is the range we observe for 'done'
If AffectedCells Is Nothing Then Exit Sub
Dim Cell As Range
For Each Cell In AffectedCells
If Cell.Value = "done" Then
Dim UpdateTimestamp As Boolean
UpdateTimestamp = True
If Range("B" & Cell.Row).Value <> vbNullString Then
UpdateTimestamp = MsgBox("Timestamp exists do you want to update it?", vbQuestion + vbYesNo) = vbYes
End If
If UpdateTimestamp Then
Me.Range("B" & Cell.Row).Value = Format$(Date, "ddd dd mmm")
End If
End If
Next Cell
End Sub
// Edit according comment:
If you want to check multiple ranges for different things you need to slightly change your code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Cell As Range
Dim ObservedRangeA As Range
Set ObservedRangeA = Intersect(Target, Me.Range("A:A")) ' Range A:A is the range we observe for 'done'
If Not ObservedRangeA Is Nothing Then
For Each Cell In ObservedRangeA
If Cell.Value = "done" And Range("B" & Cell.Row).Value = vbNullString Then
Me.Range("B" & Cell.Row).Value = Format$(Date, "ddd dd mmm")
End If
Next Cell
End If
Dim ObservedRangeB As Range
Set ObservedRangeB = Intersect(Target, Me.Range("C:C")) ' Range C:C is the range we observe for ""
If Not ObservedRangeB Is Nothing Then
For Each Cell In ObservedRangeB
If Cell.Value = "" And Range("B" & Cell.Row).Value = vbNullString Then
Me.Range("B" & Cell.Row).Value = Format$(Date, "ddd dd mmm")
End If
Next Cell
End If
End Sub

Print multiple copies of the same sheet, but replace one cell with the data from a list (range) from another sheet

I am trying to print a few months worth of time sheets. So print 20 copies of the same sheet, and change the date on one cell (cell "C1" on "Timesheets" sheet) using a list of fortnightly dates on the "Pay Periods" sheet.
Have tried multiple methods but can't get close for varying reasons...
Would be interested to learn why am getting errors or stuck on each method I have tried below.
Sub PrintAllDates()
Dim printDate As Date
Dim startDate As Date
Dim endDate As Date
startDate = Worksheets("Pay Periods").Range("A2")
endDate = Worksheets("Pay Periods").Range("A10")
For printDate = startDate To endDate
Sheets("Timesheet").Range("C1") = printDate
Sheets("Timesheet").PrintOut
Next
This works but I can't figure out how to get it to use the list.
It prints out 9 consecutive days instead, whereas my list is 9 consecutive "fortnights".
Sub PrintCopies()
Dim i As Integer
Dim VList As Variant
VList = Sheets("Pay Periods").Range("H2:H3").Value
For i = LBound(VList) To UBound(VList)
Range("C1") = VList(i)
ActiveSheet.PrintOut
Next
With the above, I get runtime error 9 "Subscript out of range" on Range("C1") = VList(i)
Sub PrintCopies()
Dim i As Date
Dim VList As Variant
VList = Array(Worksheets("Pay Periods").Range("A2:A10"))
For i = LBound(VList) To UBound(VList)
Sheets("Timesheet").Range("C1") = VList(i)
Sheets("Timesheet").PrintOut
Next
This also works, but only 1 page gets printed out.
Date also gets converted to "13 Jan 1900".
The first code does not work because it is not considering the whole range of dates; instead it takes only the value inside the first and last cell, treating them as dates. The code basically takes those dates and covers each day between them. It does not even akwnoledge the others cells between A2 and A10. This one should work:
Sub PrintAllDates()
'Declaring variables.
Dim RngDate As Range
Dim RngDates As Range
Dim RngTarget As Range
'Setting variables.
Set RngDates = Sheets("Pay Periods").Range("A2:A10")
Set RngTarget = Sheets("Timesheet").Range("C1")
'Covering each cell in RngDates.
For Each RngDate In RngDates
'Changing RngTarget.
RngTarget = RngDate.Value
'Printing RngTarget's sheet.
RngTarget.Parent.PrintOut
Next
End Sub
I've also added a feature to check if the given value is a date in this version:
Sub PrintAllDates()
'Declaring variables.
Dim RngDate As Range
Dim RngDates As Range
Dim RngTarget As Range
'Setting variables.
Set RngDates = Sheets("Pay Periods").Range("A2:A10")
Set RngTarget = Sheets("Timesheet").Range("C1")
'Covering each cell in RngDates.
For Each RngDate In RngDates
'Checking if RngDate does not contain a date value.
If Not VBA.Information.IsDate(RngDate.Value) Then
'Asking what to do in case RngDate does not contain a date value.
Select Case MsgBox("Range " & RngDate.Address(False, False) & " in sheet " & RngDate.Parent.Name & " contains the value """ & RngDate.Value & """, which is a non-date value." & vbCrLf & _
vbCrLf & _
vbCrLf & _
"Do you wish to use it and print anyway?" & vbCrLf & _
vbCrLf & _
"Press ""Yes"" to print it anyway." & vbCrLf & _
vbCrLf & _
"Press ""No"" to not print it and proceed to the next value." & vbCrLf & _
vbCrLf & _
"Press ""Cancel"" to stop the macro and print no more.", _
vbYesNoCancel, _
"Non-date value detected" _
)
'If "Cancel" is pressed, the macro is terminated.
Case Is = 2
Exit Sub
'If "Yes" is pressed, the macro goes on.
Case Is = 6
'If "No" is pressed, the macro goes to NextRngDate
Case Is = 7
GoTo NextRngDate
End Select
End If
'Changing RngTarget.
RngTarget = RngDate.Value
'Printing RngTarget's sheet.
RngTarget.Parent.PrintOut
'Checkpoint.
NextRngDate:
Next
End Sub
Your code can be something like this:
Sub PrintAllDates()
Dim listRange As Range ' Your range A2:A10 in "Pay Periods" sheet '
Dim oCurrentCell As Range ' Single cell from this range '
Dim printedSheet As Worksheet ' Target sheet - "Timesheet" '
Dim oTargetCell As Range ' C1 - target cell (to set next date from list) '
Set listRange = Worksheets("Pay Periods").Range("A2:A10")
Set printedSheet = Worksheets("Timesheet")
Set oTargetCell = printedSheet.Range("C1")
For Each oCurrentCell In listRange.Cells
oTargetCell = oCurrentCell
Rem If some cells in "Timesheet" has formulas which reffered to C1,
Rem we need recalc it before printing
printedSheet.Calculate
printedSheet.PrintOut
Next oCurrentCell
End Sub

Finding the cell position (row,column) by using variable

I have an Excel sheet having one column as date as below :
My Job is to find to the position (cell,column) for a today's date
Script I am using :
Sub MacroExample()
Dim a As Variant
Dim column_Position As Variant
Dim row_Position As Variant
a = Format(Date - 1, "MM\/dd\/yyyy")
'MsgBox "The Value of a : " & a
Dim oRange As Range
Set oRange = Worksheets(1).Range("A1:Z10000").Find(a, lookat:=xlPart)
'MsgBox oRange.Address
MsgBox column_Position
MsgBox row_Position
End Sub
My output should be:
column_Position = 5
row_Position = 1
If I understand what you are asking, I hope this helps. This code should find the first occurrence of today's date in a specified search range.
Sub testDate()
Dim a As Variant
Dim column_Position As Variant
Dim row_Position As Variant
'get today's date, formatted m/d/yyyy
a = Format(Date, "m/d/yyyy")
Dim oRange As range
Dim myCell As range
'set a range to look through
Set oRange = Worksheets(1).range("A1:Z10000")
'check each cell value if it contains today's date. If so, capture the column and row and
'exit the loop.
For Each myCell In oRange
If InStr(1, myCell.Value, a) Then
column_Position = myCell.column
row_Position = myCell.Row
Exit For
End If
Next myCell
'display the column and row position, if wanted.
MsgBox "Column Position is " & column_Position & vbNewLine & "Row Position is " & row_Position
End Sub
Code Result
I cannot say how efficient this is, but it should work.
This will work if you look for a String within some larger string:
Sub MacroExample()
Dim a As String
a = Format(Date - 1, "MM/dd/yyyy")
MsgBox "The Value of a : " & a
Dim oRange As Range
Set oRange = Sheets(1).Range("A1:Z10000").Find(what:=a, lookat:=xlPart)
MsgBox oRange.Column
MsgBox oRange.Row
End Sub
Note:
The date includes the desired leading zeros.

how to iterate over all rows of a excel sheet in VBA

I have this code (This code is in Access VBA which tries to read an excel file and after checking, possibly import it):
Set ExcelApp = CreateObject("Excel.application")
Set Workbook = ExcelApp.Workbooks.Open(FileName)
Set Worksheet = Workbook.Worksheets(1)
now I want to iterate over all rows of the excel worksheet. I want something such as this:
for each row in Worksheet.rows
ProcessARow(row)
next row
where
function ProcessARow(row as ???? )
' process a row
' how Should I define the function
' how can I access each cell in the row
' Is there any way that I can understand how many cell with data exist in the row
end function
My questions:
How to define the for each code that it iterate correctly on all
rows that has data?
How to define ProcessARow properly
How to get the value of each cell in the row.
How to find how many cell with data exist in the row?
Is there any way that I detect what is the data type of each cell?
edit 1
The link solves on problem :
How to define the for each code that it iterate correctly on all rows that has data?
but what about other questions?
For example, how to define ProcessARow correctly?
If you need the values in the Row, you need use the 'Value' Property and after do an cycle to get each value
for each row in Worksheet.rows
Values=row.Value
For each cell in Values
ValueCell=cell
next cell
next row
Unfortunately you questions are very broad however I believe the below sub routine can show you a few ways of achieving what you are after. In regards to what datatype each cell is more involved as it depends what data type you wish to compare it to however I have included some stuff to hopefully help.
sub hopefullyuseful()
dim ws as worksheet
dim rng as Range
dim strlc as string
dim rc as long, i as long
dim lc as long, j as long
dim celltoprocess as range
set ws = activeworkbook.sheets(activesheet.name)
strlc = ws.cells.specialcells(xlcelltypeLastCell).address
set rng = ws.range("A1:" & lc)
rc = rng.rows.count()
debug.print "Number of rows: " & rc
lc = rng.columns.count()
debug.print "Number of columns: " & lc
'
'method 1 looping through the cells'
for i = 1 to rc
for j = 1 to lc
set celltoprocess = ws.cells(i,j)
'this gives you a cell object at the coordinates of (i,j)'
'[PROCESS HERE]'
debug.print celltoprocess.address & " is celltype: " & CellType(celltoprocess)
'here you can do any processing you would like on the individual cell if needed however this is not the best method'
set celltoprocess = nothing
next j
next i
'method 2 looping through the cells using a for each loop'
for each celltoprocess in rng.cells
debug.print celltoprocess.address & " is " & CellType(celltoprocess)
next celltoprocess
'if you just need the data in the cells and not the actual cell objects'
arrOfCellData = rng.value
'to access the data'
for i = lbound(arrOfCellData,1) to ubound(arrOfCellData,1)
'i = row'
for j = lbound(arrOfCellData,2) to ubound(arrOfCellData,2)
'j = columns'
debug.print "TYPE: " & typename(arrOfCellData(i,j)) & " character count:" & len(arrOfCellData(i,j))
next j
next i
set rng=nothing
set celltoprocess = nothing
set ws = nothing
end sub
Function CellType(byref Rng as range) as string
Select Case True
Case IsEmpty(Rng)
CellType = "Blank"
Case WorksheetFunction.IsText(Rng)
CellType = "Text"
Case WorksheetFunction.IsLogical(Rng)
CellType = "Logical"
Case WorksheetFunction.IsErr(Rng)
CellType = "Error"
Case IsDate(Rng)
CellType = "Date"
Case InStr(1, Rng.Text, ":") <> 0
CellType = "Time"
Case IsNumeric(Rng)
CellType = "Value"
End Select
end function
sub processRow(byref rngRow as range)
dim c as range
'it is unclear what you want to do with the row however... if you want
'to do something to cells in the row this is how you access them
'individually
for each c in rngRow.cells
debug.print "Cell " & c.address & " is in Column " & c.column & " and Row " & c.row & " has the value of " & c.value
next c
set c = nothing
set rngRow = nothing
exit sub
if you want your other questions answered you will have to be more specific as to what you are trying to accomplish
While I like the solution offered by #krazynhazy I believe that the following solution might be slightly shorter and closer to what you asked for. Still, I'd use the CellType function offered by Krazynhazy rather than all the Iif I currently have in the below code.
Option Explicit
Sub AllNonEmptyCells()
Dim rngRow As Range
Dim rngCell As Range
Dim wksItem As Worksheet
Set wksItem = ThisWorkbook.Worksheets(1)
On Error GoTo EmptySheet
For Each rngRow In wksItem.Cells.SpecialCells(xlCellTypeConstants).EntireRow.Rows
Call ProcessARow(wksItem, rngRow.Row)
Next rngRow
Exit Sub
EmptySheet:
MsgBox "Sheet is empty." & Chr(10) & "Aborting!"
Exit Sub
End Sub
Sub ProcessARow(wksItem As Worksheet, lngRow As Long)
Dim rngCell As Range
Debug.Print "Cells to process in row " & lngRow & ": " & wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants).Count
For Each rngCell In wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants)
Debug.Print "Row: " & lngRow, _
"Column: " & rngCell.Column, _
"Value: " & rngCell.Value2, _
IIf(Left(rngCell.Formula, 1) = "=", "Formula", IIf(IsDate(rngCell.Value), "Date", IIf(IsNumeric(rngCell.Value2), "Number", "Text")))
Next rngCell
End Sub
Note, that you have to call the sub to call a row must also include the sheet on which a row should be processed.

Resources