Как я могу ускорить макрос OpenOffice Calc, который обновляет множество ячеек?

У меня есть макрос 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

ОБНОВЛЕНИЕ: я переписал Round, чтобы не вызывать 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()

person Paul Chernoch    schedule 27.08.2012    source источник
comment
Вам действительно нужны округленные числа или вы просто хотите, чтобы они отображались с определенным количеством десятичных знаков? Если последнее, то это просто вопрос применения форматирования (один раз) ...   -  person twalberg    schedule 28.08.2012
comment
Эта функция является частью рабочего процесса для сравнения двух рабочих листов. Я вставляю результаты из SQL Server двух запросов (устаревшего и оптимизированного), и мне нужно знать, совпадают ли результаты. Числовые значения часто отличаются одиннадцатым или двенадцатым десятичным знаком, что я не хочу отмечать как разницу. Таким образом, я заставляю округлять. Я полагаю, я мог бы сделать сравнение нечетким и фактически не изменять значения ...   -  person Paul Chernoch    schedule 28.08.2012
comment
Я думаю, что механизм пересчета может мешать. Есть ли способ включить и выключить пересчет?   -  person Paul Chernoch    schedule 28.08.2012
comment
Нашел как отключить пересчет и добавил в вопрос. Намного лучшая производительность. Если бы я мог еще раз удвоить скорость, я был бы счастлив.   -  person Paul Chernoch    schedule 28.08.2012


Ответы (1)


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

Самый эффективный метод - использовать для этого какие-нибудь собственные средства; например, чтобы задать стиль отображения для отображения в определенном формате, который будет округлять отображение в указанной точке, если это возможно.

Следующее лучшее решение - получить данные по частям и обновить данные по частям. Метод, который вы используете для получения данных, будет зависеть от единообразия данных. Например, getDataArray () и setDataArray (), вероятно, примерно в 100 раз быстрее (по крайней мере), чем просмотр одной ячейки за раз. Здесь есть два разных возможных метода:

getData () получит числовые данные как вложенную последовательность значений (массивы в массиве).

getDataArray () аналогичен getData (), но может содержать String или Double.

person Andrew    schedule 13.09.2016