libreoffice-calclibreoffice-basic

How to convert solution from MS Excel (VBA) to Libreoffice-Calc


How to do it in Libreoffice-Calc?

OnTime for less than 1 second without becoming Unresponsive

This is the solution:

Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, _ 
    ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, _
    ByVal nIDEvent As LongPtr) As Long

Public TimerID As Long

Sub StartTimer()
    ' Run TimerEvent every 100/1000s of a second
    TimerID = SetTimer(0, 0, 100, AddressOf TimerEvent)
End Sub

Sub StopTimer()
    KillTimer 0, TimerID
End Sub

Sub TimerEvent()
    On Error Resume Next
    Cells(1, 1).Value = Cells(1, 1).Value + 1
End Sub

It would be great if everything worked under different OS


Solution

  • Here is a python macro that calls a Basic routine every 10th of a second. For illustration, it stops itself after 20 seconds.

    import threading
    
    def do_call():
        desktop = XSCRIPTCONTEXT.getDesktop()
        ctx = XSCRIPTCONTEXT.getComponentContext()
        smgr = ctx.ServiceManager
        oMasterScriptProviderFactory = smgr.createInstanceWithContext(
            "com.sun.star.script.provider.MasterScriptProviderFactory", ctx)
        oScriptProvider = oMasterScriptProviderFactory.createScriptProvider("")
        oScript = oScriptProvider.getScript(
            "vnd.sun.star.script:Standard.Calc1.IncrementValue?"
            "language=Basic&location=application")
        oScript.invoke((), (), ())
    
    class BasicScriptCaller:
        def __init__(self):
            self.timer = None
    
        def call_basic_script(self):
            do_call()
            self.timer = threading.Timer(0.1, self.call_basic_script)
            self.timer.start()
    
        def stop_basic_script(self):
            if self.timer is not None:
                self.timer.cancel()
    
        def start(self):
            self.call_basic_script()
            threading.Timer(20, self.stop_basic_script).start()
    
    def start():
        caller = BasicScriptCaller()
        caller.start()
    
    g_exportedScripts = (start,)
    

    This routine increments cell A1, making something that looks like a timer.

    Sub IncrementValue
        oSheet = ThisComponent.getSheets().getByIndex(0)
        oCell = oSheet.getCellByPosition(0, 0)
        iOldVal = oCell.getValue()
        oCell.setValue(iOldVal + 1)
    End Sub
    

    To turn off, the thread could stop itself if a cell, say B1, has a non-zero value. Then you could make a button or checkbox to automatically set B1 to 1 instead of 0. Also, stop if the document is no longer open.

    Update

    Here are the code changes you requested in the comment. It runs until cell B1 has a value. Also, it allows start() to optionally accept the button event parameter.

    class BasicScriptCaller:
        def __init__(self):
            self.timer = None
    
        def stop_condition(self):
            """Return true if the condition to stop is met."""
            oSheet = XSCRIPTCONTEXT.getDocument().getSheets().getByIndex(0)
            oCell = oSheet.getCellByPosition(1, 0)  # B1
            return bool(oCell.getString()) and oCell.getString().strip()
    
        def next_call(self):
            do_call()
            if not self.stop_condition():
                self.timer = threading.Timer(0.1, self.next_call)
                self.timer.start()
    
    def start(button_evt=None):
        caller = BasicScriptCaller()
        caller.next_call()
    
    g_exportedScripts = (start,)