当另一个工作簿处于活动状态时,UDF 引用命名表错误

Posted

技术标签:

【中文标题】当另一个工作簿处于活动状态时,UDF 引用命名表错误【英文标题】:UDF referring to named table errors when another workbook is active 【发布时间】:2017-05-15 15:52:27 【问题描述】:

为了清楚起见,现在已确定解决方案

我是构建 UDF 的 VBA 新手。这个 UDF 涉及到一些 vlookup 函数,这些函数引用了工作簿中其他工作表上的 excel 表格,例如:

Twirecol = Application.WorksheetFunction.VLookup(i, Range("iterationtable"), 2, False)

问题是,如果另一个工作簿处于活动状态,则当 excel 重新计算公式时会返回 #VALUE 错误。

我看到了很多关于如何在 VBA 和 UDF 中引用其他工作簿和工作表的解决方案,但我不知道如何适当地确定这些表对象的范围,以便它们专注于存在 UDF 的工作簿。请注意,我正在寻找一种依赖于工作表名称或工作簿文件名或路径的解决方案,因为所有这些都可能随着时间而改变。

这是我为这个工作簿命名的经理:Names Manager

这是整个 UDF 代码:

Public Function voltagedrop(trenchlength As Integer, intlength As Integer) As String
Application.Volatile

Dim TLX As Integer
Dim ILX As Integer
Dim TVD As Single
Dim IVD As Single
Dim VD As Single
Dim Twirecol As Integer
Dim Iwirecol As Integer
Dim i As Integer


' Extended length variables account for extra length at end of strings
TLX = trenchlength + 10
ILX = intlength + 10

i = 0

Do
i = i + 1
    Twirecol = Application.WorksheetFunction.VLookup(i, Range("iterationtable"), 2, False)
    Iwirecol = Application.WorksheetFunction.VLookup(i, Range("iterationtable"), 3, False)

    ' Calculate voltage drops
    TVD = Application.WorksheetFunction.VLookup(TLX, Range("trenchtable"), Twirecol, False)
    IVD = Application.WorksheetFunction.VLookup(ILX, Range("inttable"), Iwirecol, False)
    VD = TVD + IVD

Loop Until VD < 0.025

VD = 100 * Round(VD, 4)
voltagedrop = Application.WorksheetFunction.VLookup(i, Range("iterationtable"), 4, False) & ": " & VD & "%"    

End Function

解决方案(感谢@DavidZemens)

(*大卫的完整答案如下,这是我的总结)

如果这是一个传统的命名范围,而不是一个表,我可以这样调用范围:

Twirecol = Application.WorksheetFunction.VLookup(i, ThisWorkbook.Names("iterationtable").RefersToRange, 2, False)

但是,由于表的行为与命名范围不同(尽管在名称管理器中显示类似),我需要这样调用范围:

Twirecol = Application.WorksheetFunction.VLookup(i, ThisWorkbook.Worksheets("Background Tables").ListObjects("iterationtable").Range, 2, False)

但是,我的理想解决方案完全避免命名工作表,以防工作表名称将来更改,因此证明我可以改用工作表CodeName(在我的情况下为sheet1):

Twirecol = Application.WorksheetFunction.VLookup(i, Sheet1.ListObjects("iterationtable").Range, 2, False)

为简单起见,我直接在此示例代码中确定了范围,但根据 David 的建议,我的最终代码确实为范围设置了一个变量。

【问题讨论】:

