表格会干扰 VBA 范围变量,具体取决于范围
Tables interfere with VBA range variables depending on scope
Excel 文件包含 VBA 编码的用户定义函数 (UDF),这些函数部署在 table 中(VBA 列表对象)。现在,出于我不明白的原因,如果 UDF 模块包含在任何子函数或函数范围之外声明的 Range 变量,打开文件时我会收到一个非常显着的警告:"Automatic error -- Catastrophic failure".
"Catastrophic" 似乎有点夸张,因为在解除警告后,该文件似乎可以正常工作。但我仍然想了解问题所在。我已经设法用 MVC 示例复制了这个问题,如下所示。我 运行 Excel 2016(更新)在 Windows 10.
有两个 table(即 VBA 列表对象):Table 1 列表 "items" 和 Table 2 列表 "item features"(两个 table 都是通过选择数据并单击 Insert
选项卡上的 Table
生成的)。 Table 2 在字段 Item_Name
中有一个名为 ITEM_NAME()
的 UDF,即 returns 项目名称作为项目 ID 的函数,请参见屏幕截图:
函数ITEM_NAME()
本质上是对常规工作sheet函数INDEX和MATCH的包装,如下代码所示:
Option Explicit
Dim mrngItemNumber As Range
Dim mrngItemName As Range
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
Set mrngItemNumber = Sheets(1).Range("A4:A6")
Set mrngItemName = Sheets(1).Range("B4:B6")
ITEM_NAME = Application.WorksheetFunction.Index(mrngItemName, _
Application.WorksheetFunction.Match(varItemNumber, mrngItemNumber))
End Function
因此,重复一下,使用此设置,我在打开文件时 收到自动化错误 。但是当我执行以下任一操作时,错误消失了:
将声明移动到函数的范围内。这个解决方案没有吸引力,因为它需要更多的代码行,每个 UDF 一行,而且有很多。
将变量类型从Range更改为其他类型,例如Integer(这样函数显然不会工作)。
将 Table 2 转换为普通范围(即删除 table)。这也是一个不方便的解决方案,因为我真的想在我的代码中将 Table 功能用于其他目的。
从Table中删除函数ITEM_NAME()
2.(显然没有吸引人的选项..)
这是怎么回事?为什么我会收到错误消息?为什么尽管有警告,文件似乎仍能正常工作?有没有我错过的解决方法?
我怀疑这可能与 sheet 对象和列表对象的交互方式有关,但不确定。 this answer 中提供了对另一个问题的可能提示:
If you want to reference a table without using the sheet, you can use
a hack Application.Range(ListObjectName).ListObject
.
NOTE: This hack relies on the fact that Excel always creates a named range for the
table's DataBodyRange with the same name as the table.
其他地方也报告了类似的问题(在 and Microsoft Technet), but not with this particular flavor. Suggested solutions include checking for broken references or other processes running in the background, and I've done that to no avail. I can also add that it makes no difference whether the function ITEM_NAME
is entered after Table 2 is created rather than before; the only difference is that it uses structured references 处(如上面的屏幕截图所示)。
更新: 受到下面@SJR 评论的启发,我尝试了以下代码变体,其中声明了一个 ListObject 变量来存储table "Items"。请注意 Range 声明现在在函数范围内,只有 ListObject 声明在函数范围之外。此 也 生成相同的自动化错误!
Option Explicit
Dim mloItems As ListObject
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
Dim rngItemNumber As Range
Dim rngItemName As Range
Set mloItems = Sheet1.ListObjects("Items")
Set rngItemNumber = mloItems.ListColumns(1).DataBodyRange
Set rngItemName = mloItems.ListColumns(2).DataBodyRange
ITEM_NAME = Application.WorksheetFunction.Index(rngItemName, _
Application.WorksheetFunction.Match(varItemNumber, rngItemNumber))
End Function
更新 2: 现在问题似乎已经解决了,但我不知道究竟是什么原因造成的。由于没有人可以复制(即使是我的朋友在不同系统上打开同一个文件),我开始认为这是一个本地问题。我尝试修复 Excel,然后甚至从头开始重新安装完整的 Office 包。但问题仍然存在,包括用于创建上述示例的 MCV 文件和我发现问题的原始文件。
我决定尝试创建一个新版本的 MCV 示例,在 AndrewD 的 启发下,我使用 .ListObjects()
来设置范围,而不是使用 .Range()
。这确实有效。我可能会根据我的工作调整该解决方案(但请参阅我在 AndrewD 的问题下的评论,解释为什么我可能更喜欢 .Range()
。)
为了仔细检查此解决方案是否有效,我着手创建两个新文件,一个用于复制我自己的上述示例,另一个唯一的区别是切换到 ListObjects()
.在此过程中,我注意到我实际上在原始文件的代码开头缩进了 Range
声明,如下所示:
Option Explicit
Dim mrngItemNumber As Range
Dim mrngItemName As Range
Public Function ITEM_NAME(...
我没有多想,就创建了新文件,但没有缩进。因此,这将是前一个文件(以及上面给出的示例)的精确副本,但 没有 缩进。但是看啊,使用这个文件我无法复制自动化错误!检查这两个文件后,我注意到唯一的区别确实是缩进,所以我将缩进重新放回新文件中,希望它再次生成自动化错误。但是问题没有再出现。然后我从第一个文件中删除了缩进(用于创建上面的示例),现在自动化错误也从该文件中消失了。有了这个观察,我回到了我最初发现问题的真实文件,并简单地删除了那里的缩进。它奏效了。
总而言之,在删除 Range
声明的缩进后,我无法在之前生成它的三个文件中的任何一个中重新创建自动化错误。而且,即使我再次将缩进放回原位,问题也不会再次出现。但是我还是不明白为什么。
感谢所有花时间看这篇文章并分享宝贵意见的人。
好的。此解决方法 应该 有效。
如果当它出现时,有一些问题和注意事项需要解决。
我也会post解释。
在 ThisWorkbook
模块中安装代码。
代码:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim rngCell As Range
For Each rngCell In ActiveSheet.UsedRange.SpecialCells(xlCellTypeFormulas)
With rngCell
If .FormulaR1C1 Like "*ITEM_NAME*" _
And Left$(.FormulaR1C1, 4) <> "=T(""" _
Then
.Value = "=T(""" & .FormulaR1C1 & """)"
End If
End With
Next rngCell
End Sub
Private Sub Workbook_Open()
Dim rngCell As Range
For Each rngCell In ActiveSheet.UsedRange.SpecialCells(xlCellTypeFormulas)
With rngCell
If .FormulaR1C1 Like "*ITEM_NAME*" _
And Left$(.FormulaR1C1, 4) = "=T(""" _
Then
.FormulaR1C1 = .Value
End If
End With
Next rngCell
End Sub
在纯代码层面上,为什么每次设置它们时都声明模块级变量来存储范围?如果您正在缓存引用并且仅在 Nothing I could understand 时设置它们...但是您将使用 Static 来缩小范围。
我的偏好是不要打扰模块化(或local/static)变量,将 Worksheet.Name 引用替换为 Worksheet.CodeName(不太可能被改变,如果你在重命名后编译你会得到一个错误)并通过 ListObject[=23= 引用 table 范围] 和 ListColumns(以防 table 大小发生变化)。
' Returns the item name for the requested item ID.
Public Function ITEM_NAME(ByVal ItemID As Variant) As String
ITEM_NAME = Application.WorksheetFunction.Index( _
Sheet1.ListObjects("Table1").ListColumns("Item_name").DataBodyRange _
, Application.WorksheetFunction.Match( _
ItemID _
, Sheet1.ListObjects("Table1").ListColumns("Item_ID").DataBodyRange _
) _
)
End Function
但最稳健的解决方案 是避免使用 UDF 并使用 =INDEX(Table1[Item_name],MATCH([@[Item_ID]],Table1[Item_ID]))
(VLOOKUP 可能稍微快一些,但 INDEX+MATCH 更稳健)。
声明 module-level 变量只是为了在每个 UDF 中保存两行,否则将需要这样,这确实是糟糕的编码习惯。但是,如果那是你的想法,为什么不一路走下去,通过避免 [=63] 来为每个 UDF 节省 四个 行=]setting它们也都在里面!
您可以使用 pseudo-constant 函数来完成此操作,如以下代码所示:
Option Explicit
Private Function rng_ItemNumber() As Range
Set rng_ItemNumber = Sheet1.Range("A4:A6")
End Function
Private Function rng_ItemName() As Range
Set rng_ItemName = Sheet1.Range("B4:B6")
End Function
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
With Application.WorksheetFunction
ITEM_NAME = .Index(rng_ItemName, .Match(varItemNumber, rng_ItemNumber))
End With
End Function
成本当然是函数调用的开销。
如果您打算在最终设计中使用 ListObject
class,那么为什么不现在就使用它,还有 使用动态命名范围(示例中的 hard-coded 范围在那里,所以它实际上按原样工作 - 这些应该替换为命名范围):
Option Explicit
Private Function str_Table1() As String
Static sstrTable1 As String
If sstrTable1 = vbNullString Then
sstrTable1 = Sheet1.Range("A4:B6").ListObject.Name
End If
str_Table1 = sstrTable1
End Function
Private Function str_ItemNumber() As String
Static sstrItemNumber As String
If sstrItemNumber = vbNullString Then
sstrItemNumber = Sheet1.Range("A4:A6").Offset(-1).Resize(1).Value2
End If
str_ItemNumber = sstrItemNumber
End Function
Private Function str_ItemName() As String
Static sstrItemName As String
If sstrItemName = vbNullString Then
sstrItemName = Sheet1.Range("B4:B6").Offset(-1).Resize(1).Value2
End If
str_ItemName = sstrItemName
End Function
Public Function ITEM_NAME(varItemNumber As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Sheet1.ListObjects(str_Table1)
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(varItemNumber, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
一旦 logic/design 准备就绪,如果速度很关键并且您需要回收函数调用开销,您可以用同名的 module-level 常量替换函数。否则,您可以保持原样。
请注意,静态变量的使用不是必需的,但应该减少执行时间。 (也可以在第一个示例中使用静态变量,但为了简短起见,我将它们省略了。)
可能没有必要将 table 名称提取到 pseudo-constant 中,但为了完整起见,我已经这样做了。
编辑: (v2)
跟进 Egalth 的两个绝妙建议,导致以下代码避免了对命名范围的需要,甚至 hard-coded 单元格地址 ,因为我们利用了 ListObject table 本身的内置动态。
我还更改了参数名称以匹配*相关列 header 名称,因此当用户按下 Ctrl+Shift+A 出现关于使用哪一列的提示。 (此提示以及有关如何添加 Intellisense tool-tips and/or 的更多信息,如果需要,可以查看出现在“函数参数”对话框中的描述 here。)
Option Explicit
Private Function str_Table1() As String
Static sstrTable1 As String
If sstrTable1 = vbNullString Then sstrTable1 = Sheet1.ListObjects(1).Name ' or .ListObjects("Table1").Name
str_Table1 = sstrTable1
End Function
Private Function str_ItemNumber() As String
Static sstrItemNumber As String
If sstrItemNumber = vbNullString Then
sstrItemNumber = Sheet1.ListObjects(str_Table1).HeaderRowRange(1).Value2
End If
str_ItemNumber = sstrItemNumber
End Function
Private Function str_ItemName() As String
Static sstrItemName As String
If sstrItemName = vbNullString Then
sstrItemName = Sheet1.ListObjects(str_Table1).HeaderRowRange(2).Value2
End If
str_ItemName = sstrItemName
End Function
Public Function ITEM_NAME(ByRef Item_ID As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Sheet1.ListObjects(str_Table1)
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(Item_ID, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
注意 .Value2
的用法。自从我发现使用 .Value
(或依赖它作为默认 属性 时隐式类型转换导致的性能拖累和其他问题以来,我一直使用 .Value2
.
* 确保在项目的 logic/design 完成后更新代码中的列 header 名称。
编辑: (re-boot)
Re-reading 你自己对你发布的问题的评论,我注意到 this one:
I might adopt that approach eventually, but I'm still in the design process and moving columns around a lot so the index number might also change
虽然上面的最后一个示例允许动态更改 header 名称,但 moving/inserting 列更改索引,需要修改代码。
看来我们又回到了使用命名范围的状态。然而,这次我们只需要静态的指向列 headers.
事实证明,对于这个新案例,静态变量在设计阶段是一个糟糕的想法。由于列索引被缓存,插入新列会破坏 UDF,直到项目被重置。
我还从您发布的问题中引用了 sheet-less table 参考 hack 的缩短版本:
Option Explicit
Private Function str_Table1() As String
str_Table1 = Sheet1.ListObjects(1).Name
End Function
Private Function str_ItemNumber() As String
With Range(str_Table1).ListObject
str_ItemNumber = .HeaderRowRange(.Parent.Range("A3").Column - .HeaderRowRange.Column + 1).Value2
End With
End Function
Private Function str_ItemName() As String
With Range(str_Table1).ListObject
str_ItemName = .HeaderRowRange(.Parent.Range("B3").Column - .HeaderRowRange.Column + 1).Value2
End With
End Function
Public Function ITEM_NAME(ByRef Item_ID As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Range(str_Table1).ListObject
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(Item_ID, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
请注意,您不能对其中一个命名范围使用 Item_name
,因为它与 UDF 相同(忽略大小写)。我建议为您的命名范围使用尾随下划线,例如 Item_name_
。
以上所有方法也都可以解决您原来的问题。我正在等待最后的信息,以便对这个问题最初发生的原因做出有根据的猜测。
Excel 文件包含 VBA 编码的用户定义函数 (UDF),这些函数部署在 table 中(VBA 列表对象)。现在,出于我不明白的原因,如果 UDF 模块包含在任何子函数或函数范围之外声明的 Range 变量,打开文件时我会收到一个非常显着的警告:"Automatic error -- Catastrophic failure".
"Catastrophic" 似乎有点夸张,因为在解除警告后,该文件似乎可以正常工作。但我仍然想了解问题所在。我已经设法用 MVC 示例复制了这个问题,如下所示。我 运行 Excel 2016(更新)在 Windows 10.
有两个 table(即 VBA 列表对象):Table 1 列表 "items" 和 Table 2 列表 "item features"(两个 table 都是通过选择数据并单击 Insert
选项卡上的 Table
生成的)。 Table 2 在字段 Item_Name
中有一个名为 ITEM_NAME()
的 UDF,即 returns 项目名称作为项目 ID 的函数,请参见屏幕截图:
函数ITEM_NAME()
本质上是对常规工作sheet函数INDEX和MATCH的包装,如下代码所示:
Option Explicit
Dim mrngItemNumber As Range
Dim mrngItemName As Range
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
Set mrngItemNumber = Sheets(1).Range("A4:A6")
Set mrngItemName = Sheets(1).Range("B4:B6")
ITEM_NAME = Application.WorksheetFunction.Index(mrngItemName, _
Application.WorksheetFunction.Match(varItemNumber, mrngItemNumber))
End Function
因此,重复一下,使用此设置,我在打开文件时 收到自动化错误 。但是当我执行以下任一操作时,错误消失了:
将声明移动到函数的范围内。这个解决方案没有吸引力,因为它需要更多的代码行,每个 UDF 一行,而且有很多。
将变量类型从Range更改为其他类型,例如Integer(这样函数显然不会工作)。
将 Table 2 转换为普通范围(即删除 table)。这也是一个不方便的解决方案,因为我真的想在我的代码中将 Table 功能用于其他目的。
从Table中删除函数
ITEM_NAME()
2.(显然没有吸引人的选项..)
这是怎么回事?为什么我会收到错误消息?为什么尽管有警告,文件似乎仍能正常工作?有没有我错过的解决方法?
我怀疑这可能与 sheet 对象和列表对象的交互方式有关,但不确定。 this answer 中提供了对另一个问题的可能提示:
If you want to reference a table without using the sheet, you can use a hack
Application.Range(ListObjectName).ListObject
.NOTE: This hack relies on the fact that Excel always creates a named range for the table's DataBodyRange with the same name as the table.
其他地方也报告了类似的问题(在 ITEM_NAME
is entered after Table 2 is created rather than before; the only difference is that it uses structured references 处(如上面的屏幕截图所示)。
更新: 受到下面@SJR 评论的启发,我尝试了以下代码变体,其中声明了一个 ListObject 变量来存储table "Items"。请注意 Range 声明现在在函数范围内,只有 ListObject 声明在函数范围之外。此 也 生成相同的自动化错误!
Option Explicit
Dim mloItems As ListObject
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
Dim rngItemNumber As Range
Dim rngItemName As Range
Set mloItems = Sheet1.ListObjects("Items")
Set rngItemNumber = mloItems.ListColumns(1).DataBodyRange
Set rngItemName = mloItems.ListColumns(2).DataBodyRange
ITEM_NAME = Application.WorksheetFunction.Index(rngItemName, _
Application.WorksheetFunction.Match(varItemNumber, rngItemNumber))
End Function
更新 2: 现在问题似乎已经解决了,但我不知道究竟是什么原因造成的。由于没有人可以复制(即使是我的朋友在不同系统上打开同一个文件),我开始认为这是一个本地问题。我尝试修复 Excel,然后甚至从头开始重新安装完整的 Office 包。但问题仍然存在,包括用于创建上述示例的 MCV 文件和我发现问题的原始文件。
我决定尝试创建一个新版本的 MCV 示例,在 AndrewD 的 .ListObjects()
来设置范围,而不是使用 .Range()
。这确实有效。我可能会根据我的工作调整该解决方案(但请参阅我在 AndrewD 的问题下的评论,解释为什么我可能更喜欢 .Range()
。)
为了仔细检查此解决方案是否有效,我着手创建两个新文件,一个用于复制我自己的上述示例,另一个唯一的区别是切换到 ListObjects()
.在此过程中,我注意到我实际上在原始文件的代码开头缩进了 Range
声明,如下所示:
Option Explicit
Dim mrngItemNumber As Range
Dim mrngItemName As Range
Public Function ITEM_NAME(...
我没有多想,就创建了新文件,但没有缩进。因此,这将是前一个文件(以及上面给出的示例)的精确副本,但 没有 缩进。但是看啊,使用这个文件我无法复制自动化错误!检查这两个文件后,我注意到唯一的区别确实是缩进,所以我将缩进重新放回新文件中,希望它再次生成自动化错误。但是问题没有再出现。然后我从第一个文件中删除了缩进(用于创建上面的示例),现在自动化错误也从该文件中消失了。有了这个观察,我回到了我最初发现问题的真实文件,并简单地删除了那里的缩进。它奏效了。
总而言之,在删除 Range
声明的缩进后,我无法在之前生成它的三个文件中的任何一个中重新创建自动化错误。而且,即使我再次将缩进放回原位,问题也不会再次出现。但是我还是不明白为什么。
感谢所有花时间看这篇文章并分享宝贵意见的人。
好的。此解决方法 应该 有效。
如果当它出现时,有一些问题和注意事项需要解决。
我也会post解释。
在 ThisWorkbook
模块中安装代码。
代码:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim rngCell As Range
For Each rngCell In ActiveSheet.UsedRange.SpecialCells(xlCellTypeFormulas)
With rngCell
If .FormulaR1C1 Like "*ITEM_NAME*" _
And Left$(.FormulaR1C1, 4) <> "=T(""" _
Then
.Value = "=T(""" & .FormulaR1C1 & """)"
End If
End With
Next rngCell
End Sub
Private Sub Workbook_Open()
Dim rngCell As Range
For Each rngCell In ActiveSheet.UsedRange.SpecialCells(xlCellTypeFormulas)
With rngCell
If .FormulaR1C1 Like "*ITEM_NAME*" _
And Left$(.FormulaR1C1, 4) = "=T(""" _
Then
.FormulaR1C1 = .Value
End If
End With
Next rngCell
End Sub
在纯代码层面上,为什么每次设置它们时都声明模块级变量来存储范围?如果您正在缓存引用并且仅在 Nothing I could understand 时设置它们...但是您将使用 Static 来缩小范围。
我的偏好是不要打扰模块化(或local/static)变量,将 Worksheet.Name 引用替换为 Worksheet.CodeName(不太可能被改变,如果你在重命名后编译你会得到一个错误)并通过 ListObject[=23= 引用 table 范围] 和 ListColumns(以防 table 大小发生变化)。
' Returns the item name for the requested item ID.
Public Function ITEM_NAME(ByVal ItemID As Variant) As String
ITEM_NAME = Application.WorksheetFunction.Index( _
Sheet1.ListObjects("Table1").ListColumns("Item_name").DataBodyRange _
, Application.WorksheetFunction.Match( _
ItemID _
, Sheet1.ListObjects("Table1").ListColumns("Item_ID").DataBodyRange _
) _
)
End Function
但最稳健的解决方案 是避免使用 UDF 并使用 =INDEX(Table1[Item_name],MATCH([@[Item_ID]],Table1[Item_ID]))
(VLOOKUP 可能稍微快一些,但 INDEX+MATCH 更稳健)。
声明 module-level 变量只是为了在每个 UDF 中保存两行,否则将需要这样,这确实是糟糕的编码习惯。但是,如果那是你的想法,为什么不一路走下去,通过避免 [=63] 来为每个 UDF 节省 四个 行=]setting它们也都在里面!
您可以使用 pseudo-constant 函数来完成此操作,如以下代码所示:
Option Explicit
Private Function rng_ItemNumber() As Range
Set rng_ItemNumber = Sheet1.Range("A4:A6")
End Function
Private Function rng_ItemName() As Range
Set rng_ItemName = Sheet1.Range("B4:B6")
End Function
Public Function ITEM_NAME(varItemNumber As Variant) As String
' Returns Item Name as a function of Item Number.
With Application.WorksheetFunction
ITEM_NAME = .Index(rng_ItemName, .Match(varItemNumber, rng_ItemNumber))
End With
End Function
成本当然是函数调用的开销。
如果您打算在最终设计中使用 ListObject
class,那么为什么不现在就使用它,还有 使用动态命名范围(示例中的 hard-coded 范围在那里,所以它实际上按原样工作 - 这些应该替换为命名范围):
Option Explicit
Private Function str_Table1() As String
Static sstrTable1 As String
If sstrTable1 = vbNullString Then
sstrTable1 = Sheet1.Range("A4:B6").ListObject.Name
End If
str_Table1 = sstrTable1
End Function
Private Function str_ItemNumber() As String
Static sstrItemNumber As String
If sstrItemNumber = vbNullString Then
sstrItemNumber = Sheet1.Range("A4:A6").Offset(-1).Resize(1).Value2
End If
str_ItemNumber = sstrItemNumber
End Function
Private Function str_ItemName() As String
Static sstrItemName As String
If sstrItemName = vbNullString Then
sstrItemName = Sheet1.Range("B4:B6").Offset(-1).Resize(1).Value2
End If
str_ItemName = sstrItemName
End Function
Public Function ITEM_NAME(varItemNumber As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Sheet1.ListObjects(str_Table1)
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(varItemNumber, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
一旦 logic/design 准备就绪,如果速度很关键并且您需要回收函数调用开销,您可以用同名的 module-level 常量替换函数。否则,您可以保持原样。
请注意,静态变量的使用不是必需的,但应该减少执行时间。 (也可以在第一个示例中使用静态变量,但为了简短起见,我将它们省略了。)
可能没有必要将 table 名称提取到 pseudo-constant 中,但为了完整起见,我已经这样做了。
编辑: (v2)
跟进 Egalth 的两个绝妙建议,导致以下代码避免了对命名范围的需要,甚至 hard-coded 单元格地址 ,因为我们利用了 ListObject table 本身的内置动态。
我还更改了参数名称以匹配*相关列 header 名称,因此当用户按下 Ctrl+Shift+A 出现关于使用哪一列的提示。 (此提示以及有关如何添加 Intellisense tool-tips and/or 的更多信息,如果需要,可以查看出现在“函数参数”对话框中的描述 here。)
Option Explicit
Private Function str_Table1() As String
Static sstrTable1 As String
If sstrTable1 = vbNullString Then sstrTable1 = Sheet1.ListObjects(1).Name ' or .ListObjects("Table1").Name
str_Table1 = sstrTable1
End Function
Private Function str_ItemNumber() As String
Static sstrItemNumber As String
If sstrItemNumber = vbNullString Then
sstrItemNumber = Sheet1.ListObjects(str_Table1).HeaderRowRange(1).Value2
End If
str_ItemNumber = sstrItemNumber
End Function
Private Function str_ItemName() As String
Static sstrItemName As String
If sstrItemName = vbNullString Then
sstrItemName = Sheet1.ListObjects(str_Table1).HeaderRowRange(2).Value2
End If
str_ItemName = sstrItemName
End Function
Public Function ITEM_NAME(ByRef Item_ID As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Sheet1.ListObjects(str_Table1)
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(Item_ID, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
注意 .Value2
的用法。自从我发现使用 .Value
(或依赖它作为默认 属性 时隐式类型转换导致的性能拖累和其他问题以来,我一直使用 .Value2
.
* 确保在项目的 logic/design 完成后更新代码中的列 header 名称。
编辑: (re-boot)
Re-reading 你自己对你发布的问题的评论,我注意到 this one:
I might adopt that approach eventually, but I'm still in the design process and moving columns around a lot so the index number might also change
虽然上面的最后一个示例允许动态更改 header 名称,但 moving/inserting 列更改索引,需要修改代码。
看来我们又回到了使用命名范围的状态。然而,这次我们只需要静态的指向列 headers.
事实证明,对于这个新案例,静态变量在设计阶段是一个糟糕的想法。由于列索引被缓存,插入新列会破坏 UDF,直到项目被重置。
我还从您发布的问题中引用了 sheet-less table 参考 hack 的缩短版本:
Option Explicit
Private Function str_Table1() As String
str_Table1 = Sheet1.ListObjects(1).Name
End Function
Private Function str_ItemNumber() As String
With Range(str_Table1).ListObject
str_ItemNumber = .HeaderRowRange(.Parent.Range("A3").Column - .HeaderRowRange.Column + 1).Value2
End With
End Function
Private Function str_ItemName() As String
With Range(str_Table1).ListObject
str_ItemName = .HeaderRowRange(.Parent.Range("B3").Column - .HeaderRowRange.Column + 1).Value2
End With
End Function
Public Function ITEM_NAME(ByRef Item_ID As Variant) As String
'Returns Item Name as a function of Item Number.
Dim ƒ As WorksheetFunction: Set ƒ = WorksheetFunction
With Range(str_Table1).ListObject
ITEM_NAME _
= ƒ.Index _
( _
.ListColumns(str_ItemName).DataBodyRange _
, ƒ.Match(Item_ID, .ListColumns(str_ItemNumber).DataBodyRange) _
)
End With
End Function
请注意,您不能对其中一个命名范围使用 Item_name
,因为它与 UDF 相同(忽略大小写)。我建议为您的命名范围使用尾随下划线,例如 Item_name_
。
以上所有方法也都可以解决您原来的问题。我正在等待最后的信息,以便对这个问题最初发生的原因做出有根据的猜测。