为啥 VBA TypeOf 运算符会失败
Posted
技术标签:
【中文标题】为啥 VBA TypeOf 运算符会失败【英文标题】:Why would VBA TypeOf operator fail为什么 VBA TypeOf 运算符会失败 【发布时间】:2013-09-01 06:44:05 【问题描述】:我这几天一直在与 Excel 2007 问题作斗争。下面列出了我能想到的所有可能相关的事实:
IDetailSheet 是一个在 VBA 项目中声明的类,具有多种方法,并且在其类初始化器中引发错误,因此无法实例化(使其抽象)。
Option Explicit在所有模块中设置。
VBA 项目中的十个工作表实现 IDetailSheet 并干净地编译(整个项目也是如此)。
CDetailSheets 是在 VBA 项目中声明的一个类,它包装了一个 Collection 对象并将 Collection 对象公开为 IDetailSheet 的一个 Collection。它还公开了一些额外的方法来对所有集合成员执行 IDetailSheet 的某些方法。
在其 Class 初始化程序(从 Workbook_Open 事件处理程序调用并分配给全局变量)中,CDetailSheet 执行以下代码填充私有集合 DetailSheets:
Dim sht as EXCEL.WorkSheet
For Each sht in ActiveWorkbook.Worksheets
If TypeOf sht is IDetailSheet Then
Dim DetailSheet as IDetailSheet
Set DetailSheet = sht
DetailSheets.Add DetailSheet, DetailSheet.Name
End If
Next sht
在某些功能区回调中,会运行以下代码:
If TypeOf ActiveWorkbook.ActiveSheet is IDetailSheet Then
Dim DetailSheet as IDetailSheet
Set DetailSheet = ActiveWorkbook.ActiveSheet
DetailSheet.Refresh *[correction]*
End If
在确定存在其他稳定性问题(最初有几十个)之后,已从工作簿中删除所有 ActiveX 控件。已创建 Fluent 界面功能区来替换最初与 ActiveX 控件关联的功能。
企业模板中有一个 Hyperion 加载项,但本工作簿中没有使用它。
说到底,运行工作簿时出现以下症状:
TypeOf Is 在 CDetailSheets 初始化程序中识别任意数量的 IDetailSheet 实例,从 1(最常见)到偶尔的 2 或 3。永远不会为零,永远不会超过 3,而且肯定永远不会全部 10 个可用。 (并不总是同一个,尽管靠近片场的前面似乎会增加被认出的可能性。) 在 CDetailSheets 初始化程序中发现的任何 IDetailSheet 实现实例(据我所知,只有这样的实例)在功能区回调中也被 TypeOf ... Is 识别。谁能解释为什么大多数 TypeOf ... Is 操作都失败了?或者如何解决这个问题?
我已经求助于手动创建 v-tables(即大丑 Select Case ... End Select 语句)来使功能正常工作,但实际上我发现在旁边有我的名字相当尴尬这样的代码。除此之外,我可以看到这是未来维护的噩梦。
考虑到这可能是一个过时的 p 代码问题,我从扩展的 XLSM zip 中删除了 Project.Bin 文件,然后手动将所有 VBA 代码重新导入。没有任何变化。我还尝试将项目名称添加到 IDetailSheet 的所有用法中以使其成为 miFab.IDetailSheet,但再次无济于事。 (miFab 是项目名称。)
【问题讨论】:
您的问题中是否有Foreach
和EndIf
拼写错误?
是的,但在 VBA 中它将是 For Each
和 End If
。
此blog entry 建议将Implements
与Worksheet
对象一起使用会导致不稳定
这当然是一个有趣的问题,而且我以前从未遇到过。如果有任何方法可以重构您的 VBA 应用程序以使用封装而不是继承,您可以定义一堆不扩展/实现任何内容的裸用户定义类型或类,并将它们声明为每个中的私有字段工作表,具有所需的属性/方法/功能来跟踪您需要的数据。这很丑,但它可能工作,因为问题似乎专门针对实现接口的工作表。
不是真的...?在您的情况下,“VBA 项目中的十个工作表实现了 IDetailSheet”。我不是这里唯一认为特别是在 Worksheet 对象上使用继承/接口是问题的原因的评论者。这就是为什么我建议您创建一个实现 IDetailSheet
的每张普通旧类模块,并将 those 放入您的集合中。您可以通过多种方式编写代码以确定哪个IDetailSheet
实例属于哪个工作表,而无需使用TypeOf
或Select ... End Select
。
【参考方案1】:
您可以通过多种方式使用 CallByName 作弊。您将不得不以一种或另一种方式解决这个错误。
一个简单的脏例子
每个以实施行开头的工作表都应该有一个公共的 GetType 函数。 我将“TestSheet”子附加到功能区上的按钮上。它将返回的类型名称放在单元格 A1 中以演示功能。
模块1
'--- Start Module1 ---
Option Explicit
Public Sub TestSheet()
Dim obj As Object
Set obj = ActiveSheet
ActiveSheet.[A1] = GetType(obj)
End Sub
Public Function GetType(obj As Object) As String
Dim returnValue As String
returnValue = TypeName(obj)
On Error Resume Next
returnValue = CallByName(obj, "GetType", VbMethod)
Err.Clear
On Error GoTo 0
GetType = returnValue
End Function
'--- End Module1 ---
表 1
'--- Start Sheet1 ---
Implements Class1
Option Explicit
Public Function Class1_TestFunction()
End Function
Public Function GetType() As String
GetType = "Class1"
End Function
'--- End Sheet1 ---
【讨论】:
我以为 VBA 有一个 CallByNae 函数,但上周我找不到它。不过我现在有了;谢谢。 不客气。我认为您可能可以通过 CallByName 函数完成所有工作,但这可能会令人讨厌且难以编写。这样,您仍然可以将工作表转换为相关接口,或者如果implements
导致其他问题,则检索包装对象。
我知道这已经足够了,并且比我上周构建的不那么混乱解决方法,但由于某种原因我当时找不到它。
【参考方案2】:
如果您不信任 TypeOf
,请继续努力并忽略错误:
Dim sht as EXCEL.WorkSheet
For Each sht in ActiveWorkbook.Worksheets
'If TypeOf sht is IDetailSheet Then
Dim DetailSheet As IDetailSheet
On Error Resume Next
Set DetailSheet = sht
On Error GoTo 0
If Not DetailSheet Is Nothing Then
DetailSheets.Add DetailSheet, DetailSheet.Name
End If
Next sht
如果这个不起作用,至少在那个时候工作表真的不是IDetailSheet
。
【讨论】:
这是我尝试的第一个解决方法;写原帖前几天。我最好的猜测是 STA 中存在某种时间问题/竞争条件。【参考方案3】:我在发布与TypeOf fails to work with Excel workbook's ActiveSheet that implements interface类似的问题后发现了这个问题
我没有明确的解释,但我认为我有一个解决方法。
我怀疑这是因为 [代码] 在 Sheet1 或 Chart 上实现了一个接口,并且正在扩展 Sheet1/Chart1,但 Sheet1 已经在扩展 Worksheet(并且 Chart1 已经在扩展 Chart)。
在我的测试中,我可以通过首先访问工作表的属性来强制 VBA 返回 TypeOf
的实际值。这意味着,做一些丑陋的事情,比如:
'Explicitly access ThisWorkbook.ActiveSheet.Name before using TypeOf
If TypeOf ThisWorkbook.Sheets(ThisWorkbook.ActiveSheet.Name) Is PublicInterface Then
【讨论】:
以上是关于为啥 VBA TypeOf 运算符会失败的主要内容,如果未能解决你的问题,请参考以下文章