LibreOffice5(70)Javaの例:GUIをPythonにする その3

2017-08-22

旧ブログ

t f B! P L
unodialogsample.pyはたくさんのコントロールを扱うのとうまく動かないコントロールがあるのであとでやります。今回はロードマップコントロールの例のunodialogsample2.pyをやります。

前の関連記事:LibreOffice5(69)Javaの例:GUIをPythonにする その2


unodialogsample2.py: ロードマップコントロールでコントロールを切り替える



GUI/unodialogsample2.py at 9084736c560967b3c0d7b3daded09d08df5b4b1d · p--q/GUI
def macro():
    ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
    smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
    doc = XSCRIPTCONTEXT.getDocument()  # マクロを起動した時のドキュメントのモデルを取得。   
    docframe = doc.getCurrentController().getFrame()  # モデル→コントローラ→フレーム、でドキュメントのフレームを取得。
    docwindow = docframe.getContainerWindow()  # ドキュメントのウィンドウ(コンテナウィンドウ=ピア)を取得。
    toolkit = docwindow.getToolkit()  # ピアからツールキットを取得。  
    controlmargin = 6  # ロードマップコントロール以外のコントロールのマージン。
    dialogwidth, dialogheight = 250, 140  # ダイアログの幅と高さ
    roadmapwidth = 80  # ロードマップの幅。
    buttonwidth, buttonheight = 50, 14  # ボタンコントロールの幅と高さ。
    buttonposx = int(dialogwidth/2 - buttonwidth/2)  # 整数に変換が必要。ボタンの位置をダイアログの中央にもってくる。
    buttonposy = dialogheight - buttonheight - controlmargin  # ボタンのY座標。下縁からcontrolmarginの位置にする。
    controlposx = roadmapwidth + controlmargin  # Stepで切り替えるコントロールのX座標。ロードマップの右縁からcontrolmarginを確保。
    controlwidth = dialogwidth - 2*controlmargin - roadmapwidth  # Stepで切り替えるコントロールの幅。左右にcontrolmarginを確保。
    listboxheight = dialogheight - 4*controlmargin - buttonheight  # リストボックスの高さ。ボタンのcontrolmarginも引く。
    dialog, addControl = dialogCreator(ctx, smgr, {"Name": "Dialog1", "PositionX": 102, "PositionY": 41, "Width": dialogwidth, "Height": dialogheight, "Title": "Inspect a Uno-Object", "Moveable": True, "TabIndex": 0, "Step": 1})  # UnoControlDialogを生成、とそれにコントロールを使いする関数addControl。最初に表示するStepを指定している。
    linecount, fixedtextheight = 4, 8  # FixedTextの行数、1行の高さ。
    label = "This Dialog lists information about a given Uno-Object.\nIt offers a view to inspect all suppported servicenames, exported interfaces, methods and properties."
    addControl("FixedText", {"PositionX": controlposx, "PositionY": 27, "Width": controlwidth, "Height": fixedtextheight*linecount, "Label": label, "NoLabel": True, "Step": 1, "MultiLine": True})  # Step1で表示させるコントロール。
    introspection = smgr.createInstanceWithContext("com.sun.star.beans.Introspection", ctx)  # ロードマップコントロールで切り替えるネタの取得のため。
    introspectionaccess = introspection.inspect(doc)  # ドキュメントモデルを調べる。
    supportedservicenames = doc.getSupportedServiceNames()  # ドキュメントモデルのサービス一覧を取得。
    interfacenames = tuple(i.typeName for i in doc.getTypes())  # ドキュメントモデルのインターフェイス一覧を取得。 getTypename()メソッドはないと言われる。
    methodnames = tuple(i.getName() for i in introspectionaccess.getMethods(MethodConcept_ALL))  # ドキュメントモデルのメソッド一覧を取得。
    propertynames = tuple(i.Name for i in introspectionaccess.getProperties(PropertyConcept_ATTRIBUTES + PropertyConcept_PROPERTYSET))  # ドキュメントモデルのプロパティ一覧を取得。
    addControl("ListBox", {"PositionX": controlposx, "PositionY": controlmargin, "Width": controlwidth, "Height": listboxheight, "Dropdown": False, "ReadOnly": True, "Step": 2, "StringItemList": supportedservicenames})  # Step2で表示させるコントロール。     
    addControl("ListBox", {"PositionX": controlposx, "PositionY": controlmargin, "Width": controlwidth, "Height": listboxheight, "Dropdown": False, "ReadOnly": True, "Step": 3, "StringItemList": interfacenames})  # Step3で表示させるコントロール。     
    addControl("ListBox", {"PositionX": controlposx, "PositionY": controlmargin, "Width": controlwidth, "Height": listboxheight, "Dropdown": False, "ReadOnly": True, "Step": 4, "StringItemList": methodnames})  # Step4で表示させるコントロール。     
    addControl("ListBox", {"PositionX": controlposx, "PositionY": controlmargin, "Width": controlwidth, "Height": listboxheight, "Dropdown": False, "ReadOnly": True, "Step": 5, "StringItemList": propertynames})  # Step5で表示させるコントロール。     
    addControl("Button", {"PositionX": buttonposx, "PositionY": buttonposy, "Width": buttonwidth, "Height": buttonheight, "Label": "~Close", "PushButtonType": 1})  # ダイアログを閉じるボタンのコントロール。PushButtonTypeの値はEnumではエラーになる。
    addControl("FixedLine", {"PositionX": 0, "PositionY": buttonposy - controlmargin - 4, "Width": dialogwidth, "Height": 8, "Orientation": 0})  # 水平線の高さをロードマップの下縁の半分に食い込ませる。
    dialog.createPeer(toolkit, docwindow)  # ダイアログを描画。親ウィンドウを渡す。ノンモダルダイアログのときはNone(デスクトップ)ではフリーズする。Stepを使うときはRoadmap以外のコントロールが追加された後にピアを作成しないとStepが重なって表示される。
    items = ("Introduction", True),\
            ("Supported Services", True),\
            ("Interfaces", True),\
            ("Methods", True),\
            ("Properties", True)  # この順に0からIDがふられる。この順に表示される。
    addControl("Roadmap", {"PositionX": 0, "PositionY": 0, "Width": roadmapwidth, "Height": dialogheight - buttonheight - 2*controlmargin, "Complete": True, "CurrentItemID": 0, "Text": "Steps", "Items": items}, {"addItemListener": ItemListener(dialog)})  # Roadmapコントロールはダイアログウィンドウを描画してからでないと項目が表示されない。
    # ノンモダルダイアログにするとき。オートメーションではリスナーが動かない。
