简体   繁体   English

如何通过VBA获取/设置Excel中单元格的唯一ID

[英]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. 我希望为Excel数据表中的每个数据行设置/定义一个唯一的ID - 这样我可以在向前传递数据时使用它,并且当在其上面添加/删除行时它保持不变。

My thoughts are to use the ID attribute of Range ( msdn link ) 我的想法是使用Range的ID属性( msdn link

So, I have a user defined function (UDF) which I place in each row that gets/sets the ID as follows: 所以,我有一个用户定义的函数(UDF),我放在每一行获取/设置ID,如下所示:

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... 我想也许它是UDF的那些限制之一,但如果我从带状按钮触发的代码中尝试它,我也会得到同样的错误...

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... 关于如何保持一致id的任何其他建议 - 也许我应该通过我的功能区按钮填充单元格,找到没有ID的单元格并生成/设置那些单元格的值...

EDIT: As Ant thought, I have the sheet protected, but even in an unlocked cell it still fails. 编辑:正如Ant想的那样,我保护了表单,但即使在未锁定的单元格中,它仍然会失败。 Unprotecting the sheet fixes the problem.... but I have used "Protect UserInterFaceOnly:=True" which should allow me to do this. 取消保护工作表修复了问题....但我使用了“保护UserInterFaceOnly:= True”这应该允许我这样做。 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... 如果我在保护工作表时手动允许“编辑对象”它也可以工作,但我没有看到编程选项 - 我需要调用AutoOpen中的Protect功能来启用UserInterfaceOnly功能...

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 :( 我想我需要在我的ID设置周围关闭/开启保护 - 假设可以在UDF中完成...它似乎不能,因为它不起作用 - 既不是ActiveSheet.unprotect也不是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. 看起来如果工作表被锁定,宏就没有对ID等低级信息的写访问权。

However, I do not think it is possible to unprotect the sheet within a UDF. 但是,我认为不可能在UDF中取消保护表单。 By design, UDFs are heavily restricted; 根据设计,UDF受到严格限制; 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. 有关详细信息,请参阅Microsoft网站上的此页面

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 放弃UDF,使用Worksheet_Change事件捕获单元格更改并在那里写入ID
  • use a UDF that writes the ID into the cell value, rather than save to ID 使用UDF将ID写入单元格值,而不是保存到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. UDF方法充满了问题,因为您正在尝试使用专门用于计算单元格的东西来在工作表上创建永久性标记。

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. 尽管如此,这是一个UDF的示例,您可以使用它将“永久”值标记到单元格上,该单元格可以在受保护工作表的未锁定单元格上运行。 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. 但是如果再次将公式输入到单元格中(只需单击它,按Enter键),就会计算出一个新值 - 这是标准的单元格行为。

I think the Worksheet_Change event is the way to go, which has much more latitude. 我认为Worksheet_Change事件是要走的路,它有更多的自由度。 Here's a simple example that updates the ID of any cell changes. 这是一个更新任何单元格更改ID的简单示例。 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. 需要将此函数添加到需要ID设置行为的每个工作表中。

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). 在所有情况下,如果您重新打开工作表,将重置您的ID计数器(至少在您的示例中提供的有限详细信息下)。

Hope this helps. 希望这可以帮助。

Concur with Ant - your code works fine here on Excel 2003 SP3. 与Ant一致 - 您的代码在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. 我认为你是从一个数组公式中调用它,它只被称为ONCE的全范围。 You can't obtain an ID for a range - only a single cell. 您无法获取范围的ID - 只有一个单元格。 This explains why Application.Caller.ID fails for you, because Range("A1:B9").ID generates an Application-defined or object-defined error . 这解释了为什么Application.Caller.ID失败,因为Range(“A1:B9”)。ID生成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. 当您使用Range(Application.Caller.Address)获取“单元格”时,您只需将此错误推迟到currCell.ID行。

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. 首先,如果从Cell以外的任何其他函数调用该函数,如立即窗口,其他代码等,将不会设置Application.Caller。 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. 其次,如果您复制/粘贴具有该功能的单元格,您也可以复制/粘贴该ID。 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.) (包括您对Application.Caller的原始使用。)

The problem is with Application.Caller. 问题出在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. 此属性返回有关如何调用Visual Basic的信息,如下表所示。

Caller - Return value 来电者 - 返回值

  • A custom function entered in a single cell - A Range object specifying that cell 在单个单元格中输入的自定义函数 - 指定该单元格的Range对象
  • A custom function that is part of an array formula in a range of cells - A Range object specifying that range of cells 自定义函数,它是一系列单元格中数组公式的一部分 - 指定该单元格范围的Range对象
  • An Auto_Open, Auto_Close, Auto_Activate, or Auto_Deactivate macro - The name of the document as text Auto_Open,Auto_Close,Auto_Activate或Auto_Deactivate宏 - 文档的名称为文本
  • 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 由OnDoubleClick或OnEntry属性设置的宏 - 宏应用的图表对象标识符或单元格引用(如果适用)的名称
  • The Macro dialog box (Tools menu), or any caller not described above - The #REF! 宏对话框(工具菜单),或上面未描述的任何调用者 - #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. 由于您是从用户定义的函数调用它,所以正在发生的事情是Application.Caller将错误代码的String返回到您的范围变量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. 之后会发生什么,你引用了curCell,它实际上不再是一个范围了。 On my machine it tries setting curCell = Range("Error 2023"). 在我的机器上,它尝试设置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. 无论该对象是什么,它可能不再具有ID属性,当您尝试设置它时,它会抛出该对象错误。

Here's what I would try... 这是我会尝试的...

  1. Try removing your error handler and see if VBA throws up any exceptions on Range(Application.Caller.Address). 尝试删除错误处理程序,看看VBA是否会抛出Range(Application.Caller.Address)上的任何异常。 This won't fix it, but it could point you in the right direction. 这不会解决它,但它可以指向正确的方向。

  2. Either through logic or Application.ActiveCell or however you want to do it, reference the cell directly. 无论是通过逻辑还是Application.ActiveCell,或者您想要这样做,直接引用单元格。 For example Range("A1") or Cells(1,1). 例如Range(“A1”)或Cells(1,1)。 Application.Caller.Address just doesn't seem like a good option to use. Application.Caller.Address似乎不是一个很好的选择。

  3. Try using Option Explicit. 尝试使用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. 这可能会使你设置curCell的行抛出一个错误,因为Range(Application.Caller.Address)看起来不像是传回一个范围,这是curCell的数据类型。

I have found that if I protect the sheet with "Protect DrawingObjects:=False", the UDF can set the Id. 我发现如果用“Protect DrawingObjects:= False”保护工作表,UDF可以设置Id。 Strange. 奇怪。

Thanks for all the help with this. 感谢您的帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM