Libre CalcのBasicで、定期的にマクロを呼び出し実行したいことがあります。
Libre CalcのBasic単体には、定期的にマクロを呼び出す手段はないのですが、Basic以外にもBeanShell、JavaScript、Pythonを組み合わせ使用することができます。
今回は、Pythonを使用してタイマー割込みを実現してみました。
といっても疑似的に実現するだけで、本当にタイマー割込みを処理するわけではないです。
Libre Calcを真剣に使い始めると、結構有用なテクニックだと思いましたので、御紹介します。
■ Basicの限界
Basicだけで、一定間隔で何かを監視するような処理を行う場合、下記のように処理される方が多いかと思います。
const cnf_task_yield = 500 ' 500ms 毎に定期的に呼び出す Private fstop as boolean ' マクロ停止フラグ sub tsst() Dim cnt as integer fstop = False cnt = 0 do call DoEvents() ' イベント操作ができるように処理をイールド call Wait(cnf_task_yield) ' 一定時間待ちを発生。 ' ステータスバーにカウンタ表示を行う ThisComponent.getCurrentController.getFrame.createStatusIndicator.start("1:" & Cstr(cnt), 32) cnt = cnt + 1 loop while (fstop = False) ' 外部からfstopが変更されるまで、待ち続ける ThisComponent.getCurrentController.getFrame.createStatusIndicator.start("" & Cstr(cnt), 32) ThisComponent.getCurrentController.getFrame.createStatusIndicator.end() end sub
このような処理を組めば、このマクロに限っては動作しますが、複数のBasicを動作させる場合には、厄介なことが発生します。例として、上記マクロ実行時の説明します。
プログラム1を起動した後にプログラム2を起動すると、下記のようにプログラム2実行中は、プログラム1は停止します。
両方のプログラムとも何かの監視をしたいのに、プログラム1は停止してしまうことになります。
■ 解決案
Libre Officeは、Basic以外の言語が使用可能です。
私は、Pythonにタイマー駆動関数を作成して問題を解消させました。
まずは、Basic側とPython側のサンプルコードを説明します。
下記は、説明のために主要部分だけ抜粋してます。
〇 Basic(Standard.timer_test)
プログラムは、timer_test()を実行すると、py_timer_proc()が500ms毎に呼び出されるようになります。
関数名 | 処理内容 |
py_timer_proc() | 一定間隔で呼び出される関数。 |
timer_test() | テストプログラムのメイン関数。 タイマー設定を行いpy_timer_proc()を500ms間隔で定期呼び出しする設定を行う。 |
timer_call() | タイマー設定用IF。このIF経由でpythonスクリプトを呼び出す |
' ' タイマーによって定期t的により呼び出されるマクロ ' function py_timer_proc() as integer if (fstop = False) then ' 終了フラグ True:終了要求 False:継続 : 定期的に行う処理 py_timer_proc = 0 ' 継続時は、0を返す else py_timer_proc = 1 ' 終了時は、1を返す end if end function ' ' タイマーテスト用マクロ ' function timer_test() Dim ms as integer fstop = False call timer_call(500, "Standard.timer_test.py_timer_proc") ' タイマー設定 500ms間隔で指定関数を呼び出す設定 ' 処理のYield。 ' マクロを終了してしまうと変数も解放されるので、維持するために終了まで待つ。 do call DoEvents() call Wait(cnf_task_yield) loop while (fstop = False) end function ' ' purpose : 指定関数をタイマー駆動する ' function timer_call(ms as integer, macro as string) Dim script as object ThisComponent.getCurrentController().getFrame().Activate() ' Python呼び出し前は、Active化が必要 script = ThisComponent.getScriptProvider().getScript("vnd.sun.star.script:timercall.py$timer_start?language=Python&location=user") script.invoke(array(ms, macro), array(), array()) end function
〇 Python(timercall.py)
PythonスクリプトはBasicより呼び出されて実行されることを前提としています。
関数名 | 処理概要 |
timer_monitor | 時間監視を行うスレッドです。 |
timer_start | BasicマクロIFです。指定された周期(ms)、マクロを実行します。 |
# # purpose : entry to calls basic macro on specified time. # def timer_monitor(ms, macro, sp): macro = 'vnd.sun.star.script:' + macro + '?language=Basic&location=document' script = sp.getScript(macro) while (True): result = script.invoke((),(),()) if (result[0] != 0): return time.sleep(ms / 1000.0) # # purpose : entry to set up timer call. # def timer_start(ms, macro): ' 実行周期(ms)と、マクロ名 doc = XSCRIPTCONTEXT.getDocument() ' ここの時点でdoc、spを取得しておけば、Exceptionを避けられる sp = doc.getScriptProvider() th = threading.Thread(target=timer_monitor, args=(ms, macro, sp)) th.setDaemon(True) th.start() ' Thread起動。timer_monitor起動
■ 動作原理
なぜこれだとうまくいくのかというと、Basic自体はいくつかのプログラムを実行しても問題はありません。ただ単に後で起動されたプログラムが終了するまで、それまで実行されていたプログラムが停止してしまうだけです。
先のサンプルプログラムでは、py_timer_proc()がPython Threadより定期的に呼び出され、すぐに終了していますので、終了と同時に実行中のプログラムに制御が移行します。
timer_test()でループして待っていますが、この部分は変数維持のためにループしています。
Pythonから呼び出されるマクロは、呼び出したプログラムが実行中でないと変数が初期化された状態で呼び出されます。
もし、変数の維持が不要であれば、ループしなくても大丈夫です。
■ 制約
使用するにあたって、下記の制約事項が発生します。
〇 制約1
このプログラムが何より先に起動されることが前提であればよいのですが、すでに実行中のプログラムがある場合、停止させてしまう可能性があります。
一番先に起動させておくなどの制約が発生します。
発生理由は、下記の変数維持のための処理で先に起動しているプログラムを停止させる可能性があります。
関数内部の変数だけで処理可能なのであれば、下記の処理は不要なので制約は発生しません。
do call DoEvents() call Wait(cnf_task_yield) loop while (fstop = False
〇 制約2
マクロ終了時には、からずタイマー処理を終了させてください。
Libre Calc終了時にタイマーが動作していると、文書が壊れていると判断されてしまいます。
■ サンプルプログラム
今回使用したサンプルプログラムです。
自己責任での御使用をお願いいたします。
test1、test2のファイルを用意していますが、並行動作の確認用に用意したもので、中身は同等です。