LibreOffice5(22)ダイアログエディタでGUIを作成しPythonで利用する:その2

前の関連記事:LibreOffice5(21)ダイアログエディタでGUIを作成しPythonで利用する:その1


前回の使ったダイアログエディタで作成したGUIを利用するPythonスクリプトについてLibreOfficeのAPIと照合して何をしているのか順番にみていきます。オートメーションではコンテクストの伝播を意識しないとダイアログエディタで作成したダイアログを取得できません。

IPython NotebookでLibreOfficeのオートメーション

ソケット通信の状態で起動したLibreOfficeに接続します。
In [1]:
import unopy
XSCRIPTCONTEXT = unopy.connect()
if not XSCRIPTCONTEXT:
    print("Failed to connect.")
    import sys
    sys.exit(0)
LibreOffice5(13)unoinsp.py:IDL名から継承図を出力するのunoinspモジュールのObjInspをインスタンス化します。
In [2]:
import unoinsp
ins = unoinsp.ObjInsp(XSCRIPTCONTEXT)
これでinsのメソッドでIDL名やオブジェクトを元にTypeDescriptionオブジェクトから属性やメソッドを調べることが出来ます。
但しXInterface、XWeak、XTypeProviderの3つのインターフェイスは省くように設定してます。

DialogProviderサービスにはインターフェイス介さないメソッドがある

まずクラス定義の部分まで実行してしまいます。
In [3]:
import uno
import unohelper
from com.sun.star.awt import XActionListener
class MyActionListener(unohelper.Base, XActionListener):  # com.sun.star.awt.XActionListenerインターフェイスを実装。
    def __init__(self, ctrl):  # インスタンス化時にコントロールオブジェクトを引数に取る。
        self.ctrl = ctrl
    def actionPerformed(self, evnt):  # イベントを受けとった時に実行するメソッド。
        self.ctrl.setText("Hello World")  # コントロールにテキストを渡す。
つぎにcom.sun.star.awt.DialogProviderサービスについてみてみます。
まずサービス名を元にIDLの定義からその属性を調べます。
In [4]:
ins.itree("com.sun.star.awt.DialogProvider")

com.sun.star.awt.DialogProvider
└─.awt.DialogProvider
      └─.awt.XDialogProvider
                  .awt.XDialog  createDialog( [in] string URL
                                   ) raises ( .lang.IllegalArgumentException)
DialogProviderサービスはXDialogProviderインターフェイスをもちそのメソッドcreateDialog()をもっていることがわかります。
ところがAPIリファレンスをみるとDialogProviderサービスはこのcreateDialog()以外に、インターフェイスを介さずにcreateWithModel()メソッド、createWithModelAndScripting()メソッドを持っているようにみえます。
UNOのインターフェイスとサービスについてはLibreOffice(11)LibreOfficeのオブジェクトとUNOのインターフェイスで学習しましたが、デベロッパーガイドのObjects, Interfaces, and Servicesを読んでもサービスがインターフェイスを介さずメソッドを持つことはないはずです。
このインターフェイスを介さないメソッドが意味するところはLibreOffice5(23)ダイアログエディタでGUIを作成しPythonで利用する:その3でDialogProviderサービスを実装したオブジェクトをinsのメソッドで調べてわかることになります。

オートメーションではコンテクストの伝播がとくに重要

「コンテクストの伝播(Context Propagation)」とはLibreOffice(15)デベロッパーガイド3 コンポーネントコンテクストで学習したものです。
UNOオブジェクトを生成するときにコンテクストの伝播を保つためにXMultiServiceFactoryインターフェイスではなくXMultiComponentFactoryインターフェイスを使いましょう、というものでした。
つまり、createInstance()メソッドやcreateInstanceWithArguments()メソッドは使わずに、createInstanceWithContext()メソッドやcreateInstanceWithArgumentsAndContext()メソッドを使いましょう、ということです。
これまでコンテクストの伝播に気を使わずに困ることはなかったのですが、今回はコンテクストの伝播の意味を実感できました。
とりあえずLibreOffice5(21)ダイアログエディタでGUIを作成しPythonで利用する:その1でやったのと同様にローカルコンテクストでcom.sun.star.awt.DialogProviderサービスをインスタンス化します。
In [5]:
localctx = uno.getComponentContext()  # ローカルコンポーネントコンテクストを取得。
dp = localctx.getServiceManager().createInstanceWithContext("com.sun.star.awt.DialogProvider", localctx)  # ローカルコンポーネントコンテクストからcom.sun.star.awt.DialogProviderサービスをインスタンス化。
このインスタンスdpはローカルコンテクストを伝播しています。
マクロモードで実行した時はこれで問題はありませんでした。
In [6]:
dialog_name = "Dialog2"  # ダイアログエディタで作成したダイアログ名。
dlg_ctrl = dp.createDialog("vnd.sun.star.script:Standard."+dialog_name+"?location=application")  # コマンドURLでダイアログエディタで作成したダイアログを取得。
---------------------------------------------------------------------------
com.sun.star.uno.DeploymentException      Traceback (most recent call last)
<ipython-input-7-e8e787b19da8> in <module>()
      1 dialog_name = "Dialog2"  # ダイアログエディタで作成したダイアログ名。
----> 2 dlg_ctrl = dp.createDialog("vnd.sun.star.script:Standard."+dialog_name+"?location=application")  # コマンドURLでダイアログエディタで作成したダイアログを取得。

com.sun.star.uno.DeploymentException: null process service factory
ところがコマンドURLでダイアログエディタで作成したダイアログを取得しようとするとnull process service factoryという例外が返ってきてしまいます。
これは今回やっているオートメーションではLibreOffice(5)PythonでLibreOfficeが動く仕組み:UNOで学習したようにLibreOfficeプロセスとは別のプロセスで動いているのでマクロモードとは違ってコンテクストがLibreOfficeプロセスとは異なるからです。
LibreOffice(4)PyCharmからLibreOfficeを動かす(オートメーション)でやったようにunopy.connect()で得たXSCRIPTCONTEXTはunopy.pyでLibreOfficeプロセスのコンポーネントコンテクストと同期させたコンポーネントコンテクストを持っているのでそこからコンポーネントコンテクストを取得し直します。
In [7]:
ctx = XSCRIPTCONTEXT.getComponentContext()  # XSCRIPTCONTEXTからコンポーネントコンテクストを取得。
dp = ctx.getServiceManager().createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)  # コンポーネントコンテクストからcom.sun.star.awt.DialogProviderサービスをインスタンス化。
これでLibreOfficeプロセスのコンテクストを伝播したインスタンスdpを取得できました。
In [8]:
dialog_name = "Dialog2"  # ダイアログエディタで作成したダイアログ名。
dlg_ctrl = dp.createDialog("vnd.sun.star.script:Standard."+dialog_name+"?location=application")  # コマンドURLでダイアログエディタで作成したダイアログを取得。
今度は問題なくダイアログを取得できました。
マクロモードではローカルコンポーネントコンテクストからインスタンス化しても自動的に同期される、、、というかマクロモードのローカルコンポーネントコンテクストはつまりLibreOfficeプロセスのコンポーネントコンテクストなので問題はおきませんが、オートメーションでは別プロセスなのでコンテクストの伝播が必要な訳です。

参考にしたサイト


[libreoffice-users] Re: Cannot instantiate SlideRenderer service from python script - The Document Foundation Mailing List Archives
このスレッドを読んでオートメーションでダイアログを取得できない原因に気が付きました。

次の関連記事:LibreOffice5(23)ダイアログエディタでGUIを作成しPythonで利用する:その3

PR

0 件のコメント:

コメントを投稿