函数的其余部分是什么?虽然是的,但你最后的猜测是正确的。您应该在范围之前添加工作表(和/或工作簿,如果您有多个打开),即...Vlookup(i, worksheets("Sheet1").Range("iterationtable"),... 谢谢@BruceWayne。编辑原始帖子以包含整个代码。工作簿处于活动状态时效果很好。 ...thisworkbook.range("iterationtable") 不起作用...相反,它给了我一个“编译错误:找不到方法或数据成员”。 另外,关于工作表名称,我试图使这个函数尽可能通用,以便未来对 VBA 不太熟悉的同事的修改不会意外破坏它,包括避免引用命名范围所在的工作表。考虑到范围已命名,是否有理由识别工作表? 是命名范围、工作簿还是工作表范围?查看名称管理器。如果它们是工作簿范围,它应该可以工作,如果不是,那么您将需要命名工作表。 请指定名称的范围是工作表还是工作簿。这可能会有所作为。还请更新您已完成的任何其他故障排除,以及您的“名称管理器”的屏幕截图,清楚地显示这样的名称确实存在,以及它所指的内容。 【参考方案1】:

我希望有一个优雅的解决方案,例如...

thisworkbook.range("iterationtable")

虽然这不起作用。

Range 不是Workbook 类的属性,它是Worksheet 类的属性。

但是,可以通过 Names 集合访问命名范围。如果Name 的范围是工作簿(我认为这是默认设置),您应该可以通过ThisWorkbook.Names("iterationtable").RefersToRange 访问它。

如果Name 被限定为特定工作表,那么您将需要改为使用ThisWorkbook.__WORKSHEET__.Names("iterationtable")...,其中__WORKSHEET__ 是包含工作表。


上面是假设这是一个Name 对象,但在查看Matt 的屏幕截图后,很明显这实际上不是Name,而是ListObject(表格)。 :

虽然它们出现在名称管理器中,并且可以类似地访问命名范围,即:

MsgBox ActiveSheet.Range("table1").Address ' etc...

它们不是 Names 集合的成员(它们实际上是 ListObjects 集合的一部分),因此尝试像下面这样调用它们会引发 1004 错误:

MsgBox ActiveSheet.Names("table1").Address

要解决此问题,您需要完全限定Range 对象,即:

ThisWorkbook.Worksheets("Background Tables").Range("iterationtable")

或者:

ThisWorkbook.Worksheets("Background Tables").ListObjects("iterationtable").Range

Range("iterationtable") 有时起作用的原因(即,当它是 ActiveSheet 时,有据可查且正常,预期功能范围不正确的标识符:Range总是 引用到任何工作表在运行时活动,除非另有明确范围。

This 是关于如何避免臭名昭著的 1004 错误的一个很好的入门书,但它归结为上述内容:适当地确定对象的范围,并理想地使用 变量 来表示它们。

Dim wsTables as Worksheet
Dim iterTable As Range
Dim trenchTable as Range
Dim intTable as Range

Set wsTables = ThisWorkbook.Worksheets("Background Tables") 'Declare the worksheet
With wsTables
    'Assigns each table's Range to a Range object variable:
    Set iterTable = .ListObjects("iterationtable").Range
    Set trenchTable = .ListObjects("Trench Table").Range
    Set intTable = .ListObjects("Int Table").Range
End With

With Application.WorksheetFunction
    Do
        i = i + 1
        Twirecol = .VLookup(i, iterTable, 2, False)
        Iwirecol = .VLookup(i, iterTable, 3, False)
        ' Calculate voltage drops
        TVD = .VLookup(TLX, trenchTable, Twirecol, False)
        IVD = .VLookup(ILX, iterTable, Iwirecol, False)
        VD = TVD + IVD
    Loop Until VD < 0.025

    VD = 100 * Round(VD, 4)
    voltagedrop = .VLookup(i, iterTable, 4, False) & ": " & VD & "%"
End With

最后:

包括避免引用命名范围所在的工作表。

好的,所以如果用户将工作表名称从“Background Tables”更改为其他任何名称,上述代码仍然会失败。有一些方法可以防止这种情况发生,例如锁定工作表以进行编辑和/或隐藏工作表(假设用户也不需要向工作表输入数据等),或者通过 CodeName 而不是 Name 引用工作表。这利用了when referred to by CodeName, the Worksheet is always implicitly a part of ThisWorkbook 的事实。要查找工作表的CodeName,它位于“项目”窗格的括号中:

在上述解决方案中,您将更改此行:

Set wsTables = ThisWorkbook.Worksheets("Background Tables") 'Declare the worksheet

收件人:

Set wsTables = Sheet2 '<~~ The CodeName goes here, modify as needed!

虽然工作表的 CodeName 是读/写的(意味着精明的用户可以更改它,但他们需要通过 VBA 或手动通过 VBE 进行更改,因此这在大多数情况下)。

【讨论】:

谢谢大卫,这正是我要找的。但是,我正在努力实施它。当我用thisworkbook.names("iterationtable").RefersToRange 替换我的range("iterationtable") 时,我得到一个VALUE 错误。我尝试制作一个测试宏来查看问题是否出在其他地方:Dim range1 as range // set range1 = thisworkbook.names("iterationtable").ReferstoRange // msgbox range1 但这也失败了。我想我忽略了一些愚蠢的事情? 请更新您的问题,包括您尝试的故障排除。在 cmets 中阅读代码很困难,而且当您将代码放入 cmets 时,唯一可能看到额外细节的人是我,这(显着)限制了可能能够帮助您的人数。 如果这在测试过程中失败,那么ThisWorkbook 对象中似乎不存在这样的范围。 当然。在我最初发布它的形式中,当工作簿在 15x20 单元矩阵中处于活动状态时,它工作得很好,计算常见可能的组合导线运行的电压降。唯一的问题是,当另一个工作簿处于活动状态时,excel 重新计算时会出错。 感谢您的帮助。您的解决方案运行良好,并且在此过程中我学会了一些重新设置变量的好习惯。两件事:(1)在您的回答中,您说CodeName 是括号中的部分,但实际上它似乎是括号之前的部分。 (2) 只需识别代号sheet1 就可以完美地工作,但我很好奇为什么当另一个工作簿处于活动状态时它会起作用,因为基本上每个工作簿都包含一个代号为sheet1 的工作表。为什么不必指定工作簿,如ThisWorkbook.Worksheets("Background Tables")【参考方案2】:

您是否尝试使用工作簿名称和工作表对工作簿进行所有调用?仅使用 "Range("whatever")" 将导致程序查看当前处于活动状态的任何工作簿的活动工作表。

尝试将调用更改为这种格式:

Twirecol = Application.WorksheetFunction.VLookup(i, CorrectWorkbookName.CorrectWorksheetName.Range("iterationtable"), 2, False)

您将“CorrectWorkbookName”更改为具有命名范围的书籍的名称,并将“CorrectWorksheetName”更改为包含命名范围的工作表的名称。

【讨论】:

仅当命名范围在工作表范围内时才成立。如果命名范围在工作簿范围内,则不应命名工作表。【参考方案3】:

或者也许事先激活正确的工作簿

Set wb = ThisWorkbook
wb.Activate
TVD = Application.WorksheetFunction.VLookup(TLX, Range("trenchtable"), Twirecol, False)

【讨论】:

谢谢约翰,命名范围仅限于工作簿,我试图避免在 UDF 中命名工作簿或工作表,以防将来看到名称更改。目标是在工作簿未处于活动状态时保持 UDF 正常工作 - 换句话说,当我在另一台监视器上打开第二个文档并重新计算某些内容时,我需要此 UDF 不会因 VALUE 错误而重新计算。 social.msdn.microsoft.com/Forums/office/en-US/…

以上是关于当另一个工作簿处于活动状态时,UDF 引用命名表错误的主要内容,如果未能解决你的问题,请参考以下文章

请参阅其他工作簿中的 UDF 参数说明

在 UDF 中使用命名范围

当另一个活动处于画中画模式时,有啥方法可以杀死一个活动?

Excel UDF 在已关闭的工作簿中引用表以进行查找

UDF 在工作簿中创建指向位置的超链接

另一个工作簿中的更改会影响主工作簿中的 UDF