Отсутствует сообщение компилятора VBA для неправильного имени метода
Рассмотрим следующий код:
Public Sub VBACompilerIsMad()
Dim Ap As Application
Dim Wb As Workbook
Dim Ws As Worksheet
Debug.Print Ap.XXX ' No compile error
Debug.Print Wb.XXX ' No compile error
Debug.Print Ws.XXX ' Compile error
End Sub
Когда я компилирую это, я получаю ошибку компилятора для ссылки на несуществующий член Worksheet
, Однако, если я закомментирую последнюю строку, ошибки компилятора не будет, даже если Application
ни Workbook
есть метод или свойство XXX
, Это как если бы я объявил Ap
а также Wb
как Object
переменные.
Почему компилятор лечит Application
/ Workbook
в отличие от Worksheet
?
Есть ли какие-либо другие классы, подобные этому, которые компилятор, кажется, обрабатывает так, как будто они Object
?
4 ответа
Как я уже объяснил (слава соответственно), это функция COM.
По умолчанию COM предполагает, что интерфейс является расширяемым, то есть он позволяет добавлять элементы во время выполнения. Если это не желаемое поведение, можно применить [nonextensible]
атрибут определения интерфейса, который объявляет, что интерфейс принимает только методы, явно определенные в библиотеке типов.
dispinterface _Application
а также dispinterface _Workbook
не устанавливайте этот флаг в библиотеке типов Excel, dispinterface _Worksheet
делает.
Точно так же ADO dispinterface _Connection
не имеет [nonextensible]
, dispinterface _Command
делает.
Чтобы узнать, какие являются расширяемыми, добавьте ссылку на TypeLib Info
в Референции проекта и запустите:
Dim t As tli.TLIApplication
Set t = New tli.TLIApplication
Dim ti As tli.TypeLibInfo
Set ti = t.TypeLibInfoFromFile("excel.exe")
Dim i As tli.InterfaceInfo
For Each i In ti.Interfaces
If (i.AttributeMask And tli.TYPEFLAG_FNONEXTENSIBLE) <> tli.TYPEFLAG_FNONEXTENSIBLE Then
Debug.Print i.Name
End If
Next
Вы увидите, что почти все интерфейсы здесь расширяемы, поэтому большинство из них выталкиваются из окна отладки, и вы увидите только последние. Изменить <>
в =
чтобы напечатать те, которые не являются расширяемыми, их намного меньше.
Немного гипотеза:
Вы можете вызвать хранимую процедуру для объекта ADODB.Connection, как собственный метод (внизу).
(Примеры для этого на нескольких сайтах MSDN выглядят странно запутанными).
Таким образом, в VBS/VBA есть некоторый механизм, например "анонимные / динамические методы". Это может быть аналогичный механизм, активированный здесь для Application
а также Workbook
занятия - хотя я не вижу, где и как именно.
Тест поддерживает основную идею:
Я проверил это со ссылкой на Microsoft ActiveX Data Objects 2.8 Library
:
Public Sub testCompiler()
Dim cn As ADODB.Connection
Dim cmd As ADODB.Command
Debug.Print cn.XXX
Debug.Print cmd.XXX
End Sub
cn.XXX
не выдает ошибку компиляции, cmd.XXX
делает.
Ответ GSerg действительно выдающийся, мне нравится весь IDL библиотеки типов COM и то, как некоторые атрибуты могут управлять поведением в Excel VBA IDE. Долго может быть передано это тайное знание COM! И я понимаю, что этот вопрос был задуман, чтобы дать этому ответу больше репутации, но когда щедрость установлена, она появляется на моем радаре, и у меня есть взгляд на этот вопрос.
Таким образом, хотя ответ GSerg дает механизм, он не дает обоснования, то есть он дает как, но не почему. Я попытаюсь ответить почему.
Некоторые из ответов, почему Мартин Роллер (OP) уже дает свои комментарии о Application
а также WorksheetFunction
, Для меня это убедительная причина сохранить Application
расширяемый и я не буду рассматривать Application
в дальнейшем.
Давайте обратимся к Workbook
а также Worksheet
и лучше всего начать с некоторого кода для демонстрации, поэтому вам нужно начать с двух свежих рабочих книг, называть их MyWorkbook.xlsm
а также OtherWorkbook.xlsm
, Итак, некоторые инструкции:
В OtherWorkbook.xlsm
иди код модуля ThisWorkbook
и вставьте код
Option Explicit
Public Function SomeFunctionExportedOffOtherWorkbook() As String
SomeFunctionExportedOffOtherWorkbook = "Hello Matt's Mug!"
End Function
В MyWorkbook.xlsm
иди Sheet1
модуль кода и вставьте код
Option Explicit
Public Function SomeFunctionExportedOffCodeBehindSheet1() As String
SomeFunctionExportedOffCodeBehindSheet1 = "Hello Martin Roller!"
End Function
Теперь в VBA IDE измените кодовое имя Sheet1
в codebehindSheet1
Теперь в новом стандартном модуле MyWorkbook.xlsm
добавьте следующий код
Sub TestingObjectLikeInterfacesOfWorkbookAndCodeBehindWorksheet_RunMany()
'* For this example please rename the 'CodeName' for Sheet1 to be "codebehindSheet1" using the IDE
Debug.Assert ThisWorkbook.Worksheets.Item("Sheet1").CodeName = "codebehindSheet1"
Dim wb As Workbook
Set wb = Application.Workbooks.Item("OtherWorkbook")
'* Workbook dispinterface needs to not marked with nonextensible attribute
'* so that it doesn't trip up over exported function in another workbook
'* below SomeFunctionExportedOffOtherWorkbook is defined in the ThisWorkbook module of the workbook "OtherWorkbook.xlsm"
Debug.Print wb.SomeFunctionExportedOffOtherWorkbook
'*Not allowed --> Dim foo As Sheet1
'*have to call by the 'code behind' name which is usually Sheet1 but which we changed to illustrate the point
Debug.Print codebehindSheet1.SomeFunctionExportedOffCodeBehindSheet1
End Sub
Теперь запустите этот код выше.
Вы, наверное, прочитали код и, надеюсь, поняли смысл, который я изложил, но позвольте мне изложить его. Нам нужно Workbook
оставаться расширяемым, потому что он может содержать ссылку на другую книгу, которая может экспортировать метод или функцию, и мы не хотели бы ошибок компиляции.
Однако для Worksheet
Чтобы сделать аналогичный экспорт, мы снова добавляем код в код позади модуля, но есть разница в ссылке на модуль: один захватывает ссылку на этот код позади модуля, используя его кодовое имя VBA, большинство людей не изменяют это с Sheet1 (именно поэтому вы были приглашены изменить его выше).
Таким образом, интерфейс, полученный с помощью кода за именем модуля, должен быть расширяемым, а не интерфейс Excel.Worksheet.
PS Кто-нибудь получил копию TLI.dll?
В качестве обходного пути все еще можно создать свой собственный interface
и реализовать этот интерфейс. Затем объявите переменную как INewInterface
и все сообщения компилятора будут там:). Вот простой пример с пользовательским интерфейсом для UserForm
, НТН
Интерфейс
Public CancelButton As MSForms.CommandButton
Public DataList As MSForms.ListBox
Public CommandBox As MSForms.TextBox
Реализация
Implements IMyForm
Private Property Set IMyForm_CancelButton(ByVal RHS As MSForms.ICommandButton)
End Property
Private Property Get IMyForm_CancelButton() As MSForms.ICommandButton
End Property
Private Property Set IMyForm_CommandBox(ByVal RHS As MSForms.IMdcText)
End Property
Private Property Get IMyForm_CommandBox() As MSForms.IMdcText
End Property
Private Property Set IMyForm_DataList(ByVal RHS As MSForms.IMdcList)
End Property
Private Property Get IMyForm_DataList() As MSForms.IMdcList
End Property
использование
Замечания: MyForm
существует форма VBA, которая была добавлена в проект.