简体   繁体   中英

Excel VBA UDF Executes in Immediate Window, Fails on Worksheet

UDF "NAV()" is designed to find the correct report on a network drive based on the first argument (always a date), then loop through all worksheets to find a piece of data with the same row as second argument and same column as third argument (second and third can be text or numbers).

Works reliably in the immediate window. Always returns #VALUE! when used on worksheet, eg =NAV(D7,D8,D9) or =NAV(2/19/2016,"Net Asset Value","221-I").

In general it looks like one could get this behaviour if trying to alter other cells in a UDF, but my functions don't do that. Also, I believe all range references specify which workbook and worksheet, so I don't think that is the problem either. I'm not sure where to look next.

Function also attempts to email me a report through Outlook when it fails to find what the user is looking for. I don't know if that is relevant.

Again, what is perplexing is that this code seems to work fine in the immediate window, but only gives #VALUE! when used on a worksheet.

Where else can I look in my code below to determine what would cause NAV() to function correctly in the immediate window, but always yield #VALUE! when used on a worksheet?

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

You can Open Workbooks within a Worksheet_Change Event.

For demonstration, if a change in Sheet1!A2 , Excel will try open the workbook name with that cell value, then Output the status to Sheet1!A4 .

Put below in a Module:

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

Then below in Worksheet Module (I used Sheet1 for demonstration):

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

So this idea is to open the Workbook only if some cell address is matched.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM