Итерация по классу в Excel VBA
Я создал модуль класса для сущности под названием Terminal. У меня есть метод, который заполняет этот класс, просматривая 175 отдельных листов и извлекая правильные данные из определенных ячеек. Этот процесс очень быстрый (около 2 секунд), однако когда я пытаюсь записать эти данные обратно на новый лист, это занимает гораздо больше времени (45 секунд). Казалось бы, этот процесс должен быть по крайней мере так же быстр, как заполнение класса, так как он никогда не должен покидать лист, однако, это так нет. Ниже приведен процесс, который я использую для записи данных на рабочий лист, я упускаю что-то, что заставляет это работать так медленно?
Application.ScreenUpdating = False
Dim rowNumber As Integer
Dim colNumber As Integer
Dim terminalCode As String
Dim terminal As clsTerminal
rowNumber = 7
colNumber = 1
For Each terminal In terminals
'Add hyperlink to each terminal code
Sheets("Terminal Summary").Hyperlinks.Add Anchor:=Cells(rowNumber, colNumber), Address:="", _
SubAddress:=terminal.terminalCode + "!A1", TextToDisplay:=terminal.terminalCode
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 1).Value = "Current"
'Current period
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 2).Value = terminal.iBShipments
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 3).Value = terminal.oBShipments
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 4).Value = terminal.iBNetRevenue
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 5).Value = terminal.oBNetRevenue
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 6).Value = terminal.iBWeight
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 7).Value = terminal.oBWeight
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 8).Value = terminal.iBMileage
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 9).Value = terminal.oBMileage
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
rowNumber = rowNumber + 1
Next terminal
Edit Я должен был заметить, что terminals-это коллекция класса terminal
3 ответа:
Общим недостатком этого типа кода является то, что каждый раз, когда вы записываете данные в электронную таблицу, Excel выполняет вычисление (он оценивает все формулы в книге, чтобы увидеть, нужно ли их вычислять с новыми данными).
Если вы отключите автоматическое вычисление перед циклом, а затем снова включите его, все будет двигаться гораздо быстрее:
Application.Calculation = xlCalculationManual For Each terminal In terminals ... Next terminal Application.Calculation = xlCalculationAutomatic
У вас уже есть большая экономия (выключение автоматического вычисления во время записи), но есть еще несколько маленьких трюков, которые нужно иметь в виду на будущее.
Во-первых, каждый раз, когда вы пишете ячейку из VBA, возникают накладные расходы, вызванные тем, что VBA переходит к адресу workbook/wootksheet/cell и выполняет запись. Запись многих ячеек в один вызов приводит к тому, что накладные расходы возникают только один раз. Таким образом, упаковка нескольких значений в массив и запись этого массива через несколько ячеек выигрывает время. Не стоит того ради ... несколько строк, но это стоит усилий для сотен.
Далее, есть еще одна небольшая накладная для каждой "точки". Термины в "точечном" выражении, такие как
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 2)
, требуют, чтобы Excel/VBA определяли, какие объекты участвуют в каждом вызове. В некоторых случаях (особенно при обращении к удаленным объектам) эти накладные расходы могут быть существенными. VB дает нам конструкциюWith...End With
, чтобы уменьшить необходимость продолжать разрешать эти ссылки: каждое выражение, начинающееся с точки, автоматически ссылается на объект в следующем крайнемWith
.Таким образом, мы могли бы получить что-то вроде этого:
With Sheets("Terminal Summary") .Cells(rowNumber, colNumber + 2).Resize(1, 8) = terminal.InfoArray .Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)" .Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)" .Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)" .Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)" .Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)" .Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)" .Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)" .Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)" End With
Я бы вставил массив в терминальный класс, что-то вроде этого:
Public Property Get InfoArray() As Variant InfoArray = Array(iBShipments, oBShipments, iBNetRevenue, oBNetRevenue, iBWeight, oBWeight, iBMileage, oBMileage) End Property
Формулы можно было бы выводить более эффективно, записывая их один раз в столбец после того, как вся терминальная информация будет выполнена.
Некоторые вещи, чтобы рассмотреть, по крайней мере...
У вас уже есть 90% ответа, который я бы дал, но вот еще один совет по производительности. Вместо того, чтобы делать:
Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
Можно использовать тот факт, что вы присваиваете одну и ту же формулу всем ячейкам столбца, и сделать это в одном операторе:
Sheets("Terminal Summary").Cells(7, 11).Resize(terminals.Count, 1).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"