Итерация по классу в Excel VBA - PullRequest
2 голосов
/ 17 ноября 2009

Я создал модуль класса для сущности с именем 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

Редактировать Я должен был отметить, что терминал является коллекцией класса терминала

Ответы [ 3 ]

4 голосов
/ 17 ноября 2009

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

Если вы отключите автоматический расчет перед циклом, а затем снова включите его, все будет двигаться гораздо быстрее:

Application.Calculation = xlCalculationManual
For Each terminal In terminals
...
Next terminal
Application.Calculation = xlCalculationAutomatic
3 голосов
/ 17 ноября 2009

У вас уже есть большая экономия (отключение автоматического расчета во время записи), но есть еще несколько маленьких хитростей, которые следует учитывать в будущем.

Во-первых, каждый раз, когда вы пишете ячейку из VBA, возникает дополнительная нагрузка, вызванная переходом VBA на адрес рабочей книги / рабочего листа / ячейки и выполнением записи. Запись множества ячеек в одном вызове приводит к накладным расходам только один раз. Таким образом, упаковка нескольких значений в массив и запись этого массива в несколько ячеек выигрывает время. Не стоит нескольких строк, но стоит сотен.

Кроме того, для каждой "точки" есть еще один маленький заголовок. Термины в выражении «пунктир», такие как 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

Я бы поместил массив в класс Terminal, примерно так:

Public Property Get InfoArray() As Variant
    InfoArray = Array(iBShipments, oBShipments, iBNetRevenue, oBNetRevenue, iBWeight, oBWeight, iBMileage, oBMileage)
End Property

Формулы могут выводиться более эффективно, если их записать один раз в столбец после того, как вся информация о Терминале завершена.

Некоторые вещи, которые нужно учитывать, по крайней мере ...

1 голос
/ 17 ноября 2009

У вас уже есть 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)"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...