Excel VBA UDF выполняется в немедленном окне, сбой на рабочем листе

UDF "NAV()" предназначен для поиска правильного отчета на сетевом диске на основе первого аргумента (всегда дата), а затем перебирает все рабочие листы, чтобы найти фрагмент данных с той же строкой, что и второй аргумент, и тем же столбцом, что и третий аргумент (второй и третий могут быть текстом или числами).

Надежно работает в ближайшем окне. Всегда возвращает #ЗНАЧ! при использовании на листе, например. =СЧА(D7,D8,D9) или =СЧА(19.02.2016, "Стоимость чистых активов", "221-I").

В целом похоже, что такое поведение можно получить, если попытаться изменить другие ячейки в UDF, но мои функции этого не делают. Кроме того, я считаю, что все ссылки на диапазоны указывают, какая книга и рабочий лист, поэтому я не думаю, что это проблема. Я не уверен, где искать дальше.

Функция также пытается отправить мне отчет по электронной почте через Outlook, когда не может найти то, что ищет пользователь. Не знаю, актуально ли это.

Опять же, что вызывает недоумение, так это то, что этот код, кажется, отлично работает в непосредственном окне, но дает только #ЗНАЧ! при использовании на рабочем листе.

Где еще я могу посмотреть в своем коде ниже, чтобы определить, что заставит NAV() правильно работать в непосредственном окне, но всегда дает #VALUE! при использовании на листе?

Option Explicit

Function NAV(ByVal NAVDate As Date, ByVal matchRow As Variant, ByVal matchColumn As Variant) As Variant
'Application.ScreenUpdating = False
Application.Volatile True

    NAV = FindItemOnWorksheet(NAVDate, matchRow, matchColumn)

'Application.ScreenUpdating = True
End Function


Function FindItemOnWorksheet(ByVal NAVDate As Date, ByVal ItemSpecies As Variant, ByVal ItemGenus As Variant) As Variant
' Finds Item by opening NAV workbook with correct date, activating correct worksheet, and searching for correct row and column
Dim startingRange As Range
Dim ws As Worksheet
Dim wb As Workbook
Dim theDate As Date
Dim theItemSpecies As String
Dim theItemGenus As String

theDate = NAVDate
theItemSpecies = ItemSpecies
theItemGenus = ItemGenus

Set wb = GetWB(NAVDate)

'Loop through ws
Dim WS_Count As Integer
Dim i As Integer

WS_Count = wb.Worksheets.Count

For i = 1 To WS_Count


    Set ws = wb.Worksheets(i)
    Set startingRange = ws.Range("A1:Z100")

    Dim theRow As Range
    Dim theColumn As Range

    Set theRow = startingRange.Cells.Find(theItemSpecies, SearchDirection:=xlPrevious, lookat:=xlWhole)
    If Not (theRow Is Nothing) Then
        Set theColumn = startingRange.Cells.Find(theItemGenus, SearchDirection:=xlPrevious, lookat:=xlWhole)
        If Not (theColumn Is Nothing) Then
            FindItemOnWorksheet = ws.Cells(theRow.Row, theColumn.Column).Value

            wb.Close
            Exit Function
        End If
   End If

Next i
'Loop if no hit on either row or column Find()

'following executes only if no match found
MsgBox "No Match Found. Make sure you are entering arguments--" & vbNewLine & _
        "       The Date of NAV, " & vbNewLine & _
        "       the entry found in the right row of NAV workbooks (e.g. 'Net Asset Value'), " & vbNewLine & _
        "       the right column (e.g. 'Fund')." & vbNewLine & _
        " This function will only find exact matches." & vbNewLine & vbNewLine & _
        "Now emailing developer to ask for a fix."

Dim OutApp As Object
Dim OutMail As Object
Dim strbody As String

Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)

