VBA code running horrendously slow - excel

I have a loop that can go on for ages, although the "Enheder" worksheet only has like 10 rows, and the dataset im loadin has maybe 300 rows, it's taking a REALLY long time when I try to import.
Public Function ImportData()
Dim resultWorkbook As Workbook
Dim curWorkbook As Workbook
Dim importsheet As Worksheet
Dim debugsheet As Worksheet
Dim spgsheet As Worksheet
Dim totalposts As Integer
Dim year As String
Dim month As String
Dim week As String
Dim Hospital As String
Dim varType As String
Dim numrows As Integer
Dim Rng As Range
Dim colavg As String
Dim timer As String
Dim varKey As String
year = ImportWindow.ddYear.value
month = ImportWindow.ddMonth.value
week = "1"
varType = ImportWindow.ddType.value
Hospital = ImportWindow.txtHospital.value
Set debugsheet = ActiveWorkbook.Sheets("Data")
Set spgsheet = ActiveWorkbook.Sheets("Spørgsmål")
Set depsheet = ActiveWorkbook.Sheets("Enheder")
Set resultWorkbook = OpenWorkbook()
setResultColVars debugsheet
'set sheets
Set importsheet = resultWorkbook.Sheets("Dataset")
numrows = debugsheet.UsedRange.Rows.Count
'make sure that the enhed can be found in the importsheet, so the units can be extracted accordingly
If Not (importsheet.UsedRange.Find("afdeling") Is Nothing) Then
Dim DepColumn
Dim aCell
DepColumn = importsheet.UsedRange.Find("afdeling").column
'sort importsheet to allow meaningfull row calculations
Set aCell = importsheet.UsedRange.Columns(DepColumn)
importsheet.UsedRange.Sort Key1:=aCell, Order1:=xlAscending, Header:=xlYes
Dim tempRange As Range
Dim SecColumn
Dim secRange As Range
'find row ranges for departments
Application.ScreenUpdating = False
'**Here's the loop that will go on for aaaaaages until I decide to ctrl+pause**
For Each c In depsheet.UsedRange.Columns(1).Cells
splStr = Split(c.value, "_")
If UBound(splStr) = -1 Then
ElseIf UBound(splStr) = 0 Then
totalposts = totalposts + IterateColumns(GetRowRange(importsheet, DepColumn, splStr(0)), spgsheet, importsheet, debugsheet, year, month, week, Hospital, splStr(0), 0, varType, False)
ElseIf UBound(splStr) = 1 And Not (importsheet.UsedRange.Find("afdeling_" & splStr(0)) Is Nothing) Then
totalposts = totalposts + IterateColumns(GetRowRange(importsheet, importsheet.UsedRange.Find("afdeling_" & splStr(0)).column, splStr(1)), spgsheet, importsheet, debugsheet, year, month, week, Hospital, splStr(0), splStr(1), varType, False)
End If
Next
Application.ScreenUpdating = True
' go through columns to get total scores
totalposts = totalposts + IterateColumns(importsheet.UsedRange, spgsheet, importsheet, debugsheet, year, month, week, Hospital, 0, 0, varType, True)
resultWorkbook.Close Saved = True
ResultsWindow.lblPoster.Caption = totalposts
ImportWindow.Hide
ResultsWindow.Show
Else
MsgBox "Kunne ikke finde afdelingskolonnen. Kontroller at der er er en kolonne med navnet 'afdeling' i dit datasæt"
End If
End Function
Function GetRowRange(sheetRange, column, value) As Range
'check for a valid section column
sheetRange.AutoFilterMode = False
sheetRange.UsedRange.AutoFilter Field:=column, Criteria1:=value
Set GetRowRange = sheetRange.UsedRange.SpecialCells(xlCellTypeVisible)
sheetRange.AutoFilterMode = False
End Function
'iterates through columns of a range to get the averages based on the column headers
Function IterateColumns(varRange As Range, spgsheet, importsheet, resultsheet, year, month, week, Hospital, dep, sec, varType, sortspg As Boolean)
Dim numrows
Dim totalposts
Dim usedRng
totalposts = 0
numrows = resultsheet.UsedRange.Rows.Count
Dim insert
insert = True
If Not (varRange Is Nothing) Then
' go through columns to get scores
For i = 1 To varRange.Columns.Count
Dim tempi
tempi = numrows + totalposts + 1
Set Rng = varRange.Columns(i)
With Application.WorksheetFunction
'make sure that the values can calculate
If (.CountIf(Rng, "<3") > 0) Then
colavg = .SumIf(Rng, "<3") / .CountIf(Rng, "<3")
insert = True
Else
insert = False
End If
End With
'key is the variable
varKey = importsheet.Cells(1, i)
'only add datarow if the data matches a spg, and the datarow is not actually a department
If (sortSpgs(varKey, spgsheet, sortspg)) And (insert) And Not (InStr(key, "afdeling")) Then
resultsheet.Cells(tempi, WyearCol).value = year
resultsheet.Cells(tempi, WmonthCol).value = month
resultsheet.Cells(tempi, WweekCol).value = "1"
resultsheet.Cells(tempi, WhospCol).value = "Newport Hospital"
resultsheet.Cells(tempi, WdepCol).value = "=VLOOKUP(N" & tempi & ",Enheder!$A:$B,2,0)"
resultsheet.Cells(tempi, WsecCol).value = "=IFERROR(VLOOKUP(O" & tempi & ",Enheder!$A:$B,2,0),"" "")"
resultsheet.Cells(tempi, WdepnrCol).value = dep
resultsheet.Cells(tempi, WsecnrCol).value = dep & "_" & sec
resultsheet.Cells(tempi, WjtypeCol).value = varType
resultsheet.Cells(tempi, WspgCol).value = varKey
resultsheet.Cells(tempi, WsporgCol).value = "=VLOOKUP(H" & tempi & ",Spørgsmål!$D:$I,6,0)"
resultsheet.Cells(tempi, WtestCol).value = ""
resultsheet.Cells(tempi, Wsv1Col).value = colavg
resultsheet.Cells(tempi, Wsv2Col).value = (1 - colavg)
resultsheet.Cells(tempi, Wsv3Col).value = ""
resultsheet.Cells(tempi, WgrpCol).value = "=VLOOKUP(H" & tempi & ",Spørgsmål!$D:$I,4,0)"
totalposts = totalposts + 1
End If
Next
End If
IterateColumns = totalposts
End Function
'Function that gets the workbook for import
Function OpenWorkbook()
Dim pathString As String
Dim resultWorkbook As Workbook
pathString = Application.GetOpenFilename(fileFilter:="All Files (*.*), *.*")
' check if it's already opened
For Each wb In Workbooks
If InStr(pathString, wb.Name) > 0 Then
Set resultWorkbook = wb
Exit For
End If
Next wb
If Not found Then
Set resultWorkbook = Workbooks.Open(pathString)
End If
Set OpenWorkbook = resultWorkbook
End Function
'find column numbers for resultsheet instead of having to do this in every insert
Function setResultColVars(rsheet)
WyearCol = rsheet.UsedRange.Find("År").column
WmonthCol = rsheet.UsedRange.Find("Måned").column
WweekCol = rsheet.UsedRange.Find("Uge").column
WhospCol = rsheet.UsedRange.Find("Hospital").column
WdepCol = rsheet.UsedRange.Find("Afdeling").column
WsecCol = rsheet.UsedRange.Find("Afsnit").column
WdepnrCol = rsheet.UsedRange.Find("Afdelingsnr").column
WsecnrCol = rsheet.UsedRange.Find("Afsnitnr").column
WjtypeCol = rsheet.UsedRange.Find("Journaltype").column
WspgCol = rsheet.UsedRange.Find("spg").column
WsporgCol = rsheet.UsedRange.Find("spørgsmål").column
WtestCol = rsheet.UsedRange.Find("test").column
Wsv1Col = rsheet.UsedRange.Find("Svar 1").column
Wsv2Col = rsheet.UsedRange.Find("Svar 0").column
Wsv3Col = rsheet.UsedRange.Find("Svar 3").column
WgrpCol = rsheet.UsedRange.Find("Gruppering").column
End Function
Function sortSpgs(key, sheet, sortspg As Boolean)
If Not (sheet.UsedRange.Find(key) Is Nothing) Then
If (sortspg) Then
ResultsWindow.lstGenkendt.AddItem key
End If
sortSpgs = True
Else
If (sortspg) Then
ResultsWindow.lstUgenkendt.AddItem key
End If
sortSpgs = False
End If
End Function
Function Progress()
iProgress = iProgress + 1
Application.StatusBar = iProgress & "% Completed"
End Function

Difficult to debug without the source files.
I see the following potential problems:
GetRowRange: .UsedRange might return more columns than you expect. Check by pressing Ctrl-End in the worksheet and see where you end up
Some thing in your main routine - depsheet.UsedRange.Columns(1).Cells might just result in much more rows than expected
someRange.Value = "VLOOKUP(... will store the formula as text. You need .Formula = instead of .Value (this will not solve your long runtime but certainly avoid another bug)
In sortSpgs you add know or unknow items to a control. Not knowing if there's any event code behind these controls, disable events with Application.EnableEvents=False (ideally in the beginning of your main sub together with the .ScreenUpdating = False)
Also, set Application.Calculation = xlCalculationManual at the beginning and Application.Calculation = xlCalculationAutomatic at the end of your code
You're performing a lot of .Find - esp. in sortSpgs - this is potentially slow in large sheets, as it has to loop over quite some data, depending on the underlying range.
Generally, a few more "best practise remarks":
* Dim your variables with the correct types, same for returns of functions
* Use With obj to make the code cleaner. E.g. in setResulcolVars you could use With rsheet.UsedRange and remove this part in the following 15 or so lines
* In modules of small scope, it is okay to dim some variable with a module wide scope - esp. if you hand them over with every call. This will make your code much easier to read
Hope that helps a bit... mvh /P.

My guess is that Application.Screenupdating is the problem. You set to false inside the:
if Not (importsheet.UsedRange.Find("afdeling") Is Nothing) Then
block. So if the isn't the case then screenupdateing isn't disabled. you should move it to the beginning of the function.

you could also try to write the usedrange in an array, work with it , and write it back if needed.
code example
dim MyArr() as Variant
redim MyArray (1 to usedrange.rows.count, 1 to usedrange.columns)
MyArray=usedrange.value
'calculating with Myarray instead of ranges (faster)
usedrange.value=myarray 'writes changes back to the sheet/range
also, maybe you can use .match instead of .find, wich is faster.
with arrays you use application.match( SearchValue, Array_Name, False) 'false if for exact match
the same thing works for range.find() , becoming application.find()...
save first your master workbook under a new name before making such a big change...

Related

VBA MS Project output hours per resource per task per day to Excel

I'm trying to create an Excel spreadsheet from MS Project that will iterate through the project start to finish dates, & for each date (in ascending sequence from the start) output the amount of work hours each resource is assigned for each task that is scheduled to take place on that day, like this:
Excel output from MS Project
I've almost managed to get it working to some extent, but am struggling with showing the number of hours worked PER DAY, as opposed to the whole amount of work hours for the task (which is what it's currently doing).
Option Explicit
Sub exportViaArray()
' Declare in memory
Dim xl As Excel.Application
Dim XLbook As String
Dim xlRange As Excel.Range
Dim tsk As Task
Dim tsksList As Tasks
Dim person As Resource
Dim resList As Resources
Dim prjStart As Date, prjFinish As Date, prjDate As Date, dateLoop As Date, dateArray() As Date
Dim counter As Integer
Dim totalDates As Long
Dim day As Variant
' Define variable values
prjStart = ActiveProject.ProjectStart
prjFinish = ActiveProject.ProjectFinish
Set tsksList = ActiveProject.Tasks
Set resList = ActiveProject.Resources
' assigning the project start date for loop var prjDate
prjDate = prjStart
' assign specific dates, for dev/testing
prjStart = "02/12/2022 08:00:00"
prjFinish = "22/12/2022 08:00:00"
' prjDate = "12/12/2022 08:00:00"
' create an array of dates to iterate through
totalDates = DateDiff("d", prjStart, prjFinish)
ReDim dateArray(totalDates)
counter = 0
dateLoop = prjStart
Do While dateLoop 0 Then
On Error GoTo 0
Set xl = CreateObject("Excel.Application")
If Err 0 Then
MsgBox "Excel application is not available on this workstation" _
& vbCr & "Install Excel or check network connection", vbCritical, _
"Notes Text Export - Fatal Error"
FilterApply Name:="all tasks"
Set xl = Nothing
On Error GoTo 0 'clear error function
Exit Sub
End If
End If
On Error GoTo 0
xl.Workbooks.Add
XLbook = xl.ActiveWorkbook.Name
' Keeping these True for dev/testing
xl.Visible = True
xl.ScreenUpdating = True
xl.DisplayAlerts = True
ActiveWindow.Caption = " Writing data to worksheet"
' Excel - create column headings
Set xlRange = xl.Range("A1")
xlRange.Range("A1") = "Date"
xlRange.Range("B1") = "Resource"
xlRange.Range("C1") = "Duration"
' Set all column headers
With xlRange.Range("A1:C1")
.Font.Bold = True
.VerticalAlignment = xlVAlignCenter
End With 'XLrange
' Export Schedule Report Information
Set xlRange = xlRange.Range("A2")
' date iterator
Do While prjDate "" Then
With xlRange
.Range("A1") = Format(tsk.Start, "short Date")
.Range("B1") = tsk.ResourceNames
.Range("C1") = tsk.Duration
End With
' Go to next row in Excel
Set xlRange = xlRange.Offset(1, 0)
End If
Next tsk
'increment date
prjDate = DateAdd("d", 1, prjDate)
'check current loop date is not greater than end date
If prjDate > prjFinish Then
Exit Do
End If
Loop
xlRange.Range("A1:C1").EntireColumn.AutoFit
Set xl = Nothing
' Reset window to project name
ActiveWindow.Caption = ActiveProject.Name
End Sub
I'm not a developer, but can generally hack stuff together to get a result, & I'm sure there's errors in the above, but this last piece of the puzzle has really got me.
I was hoping it'd be something along the lines of using something like this: day.task.resource.work but I've tried & can't get that to work.
Any help would be greatly appreciated.
Cheers!
Derrick, you need to export timescaled data (i.e. Project's Resource Usage view). It sounds like you are trying to export static data. This code should get you started with a few tweaks to export resource hours instead of % allocation. I'll leave that as an "exercise for the student." But, if you need more help, I'll be available.
'Exports resource and assignment percent allocation
'Author: John-Project
'Initial release: 7/6/21 11:00 AM
Option Explicit
Public Const ver = " 1.0"
Public xl As Excel.Application
Public WS1 As Worksheet, WS2 As Worksheet
Public xlRange As Range
Public TotMon As Integer
Public PrSt As Date, PrFi As Date, Dat As Date
Public i As Integer, j As Integer, p1 As Integer, Delta As Integer
Public k As Long, TimSt As Long, TotTim As Long
Sub ExportFTEdata()
Dim r As Resource
Dim a As Assignment
Dim ResSt As Date, ResFin As Date
Dim TSV1 As TimeScaleValues
'opening user interface
MsgBox "This macro exports Monthly FTE data (% Allocation)" & vbCr _
& "by resource and resource assignments to a new Excel Workbook." & vbCr _
& vbCr & "When complete the user will be shown an Excel Save As prompt", _
vbInformation, "Timescale Export - ver" & ver
'find start and finish of plan to establish index reference for weekly values
PrSt = ActiveProject.ProjectStart
PrFi = ActiveProject.ProjectFinish
TotMon = DateDiff("m", PrSt, PrFi)
'set up an new instance of Excel, or if Excel is not running, start it
On Error Resume Next
Set xl = GetObject(, "Excel.application")
If Err <> 0 Then
On Error GoTo 0
Set xl = CreateObject("Excel.Application")
If Err <> 0 Then
MsgBox "Excel application is not available on this workstation" _
& vbCr & "Install Excel or check network connection", vbCritical, _
"Project Data Export - Fatal Error"
FilterApply Name:="all tasks"
Set xl = Nothing
On Error GoTo 0 'clear error function
Exit Sub
End If
End If
On Error GoTo 0
'create a workbook with two worksheets
xl.Workbooks.Add
xl.ActiveWorkbook.Worksheets(1).Name = "FTE Data"
Set WS1 = xl.ActiveWorkbook.Worksheets(1)
'Keep Excel in the background and minimized until spreadsheet is done (speeds transfer)
xl.Visible = True
xl.ScreenUpdating = True
xl.DisplayAlerts = False
TimSt = Timer 'capture start time of export
'pre-format worksheet
ShFormat1
'Populate monthly data worksheet
Set xlRange = WS1.Range("C2")
'initialize worksheet row counter
i = 0
For Each r In ActiveProject.Resources
xlRange.Offset(i, -2) = r.Name
If r.Assignments.Count > 0 Then
'resource start and finish fields are not directly readable with VBA
' so need to cycle through all assignments and find earliest and latest
ResSt = "12/31/2049": ResFin = "1/1/1984"
For Each a In r.Assignments
If a.Start < ResSt Then ResSt = a.Start
If a.Finish > ResFin Then ResFin = a.Finish
Next a
'determine resource start offset from project start
Delta = DateDiff("m", PrSt, ResSt)
'write monthly percent allocation values for resource
Set TSV1 = r.TimeScaleData(StartDate:=ResSt, EndDate:=ResFin, _
Type:=pjResourceTimescaledPercentAllocation, timescaleunit:=pjTimescaleMonths)
p1 = Delta 'set column start pointer
For k = 1 To TSV1.Count
If IsNumeric(TSV1(k)) Then
xlRange.Offset(i, p1).Value = Round(TSV1(k).Value, 0) & "%"
End If
p1 = p1 + 1
Next k
'increment row and reset start pointer
i = i + 1
'write monthly percent allocation vlaues for assignments
For Each a In r.Assignments
'reset start pointer for this assignment
Delta = DateDiff("m", PrSt, a.Start)
p1 = Delta
xlRange.Offset(i, -1).Value = a.TaskName
Set TSV1 = a.TimeScaleData(StartDate:=a.Start, EndDate:=a.Finish, _
Type:=pjAssignmentTimescaledPercentAllocation, timescaleunit:=pjTimescaleMonths)
For k = 1 To TSV1.Count
If IsNumeric(TSV1(k)) Then
xlRange.Offset(i, p1).Value = Round(TSV1(k).Value, 0) & "%"
End If
p1 = p1 + 1
Next k
i = i + 1 'next assignment row
Next a
Else
'no assignments for this resource so increment to next row
i = i + 1
End If
Next r
'format completed worksheet
xl.Visible = True
WS1.Activate
WS1.UsedRange.Columns.AutoFit
WS1.Rows(2).Select
xl.ActiveWindow.FreezePanes = True
TotTim = Timer - TimSt
xl.Visible = False
MsgBox "Export is complete" & vbCr & _
" Export time: " & TotTim & " sec", vbInformation
xl.Visible = True
xl.GetSaveAsFilename InitialFileName:="Resource Tracking"
Set xl = Nothing
End Sub
'subroutine to pre-format worksheet
Sub ShFormat1()
WS1.Range("A1") = "Resource Name"
WS1.Range("B1") = "Assignment"
Set xlRange = WS1.Range("B1")
Dat = PrSt
'write weekly dates starting with cell B1 offset by i index
For i = 1 To TotMon
xlRange.Offset(0, i).Value = Format(Dat, "mmm-yy")
Dat = DateAdd("m", 1, Dat)
Next i
WS1.Rows(1).Font.Bold = True
End Sub

VBA for each loop with 64K+ ListRows (ouf of memory)

I'm running a VBA for each loop through an Excel table (Listobject) which checks if a file exists based on a given path. My table has expanded though and has 68K Listrows. After launching the code, it quickly gives an error Run-time-error '7': Out of memory
It runs OK with 63K lines (done within 5 minutes) and based on googling there appears to be something called "64K segment boundary". Is this what's affecting my code to run since it really feels like it buffers the row count at first and then bounces back w/o starting to actually run anything. Is there an easy workaround for this without the need to split up my dataset into multiple batches? Frankly, I was quite surprised that 64K limits would still be a thing in Excel in 2021.
Running it on 64bit Excel 2019, but no luck with Office365 either.
Sub CheckFiles()
Dim Headers As ListObject
Dim lstrw As ListRow
Dim strFileName As String
Dim strFileExists As String
Application.ScreenUpdating = False
Set ws = ThisWorkbook.Sheets("Import")
Set Headers = ws.ListObjects("Import")
For Each lstrw In Headers.ListRows
strFileName = lstrw.Range(7)
strFileExists = Dir(strFileName)
If strFileExists = "" Then
lstrw.Range(4) = "not found"
Else
lstrw.Range(4) = "exists"
End If
Next lstrw
Set ws = Nothing
Set Headers = Nothing
Application.ScreenUpdating = True
End Sub
Avoid Accessing the Worksheet
Since you cannot avoid looping, you better do it in the computer's memory, i.e. rather through the elements of an array than through the cells of a range.
The code is still slow, about 10s for 200k rows on my machine, but that's because of Dir.
Note how easy (one line only, when the range contains more than one cell) and how fast (a split second) it is to write (copy) a range to an array (Data = rg.Value) and write (copy) the array back to a range (rg.Value = Data).
Adjust the values in the constants section.
Option Explicit
Sub CheckFiles()
Const wsName As String = "Import" ' Worksheet Name
Const tblName As String = "Import" ' Table Name
Const cCol As Long = 7 ' Criteria Column
Const dCol As Long = 4 ' Destination Column
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim Headers As ListObject: Set Headers = ws.ListObjects(tblName)
Dim Data As Variant ' Data Array
With Headers.ListColumns(cCol).DataBodyRange
If .Rows.Count = 1 Then
ReDim Data(1 To 1, 1 To 1): Data = .Value
Else
Data = .Value
End If
End With
Dim r As Long ' Array Row Counter (DataBodyRange Row Counter)
Dim FileName As String ' File Name Retrieved by Dir
For r = 1 To UBound(Data, 1)
FileName = Dir(CStr(Data(r, 1)))
If Len(FileName) = 0 Then
Data(r, 1) = "not found"
Else
Data(r, 1) = "exists"
End If
Next r
Headers.ListColumns(dCol).DataBodyRange.Value = Data
End Sub
Thank you all! A few takeaways. While obviously trying to write as efficient code as possible, any reasonable performance here is acceptable. With that said, for each loop took approx 5 minutes to run with 63K lines, meawhile it was done in about 15 seconds by the code I accepted as an answer by #VBasic2008 - without capacity problems either.
The only problem I had with this particular code was it being somewhat new approach for me, so possibly building on it in the future needs some dedication in looking deeper into it - but it sure looks efficient. I also put together a regular for ... to loop which also didn't run into problems with 68K lines and would steer between rows and columns with offset function.
Clearly faster than for each as #Pᴇʜ suggested but took approx 2x as long as the array method (30 seconds or so).
Sub CheckFiles_2()
Dim strFileName, strFileExists As String
Dim ws As Worksheet
Dim Headers As ListObject
Dim result As String
Dim counter, RowCount As Long
Application.ScreenUpdating = False
Set ws = ThisWorkbook.Sheets("Import")
Set Headers = ws.ListObjects("Import")
RowCount = Headers.ListRows.Count
For counter = 1 To RowCount
strFileName = Range("anchorCell").Offset(counter, 3)
strFileExists = Dir(strFileName)
If strFileExists = "" Then
result = "not found"
Else
result = "exists"
End If
Range("anchorCell").Offset(counter, 0) = result
Next counter
Set ws = Nothing
Set Headers = Nothing
Application.ScreenUpdating = True
End Sub

Results of a vba function not refreshed

I am creating a spreadsheet for a client to manage his ALM. I developped it under Excel and VBA, request of my client.
One sheet "Data" calculates all the vba functions. If i calculate manually each cell all works fine, but if i run the macro it did not.
Do you have a solution? I can post the entire file if needed, for a better investigation.
At the beginning all the calculation where in excel cell, but i created dedicated function for each table, because the file was too big when saved.
Public Sub Main()
Dim i, nb_tableaux As Integer
Dim j, lignemax, BarWidth As Long
Dim ProgressPercentage As Double
Dim echeancier, nomtableau As String
Dim ws_data As Worksheet
Dim c As Range
Me.ProgressLabel.Caption = "Initialisation terminée. "
Set ws_data = Sheets("Data")
lignemax = ws_data.Range("DATA").Rows.Count
Application.ScreenUpdating = True
Application.EnableEvents = True
nb_tableaux = 17
For i = 1 To nb_tableaux
echeancier = tab_Tableaux(i, 0)
nomtableau = tab_Tableaux(i, 1)
Me.ProgressLabel.Caption = "En cours : " & echeancier
ws_data.Range(nomtableau).Calculate
'With Worksheets("Data")
For j = 1 To lignemax
For Each c In ws_data.Range(nomtableau).Rows(j)
formulaToCopy = c.Formula
c.ClearContents
c.Value = formulaToCopy
DoEvents
Next
Me.ProgressLabel.Caption = "En cours : " & echeancier & ", " & Format(j / lignemax, "0.0%") & " completed"
Me.Repaint
Next j
'End With
Me.Bar.Width = i * 200 / nb_tableaux
Me.Bar.Caption = Format(i / nb_tableaux, "0%") & " completed"
Next i
Application.ScreenUpdating = False
Application.EnableEvents = False
End Sub
after taking into account the recommandations you gave me for my previous answers, the code works better, but still not for some of the ranges.
My issue come from a wrong calculation of a argument in the fonction.
In fact, I use ligne=activecell.row - 8, to get the ligne of the range to calculate. But it works if i do it manually, as the actual cell is activated, but not when i call the function many times, as i can not activate each cell, it will be too long for the spreadsheet.
How can i get ligne calculated, with the correct address of the cell where the function is written?
I hope i am clear enough. Sorry for my English.
Public Function Taux_Mois(ByVal mMois As Range, ByVal sScenario As Range)
Dim ligne As Long
ligne = ActiveCell.row - 8
Select Case (Range("DATA[Flag]").Cells(ligne).Value = 0) Or (Range("DATA[frequence fixing]").Cells(ligne).Value = 0)
Case True
Taux_Mois = 0
Exit Function
Case False
Dim index_taux As Integer
Dim ajust As Long
index_taux = CInt(Range("DATA[Indexation ID]").Cells(ligne).Value)
If index_taux = 1 Then
ajust = 0
Else
Dim ajust1, dernierfixingt0, freqfixing As Integer
dernierfixingt0 = Range("DATA[Dernier fixing t0]").Cells(ligne).Value
freqfixing = Range("DATA[frequence fixing]").Cells(ligne).Value
ajust1 = (Int((mMois.Value - dernierfixingt0) / freqfixing) * freqfixing)
ajust = Worksheets("Market Data").Range("Taux_" & sScenario.Value).Offset(12 + dernierfixingt0 + ajust1, 1 + index_taux).Value
End If
Taux_Mois = Range("DATA[facteur taux (TVA, base)]").Cells(ligne).Value * (ajust + Range("DATA[Spread / Taux]").Cells(ligne).Value / 10000)
Exit Function
End Select
End Function

Access to Excel: Decrease Runtime of Excel VBA

Similar versions of this question probably have been asked before, but I had questions regarding this issue.
Basically for my function, I just want to run simple a spell check on selected tables from Microsoft Access. Since Access doesn't support individual highlighting all too well in reports, I have the data exported to an Excel file and have VBA run tests for any errors there. After searching online for tips, I have the current code to run faster than what I originally had. But ideally no matter the size of the table I want the function to run under 10 minutes. But currently for some of them, for tables that have 500k+ cells the runtime can still go past 30 minutes. So I was wondering if anything further can be done to better enhance the runtime of this.
Private Function Excel_Parser(outFile As String, errorCount As Integer, ByVal tName As String)
' EXCEL SETUP VARIABLES
Dim OpenApp As Excel.Application
Set OpenApp = CreateObject("Excel.Application")
Dim parserBook As Excel.Workbook
Dim parserSheet As Excel.Worksheet
' Opening exported file
Set parserBook = OpenApp.Workbooks.Open(outFile, , , , , , , , , , , , , , XlCorruptLoad.xlRepairFile)
If parserBook Is Nothing Then
status2 = "Failed to set Workbook"
Exit Function
Else
status3 = "Searching [" & tName & "] for errors"
Set parserSheet = parserBook.Worksheets(1)
' --------------------------------------------------------------------------------
' Fetch Table information
lastCellAddress = parserSheet.Range("A1").SpecialCells(xlCellTypeLastCell).Address
Dim rng As Range
Set rng = parserSheet.Range("A1:" & lastCellAddress)
' --------------------------------------------------------------------------------
' Populating entire table data from Excel into array to save runtime.
Dim dataArr() As Variant, R As Long, C As Long
dataArr = rng.Value2
' Parsing through table data array
nRows = UBound(dataArr, 1)
nCols = UBound(dataArr, 2)
fileOuterLoop1 = Time
For R = 1 To nRows
For C = 1 To nCols
cCell = CStr(dataArr(R, C))
status4 = "Now running check on cell: [" & cCell & "]"
If cCell <> "" Or Not (IsNull(cCell)) Then
If Not OpenApp.Application.CheckSpelling(cCell) Then
errorCount = errorCount + 1
' Change cell status
vArr = Split(parserSheet.Cells(1, C).Address(True, False), "$")
fCol = vArr(0)
xDef = fCol & R
parserSheet.Range(xDef).Interior.Color = RGB(255, 213, 124)
End If
End If 'End of cCell is null check
Next C
Next R
fileOuterLoop2 = Time
fCheck = Format(fileOuterLoop2 - fileOuterLoop1, "hh:mm:ss")
' --------------------------------------------------------------------------------
parserSheet.Columns.AutoFit
status7 = "Loop Finished. Runtime: " & fCheck
' Save and Cleanup
OpenApp.DisplayAlerts = False
parserBook.SaveAs FileName:=outFile, FileFormat:=xlWorkbookDefault, ConflictResolution:=xlLocalSessionChanges
parserBook.Saved = True
parserBook.Close SaveChanges:=False
OpenApp.DisplayAlerts = True
Set parserSheet = Nothing
Set parserBook = Nothing
Set OpenApp = Nothing
' Return errorCount for database
Excel_Parser = errorCount
End If
End Function
outFile is a PATH string, where file exists from a TransferSpreadsheet command. And "status" variables are just error log textboxes in the Access form. I have tried adding in both Access' and Excel's versions of ScreenUpdating or Echo but I found that these commands actually make my function runtime slightly slower.
Two things:
Do you use status4 somewhere in your code to show current state of work and just omitted it here in the sample? If so, think about not displaying it for every loop, but maybe only every 50 steps by using Mod operator.
See https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/mod-operator
You should avoid screen refreshs and more on every loop in Excel by setting this before the loop:
OpenApp.ScreenUpdating = False
OpenApp.EnableEvents = False
OpenApp.Calculation = Excel_XlCalculation.xlCalculationManual
And this after the loop:
OpenApp.ScreenUpdating = True
OpenApp.EnableEvents = True
OpenApp.Calculation = Excel_XlCalculation.xlCalculationAutomatic
It can end in a massive speed up. Give it a try.

Timer or Code not sticking to schedule

I have code where I am accessing an xml feed for live currency data. I need to capture the price every 3 minutes, and it has to be pretty spot on, otherwise it will throw off the other calculations.
I set up a timer to run the code every 3 minutes, or whatever interval I choose.
I started noticing slippage, which was a second or two at first but has turned into 10 seconds in places. Any thoughts?
Sub xmlData()
Dim aSwitch As String: aSwitch = Sheet2.[Switch].Value
Dim aSymbol As String: aSymbol = Sheet2.[Symbol].Value
'check [switch] status
If aSwitch = "OFF" Then
MsgBox "Switch is OFF!", vbCritical, "Program Status"
Exit Sub
End If
'MsgBox "Program is ON!", vbCritical, "Program Status"
'refresh xml data
Dim iMap As XmlMap
Set iMap = ActiveWorkbook.XmlMaps(1)
iMap.DataBinding.LoadSettings "http://****.com/webservice/v1/symbols/" & aSymbol & "/quote"
iMap.DataBinding.Refresh
'dim inputs
Dim aStart As String: aStart = Sheet2.Range("c3").Text
Dim aInterval As String: aInterval = Sheet2.Range("d3").Text
Dim aStatus As String: aStatus = Sheet2.[Status].Value
'oth
Dim aSecurity As String: aSecurity = Sheet2.[Security].Value
Dim aPrice As String: aPrice = Sheet2.[Price].Value
Dim aDatetime As String: aDatetime = Sheet2.[DateTime].Value
'separate adatetime
Dim aDate As String: aDate = Mid(aDatetime, 1, 10)
Dim aTime As String: aTime = Mid(aDatetime, 12, 10)
'Time actual
Dim aTimeNow As String: aTimeNow = Format(Now(), "HH:mm:ss")
'copy xml data to table
Dim aRow As Long
aRow = Sheet2.Cells.Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row + 1
Sheet2.Cells(aRow, 1).Value = aDate
Sheet2.Cells(aRow, 2).Value = aTime
Sheet2.Cells(aRow, 3).Value = aSecurity
Sheet2.Cells(aRow, 4).Value = aPrice
Sheet2.Cells(aRow, 5).Value = aTimeNow
'start timer for reload
Application.OnTime Now + TimeValue(aInterval), "xmlData"
End Sub
EDIT 160627
Is it possible the XML is not fetching right away?
Calculate the scheduled time in absolute terms, rather than as a differential from Now. (Now will always be some amount of time after the last scheduled time, so will always creep)
Sub xmlData()
Static ScheduleTime As Variant ' Statis retains value between executions
Dim aInterval As String
' other code
' ...
If ScheduleTime = 0 Then
' initialise on first execution
ScheduleTime = Now + TimeValue(aInterval)
Else
' schedule based on last scheduled time
' this assumes execution time is always less than interval
ScheduleTime = ScheduleTime + TimeValue(aInterval)
End If
Application.OnTime Now + TimeValue(aInterval), "xmlData"
End Sub

Resources