How do I use Index & Match to reference a separate sheet? - excel

Background:
I have the following test code that is working where column H is equal to what's in column B versus column D
Range("H2:H17") = "=INDEX(D2:D17,MATCH(B2:B17,B2:B17,0))"
Question:
How do I use this in code to reference a separate sheet called "temp" to do the same thing. The idea is for each time the code looks for 'target' it does
an index and match checking column B to equal what's in column D so if the valuue A is passed then it would become Test1?
I tried the following code, but target is not getting updated with any value.
Dim Target As Variant
With Application
Target = .Index(Sheets("Temp").Range("D2:D17"), .Match(Sheets("Temp").Range("B2:B17"), Sheets("Temp").Range("B2:B17"), 0))
End With
Debugging shows the following for Target
Any help is appreciated!

Match is a member of the WorksheetFunction interface; you need a WorksheetFunction object instance to invoke it off of - a With block could hold that object reference for you so you only need to type it once:
With Application.WorksheetFunction
Target = .Index(sheet.Range("D2:D17"), .Match(sheet.Range("B2:B17"), sheet.Range("B2:B17"), 0))
End With
Where sheet would be a Worksheet variable to work with, or a Worksheet parameter to your procedure.
Something looks wrong with the first argument to Match though: lookup_value wants to be a single value: the early-bound Application.WorksheetFunction.Match method is rather picky about what Variant subtypes it's willing to play along with nicely, and will throw a type mismatch error as-is.
The late-bound version (watch out for typos! Option Explicit can't save you from late-bound code!) works as expected with the range/array lookup value argument, and yields a Variant() array:
With Application
Target = .Index(sheet.Range("D2:D17"), .Match(sheet.Range("B2:B17"), sheet.Range("B2:B17"), 0))
End With
Make sure Target is a Variant, because this late-bound Match will yield a Variant/Error value if the lookup fails (the early-bound version raises a run-time error instead) - and that'll be a type mismatch error if you try to assign to anything but a Variant.

Related

"Wrong Data Type" error for setting cell value [duplicate]

I am building a User Defined Function.
I get an error
A Value used in the formula is of the wrong data type
I am trying to build a function that adding one comment, will also add the comment to another location as the comments always come in pairs.
Public Function AddComments(vesselCell As Variant, shopCell As Variant, comment As String) As Variant
Range(vesselCell).AddComment (comment)
Worksheets("Shop").Range(shopCell).Value = comment
End Function
I have singled it out to the third line of code causing the problem.
The setup currently is the Vessel Cell will be a comment added to the sheet, and then the Shop sheet has a section for comments as a column.
Assuming vesselCell and shopCell are both Range objects, Range(vesselCell) and Worksheets("Shop").Range(shopCell) are part of the problem.
Make them Range, not Variant.
vesselCell.AddComment comment
shopCell.Value = comment
Now, this code isn't legal in a UDF. Make your procedure a Sub procedure (remove the As Variant return type), and invoke it from other VBA code, or attach it to a shape or button's OnAction property.
User-Defined Functions take inputs, compute a result, and then return that result to the calling cell: your code not returning anything is a strong indicator that a Function isn't appropriate here.

Worksheet.Columns property asks me for a RowIndex?

If I write this in the VBA editor:
Dim ws As Worksheet: set ws = ActiveSheet
ws.Columns(
IntelliSense shows me a seemingly unrelated tooltip:
_Default([RowIndex], [ColumnIndex])
The Worksheet.Columns property only accepts the index (column) number as far as I can see in the documentation.
So why am I asked for a RowIndex? Why does it refers to _Default (and what is it)?
The Worksheet.Columns property only accepts the index (column) number as far as I can see in the documentation.
Nowhere in the documentation does it say the Columns property takes a parameter, and indeed, it would be wrong to mention that, because it doesn't have any:
Like Worksheet.Rows, Worksheet.Columns yields a Range object. So when you "parameterize" it, what's really happening is this:
Set foo = ws.Columns.[_Default](value)
Any argument you provide, get interpreted as arguments to an implicit default member call against the Range object that was returned by the call to Columns.
You may have read somewhere that the default member of a Range is its Value - and that is not true. The default member of a Range is a hidden property named [_Default] (square brackets are required in VBA if you want to invoke it explicitly, because no legal VBA identifier can begin with an underscore), that takes two optional parameters:
When you read ("get") this default property without providing any arguments, this default property does get you the Range.Value (i.e. a single Variant value for a single cell, or a 2D Variant array for multiple cells). When you assign to this default property, you are assigning the Range.Value.
But when any arguments are provided when reading ("get") this default property, what you get is a call to the very standard Range.Item indexer property:
So what Columns does, is simply take your input range, and yield a Range object laid out in such a way that it can be accessed using a RowIndex argument - we can prove this using named arguments, which show that this code is illegal:
?Sheet1.Range("A1:C1").Columns.Item(ColumnIndex:=2).Address
>> "wrong number of arguments"
As is this equivalent code:
?Sheet1.Range("A1:C1").Columns(ColumnIndex:=2).Address
>> "error 1004"
Note that the _Default property yields a Variant, so the above .Address member call can only be resolved at run-time (and you don't get any intellisense for it, and the compiler will not flinch at any typo, even with Option Explicit specified - you will get error 438 at run-time though).
Best stick to safe early-bound land, and pull the returned object reference into a local variable:
Dim foo As Range
Set foo = ws.Columns(1)
Debug.Print foo.Address '<~ early-bound w/intellisense & compile-time validation
TL;DR: You're being prompted for a RowIndex argument because you are making a call (albeit an implicit one) to a hidden _Default property that accepts a RowIndex argument.

Dictionary lookup fails

I am writing an Excel VBA program that validates a school course schedule. A key component is a global dictionary object that keeps track of the course number (the key) and the number of times that course is scheduled (the item). I have successfully created and loaded the dictionary. I'm trying to lookup the value associated with the course key, but have been unable to do so using the one-line examples I've found at this site. I'd like to use this line of code:
intCourseCnt = gdicCourses("BAAC 100")
or
intCourseCnt = gdicCourses.Item("BAAC 100")
but neither work (actually, the "BAAC 100" part is a string variable, but it won't even work if I hardcode a course in.) Instead, I have to use the kludgy loop code below to lookup the course count:
Private Function Check_Course_Dup_Helper(strCourse As String) As Boolean
Dim k As Variant
Check_Course_Dup_Helper = False
' Read thru dictionary. Look to see if only 1 occurrence then jump out.
For Each k In gdicCourses.Keys
If k = strCourse Then
If gdicCourses.Item(k) = 1 Then
Check_Course_Dup_Helper = True
Exit Function
End If
Exit Function
End If
Next
End Function
Is there a way to rewrite this so that I can lookup of the item value without the loop?
Thank you.
Thanks for the prompt replies. Answers below:
David, the gdicCourses("BAAC 100") code value while the program is running is "empty" which makes the receiving variable equal to 0. The result is the same if I use strCourse variable. Also, the dictionary populating code is shown below. I do not believe it is a problem because I can correctly access the values elsewhere in the program where For-Each-Next loops that use a range variable are employed. Whitespace and non-printable characters are not present.
My guess is that I need to use a range to reference the position in the dictionary rather than a string. I've tried pretty much every combination of this that I can think of, but the value is still "empty".
Set gdicCourses = New Scripting.Dictionary
For Each c In Worksheets("Tables").Range("combined_courses").Cells
If Not (gdicCourses.Exists(c)) Then
gdicCourses.Add c, (Application.WorksheetFunction.CountIF(Range("MWF_Table_Full"), c
(Application.WorksheetFunction.CountIf(Range("TTh_Table_Full"), c)))
End If
Next

XLL issue with xlfEvaluate

I have this problem with a very simple function written in an XLL, using VS2012. I have tried reading up in MSDN and Steve Dalton's book, and I cannot see what I am doing wrong.
The tricky bit is that I need my function to read values in worksheet cells other than the one from which it is called. The function takes no arguments, and returns an integer. I have declared it as J# (the # signifying that it can call XLM functionality as advised by Dalton...although I still get the same problem without the #). I have not included the declaration of my function to save space, but it is simple and I do not think it is the cause of the problem.
This first block of code works fine. I wrote it just to build confidence.
//This block works correctly. A trial copied from the old Excel 97 documentation
XLOPER12 xlInput1, xlOutput2;
/* Evaluate the string "2+3" */
xlInput1.xltype = xltypeStr;
xlInput1.val.str = L"\0032+3"; //prefix with string length in Octal
Excel12(xlfEvaluate, &xlOutput2, 1, (LPXLOPER12) &xlInput1);
//works OK, and xlOutput2 contains 5
But this second block does not work. I cannot see why. I am trying to read a value from a cell, which is a different cell from that from which the function was called. What I get is an return XLOPER12 that contains an error (xltypeErr) and junk values in the val.num field (the worksheet cell does contain an integer value).
//This block does not work
XLOPER12 xlInput3, xlOutput3;
/* Look up the name Tst on the active sheet called Sht */
xlInput3.xltype = xltypeStr;
xlInput3.val.str = L"\003Tst"; //this also gives problems regardless of whether the string is defined as \004!Tst or \007Sht!Tst
Excel12(xlfEvaluate, &xlOutput3, 1, (LPXLOPER12) &xlInput3); //xlOutput3 now has a type of xltypeErr, rather than the correct integer value on the worksheet
Can you kindly explain what is going wrong?
If you're trying to read a value from a cell that is different from the cell calling the function you'll need a parameter to refer to that different cell. For example in A1 you may have '=myfunc(A2)'. Then your C++ extension func will need to be declared 'JP#', with the P corresponding to the A2 cell reference parameter. If Excel can resolve the 'A2' reference it will pass in an XLOPER with that value as xltypeNum, xltypeInt or xltypeStr depending on the the contents of A2. If not you may get an xltypeSRef.
xlfEvaluate: here's the MS doc https://msdn.microsoft.com/en-us/library/office/bb687913(v=office.15).aspx
Note that MS specify that the string passed to xlfEvaluate must 'contain only functions, not command equivalents'. I suspect L"\003Tst" doesn't correspond to any function known to your Excel. There's no built in function called Tst in my Excel 2013. It's possible you have an addin that supplies a function called Tst, but I'm guessing not. So try changing xlInput3.val.str to L"\006RAND()" and see what happens.

User defined function to get a value from a particular row with in the column of the calling function

I'm wondering if it's possible to write a user define function in VBA to get a value from a fixed row within the column of the calling function.
Ie I have some fixed values on row 2 of a Excel table; I want to refer to those values from a user defined function.
The caveat is I want the value in row 2, of the calling functions column.
Note : the actual function is more complex than this, however solving this problem would get me where I need to be.
Pseudocode:
Function GetStandardPayment()
GetStandardPayment = CallingColumn.Row(2).Value
End Function
What I have is the following:
Function GetStandardPayment()
GetStandardPayment = ActiveCell.Offset(ActiveCell.Column:2).Value
End Function
However it has syntax errors.
Edited to remove the possibly unneccessary variable.
You can use the Caller property of the Application object. When using a UDF in a worksheet cell, this will return a range object -- the cell where the function exists.
Function GetStandardPayment()
Application.Volatile
GetStandardPayment = Cells(2, Application.Caller.Column)
End Function
The Volatile property is used else the function won't update, since there are no cell references in the function argument.

Resources