How do I get a function to refer to the cell it is residing in? - vba

I am writing a function and I need the function to refer to a cell offset from where it resides in. I've got rows of data with each column containing a specific variable. The function resides in the last column, and is supposed to check if each variable defined in it matches the variables in the row preceding it. Each match is supposed to score a +1 to the final value of the function. Here's a short version of my function.
Public Function cellscore(testvar1)
totalscore = 0
If testvar1= activecell.Offset(-1, -10) Then
cellscore = totalscore + 1
End If
End Function
What do I replace activecell with, so that the function runs correctly for each row of data that I have?
I'm new at VBA so please bear with me if this is a simple question. Thanks in advance.

The easiest way would be to pass a numeric value to the function which represents the row number. This means that your code would become something like:
Public Function cellscore(testvar1,RowNum as Long)
totalscore = 0
If testvar1= Range("SOMECELL").Offset(RowNum-2, -10) Then
cellscore = totalscore + 1
End If
End Function
You will need to change SOMECELL to represent the cell in the first row. For example if your formula is placed in the T column, then you would replace SOMECELL with T1.
The clever part now comes with how you call the function within the worksheet. You do this by using the formula:
=cellscore(SOMEVALUE,ROW())
Obviously replace SOMEVALUE with the value you are testing for. Now regardless of which row this formula is placed it will reference the current one when calling the function. Hope this helps!

Related

Excel vba: check if VLookup returns empty value, given that Vlookup substitutes empty with zero

So I have a table for different regions and corresponding values for different years 2014-2017. Not all regions have value for the year 2017. However, if I make a WorksheetFunction.VLookup(...) it will return 0 if the cell in question is empty.
I need to put my special value (-1) instead of 0 if the cell for a given region is empty, however, I can't differentiate between real zeroes and zeroes Vlookup returns instead of empty values. How would I do it?
EDIT: I can't use IsEmpty on individual cells, because I don't know the result's cell's address - I loop though them in a cycle without even knowing a first argument's address. All I have is the result - either a real or a fake zero.
You can check if VLOOKUP returns an empty cell.
As a worksheet formula you can use: =IF(VLOOKUP(K9,$F$2:$G$6,2,FALSE)="",-1,VLOOKUP(K9,$F$2:$G$6,2,FALSE))
As a VBA example you could use:
Public Function MyVlookup(lookup_value, table_array As Range, col_index_num As Long, Optional range_lookup As Boolean = False) As Variant
Dim ReturnValue As Variant
ReturnValue = WorksheetFunction.VLookup(lookup_value, table_array, col_index_num, range_lookup)
If IsEmpty(ReturnValue) Then
MyVlookup = -1
Else
MyVlookup = ReturnValue
End If
End Function
Worked this answer
=if(len(vlookup(a2,f$2:g$20,2))=0,"",vlookup(a2,f$2:g$20,2))
The trick is to use any checking functions not on result = VLookup(...) but rather on VLookup itself, before it had time to change emptiness to zero.
I am not sure but I think using IF, ISEmpty will solve your problem.
Use WorksheetFunction.Match to get cell address
currentCell= the cell in which you'll be putting value
concernedCell=The cell which can be empty
If(Isempty(concernedCell)) Then
currentCell.value=-1
Else
currentCell.value=concernedCell.value

What am I missing in this IsEmpty clause?

