Returning the Cell location of specific value in a separate Workbook - vba

To clarify. I have 1 spreadsheet I am creating the VBA in. When the Macro runs, I want the code to find a specific value in a separate WorkBook sheet and return the location of the value in the other WorkBook.
My initial thought was to use HLookUp, however that will only return the value, not the location of the value. My next thought was to use the Find function, but I can't get it to run. The error I get is: Run-time error "438': Object doesn't support this property or method
startValue = enrollBook.Sheets("Pop-FY").Range("D:Z"). _
Applications.WorksheetFunction. _
Find(What:=FYString, LookIn:=xlValues)
enrollBook is the other workbook.
startValue is supposed to be the location of the found value in the other spreadsheet

This is a weird way of calling a function & formatting you've got there, I first even thought that code was invalid.
First, you are confusing Range.Find with Application.WorksheetFunction.Find. You need the Range one, but are calling the other one.
Second, the error is because it's Application, not Applications.
Third, you will need a Set:
Set startValue = enrollBook.Sheets("Pop-FY").Range("D:Z").Find(What:=FYString, LookIn:=xlValues)

You mean something like this?
Dim sTest As String
Dim oRange As Range
Set oRange = Worksheets(1).Range("A1:Z10000").Find("Test", lookat:=xlPart)
MsgBox oRange.Address
I tested this, but you need to change your parameters.

Related

VBA Runtime error 1004 when trying to access range of sheet

