Отсутствует сообщение компилятора 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
?
Ответы
Ответ 1
Как мне объяснили (kudos go соответственно), это 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
Вы увидите, что здесь почти все интерфейсы расширяемы, поэтому большинство из них вытесняются из окна отладки, и вы увидите только последние. Измените <>
на =
, чтобы напечатать те, которые не расширяемы, их гораздо меньше.
Ответ 2
Немного гипотезы:
Вы можете вызвать хранимую процедуру на объекте 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
делает.
Ответ 3
Ответ 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 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.
P.S. Кто-нибудь получил копию TLI.dll?
Ответ 4
В качестве обходного решения все же можно создать собственный 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, добавленная в проект.