In an Excel VBA module I'm building, I have some code to execute if a table of out-of-gauge cargo contains anything. I initially wrote this:
If Not IsEmpty(Range("OOGData")) Then
...
Else
...
End If
But even when OOGData is empty, it keeps returning False. I've tried it with If IsEmpty(Range("OOGData")) = False Then` but that doesn't seem to make any difference. My current code is
If IsEmpty(Range("OOGData")) = False Then
...but that still activates with the empty range.
I've made sure there are no formulae, hidden values or anything that could be showing up.
Any idea what the problem could be?
According to this information:
Returns a Boolean value indicating whether a variable has been
initialized.
In your code you are not working with variable therefore you shouldn't expect correct value.
When checking single cell you should use Len() function instead:
If Len(Range("OOGData"))=0 Then
'cell is empty
When checking if range of cells is empty use this solution:
If WorksheetFunction.CountA(Range("OOGData"))=0 Then
'rabge is empty
The final alternative I can think of is to use loops.
I've decided to cheat. Instead of checking the list there, I've added a Boolean variable (bContainsOOG) that's set to True any time an OOG cargo item is added to the OOG list, and then the reporting sub checks against that instead. But thanks both of you for those suggestions, they'll come in handy in another bit I was getting stuck on. :-)
This is a function that I thinks very closely fits:-
' Function to determine the last (first blank/null/empty) cell in a series of data in either a column
' or row
' Usage :- DataSeriesEnd(w,r,b)
' where w is the name of the worksheet.
' r is the row or column as an integer
' b is a boolean (true/false) if true then processing a column, if false a row
Public Function DataSeriesEnd(Worksheet As String, rc_index As Integer, bycolumn As Boolean) As Integer
Dim cv As Variant
For DataSeriesEnd = 1 To 500
If bycolumn Then
cv = Worksheets(Worksheet).Cells(DataSeriesEnd, rc_index)
Else
cv = Worksheets(Worksheet).Cells(rc_index, DataSeriesEnd)
End If
If IsNull(cv) Or IsEmpty(cv) Or cv = "" Then Exit For
Next DataSeriesEnd
' Position to previous cell (i.e. this one is empty)
DataSeriesEnd = DataSeriesEnd - 1
End Function
Note! There is a limit of 500 rows/columns

How to pass a date as an argument in an Excel VBA function

I'm trying to make a function that will take a date in a cell as an argument, then use that date to lookup a value. The date to be passed will be in the variable EffDate. Then the function should go to the worksheet RateChgs, check the NewPymtEffDateRange for the EffDate, and, upon finding it, go to the EscrowPymtAmtRange (one column wide) and return the value on the same row there.
I've only gotten to the point of testing it in the immediate window by typing GetEscrowPymt(8/1/2000) (or some other date). From the value of the Position variable, I can tell that the function isn't finding the date even though it's there. Is this a problem with how I'm passing the date?
Function GetEscrowPymt(EffDate As Date)
Dim PymtEffDateRange As Range
Dim EscrowPymtAmtRange As Range
Dim Position As Integer
Set PymtEffDateRange = Worksheets("RateChgs").Range("NewPymtEffDate")
Set EscrowPymtAmtRange = Worksheets("RateChgs").Range("EscrowPymt")
Position = Application.WorksheetFunction.Match(EffDate, PymtEffDateRange, 1)
MsgBox (Position)
End Function
The last argument in the Match function allows for returning an approximate match. If you require an exact match, then you should use the last argument of 0 to require an exact match. Otherwise, using the arguments 1 or -1 will return approximate match and assume also that the data is sorted ascending.
Position = Application.WorksheetFunction.Match(EffDate, PymtEffDateRange, 0)
The Match function will error if the effDate value is not found in the lookup array, so you may need error handling logic to account for that possibility. I would probably use the Application.Match function which can accept an error type, where the Match function in the Worksheet class will only accept long/integer values and will raise an error if the value isn't found:
Dim Position as Variant
Position = Application.Match(EffDate, PymtEffDateRange, 0)
If IsError(Position) Then
MsgBox EffDate & " not found!", vbInformation
Exit Function
' -- OR --
' assign some other return value for the function, etc.
End If
Some functions also have difficulty working with date values, so let me know if that doesn't solve the issue.
VBA also doesn't play well with various system locales, if you're expecting "8/1/2000" to be anything other than August 1, 2000, you may have more problems since VBA will interpret that by the US date format, not the system locale (e.g., in the UK that date would be 8 January, 2000). In that case, it may be best to treat the date as text and do a match based on text rather than date.

How to SUMIF (or ??) using criteria from another function value return?

