如何加快更新许多单元格的OpenOffice Calc Macro?

时间:2012-08-27 19:28:21

标签: performance openoffice-calc openoffice-basic

我有一个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()

1 个答案:

答案 0 :(得分:0)

我的时间测试表明,您最大的问题是您一次只能访问一次。我的测试表明有两种方法可以显着增加这种类型的操作。

最有效的方法是使用一些原生设施来做到这一点;例如,将显示样式设置为以特定格式显示,如果可能,将在指定点处围绕显示。

下一个最佳解决方案是以块的形式获取数据并以块的形式更新数据。用于获取数据的方法取决于数据的一致性。例如,getDataArray()和setDataArray()可能大约快100倍(至少),然后一次查看一个单元格。这里有两种不同的方法:

getData()将获取数值数据作为嵌套的值序列(数组中的数组)。

getDataArray()与getData()相同,但可能包含String或Double。