How to lock clipboard or avoid using it in MS Excel? - vba

I'm writing a program under Excel that generate a lot of spreadsheets by copying / pasting some data from one worksheet to another (example: using a "Layout" worksheet with some header / footer cells which will be copied/pasted to the generated worksheets).
My problem is that, some times (not every time), when running my "generation process", Excel generate this error (sorry this is an English translation from my french Excel error) :
Error 1004 : The 'Paste' method of the '_Worksheet' object has failed
So I'm assuming that there is a problem with the Clipboard (with other software on my computer which probably used the clipboard at the same time :/)
I firstly try to find a way to copy/paste my cells (and other stuff) without using the clipboard, with code like that :
ThisWorkbook.Sheets("Layout").Range("A1").Copy Destination:=ThisWorkbook.Sheets("Test").Range("A1")
or that
ThisWorkbook.Sheets("Test").Range("A1") = ThisWorkbook.Sheets("Layout").Range("A1")
But it seems that we can only copy text or formula, not all the stuff (border, color,...) and also not Chart object (I have ones)!
So I try to find a way to lock/unlock the clipboard during the copy/paste.
I found this code to do that:
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function CloseClipboard Lib "user32" () As Long
Declare Function EmptyClipboard Lib "user32" () As Long
Public Sub Lockk()
If OpenClipboard(0) = 0 Then
MsgBox "cannot open clipboard."
ElseIf EmptyClipboard() = 0 Then
MsgBox "cannot clear clipboard."
End If
End Sub
Public Sub Unlockk()
CloseClipboard
End Sub
It seems working when copying cells: I can lock the clipboard under excel, go to another software (notepad for example), and can't copy paste some data into this software; go back to excel and I can copy/paste data (manually or with a macro).
But:
It seems that pasting a cell will unlock the clipboard (I can lock, go to notepad, notepad has not access to the clipboard, go back to excel, copy/paste a cell, go back to notepad, and then the notepad can access to the clipboard; and I have not unlock explicitly the clipboard). That is not really a problem for me.
After locking the clipboard, we can't copy/past a Chart object (manually or with a macro). With a macro, I get exactly the same error as before).
So, is someone as an idea about how to lock/unlock the clipboard to copy chart object? Or to copy them without using the clipboard?
Edit:
The code used to copy/paste the graph object:
Utils_Clipboard.Lockk
ThisWorkbook.Sheets("Layout").ChartObjects("CHART_TEMPLATE").Copy
DoEvents
worksheet_p.Paste Destination:=where_p
Utils_Clipboard.Unlockk
where worksheet_p is a Worksheet object, adn where_p is a range.
Note that without the first and last lines (Lockk the clipboard), it's working fine (except some time).