strbody = "User attempted" & _
                "=FindItemOnWorksheet( " & theDate & ", " & theItemSpecies & ", " & theItemGenus & " )" & vbNewLine & _
                "theDate type " & TypeName(theDate) & vbNewLine & _
                "theItemSpecies type " & TypeName(theItemSpecies) & vbNewLine & _
                "theItemGenus type " & TypeName(theItemGenus)

On Error Resume Next
With OutMail
    .To = <Address Removed>
    .CC = ""
    .BCC = ""
    .Subject = "FindItemOnWorksheet Error"
    .Body = strbody
    '.Attachments.Add ("C:\file.xlsx")
    .Send
End With
On Error GoTo 0

Set OutMail = Nothing
Set OutApp = Nothing

FindItemOnWorksheet = "Error"
'wb.Close
Exit Function
End Function


Function GetWB(ByVal NAVDate As Date) As Workbook
'Open requested workbook, return to parent procedure

Dim wbPath As String
Dim wbYear As String
Dim wbMonth As String

Dim wbWeek As String

Dim wbFile As String
Dim wbString As String
Dim wb As Workbook
Dim BackADay As Boolean

Dim OriginalNAVDateRequested As Date
OriginalNAVDateRequested = NAVDate

BackADay = True

'Loop through possible file tree structures and dates to find the closest NAV in the past to the date requested.
Do While BackADay = True

    'Don't go back to a previous week if cannot find current NAV
    If OriginalNAVDateRequested - NAVDate > 4 Then
        BackADay = False
    End If

    wbPath = <Network Path Removed>
    wbYear = CStr(Year(NAVDate)) & "\"
    wbMonth = MonthName(Month(NAVDate)) & " " & wbYear

    wbWeek = DateFormat(NAVDate) & "\"

    wbFile = Dir(wbPath & wbYear & wbMonth & wbWeek & "*Valuation Package*.xlsx")

    'Pricings with distributions have differing tree structure
    If wbFile = "" Then
        wbWeek = wbWeek & "POST Distribution " & wbWeek
        wbFile = Dir(wbPath & wbYear & wbMonth & wbWeek & "*Valuation Package*.xlsx")
        If wbFile = "" Then
            NAVDate = NAVDate - 1
        Else: BackADay = False
        End If
    Else: BackADay = False
    End If

Loop

wbString = wbPath & wbYear & wbMonth & wbWeek & wbFile

Set wb = Workbooks.Open(wbString, UpdateLinks:=False, ReadOnly:=True)
Set GetWB = wb

End Function

Function DateFormat(ByVal X As Date) As String
'Appends leading zeroes if needed to achieve form "00" for any two digit integer, and converts to string
Dim MM As String
Dim DD As String
Dim YYYY As String

If Month(X) < 10 Then
    MM = "0" & CStr(Month(X))
Else
    MM = CStr(Month(X))
End If

If Day(X) < 10 Then
    DD = "0" & CStr(Day(X))
Else
    DD = CStr(Day(X))
End If

YYYY = CStr(Year(X))

DateFormat = MM & "." & DD & "." & YYYY

End Function