#     showModelessly(ctx, smgr, docframe, dialog)  
    # モダルダイアログにする。フレームに追加するとエラーになる。
    dialog.execute()  
    dialog.dispose()   
ドキュメントを開いた状態でmacro()をマクロセレクターで呼び出すとロードマップコントロールを使ったダイアログが表示されます。
class ItemListener(unohelper.Base, XItemListener): 
    def __init__(self, dialog):
        self.dialogmodel = dialog.getModel()
#     @enableRemoteDebugging
    def itemStateChanged(self, itemevent):
        dummy_control, dummy_controlmodel, name = eventSource(itemevent)
        if name == "Roadmap1":  # ロードマップコントロールのアイテムの選択が変更されたとき
            itemid = itemevent.ItemId + 1  # ItemIdは0から始まるのでStepと合わせるため1足す。
            step = self.dialogmodel.getPropertyValue("Step")  # 現在のダイアログのStepを取得。
            if itemid != step:  # ItemIdが現在のStepと異なるとき。
                self.dialogmodel.setPropertyValue("Step", itemid)  # StepをItemIdにする。
    def disposing(self, eventobject):
        pass 
XItemListenerインターフェイスを継承したItemListenerクラスをロードマップコントロールに付けて、項目が変更されたのに合わせてStepを切り替えて表示されるコントロールを変化させています。

