LibreOffice5(115)イベントが呼ばれる順を調べるマクロ

2017-12-23

旧ブログ

t f B! P L
ドキュメントのイベントが実際にどの場合にどの順番で呼ばれるのかが確認できるCalcのドキュメントのマクロを作成しました。

前の関連記事:LibreOffice5(114)ドキュメントのイベント名一覧


Calcドキュメントでイベントが呼ばれる順を調べるマクロ

#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
from itertools import zip_longest
from com.sun.star.sheet import CellFlags as cf # 定数
ORDER = 0  # 呼び出された順番。
RESULTS = [("Order", "Event Name")]
DIC = {'OnStartApp': ("アプリケーションの開始時", "Start Application"),\
  'OnCloseApp': ("アプリケーション終了時", "Close Application"),\
  'OnCreate': ("文書作成時", "Document created"),\
  'OnNew': ("新規文書の開始時", "New Document"),\
  'OnLoadFinished': ("文書の読み込み終了時", "Document loading finished"),\
  'OnLoad': ("文書を開いた時", "Open Document"),\
  'OnPrepareUnload': ("文書が閉じられる直前", "Document is going to be closed"),\
  'OnUnload': ("文書を閉じた時", "Document closed"),\
  'OnSave': ("文書を保存する時", "Save Document"),\
  'OnSaveDone': ("文書を保存した時", "Document has been saved"),\
  'OnSaveFailed': ("文書の保存が失敗した時", "Saving of document failed"),\
  'OnSaveAs': ("別名で保存する時", "Save Document As"),\
  'OnSaveAsDone': ("文書を別名で保存した時", "Document has been saved as"),\
  'OnSaveAsFailed': ("'別名で保存'が失敗した時", "'Save as' has failed"),\
  'OnCopyTo': ("文書の保存もしくはエクスポート", "Storing or exporting copy of document"),\
  'OnCopyToDone': ("文書のコピーを作った時", "Document copy has been created"),\
  'OnCopyToFailed': ("文書のコピーが失敗した時", "Creating of document copy failed"),\
  'OnFocus': ("文書を有効化した時", "Activate Document"),\
  'OnUnfocus': ("文書を無効化した時", "Deactivate Document"),\
  'OnPrint': ("文書の印刷時", "Print Document"),\
  'OnViewCreated': ("ビューの作成時", "View created"),\
  'OnPrepareViewClosing': ("ビューが閉じられる直前", "View is going to be closed"),\
  'OnViewClosed': ("ビューを閉じた時", "View closed"),\
  'OnModifyChanged': ("'変更'ステータス変更時", "'Modified' status was changed"),\
  'OnTitleChanged': ("文書のタイトルを変更した時", "Document title changed"),\
  'OnVisAreaChanged': ("OnVisAreaChanged", "OnVisAreaChanged"),\
  'OnModeChanged': ("OnModeChanged", "OnStorageChanged"),\
  'OnStorageChanged': ("OnStorageChanged", "OnStorageChanged")}  # イベント名の辞書。 
def macro(arg=None):  # 引数は文書のイベント駆動用。  
 global ORDER  # RESULTSはリストなのでglobalに指定しなくても書き込める。
 if arg.typeName=="com.sun.star.document.DocumentEvent":  # 引数がDocumentEventのとき。
  eventname = arg.EventName  # イベント名を取得。
  RESULTS.append((ORDER, eventname, *DIC[eventname]))  # 呼び出され順、イベント名、イベントの日本語UI名、英語UI名、arg.Souruce(イベントを発火させたオブジェクト)はドキュメントモデル。。
 else:  # 引数がDocumentEvent以外のときはない?
  RESULTS.append((ORDER, str(arg)))
 ORDER += 1
def output(documentevent=None):  # Calcのシートにイベントの呼び出し順を書き出す。
 macro(documentevent)  # このイベント自身の呼び出し順を取得。
 results = RESULTS.copy()  # この時点の結果を出力する。イベントを無効にする方法は?
 doc = XSCRIPTCONTEXT.getDocument()  # ドキュメントのモデルを取得。 
 sheet = getNewSheet(doc, "Events")  # 連番名の新規シートの取得。OnTitleChanged→OnModifyChangedが呼ばれてしまう。
 rowsToSheet(sheet[0, 0], results)  # 結果をシートに出力。シートを書き込むときのイベントを取得しないためにこの時点のRESULTSのコピーを渡す。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.setActiveSheet(sheet)  # 新規シートをアクティブにする。
def getNewSheet(doc, sheetname):  # docに名前sheetnameのシートを返す。sheetnameがすでにあれば連番名を使う。
 cellflags = cf.VALUE+cf.DATETIME+cf.STRING+cf.ANNOTATION+cf.FORMULA+cf.HARDATTR+cf.STYLES
 sheets = doc.getSheets()  # シートコレクションを取得。
 c = 1  # 連番名の最初の番号。
 newname = sheetname
 while newname in sheets: # 同名のシートがあるとき。sheets[newname]ではFalseのときKeyErrorになる。
  if not sheets[newname].queryContentCells(cellflags):  # シートが未使用のとき
   return sheets[newname]  # 未使用の同名シートを返す。
  newname = "{}{}".format(sheetname, c)  # 連番名を作成。
  c += 1 
 index = len(sheets)  # 最終シートにする。
#  index = 0  # 先頭シートにする。
 sheets.insertNewByName(newname, index)   # 新しいシートを挿入。同名のシートがあるとRuntimeExceptionがでる。
 if "Sheet1" in sheets:  # デフォルトシートがあるとき。
  if not sheets["Sheet1"].queryContentCells(cellflags):  # シートが未使用のとき
   del sheets["Sheet1"]  # シートを削除する。
 return sheets[newname]
def rowsToSheet(cellrange, datarows):  # 引数のセル範囲を左上端にして一括書き込みして列幅を最適化する。datarowsはタプルのタプル。
 datarows = tuple(zip(*zip_longest(*datarows, fillvalue="")))  # 一番長い行の長さに合わせて空文字を代入。
 sheet = cellrange.getSpreadsheet()  # セル範囲のあるシートを取得。
 cellcursor = sheet.createCursorByRange(cellrange)  # セル範囲のセルカーサーを取得。
 cellcursor.collapseToSize(len(datarows[0]), len(datarows))  # (列、行)で指定。セルカーサーの範囲をdatarowsに合せる。
 cellcursor.setDataArray(datarows)  # セルカーサーにdatarowsを代入。代入できるのは整数(int、ただしboolを除く)か文字列のみ。
 cellcursor.getColumns().setPropertyValue("OptimalWidth", True)  # セルカーサーのセル範囲の列幅を最適化する。
g_exportedScripts = macro, output #マクロセレクターに限定表示させる関数をタプルで指定。 
ツール→カスタマイズ、イベントタブで順番を調べたいドキュメントイベントにmacroを登録します。

マクロの引数にDocumentEvent Structを想定しているので、マクロセレクターからは起動できません。

結果をシートに出力したいタイミングのドキュメントイベントにはoutputを登録します。

このoutputを割り当てたイベントが呼ばれるとCalcのシートにそれまでに呼ばれたイベントの順番とAPI名、日本語UI名、英語UI名がEventを接頭語とした連番名の新しいシートに出力されます。

UI名をxcdファイルから取得したかったのですが、どこに保存されているかわかりませんでしたので、UI名の辞書を使っています。

OnVisAreaChanged、OnModeChanged、OnStorageChangedの3つのイベントについては該当するUI名が見つけられませんでしたのでAPI名をそのまま書いています。
(2018.1.6追記。ドキュメントのイベントはリスナーのXDocumentEventListenerの発生するイベントとしても取得でき、OnModeChangedとOnStorageChangedはドキュメントを保存するときに発生しました(Calc(58)追加できるリスナー一覧: その9)。しかしOnVisAreaChangedの発生は確認できませんでした。)

関数getNewSheet()で新規シートを追加してシート名を変更するとOnTitleChanged→OnModifyChangedという順でイベントが発生します。

なので、46行目で出力結果を取得しているリストRESULTSをコピーして、その時点のリストを結果として出力しています。

ドキュメントイベントを無効にするAPIはわかりませんでした。

ドキュメントイベントをキャンセルする一つの方法としてはフラグを使う方法があります(下記documentoevent2.ods参照)。

イベントが呼ばれる順を調べるマクロを埋め込んだCalcドキュメント


documentevent.ods

このマクロを埋め込んだCalcドキュメントです。


「文書が閉じられる直前」にだけoutputを割り当てています。

「文書を閉じた時」だとすでにドキュメントが閉じた後なのでイベントの結果をドキュメントに出力するのは難しいのでmacroは割り当てていません。

同様の理由で「アプリケーション終了時」にもmacroを割り当てていません。

「アプリケーションの開始時」は埋め込みマクロは有効になっていないのでこれにもmacroは割り当てていません(Calc(49)追加できるリスナー一覧: その1で「アプリケーションの開始時」に割り当てました。)。

他のイベントにはすべてmacroを割り当てました。

linuxBeans14.04でもWindows10でも実行できましたが、ドキュメントを開くタイミングによってはよくわからないエラーがでるときもあります。

エラーがでるのはバックグランドでドキュメントを閉じているタイミングのように思います。

イベントに登録したマクロをキャンセルする方法


ExcelのVBAではリスナーのメソッドの中でCancel=Trueとするとイベントの実行をキャンセル方法があるようです(OOobbs2/122 - ...?)。