I am trying to do a very simple sum of a column that excludes the colored ones. The column I wish to sum is all my accounts and the green ones represent the paid accounts. I want a sum that represents the "left to pay" value to keep track of my progress without redoing my formula every time. The color is not conditional, nor can it be.
I have 2 functions created already:
Function GetColor(MyCell As Range)
GetColor = MyCell.Interior.ColorIndex
End Function
and
Function PAID(MyCell As Range) As Boolean
If MyCell.Interior.ColorIndex = 50 Then
PAID = True
Else
PAID = False
End If
End Function
So I have already created one column next to my numbers that have the formula (with changing cell number):
=PAID(C13)
and this spits out TRUE or FALSE values that I can then based my SUMIF formula off of, currently I have this (E column containing values from the PAID function, C contains my account values):
=SUMIF(E2:E18,"FALSE",C2:C18)
I would like to see if it's possible to bypass making this extra column and run the function directly in the SUMIF (or maybe another function?) so that all I have to do is color my cell and refresh only one formula.
Using colours as part of the program decision process is not ideal, and is overly complex for a simple task.
But assuming you want this (or have no control over this), and the cells you want to sum are NOT coloured with Interior.ColorIndex = 50 - and assuming your values are in range C2:C18 (or wherever), a VBA function to do this is below.
Use the function as =PAID(C2:C18,50)
Function PAID(MyCells As Range, colour_avoid As Integer) As Double
Dim cc As Range
Dim accumulate As Double
accumulate = 0 'not needed but good practice
For Each cc In MyCells
If (cc.Interior.ColorIndex <> colour_avoid) Then
accumulate = accumulate + cc.Value
End If
Next cc
PAID = accumulate
End Function
To repeat, this function will sum all the cells NOT coloured with a colour.index that you give to it (say, 50).

#VALUE error with Excel VBA Function

In my Excel spreadsheet I have two columns.
A contains strings with the values 'Yes', 'No' or 'Maybe'.
B contains strings with a year in.
I need a function to determine the number of occurrences of a year in column B, where the equivalent value in column A is 'Yes'.
I currently have the following code:
Function CountIfYearAndValue(Rng As Range, YNM As String, Year As String) As Integer
Dim count As Integer
count = 0
For Each c In Rng.Cells
If (StrComp(Abs(c.Value), Year, vbTextCompare) = 0) And (StrComp(Cells(c.Row, A), YMN, vbTextCompare) = 0) Then count = count + 1
Next
CountIfYearAndValue = count
End Function
The idea of this code is that we iterate through every cell in the range given (a range on column B) and check if the year is equal to the Year parameter. And if the equivalent cell on column A is equal to the YNM parameter we increment the count variable.
For some reason this code does not work when I use the following parameter:
=CountIfYearAndValue('Years'!B1:B7,"Yes","Year 7")
It just does the #VALUE error and refuses to display any outcome.
Any help would be much appreciated.
Edit: All of the values in both cells are on of an unformatted datatype ('General') and no cells are blank.
It sounds like you are reinventing the wheel... There already is a built in function (advantage: being much faster than a UDF) that does exactly what you are after. It is called COUNTIFS()
All YESes for Year 7 in rows 1 to 10.
=COUNTIFS(B1:B10, "Year 7",A1:A10, "Yes")
I just had a quick look at your code and I think there are possibly a few reasons why your original code is not working as expected.
YNM is a valid column name therefore it should not be used as a variable name. You should avoid naming your variables like that - give it a more meaningful name
YNM != YMN as you had it in your code (see function definition and then the misspelled version in the StrComp() function)
Year is a valid VBA built in function, therefore once again you should avoid using it as a variable name as you're exposing yourself to a naming collision.
Add Option Explicit at the top of your module. This requires you to Dimension all you variables. It's always recommended for many many reasons.
rng variable is of Range type therefore you do not need to explicitly add the .Cells property to it. Even though it may help in some cases - at a bit more advanced level you may face some runtime type compatibility issues. ( runtime may convert your rng Range variable to a 2D array etc )
Added an explicit conversion in the second StrComp() function around the c.Offset(0, -1) as you don't want the runtime to (rare but still possible) convert your Yes to a Boolean data type. Explicit conversion to a String just gives you that extra protection ;p (lol)
therefore, something like this returns the correct value
Function CountIfYearAndValue(rng As Range, choice As String, myYear As String) As Long
Dim count As Long
count = 0
Dim c As Range
For Each c In rng
If (StrComp(c, myYear, vbTextCompare) = 0) And (StrComp(CStr(c.Offset(0, -1)), choice, vbTextCompare) = 0) Then
count = count + 1
End If
Next c
CountIfYearAndValue = count
End Function
Right, I hope this helps you understand bits and pieces :) any questions please leave a comment