UnoControlDialogModel(正確にはそのUnoControlDialogElement)のStepで起動した時に表示するStepを指定します。

Stepは0から始まりますが、0はデフォルトですべてのStepで表示される場合なので、1から使うことになります。

ダイアログのcreatePeer()を実行するタイミングは通常はコントロールを追加する前でも後でも同じですが、Stepを切り替えるときはすべてのStepのコントロールを追加した後にcreatePeer()しないと、コントロールが重なって表示されてしまいます。

ところが、UnoControlRoadmapだけはcreatePeer()してから追加しないと項目(RoadmapItem)が表示されません。

さらにRoadmapItemはコントロール(UnoControlRoadmap)にsetModel()されたコントロールモデル(UnoControlRoadmapModel)にしか挿入できません。

LibreOffice5(58)モードレスダイアログの例をPythonに翻訳する:その4で学習したようにコントロールはMVCパラダイムになっているので、モデルに設定する項目であっても、ビューに関連する項目はモデルをMVCパラダイムに組み入れたあとでないと設定できないようです。

RoadmapItemをUnoControlRoadmapModelに挿入するときはinsertByIndex()メソッドを使います。

insertByIndex()メソッドの第一引数の数字がロードマップコントロールの項目の先頭に表示される番号になり、この番号は自然数でなければなりません。

RoadmapItemにはIDもあり、インデックスと違いIDは0から始まる整数が空いている小さい方から自動採番されます。

RoadmapItemをUnoControlRoadmapModelに挿入するたびにCurrentItemIDが新たに挿入されたRoadmapItemのIDに切り替わってしまうので、最終的にハイライトさせたい項目のID(IDは0から始まることに注意)をすべてのRoadmapItemを挿入した後にCurrentItemIDに設定します。

こんなにややこしいことはすぐ忘れてしまうのでLibreOffice5(69)Javaの例:GUIをPythonにする その2で解説した汎用関数addControl()では、attrsのキーにitemsを作成してその値にRoadmapItemの属性LabelとEnabledの値をタプルにして、さらにそれらをタプルにして入れています。

IDも設定できるのですが、面倒なので先ほど述べたように自動採番に頼っています。

このIDはXItemListenerリスナーのitemStateChanged()メソッドの引数で返ってくるItemEvent StructのItemIDで取得できます。

unodialogsample2.pyではこのIDでStepを切り替えています。

IDは0から始まるので1を加えて、1から始まるStepに対応させています。

unodialogsample2.pyで使っている汎用関数


最初にでてくる関数enableRemoteDebugging()はEclipse: PyDevメモ: LibreOfficeのPythonマクロのデバッグで解説したデバッグコードです。

デバッガを使うとAPIリファレンスに載っていないプロパティに気がつくときがありますが、大多数のそういうプロパティはAPIリファレンスに載っているメソッドで取得できるものです。

あとでみたときにAPIリファレンスを参照できるので可能な限りAPIリファレンスに載っている方法を採用しています。

showModelessly()とdialogCreator()、オートメーションで実行するための部分はLibreOffice5(69)Javaの例:GUIをPythonにする その2で解説しているものと同じです。

今回新たに使っている汎用関数は次の一つだけです。

eventSource(event)
def eventSource(event):  # イベントからコントロール、コントロールモデル、コントロール名を取得。
    control = event.Source  # イベントを駆動したコントロールを取得。
    controlmodel = control.getModel()  # コントロールモデルを取得。
    name = controlmodel.getPropertyValue("Name")  # コントロール名を取得。    
    return control, controlmodel, name    
これはリスナーのメソッドの引数に与えられるイベントからコントロールと、コントロールモデル、コントロール名のタプルを返します。
(2017.9.25追記。コントロールのモデルのNameプロパティはUnoControlDialogModelのcreateInstance()メソッドでインスタンス化したコントロールモデルにしかないので、そうやってインスタンス化されてコントロールにしか使えません。)

次の関連記事:LibreOffice5(71)Javaの例:GUIをPythonにする その4

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