つまりはVBAでは「Cancel」というグローバル変数をフラグに使っているということなので、同様にグロバール変数のフラグを用意すれば同じようにできます。
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
from itertools import zip_longest
from com.sun.star.sheet import CellFlags as cf # 定数
FLG = True  # ドキュメントイベントを有効にするフラグ。
ORDER = 0  # 呼び出された順番。
RESULTS = [("Order", "Event Name")]
DIC = {'OnStartApp': ("アプリケーションの開始時", "Start Application"),\
  'OnCloseApp': ("アプリケーション終了時", "Close Application"),\
  'OnCreate': ("文書作成時", "Document created"),\
  'OnNew': ("新規文書の開始時", "New Document"),\
  'OnLoadFinished': ("文書の読み込み終了時", "Document loading finished"),\
  'OnLoad': ("文書を開いた時", "Open Document"),\
  'OnPrepareUnload': ("文書が閉じられる直前", "Document is going to be closed"),\
  'OnUnload': ("文書を閉じた時", "Document closed"),\
  'OnSave': ("文書を保存する時", "Save Document"),\
  'OnSaveDone': ("文書を保存した時", "Document has been saved"),\
  'OnSaveFailed': ("文書の保存が失敗した時", "Saving of document failed"),\
  'OnSaveAs': ("別名で保存する時", "Save Document As"),\
  'OnSaveAsDone': ("文書を別名で保存した時", "Document has been saved as"),\
  'OnSaveAsFailed': ("'別名で保存'が失敗した時", "'Save as' has failed"),\
  'OnCopyTo': ("文書の保存もしくはエクスポート", "Storing or exporting copy of document"),\
  'OnCopyToDone': ("文書のコピーを作った時", "Document copy has been created"),\
  'OnCopyToFailed': ("文書のコピーが失敗した時", "Creating of document copy failed"),\
  'OnFocus': ("文書を有効化した時", "Activate Document"),\
  'OnUnfocus': ("文書を無効化した時", "Deactivate Document"),\
  'OnPrint': ("文書の印刷時", "Print Document"),\
  'OnViewCreated': ("ビューの作成時", "View created"),\
  'OnPrepareViewClosing': ("ビューが閉じられる直前", "View is going to be closed"),\
  'OnViewClosed': ("ビューを閉じた時", "View closed"),\
  'OnModifyChanged': ("'変更'ステータス変更時", "'Modified' status was changed"),\
  'OnTitleChanged': ("文書のタイトルを変更した時", "Document title changed"),\
  'OnVisAreaChanged': ("OnVisAreaChanged", "OnVisAreaChanged"),\
  'OnModeChanged': ("OnModeChanged", "OnStorageChanged"),\
  'OnStorageChanged': ("OnStorageChanged", "OnStorageChanged")}  # イベント名の辞書。 
def macro(arg=None):  # 引数は文書のイベント駆動用。  
 if FLG:  # フラグが立っている時のみ実行。
  global ORDER  # RESULTSはリストなのでglobalに指定しなくても書き込める。
  if arg.typeName=="com.sun.star.document.DocumentEvent":  # 引数がDocumentEventのとき。
   eventname = arg.EventName  # イベント名を取得。
   RESULTS.append((ORDER, eventname, *DIC[eventname]))  # 呼び出され順、イベント名、イベントの日本語UI名、英語UI名、arg.Souruce(イベントを発火させたオブジェクト)はドキュメントモデル。。
  else:  # 引数がDocumentEvent以外のときはない?
   RESULTS.append((ORDER, str(arg)))
  ORDER += 1
def output(documentevent=None):  # Calcのシートにイベントの呼び出し順を書き出す。
 macro(documentevent)  # このイベント自身の呼び出し順を取得。
 global FLG
 FLG = False  # フラグを倒してドキュメントイベントの結果をRESULTSに取得しないようにする。
 doc = XSCRIPTCONTEXT.getDocument()  # ドキュメントのモデルを取得。 
 sheet = getNewSheet(doc, "Events")  # 連番名の新規シートの取得。OnTitleChanged→OnModifyChangedが呼ばれてしまう。
 rowsToSheet(sheet[0, 0], RESULTS)  # 結果をシートに出力。シートを書き込むときのイベントを取得しないためにこの時点のRESULTSのコピーを渡す。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.setActiveSheet(sheet)  # 新規シートをアクティブにする。
 FLG = True  # フラグを戻す。
56行目以降は最初のマクロの52行目以降と同じなので省略しています。

6行目がドキュメントイベントの実行をキャンセルするグローバル変数FLGのフラグです。

49行目でFLGをFalseにしてイベントの実行がキャンセルされるようにしています。

documentevent2.ods

このマクロを埋め込んだドキュメントです。

動作結果は上記のdocumentevent.odsと同じなはずです。

ちなみに、シートイベントに対しても同様のことをしようと思いましたが、シートイベントから呼び出したマクロではグローバル変数を使えませんでした。

参考にしたサイト


OOobbs2/122 - ...?
ExcelのVBAでイベントの実行をキャンセルする方法。

次の関連記事:LibreOffice5(116)埋め込みマクロからPythonモジュールをロードする方法

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