That's right, execution of user-defined functions has certain limitations. There is few tricks to do what you want within UDF and not to violate an order.
1. Get another instance of Excel.Application via late binding, open workbook with it, perform all the necessary calculations by referring to the instance. It's critical to refer exactly to the created instance, so that some nested With ... End With
statements or additional syntax e. g. before .Cells()
, .Sheets()
may seem unusual. There is an UDF example how to obtain the first line on the sheet from the closed file:
Function GetFirstRowLbind(FileName, SheetName) ' UDF function that calculates value, works with certain limitations
On Error Resume Next
With CreateObject("Excel.Application") ' late binding
.Workbooks.Open (FileName)
GetFirstRowLbind = .Sheets(SheetName).UsedRange.Row
.Quit
End With
End Function
OERN
used only to skip errors like missing file and others so that .Quit
statement be surely executed to prevent memory leakage, otherwise launched excel processes will stay in memory after each sheet recalc and UDF call.
2. Implement some UDF extending by scheduling to another procedure the actions that should be done after the UDF completion, with execution based on the sheet recalc event. This way more complex and hard to debug, but more flexible and it gives opportunity to do much more "within" UDF, like changing neighbour cells, or even any accessible data in the whole application. Example with scheduling:
Place code to one of the module of VBAProject:
Public Tasks, Permit, Transfer
Function GetFirstRowSched(FileName, SheetName) ' UDF function, schedules filling this UDF cell with a value after all UDFs to be completed
If IsEmpty(Tasks) Then TasksInit
If Permit Then Tasks.Add Application.Caller, Array(FileName, SheetName) ' pack arguments to array, the dictionary key is actually this cell object
GetFirstRowSched = Transfer
End Function
Sub TasksInit() ' function for initial setting values
Set Tasks = CreateObject("Scripting.Dictionary")
Transfer = ""
Permit = True
End Sub
Function GetFirstRowConv(FileName, SheetName) ' function that actually calculates the value, runs without UDF limitations like an usual function
With Application.Workbooks.Open(FileName)
GetFirstRowConv = .Sheets(SheetName).UsedRange.Row
.Close
End With
End Function
Place code to ThisWorkbook section of Microsoft Excel Objects in VBAProject:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object) ' sheets recalc event that perform all scheduled calls, puts data to each of UDFs cells
Dim Task, TempFormula
If IsEmpty(Tasks) Then TasksInit
Application.EnableEvents = False
Permit = False
For Each Task In Tasks ' cycle trough all stored cell objects
TempFormula = Task.FormulaR1C1
Transfer = GetFirstRowConv(Tasks(Task)(0), Tasks(Task)(1)) ' unpack arguments from array to perform calculations
Task.FormulaR1C1 = TempFormula
Tasks.Remove Task
Next
Application.EnableEvents = True
Transfer = ""
Permit = True
End Sub