Подтвердить что ты не робот

Отсутствует сообщение компилятора 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?

4b9b3361

Ответ 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, добавленная в проект.