4

アクティブなワークシートのすべての数値を指定された小数点以下の桁数に丸める OpenOffice Calc マクロ (Basic) があります。9000 行のスプレッドシートの 100 行を処理するのに約 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 を呼び出さないようにしました。これにより、速度が 2 倍になりました。まだ遅すぎます。1 秒あたり 50 行よりもはるかに優れている必要があります。

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:

マクロの実行中に自動更新と画面の更新を無効にする方法が見つかりました。これにより、速度が再び 3 倍になります (現在は毎秒 200 行です)。

myDoc = ThisComponent
myDoc.lockControllers()
myDoc.addActionLock()
' --- modify your cells here ---
myDoc.removeActionLock()
myDoc.unlockControllers()
4

1 に答える 1

1

私のタイミング テストによると、最大の問題は一度に 1 つのセルにアクセスしていることです。私のテストでは、このタイプの操作を劇的に増やすには 2 つのアプローチがあることが示されています。

最も効果的な方法は、ネイティブ機能を使用してこれを行うことです。たとえば、表示スタイルを特定の形式で表示するように設定し、可能であれば指定したポイントで表示を丸めます。

次善の策は、データをチャンクで取得し、チャンクでデータを更新することです。データを取得するために使用する方法は、データの均一性によって異なります。たとえば、getDataArray() と setDataArray() は、一度に 1 つのセルを調べるよりも (少なくとも) 約 100 倍高速です。ここには 2 つの異なる方法があります。

getData() は、ネストされた一連の値 (配列内の配列) として数値データを取得します。

getDataArray() は getData() と同じですが、String または Double を含む場合があります。

于 2016-09-13T17:07:34.890 に答える