Ok I've found a solution (maybe not the best one?)
I can use my Utils_Clipboard.Lockk and Utils_Clipboard.Unlockk to ensure that the clipboard will not be used by another software when duplicating (copy/paste) cell, merged cells,...
But it seems that, when the clipboard is locked, we can't copy/paste chart object (manualy by pressing ctrl+c and ctrl+v keys; or automatically in vba with the metod Copy and Past of objects).
My solution for chart object is to use the functions Duplicate (http://msdn.microsoft.com/en-us/library/office/ff840956.aspx) and Move (http://msdn.microsoft.com/en-us/library/office/ff840583.aspx) like this (where worksheet_p is the Worksheet where I want to put the chartobject) :
Utils_Clipboard.Lockk
Dim newchart_l As Shape
Set newchart_l = ThisWorkbook.Sheets("Layout").ChartObjects("CHART_TEMPLATE").Duplicate
newchart_l.Chart.Location xlLocationAsObject, worksheet_p.Name
Utils_Clipboard.Unlockk
Note that the duplicated object is a Shape not a ChartObject (which will make an error when executing the code if we type as a ChartObject).
So ok it's working (and I think there is no need to lock the clipboard here), but the chart object is not where I want (at the correct top/left coordinates on worksheet_p). To do that, I found that we must move the ChartArea (the parent of the duplicated object), but we can't manipulate the "newchart_l" directly (it seems that Excel don't update all its internal variables after the call of Duplicate, why???). So my solution is to firstly retrieve the new duplicate chart object, with this :
Dim ChartObject_m As Chart
Set ChartObject_m = worksheet_p.ChartObjects(worksheet_p.ChartObjects.Count).Chart
And then move the chartarea of that object (where 'where_p' is the range/cell where I want to put my charobject) :
ChartObject_m.ChartArea.Left = where_p.Left
ChartObject_m.ChartArea.Top = where_p.Top
Et voila!

Inspired by your solution I have a improved piece of code that I wanted to share. The improvement is, that it does not rely on assumptions like "The order of charts in the ChartObjects-Collection reflects the order of insertion":
Private Function copyGraph(source As ChartObject, pos As Range) As ChartObject
' Copies a given graph to the given position and returns the resulting
' ChartObject. The destination position is expected to be a cell in the desired
' target worksheet. The resulting ChartObject will be aligned to the Top/Left-
' Border of the given cell.
Dim dup As Object
Dim dstChart As Chart
' First just duplicate the graph. This operation leaves it on the original
' worksheet.
Set dup = source.Duplicate
' In case the duplication failed, ...
If (Not dup.HasChart) Then
' ... we remove the duplicated object and leave the copy function.
' This yields a Nothing-reference as return value to signal the error
' to the caller.
dup.Delete
set copyGraph = Nothing
Exit Function
End If
' Then we move the graph to the target worksheet passed as parameter. This
' gives us the new link to the moved chart.
'
' Excel displays some weired behavior when using the reference returned by ChartObject.Duplicate.
' Namely it yields sporadic 1004 runtime errors without further specification. However it seems,
' that activating the chart and calling location for ActiveChart gets around this problem.
'
' Therefor the original code:
' Set dstChart = dup.Chart.Location(xlLocationAsObject, pos.Parent.Name)
' has been replaced with the following
dup.Chart.parent.Activate
Set dstChart = ActiveChart.Location(xlLocationAsObject, pos.Parent.Name)
' As we relocated the chart as an object, the parent of the chart object is
' an instance of ChartObject. Hence we use it as the return value.
Set copyGraph = dstChart.parent
' Finally we move the graph to the requested position passed by the pos
' parameter.
With copyGraph
.Top = pos.Top
.Left = pos.Left
End With
End Function
I hope this helps other users looking for a simple solution on this issue.

Related

VBA is skipping code when a userform reference is reached? Attempting to write to another wbk

I'm using a userform to collect data and add it to an empty line in a workbook.
Structure of code is as follows:
Main sub s_OpenWriteToTargetFile is called from userform mainForm.
It checks availability of the target workbook.
It opens the target workbook.
It calls sub "s_WriteLines". Everything is OK up to this point.
Sub s_WriteLines should load textbox values from mainForm into various variables and paste them into the target workbook.
For some reason, code execution jumps out of s_WriteLines as soon as it reaches With MainForm..., and it returns to the mother sub.
s_WriteLines sub
Sub s_WriteLines
Dim a,b as integer
With mainForm
a = .tb_a.Value
b = .tb_b.Value
End With
End Sub
I can't wrap my head around it. Does this have something to do with the modality of the userform?
As AcsErno suggested in the comments, there was a on error resume next that I didn't notice, and it kept me from learning that the form is failing to load a rowsource property of a combobox.
The rowsource was specified as follows:
mainForm.cb_Wiresize.RowSource = wiresizesWSheet.Name & "!" & wiresizesFinalRange.Address
The workbook that is opened to be written in also becomes active, and then the range that I specified as rowsource refers to a worksheet that doesn't exist - because I specified it only as "worksheet + range", instead of "workbook + worksheet + range".
To expand on my question, how can I refer to the specific workbook object using the syntax posted above? I tried different formulations but none worked.

Error in Code to Paste / Copy Shapes from Excel to PowerPoint VBA

I created a code in VBA to copy shapes from a sheet in Excel to an existing PowerPoint file, these shapes are added to existing slides. In Excel I have a table that indicates which slide and in which position to paste each shape (I named all shapes so that I can reference them by name). My code loops through this table to get the shape and paste it to the designated slide. This is my code:
Dim nombrePic As String, auxi As String, i As Integer
Dim PPT As Object
Dim PPSlide As PowerPoint.Slide
Dim numSlide As Integer, posVertical As Double, posHorizontal As Double
'Abrir PPT
Set PPT = CreateObject("PowerPoint.Application")
PPT.Visible = True
PPT.Presentations.Open "c:\prueba.pptx"
auxi = Sheets("FigurasResumen_RangPD").Cells(2, 5)
i = 2
Do
Sheets("FigurasResumen_RangPD").Shapes(auxi).Copy
'Get paste information about shape
numSlide = Sheets("FigurasResumen_RangPD").Cells(i, 8)
posHorizontal = Sheets("FigurasResumen_RangPD").Cells(i, 9)
posVertical = Sheets("FigurasResumen_RangPD").Cells(i, 10)
PPT.ActiveWindow.View.GotoSlide (numSlide)
PPT.ActiveWindow.View.PasteSpecial DataType:=ppPasteDefault
PPT.ActiveWindow.Selection.ShapeRange.Left = Format(Application.CentimetersToPoints(posHorizontal), "0.00")
PPT.ActiveWindow.Selection.ShapeRange.Top = Format(Application.CentimetersToPoints(posVertical), "0.00")
PPT.ActiveWindow.Selection.ShapeRange.ZOrder msoSendToBack
i = i + 1
auxi = Sheets("FigurasResumen_RangPD").Cells(i, 5)
Loop Until auxi = ""
End Sub
When I first tried it, it work fine. I created a sample PPT and everything went great.
Once I changed the path to use the official PPT (which weights around 120MB) it shows the following error:
View.PasteSpecial : Invalid Request.
Clipboard is empty or contains data which may not be pasted here.
[Answer replaced: I wrote an answer yesterday but I think this one is better.]
VBA sees "copy" and "paste" operations as unrelated to each other. It also executes asynchronously - it starts executing an instruction, and then it may move on to executing the next one while the previous instruction is still being executed. It shouldn't do so if the second instruction depends on the first, but "paste" just takes ANYTHING from the clipboard, it is not aware of how things got on the clipboard. In particular, it doesn't relate specifically to the "copy" command right before it. Then, copying to the clipboard is a slow process; program execution gets to "paste" before anything is on the clipboard (or only partial, incomplete data is on it).
There are two ways to address the problem. One is to add a Sleep xxx instruction before the paste command. xxx is a number of milliseconds - and for Sleep to work, you need to declare it properly at the top of your module (it is a Windows function, not a VBA function). Google "VBA Sleep function" or something similar. With this solution, you have to experiment with xxx, sometimes 2 ms will suffice and sometimes 50 ms will not be enough. Not a good solution.
The better solution in your case would be to declare a Shape variable, assign to it the shape you were copying, and then add it to your target slide. This should work, because the shape (saved as the Shape variable) cannot be added to the new slide until the variable assignment completes. ASSUMING, of course, that VBA has a method for adding a shape to a slide...

VBA Error 1004 - PasteSpecial method of Range class

Recently I started getting the error 1004: PasteSpecial method of Range class failed. I know people have said before that it might be that it is trying to paste to the active sheet when there is none, however, as you can see, everything is based on ThisWorkbook so that shouldn't be the problem. It happens extra much when Excel doesn't have the focus.
'references the Microsoft Forms Object Library
Sub SetGlobals()
Set hwb = ThisWorkbook' home workbook
Set mws = hwb.Worksheets("Code Numbers") ' main worksheet
Set hws = hwb.Worksheets("Sheet3") ' home worksheet (Scratch pad)
Set sws = hwb.Worksheets("Status") ' Status sheet
Set aws = hwb.Worksheets("Addresses") ' Addresses sheet
End Sub
Sub Import()
Call SetGlobals
hws.Select
'a bunch of code to do other stuff here.
For Each itm In itms
Set mitm = itm
body = Replace(mitm.HTMLBody, "<img border=""0"" src=""http://www.simplevoicecenter.com/images/svc_st_logo.jpg"">", "")
Call Buf.SetText(body)
Call Buf.PutInClipboard
Call hws.Cells(k, 1).Select
Call hws.Cells(k, 1).PasteSpecial
For Each shape In hws.Shapes
shape.Delete
Next shape
'Some code to set the value of k
'and do a bunch of other stuff.
Next itm
End Sub
Update: mitm and itm have two different types, so I did it for intellisense and who knows what else. This code takes a list of emails and pastes them into excel so that excel parses the html (which contains tables) and pastes it directly into excel. Thus the data goes directly into the sheet and I can sort it and parse it and whatever else I want.
I guess I'm basically asking for anyone who knows another way to do this besides putting it in an html file to post it. Thanks
This probably will not exactly answer your problem - but I noticed a few things in your source code that are too long to place in a comment, so here it is. Some of it is certainly because you omitted it for the example, but I'll mention it anyway, just in case:
Use Option Explicit - this will avoid a lot of errors as it forces you to declare every variable
Call SetGlobals can be simplified to SetGlobals - same for Call Buf.SetText(body) = Bof.SetText Body, etc.
No need to '.Select' anything - your accessing everything directly through the worksheet/range/shape objects (which is best practice), so don't select (hws.Select, hws.Cells(k,1).Select)
Why Set mitm = itm? mitm will therefore be the same object as itm - so you can simply use itm
You're deleteing all shapes in hwsmultiple times - for each element in itms. However, once is enough, so move the delete loop outside of the For Each loop
Instead of putting something in the clipboard and then pasting it to a cell, just assign it directly: hws.Cells(k, 1).Value = body - this should solve your error!
Instead of using global variables for worksheets that you assign in 'SetGlobals', simply use the sheet objects provided by Excel natively: If you look at the right window in the VBE with the project tree, you see worksheet nodes Sheet1 (sheetname), Sheet2 (sheetname), etc.. You can rename these objects - go to their properties (F4) and change it to meaningful names - or your current names (hwb, mws, ...) if you want. Then you can access them throughout your code without any assignment! And it'll work later, even if you change the name of Sheet3to something meaningful! ;-)
Thus, taking it all into account, I end up with the following code, doing the same thing:
Option Explicit
Sub Import()
'a bunch of code to do other stuff here.
For Each shape In hws.Shapes
shape.Delete
Next shape
For Each itm In itms
Call hws.Cells(k, 1) = Replace(itm.HTMLBody, "<img border=""0"" src=""http://www.simplevoicecenter.com/images/svc_st_logo.jpg"">", "")
'Some code to set the value of k
'and do a bunch of other stuff.
Next itm
End Sub

Display custom document property value in Excel 2007 worksheet cell

I've created a program that creates and populates a custom document property in an Excel 2007 workbook file. However I haven't been able to show the value of this property in a worksheet cell. In Word 2007 you can just select "Insert -> Quick Parts -> Field..." and use the DocProperty field to show the value of the custom field in a document. However I haven't found a similar function in Excel 2007.
Does anybody know how to display the value of a custom document property in an Excel worksheet cell? I would prefer a solution similar to the Word 2007 solution mentioned above. I rather not use a macro/custom code for this.
Unfortunately I believe you need to use an user defined function. Add a new VBA module to your workbook and add this function:
Function DocumentProperty(Property As String)
Application.Volatile
On Error GoTo NoDocumentPropertyDefined
DocumentProperty = ActiveWorkbook.BuiltinDocumentProperties(Property)
Exit Function
NoDocumentPropertyDefined:
DocumentProperty = CVErr(xlErrValue)
End Function
The call to Application.Volatile forces the cell to be updated on each recalculation ensuring that it will pick up changes in the document properties.
The equivalent in Excel would be via formula and I don't think it's possible to extract a document property without code. There are no native functions to pick out document properties. (An alternative could be to store information in workbook/worksheet Names, which ARE accessible via formula)
In VBA you'd have to create a function something like:
Public Function CustomProperty(ByVal prop As String)
CustomProperty = ActiveWorkbook.CustomDocumentProperties(prop)
End Function
and then call it in a formula with =CustomProperties("PropertyName").
There is another subtle point. Formula dependencies only relate to other cells; this formula depends on a custom property. If you update the custom property a pre-existing formula involving CustomProperty will not be updated automatically. The cell will have to be re-evaluated manually or the entire workbook forced through a recalc. Your best chance would be to make the function volatile, which means the formula would be recalc'd on every cell change -- but this still means you only get an update if a cell has been changed.
Select the cell you want to extract
Rename the cell to some useful. From "B1" to "Project_Number".
Open "Advance Properties" click the "Custom" tab. Enter a name for the new property. click "Link to content" the select the cell name from the "Value" pull down list.
I wish i could take cerdit but I found the answer online:
http://pdmadmin.com/2012/03/displaying-custom-property-values-in-excel-using-a-named-range/
You can link a named range to a custom property, but then the custom property reflects the value of the [first cell in the] range. It's effectively read-only; you can change the content of the cell to update the property, but not the other way around.
I know you want to avoid it, but if you want to use the property value in a formula, you'll have to create a custom worksheet function to do so.
I have experienced the same issues other people have. So I will try to comprehensively cover how I addressed it.
First of all, you have no other option than writing a function meant to get whatever you put in a custom or built-in property and make the "problem" cell to point at it this way:
=yourPropertyGettingFunctionName(PropertyName)
PropertyName being a string referring to the name of the custom/built-in property whose value you want to be shown in the cell.
The function could be written (as formerly suggested) as:
Public Function StdProp(ByVal sPropName As String) As String
Application.Volatile
StdProp = ActiveWorkbook.BuiltinDocumentProperties(sPropName).Value
End Function
for a built-in property, or as:
Public Function UsrProp(ByVal sPropName As String) As String
Application.Volatile
On Error GoTo UndefinedProp
UsrProp = ActiveWorkbook.CustomDocumentProperties(sPropName)
GoTo Exit
UndefinedProp:
UsrProp = "n/a"
Exit:
End Function
As already mentioned, including Application.Volatile will allow for a semi-automatic cell contents update.
However, this poses a problem on its own: whenever you open your Excel file, all the cells using such a relationship will get updated and, by the time you exit the file, Excel will ask you for your permission to update it, no matter if you did introduce any change on it or not, because Excel itself did.
In my development group, we use SubVersion as a version control system. In case you inadvertently hit "update" on exit, SVN will notice it and next time you want to commit your changes, the excel file will be included in the pack.
So I decided to use everything at hand to do whatever I needed and avoid, at the same time, this self-update effect I didn't want.
That means using named ranges in combination with property accessing function/s.
Given the fact I can't expect old files to have provision for my new needs, I wrote this function:
Private Function RangeAssign(sRange As String, sValue As String) As Integer
Dim rDest As Range
If RangeCheck(sRange) Then
Set rDest = Range(sRange)
Else
Set rDest = Application.InputBox(sMsg + vbCrLf + vbCrLf + _
"Please, select a cell to get" + vbCrLf + _
"the name " + sRange + " assigned", sCopyRight, Type:=8)
rDest.Name = sRange
End If
rDest.Cells(1, 1).NumberFormat = "#"
rDest.Cells(1, 1).Value = sValue
RangeAssign = True
End Function
It allows for a proper selection of the destination cell. When assigning values to a property (let's say "Author", which happens to be a built-in one), I also update the value stored in the named range, and can write in a cell:
=Author
if I happen to have defined a range named "Author" and filled its "A1" cell with the value for built-in property "Author", which I need to have updated for our own external tracking purposes.
This all didn't happen overnight. I hope it can be of some help.
I used this for extracting the SharePoint properties (based on Martin's answer):
Public Function DocumentProperty(Property As String)
Application.Volatile
On Error GoTo NoDocumentPropertyDefined
DocumentProperty = ActiveWorkbook.ContentTypeProperties(Property).Value
Exit Function
NoDocumentPropertyDefined:
DocumentProperty = CVErr(xlErrValue)
End Function

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.