person Micah Neely    schedule 25.02.2016    source источник
comment
Вы должны определить все входные аргументы как Variant.   -  person PatricK    schedule 26.02.2016
comment
Спасибо, ПатрикК. Я определил все входные аргументы как Variant, а затем попытался просто удалить все определения типов. Оба дают одинаковое поведение.   -  person Micah Neely    schedule 26.02.2016
comment
Variant тип входных данных для NAV() и FindItemOnWorksheet()? Вам нужно будет изменить код для преобразования входных данных. Попробуйте пройти через это. Я думаю, что если вы сделаете это в ближайшем окне, вы получите ту же ошибку: ?NAV(Range("D7"),Range("D8"),Range("D9"))   -  person PatricK    schedule 26.02.2016
comment
Таким образом, =NAV(D7,D8,D9) на рабочем листе по-прежнему дает «#ЗНАЧ!», но Print NAV(Range("D7"),Range("D8"),Range("D9")) в непосредственном окне дает правильный ответ, точно так же, как когда я определял три аргумента непосредственно в непосредственном окне.   -  person Micah Neely    schedule 26.02.2016
comment
Пробовали закомментировать Application.Volatile True? Open up wb и подобные вещи могут пересчитываться, пока первый расчет не завершен (попытка открыть открытый wb), а затем терпит неудачу... вообще не рекомендуется выполнять действия в UDF.   -  person Dirk Reichel    schedule 26.02.2016
comment
Также для таких случаев просто поставьте debug.print "start" в качестве первого действия и debug.print "end" в качестве последнего (также перед функцией выхода)... если есть 2 запуска друг за другом без конца, то вы зацикливаете функцию... (не происходит в прямом окне, потому что всегда выполняется только один раз)   -  person Dirk Reichel    schedule 26.02.2016
comment
Только что написал тестовый макрос и подтвердил, что функции, запущенные на листе, не могут выполнять Workbooks.Open. Возможно, вам придется попробовать другой подход. Попробуйте использовать событие Worksheet Change, а затем вызовите эту функцию. Кстати, функцию DateFormat можно заменить встроенной Format(NAVDate,"MM.DD.YYYY").   -  person PatricK    schedule 26.02.2016
comment
Функции, используемые как UDF и вызываемые из ячеек листа, имеют ряд существенных ограничений: support.microsoft. com/en-us/kb/170787   -  person Tim Williams    schedule 26.02.2016
comment
Спасибо за предложения, Дирк Рейхел и Тим Уильямс. Это, наряду со специальным тестированием PatricK того, что функции рабочего листа не могут открывать рабочие книги, заставляет меня сделать вывод, что мне нужен другой подход. Обидно, так как это затрудняет распространение и делает его менее гибким, но мне, вероятно, потребуется установить формат рабочего листа и запустить его как макрос, управляемый событиями. Смотрите ответ Патрика ниже. Я также попытаюсь обновить свой код выше, когда я его пересмотрю.   -  person Micah Neely    schedule 26.02.2016


Ответы (1)


Вы можете открывать рабочие книги в рамках события Worksheet_Change.

Для демонстрации: при изменении Sheet1!A2 Excel попытается открыть имя книги с этим значением ячейки, а затем выведет статус на Sheet1!A4.

Поместите ниже в модуль:

Option Explicit

Function TryOpenWB(ByVal oItem As Variant) As Variant
    Dim sOut As String
    Dim oWB As Workbook
    On Error Resume Next
    Set oWB = Workbooks.Open(CStr(oItem))
    If oWB Is Nothing Then
        sOut = "Cannot open """ & CStr(oItem) & """"
    Else
        sOut = "Opened """ & CStr(oItem) & """ successfully."
        'oWB.Close
    End If
    TryOpenWB = sOut
End Function

Затем ниже в модуле рабочего листа (я использовал Sheet1 для демонстрации):

Option Explicit

Private Sub Worksheet_Change(ByVal Target As Range)
    If Target.Address = Range("A2").Address Then
        Application.EnableEvents = False
        Range("A4").Value = TryOpenWB(Target)
        Application.EnableEvents = True
    End If
End Sub

Таким образом, эта идея состоит в том, чтобы открывать книгу только в том случае, если какой-либо адрес ячейки совпадает.

person PatricK    schedule 26.02.2016
comment
Спасибо, что остаетесь со мной, ПатрикК. Настоящим ключом к решению моей проблемы была просто информация о том, что функции рабочего листа не могут открывать рабочие книги, но события рабочего листа могут запускать подпрограммы, открывающие рабочие книги. Мой код будет сильно отличаться от приведенного выше; Я, вероятно, сделаю его кнопочным; Я обновлю свой первоначальный вопрос, когда он будет исправлен. - person Micah Neely; 26.02.2016