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:
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.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With