Как я могу ускорить OpenOffice Calc Macro, который обновляет много ячеек?
У меня есть макрос OpenOffice Calc (в Basic), который округляет все числа в активном листе до заданного числа десятичных знаков. Обработка 100 строк электронной таблицы на 9000 строк занимает около 4 секунд. Каждая строка имеет 35 столбцов, из которых 19 столбцов являются числовыми.
Как я могу сделать это быстрее? Это первый макрос, который я когда-либо писал для OpenOffice, так что, скорее всего, есть более быстрые методы, о которых я никогда не слышал. Вот мой код:
Dim oFunction as Object
' Round all Value cells (cells that do not contain Text or Functions) on the current sheet to as many decimal places as user requests.
Sub RoundUsedCells
Dim oSheet As Object, oUsedRange as Object, oCursor as Object, oCell As Object
Dim row, column,lastRow, lastColumn
Dim places$, placesToRound
Dim roundedValue as Double
' Ask user how many decimal places to round
places$ = InputBox ("Round to how many places (leave blank to cancel)?")
if (places$ is Nothing or places$ = "") then
' Do nothing.
else
placesToRound = CInt(places$)
' Get the currently active sheet.
oSheet = thiscomponent.getcurrentcontroller.activesheet
' Create a one cell range at the origin,
' then create a cursor that allows us to extend the range to include all the "used" cells.
' The cursor will then allow us to find out how large is that used range.
oUsedRange = oSheet.getCellRangeByPosition(0,0,0,0)
oCursor = oSheet.createCursorByRange(oUsedRange)
oCursor.GotoEndOfUsedArea(false)
' Obtain the last rows and colums in the "used" range from the cursor.
lastRow = oCursor.RangeAddress.EndRow
lastColumn = oCursor.RangeAddress.EndColumn
' Loop through all cells from the origin (0,0) to the last used column and row.
for row = 0 to lastRow
if (row mod 50 = 0) then
StatusBar "Rounding row " + (row + 1) + " of " + (lastRow + 1) + "..."
endif
for column = 0 to lastColumn
oCell = oSheet.getCellByPosition(column, row)
Select Case oCell.Type
Case com.sun.star.table.CellContentType.VALUE
' Only round value cells. Skip over empty cells, formuls and text.
roundedValue = Round(oCell.Value, placesToRound)
if (roundedValue <> oCell.Value) then
oCell.Value = roundedValue
endif
Case com.sun.star.table.CellContentType.EMPTY
Case com.sun.star.table.CellContentType.TEXT
Case com.sun.star.table.CellContentType.FORMULA
End Select
next column
next row
StatusBar "Rounding complete."
endif
End Sub
' Obtain access to worksheet functions, such as the "round" function.
Sub InitRound
if (oFunction is Nothing) then
oFunction = createUnoService("com.sun.star.sheet.FunctionAccess")
endif
End Sub
' Round the value to the given number of places after the decimal.
Function Round(value, decimalPlaces)
InitRound()
Dim args( 1 to 2 ) As Variant
args(1) = value
args(2) = decimalPlaces
Round = oFunction.callFunction( "round", args() )
End Function
global vStatusBarText as string '=text that is been displayed on the statusbar
sub StatusBar(optional vNewText, optional vAddText) 'set or add text to the statusbar, nothing = clear&reset it.
if isError(vNewText) then
if isError(vAddText) then 'clear statusbar
vStatusBarText=""
else 'add text to the previous statusbar
vStatusBarText=vStatusBarText & vAddText
endif
else
if isError(vAddText) then 'use new text
vStatusBarText=vNewText
else 'use new text and add the other text as well
vStatusBarText=vNewText & vAddText
endif
endif
vStatusBarText=right(vStatusBarText,int(ThisComponent.CurrentController.VisibleArea.Width/150)) 'Select last part that could be displayed.
if isNull(ThisComponent.CurrentController) then exit sub 'Because the last XEventListener-event can't be written to the statusbar, because it's no longer there!
if vStatusBarText="" then 'reset statusbar
ThisComponent.CurrentController.StatusIndicator.Reset
else 'change the text in the statusbar
ThisComponent.CurrentController.StatusIndicator.Start(vStatusBarText,0)
endif
end sub
ОБНОВЛЕНИЕ: я переписал Раунд, чтобы не вызвать Calc, который удвоил скорость. Это все еще слишком медленно. Должен работать намного лучше, чем пятьдесят строк в секунду.
Function Round2(value, decimalPlaces) as Double
Round2 = Int(value * 10 ^ decimalPlaces + 0.5) / 10 ^ decimalPlaces
'Dim multiplier as Double, bigValue as Double
'multiplier = 10 ^ decimalPlaces
'bigValue = (value * multiplier) + 0.5
'Round2 = CDbl( CLng(bigValue) ) / multiplier
End Function
ОБНОВЛЕНИЕ 2:
Найден способ отключить автоматическое обновление и обновление экрана во время выполнения макроса, что снова увеличивает скорость в три раза (теперь это 200 строк в секунду):
myDoc = ThisComponent
myDoc.lockControllers()
myDoc.addActionLock()
' --- modify your cells here ---
myDoc.removeActionLock()
myDoc.unlockControllers()
1 ответ
Мои временные тесты показывают, что ваша самая большая проблема в том, что вы обращаетесь к одной ячейке за раз. Мое тестирование показывает, что есть два подхода, чтобы значительно увеличить этот тип операции.
Самый эффективный метод - использовать некоторые нативные средства для этого; например, чтобы установить стиль отображения для отображения в определенном формате, который будет округлять отображение в указанной точке, если это возможно.
Следующим лучшим решением является получение данных в виде фрагментов и обновление данных в виде фрагментов. Метод, который вы используете для получения данных, будет зависеть от однородности данных. Например, getDataArray() и setDataArray(), вероятно, примерно в 100 раз быстрее (по крайней мере), чем просмотр одной ячейки за раз. Здесь есть два возможных метода:
getData() получит числовые данные в виде вложенной последовательности значений (массивов в массиве).
getDataArray () аналогичен getData(), но может содержать String или Double.