I am building a small vba script that is merging tables from several workbook into one single worksheet of another workbook. The error is raised when I try to set the destination range's value:
wksPivotData.Range(wksPivotData.Cells(CurrentRow, 1)).Resize(tbl.ListRows.Count, tbl.ListColumns.Count).Value = _
tbl.Range.Value
The error: "Run-time error '1004': Application-Defined or object-defined error"
I went through similar questions, and the general answer is what I found in this one: The selected cell belongs to another worksheet than the one desired.
While this makes complete sense, I still can't figure why my code breaks as I'm only using numerical reference (CurrentRow is a Long) and Resize, which should prevent me from doing such a mistake.
Additionally, I ran a couple quick tests in the Immediate window and it turns out that while the worksheet wksPivotData exists and I can access its name and a cell value, the range function simply doesn't work:
Debug.Print wksPivotData.Name
PivotData
Debug.Print wksPivotData.Cells(1, 1).Value
123
Both of those work but the next one doesn't:
Debug.Print wksPivotData.Range(1, 1).Value
Your last line, Debug.Print wksPivotData.Range(1, 1).Value won't print because you're misuing Range(). I assume you want A1?
When using Range(1,1), you're referring to a non-existent range. If you want to do cell A1, you need
With wksPivotData
myData = .Range(.Cells(1,1),.Cells(1,1)).Value
End with
Since you're using multiple worksheets, I'd use the with statement as above. Another way to write the same thing is wksPivotData.Range(wksPivotData.Cells(1,1),wksPivotData.Cells(1,1)) (You need to explicitly tell Excel what sheet you want to refer to when using Range() and cells().
Finally, for your resize, if I recall correctly, you're going to have to add the same Cell() twice in your range:
wksPivotData.Range(wksPivotData.Cells(CurrentRow, 1),ksPivotData.Cells(CurrentRow, 1)).Resize(tbl.ListRows.Count, tbl.ListColumns.Count).Value = _
tbl.Range.Value
Or, for the same thing, but different way of doing it:
With wksPivotData
.Range(.Cells(currentRow, 1), .Cells(currentRow, 1)).Resize(tbl.ListedRows.Count, tbl.ListColumns.Count).Value = tbl.Range.Value
End With

Excel vba code using ThisWorkbook.Sheets(1).Range not working, but Sheet1.Range works fine. Why?

When I use wb.Sheets(1).Range("A:A").Find(What:=ID, LookIn:=xlValues) I get error 91 - Object Variable or With Block not set. When I use Sheet1.Range("A:A").Find(What:=ID, LookIn:=xlValues) it returns correct value.
Why the difference?
Is there a flowchart I can reference or any simple information available to understand which sub-commands (I don't know the proper word) work with ThisWorkbook and Sheets(#) versus Sheet#.Whatever?
I am hesitant to use Sheet("Name") because names might change later. I am using ThisWorkbook rather than ActiveWorkbook to keep all code attached to the appropriate workbook.
Anything you can offer for simple reference info would be great. I have researched, but still don't understand which sub-commands work with which parent commands. (Again, probably wrong terminology).
Private Sub lstExample_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Dim wb As Workbook
Dim I As Integer
Dim ID As String
Dim findValue As Range
Set wb = ThisWorkbook
'Get the values of the selected row in listbox on doubleclick
For I = 0 To lstExample.ListCount - 1
If lstExample.Selected(I) = True Then
'Set listbox column 1 as ID value to find
ID = lstExample.List(I, 1)
End If
Next I
'Match ID (column A) on Sheet1
Set findValue = wb.Sheets(1).Range("A:A").Find(What:=ID, LookIn:=xlValues)
MsgBox findValue
End Sub
There is no difference between the properties of Sheets(1) and Sheet1 as long as both are the same object - Worksheet Object in your case.
You're getting that error because findValue Is Nothing. That is, it couldn't find the ID in the column. When using the Find method, it's best to specify every argument. Find remembers the last find you did, even if you did it in the UI and your using Find in VBA.
For instance, if you check MatchCase in the UI when you do a Find. Then you do a Find in VBA and don't specify the MatchCase property, it will use whatever you set the last time.

Type Mismatch error in function using DateValue(), how to troubleshoot?

Warning: I'm a noob.
I've written a Sub to find cells with red text and alter them.
#ThinkerIV gave me a great function to put in a cell and drag the formula out into adjoining cells, but that won't work due to the number of sheets to work on.
So I wrote my Sub, calling his function (see code below). I passed it a Range of one cell, so it seems to me it should work?
But, it keeps throwing out Type Mismatch (run-time error code 13) on the line where the function calls the DateValue()! The passed range shows a value of 1 (which is the number in the cell it refers to) when I hover over it in the editor, but I don'rt know if that's the cell's contents or some other value 1 being shown.
So, I really don't know how to find out exactly why this is happening. Is it that the range I passed is somehow not the right kind? Please inform me of why this code won't work!
I tried to change that line to the comment line below it (and a couple other blind-guess changes), but that has the same error.
Thanks in advance!
Sub redTextToRealDates()
Dim dateTemp As Date
Dim redCell As Range
Dim foundCell As Range
Dim thisSheetsRange As Range
Dim busyCell As Range
Dim redTextCells As Range
Set thisSheetsRange = ActiveSheet.usedRange
'Build a range containing all the cells in a sheet containing red text.
' well... all cells formatted to HAVE red text, anyway.
' Anyone want to tell me how to write this to skip empty cells?
' Because I don't need to grab empty cells into this range...
For Each busyCell In thisSheetsRange
If (busyCell.Font.ColorIndex()) = 3 Then
If redTextCells Is Nothing Then
Set redTextCells = busyCell
Else: Set redTextCells = Union(redTextCells, busyCell)
End If
End If
Next busyCell
'Change unknown format cells to date cells populated with concantenated
'string of original contents and the active sheet's name.
For Each foundCell In redTextCells
foundCell.NumberFormat = "#"
foundCell = GetConcantDate(foundCell)
Next foundCell
redTextCells.NumberFormat = "dd/mm/yy"
On Error Resume Next
End Sub
Function GetConcantDate(rng As Range) As Date
'Original code supplied by ThinkerIV on StackOverflow.com
Dim dtTemp As Date
dtTemp = DateValue(rng.Range("A1").Value & " " & rng.Parent.Name)
'dateTemp = DateValue(foundCell.Value & " " & ActiveSheet.Name)
GetConcantDate = dtTemp
End Function
EDIT
I cant post my own answer yet, so I am adding this solution:
When feeding data to Format(), the contents of the first cell formatted for red were NOT in text form. I had not put in place any way to ensure that I passed the proper data type. So, the line to format the cell as text (foundCell.NumberFormat = "#") before passing it to the function is what fixed it.
The solution was actually already written when I copy/pasted the code into the question - I just wasn't aware that it had fixed it because of another error on a different Sub. (I'm a noob and was confused dealing with multiple errors in multiple subs) I thought I had tried it again with that new line, but HADN'T, so still thought it was not working.
Thanks to all who helped. I feel a bit of a fool now, having found it like that. Hope you forgive me for my rookie flubber - too many Subs and Functions in a huge list in the editor and I got 'dizzy'... At least I can post a solution in case some other noob needs it!
Ok, I think there are two things here. Firstly, the DateValue function takes a string representation of a date, e.g. "01/01/2013", when you pass through an excel date from a range, you are passing through a number, like 41275. This throws the run time error 13.
However, if you already have a date, why bother converting it? You seem to want all red cells to be converted to a date + the sheetname. To do this you'll have to have strings e.g. "01/01/2013 Sheet1", so you couldn't use DateValue here. Instead perhaps try something like this:
Public Function GetConcatDate(rng As Range) As String
Dim dtTemp As String
dtTemp = Format(rng.Range("A1").Value, "dd/mm/yyyy") & " " & rng.Parent.Name
GetConcatDate = dtTemp
End Function

References to cells and worksheets in Excel

I have a problem that I don't understand at all:
i = 150
If ActiveWorkbook.Worksheets("foobar").Cells(i, 3).Value Like "*:*" Then
MsgBox "I found a colon!"
End If
As you might guess, the sheet foobar has at position (150, 3) a cell containing a colon, thus the message box is shown.
Now I want to do this:
i = 150
Cell = ActiveWorkbook.Worksheets("foobar").Cells(i, 3).Value 'fails right here
If Cell Like "*:*" Then
MsgBox "I found a colon!"
End If
Here it gives me an error saying "Object variable or With block variable not set. In fact saying:
Sheet = ActiveWorkbook.Worksheets("foobar")
gives a similar message. Why? What am I doing wrong? I just want a reference of that object, or at least a refence.
Bottomline:
When handling objects, use Set.
When handling primitive data (integers, longs, strings*, variants*) you do not use it.
Sheet, Workbook, Range, are objects. Therefore, you need to use Set when assigning them to variables.
A Range.Value returns a Variant (that can be a long, a string, etc.) So, you cannot use Set.
==========================
Now, about your error message... I'd say then that maybe before in your code, Cell is being declared as object. Try use another variable name, or check the Cell variable type.
To check this, right click on it and then click in 'Definition'. Have it declared as Variant might fix the problem (be aware of side effects it might cause, though).
==========================
*I know these types aren't 'primitive'; I used as an example here for the sake of explanation's cleanliness.
As your assigning an object the line
Sheet = ActiveWorkbook.Worksheets("foobar")
should read
Set Sheet = ActiveWorkbook.Worksheets("foobar")
In your line
Cell = ActiveWorkbook.Worksheets("foobar").Cells(i, 3).Value
you are trying to assign the value of cell (probably a string) to a cell. Take the .Value off and the assignment should work better. The Excel message you encountered is not the best: sometimes you get it when you assign variables of the wrong type.
However, the If Cell Like might not work. (Hint: the .Value has to get moved, not deleted.)
I just tried the following code and it works probably in your code make sure the ActiveWorkBook is actually the Active Work book you want(if you are working with multiple work books)
Sub test()
i = 3
If ActiveWorkbook.Worksheets("Sheet1").Cells(i, 2).Value Like ":" Then
MsgBox "I found a colon!"
Else
MsgBox "didn't find a colon!"
End If
Cell = ActiveWorkbook.Worksheets("Sheet1").Cells(i, 2).Value
'fails right here
If Cell Like ":" Then
MsgBox "I found a colon!"
End If
End Sub
Use Dim MyCell as Variant (or as string) since Cell is an existing object

How to get/set unique id for cell in Excel via VBA

I want to have/define a unique id for each data row in my Excel data sheet - such that I can use it when passing the data onwards and it stays the same when rows are added/deleted above it.
My thoughts are to use the ID attribute of Range (msdn link)
So, I have a user defined function (UDF) which I place in each row that gets/sets the ID as follows:
Dim gNextUniqueId As Integer
Public Function rbGetId(ticker As String)
On Error GoTo rbGetId_Error
Dim currCell As Range
'tried using Application.Caller direct, but gives same error
Set currCell = Range(Application.Caller.Address)
If currCell.id = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.id = Str(gNextUniqueId)
End If
rbGetId = ticker & currCell.id
Exit Function
rbGetId_Error:
rbGetId = "!ERROR:" & Err.Description
End Function
But this fails at the line mentioned with
"Application-defined or object-defined error"
I thought perhaps its one of those limitations of UDFs, but I also get the same error if I try it from code triggered from a ribbon button...
Any other suggestions on how to keep consistent ids - perhaps I should populate the cells via my ribbon button, finding cells without IDs and generating/setting the cell value of those...
EDIT:
As Ant thought, I have the sheet protected, but even in an unlocked cell it still fails. Unprotecting the sheet fixes the problem.... but I have used "Protect UserInterFaceOnly:=True" which should allow me to do this. If I manually allow "Edit Objects" when I protect the sheet it also works, but I don't see a programmatic option for that - and I need to call the Protect function in AutoOpen to enable the UserInterfaceOnly feature...
I guess I need to turn off/on protect around my ID setting - assuming that can be done in a UDF... which it seems it cannot, as that does not work - neither ActiveSheet.unprotect nor ActiveWorkbook.unprotect :(
Thanks in advance.
Chris
Okay...
It does appear that if the sheet is locked, macros do not have write access to low-level information such as ID.
However, I do not think it is possible to unprotect the sheet within a UDF. By design, UDFs are heavily restricted; I think having a cell formula control the sheet protection would break the formula paradigm that a cell formula affects a cell only.
See this page on the Microsoft website for more details.
I think this limits your options. You must either:
give up sheet protection
give up the UDF, use a Worksheet_Change event to capture cell changes and write to ID there
use a UDF that writes the ID into the cell value, rather than save to ID
The UDF approach is fraught with problems as you are trying to use something designed for calculation of a cell to make a permanent mark on the sheet.
Nonetheless, here's an example of a UDF you can use to stamp a "permanent" value onto a cell, which works on unlocked cells of a protected sheet. This one only works for single cells (although it could be adapted for an array formula).
Public Function CellMark()
Dim currCell As Range
Set currCell = Range(Application.Caller.Address)
Dim myId As String
' must be text; using .value will cause the formula to be called again
' and create a circular reference
myId = currCell.Text
If (Trim(myId) = "" Or Trim(myId) = "0") Then
myId = "ID-" & Format(CStr(gNextUniqueId), "00000")
gNextUniqueId = gNextUniqueId + 1
End If
CellMark = myId
End Function
This is quite flawed though. Using copy or the fillbox will, however, retain the previous copied value. Only by explicitly setting cells to be a new formula will it work. But if you enter in the formula into the cell again (just click it, hit ENTER) a new value is calculated - which is standard cell behaviour.
I think the Worksheet_Change event is the way to go, which has much more latitude. Here's a simple example that updates the ID of any cell changes. It could be tailored to your particular scenario. This function would need to be added to every Worksheet the ID setting behaviour is required on.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim currCell As Range
Set currCell = Target.Cells(1, 1)
Dim currId As String
currId = currCell.ID
If Trim(currCell.ID) = "" Then
Target.Parent.Unprotect
currCell.ID = CStr(gNextUniqueId)
Target.Parent.Protect
gNextUniqueId = gNextUniqueId + 1
End If
End Sub
Last note; in all cases, your ID counter will be reset if you re-open the worksheet (at least under the limited details presented in your example).
Hope this helps.
Concur with Ant - your code works fine here on Excel 2003 SP3.
I've also been able to use:
Set currCell = Application.Caller
If Application.Caller.ID = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.ID = Str(gNextUniqueId)
End If
Aha! I think I have it.
I think you're calling this from an array formula, and it only gets called ONCE with the full range. You can't obtain an ID for a range - only a single cell. This explains why Application.Caller.ID fails for you, because Range("A1:B9").ID generates an Application-defined or object-defined error.
When you use Range(Application.Caller.Address) to get the "cell" you just defer this error down to the currCell.ID line.
I think we may have a few issues going on here, but I think they are testing issues, not problems with the code itself. First, if you call the function from anything other than a Cell, like the immediate window, other code, etc. Application.Caller will not be set. This is what is generating your object not found errors. Second, if you copy/paste the cell that has the function, they you will by copy/pasting the ID too. So wherever you paste it to, the output will stay the same. But if you just copy the text (instead of the cell), and then paste then this will work fine. (Including your original use of Application.Caller.)
The problem is with Application.Caller.
Since you are calling it from a user defined function it is going to pass you an error description. Here is the remark in the Help file.
Remarks
This property returns information about how Visual Basic was called, as shown in the following table.
Caller - Return value
A custom function entered in a single cell - A Range object specifying that cell
A custom function that is part of an array formula in a range of cells - A Range object specifying that range of cells
An Auto_Open, Auto_Close, Auto_Activate, or Auto_Deactivate macro - The name of the document as text
A macro set by either the OnDoubleClick or OnEntry property - The name of the chart object identifier or cell reference (if applicable) to which the macro applies
The Macro dialog box (Tools menu), or any caller not described above - The #REF! error value
Since you are calling it from a user defined function, what is happening is Application.Caller is returning a String of an error code to your range variable curCell. It is NOT causing an error which your error handler would pick up. What happens after that is you reference curCell, it's not actually a range anymore. On my machine it tries setting curCell = Range("Error 2023"). Whatever that object is, it might not have an ID attribute anymore and when you try to set it, it's throwing you that object error.
Here's what I would try...
Try removing your error handler and see if VBA throws up any exceptions on Range(Application.Caller.Address). This won't fix it, but it could point you in the right direction.
Either through logic or Application.ActiveCell or however you want to do it, reference the cell directly. For example Range("A1") or Cells(1,1). Application.Caller.Address just doesn't seem like a good option to use.
Try using Option Explicit. This might make the line where you set curCell throw up an error since Range(Application.Caller.Address) doesn't look like it's passing a range back, which is curCell's datatype.
I have found that if I protect the sheet with "Protect DrawingObjects:=False", the UDF can set the Id. Strange.
Thanks for all the help with this.