前の関連記事:LibreOffice5(136)Map AppFont (ma)とピクセルと1/100mmの変換
グリッドコントロールをダイアログコントロールに表示するマクロ
import unohelper # オートメーションには必須(必須なのはuno)。
from datetime import datetime
from com.sun.star.style.VerticalAlignment import MIDDLE
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XMouseListener
from com.sun.star.accessibility import AccessibleRole # 定数
from com.sun.star.awt import ScrollBarOrientation # 定数
from com.sun.star.awt import MouseButton # 定数
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.util import XCloseListener
def macro(documentevent=None): # 引数は文書のイベント駆動用。import pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True)
ctx = XSCRIPTCONTEXT.getComponentContext() # コンポーネントコンテクストの取得。
smgr = ctx.getServiceManager() # サービスマネージャーの取得。
doc = XSCRIPTCONTEXT.getDocument() # マクロを起動した時のドキュメントのモデルを取得。
controller = doc.getCurrentController() # コントローラの取得。
controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler(controller, ctx, smgr, doc)) # EnhancedMouseClickHandler
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler):
def __init__(self, subj, ctx, smgr, doc):
self.subj = subj
self.args = ctx, smgr, doc
def mousePressed(self, enhancedmouseevent):
ctx, smgr, doc = self.args
target = enhancedmouseevent.Target # ターゲットのセルを取得。
if enhancedmouseevent.Buttons==MouseButton.LEFT: # 左ボタンのとき
if target.supportsService("com.sun.star.sheet.SheetCell"): # ターゲットがセルの時。
if enhancedmouseevent.ClickCount==2: # ダブルクリックの時
cellbackcolor = target.getPropertyValue("CellBackColor") # セルの背景色を取得。
if cellbackcolor==0x8080FF: # 背景が青紫色の時。
createDialog(ctx, smgr, doc, True) # ノンモダルダイアログにする。
return False # セル編集モードにしない。
elif cellbackcolor==0xFFFF80: # 背景が黄色の時。
createDialog(ctx, smgr, doc, False) # モダルダイアログにする。
return False # セル編集モードにしない。
return True # セル編集モードにする。
def mouseReleased(self, enhancedmouseevent):
return True # シングルクリックでFalseを返すとセル選択範囲の決定の状態になってどうしようもなくなる。
def disposing(self, eventobject): # ドキュメントを閉じる時でも呼ばれない。
self.subj.removeEnhancedMouseClickHandler(self)
def createDialog(ctx, smgr, doc, flg):
frame = doc.getCurrentController().getFrame() # モデル→コントローラ→フレーム、でドキュメントのフレームを取得。
containerwindow = frame.getContainerWindow() # ドキュメントのウィンドウ(コンテナウィンドウ=ピア)を取得。
toolkit = containerwindow.getToolkit() # ピアからツールキットを取得。
m = 6 # コントロール間の間隔
grid = {"PositionX": m, "PositionY": m, "Width": 145, "Height": 100, "ShowColumnHeader": True, "ShowRowHeader": True} # グリッドコントロールの基本プロパティ。
label = {"PositionX": m, "Width": 45, "Height": 12, "Label": "Date and time: ", "NoLabel": True, "Align": 2, "VerticalAlign": MIDDLE} # ラベルフィールドコントロールの基本プロパティ。
x = label["PositionX"]+label["Width"] # ラベルフィールドコントロールの右端。
textbox = {"PositionX": x, "Width": grid["PositionX"]+grid["Width"]-x, "Height": label["Height"], "VerticalAlign": MIDDLE} # テクストボックスコントロールの基本プロパティ。
button = {"PositionX": m, "Width": 30, "Height": label["Height"]+2, "PushButtonType": 0} # ボタンの基本プロパティ。PushButtonTypeの値はEnumではエラーになる。
controldialog = {"PositionX": 100, "PositionY": 40, "Width": grid["PositionX"]+grid["Width"]+m, "Title": "Grid Example", "Name": "controldialog", "Step": 0, "Moveable": True} # コントロールダイアログの基本プロパティ。幅は右端のコントロールから取得。高さは最後に設定する。
dialog, addControl = dialogCreator(ctx, smgr, controldialog) # コントロールダイアログの作成。
mouselister = MouseListener(doc)
actionlistener = ActionListener()
grid1 = addControl("Grid", grid, {"addMouseListener": mouselister}) # グリッドコントロールの取得。
gridmodel = grid1.getModel() # グリッドコントロールモデルの取得。
gridcolumn = gridmodel.getPropertyValue("ColumnModel") # DefaultGridColumnModel
column0 = gridcolumn.createColumn() # 列の作成。
column0.Title = "Date" # 列ヘッダー。
column0.ColumnWidth = 60 # 列幅。
gridcolumn.addColumn(column0) # 列を追加。
column1 = gridcolumn.createColumn() # 列の作成。
column1.Title = "Time" # 列ヘッダー。
column1.ColumnWidth = grid["Width"] - column0.ColumnWidth # 列幅。列の合計がグリッドコントロールの幅に一致するようにする。
gridcolumn.addColumn(column1) # 列を追加。
griddata = gridmodel.getPropertyValue("GridDataModel") # GridDataModel
now = datetime.now() # 現在の日時を取得。
griddata.addRow(0, (now.date().isoformat(), now.time().isoformat())) # グリッドに行を挿入。
y = grid["PositionY"] + grid["Height"] + m # 下隣のコントロールのY座標を取得。
label["PositionY"] = textbox["PositionY"] = y
textbox["Text"] = now.isoformat().replace("T", " ")
addControl("FixedText", label)
addControl("Edit", textbox)
y = label["PositionY"] + label["Height"] + m # 下隣のコントロールのY座標を取得。
button1, button2 = button.copy(), button.copy()
button1["PositionY"] = button2["PositionY"] = y
button1["Label"] = "~Now"
button2["Label"] = "~Close"
button2["PushButtonType"] = 2 # CANCEL
button2["PositionX"] = grid["Width"] - button2["Width"]
button1["PositionX"] = button2["PositionX"] - m - button1["Width"]
addControl("Button", button1, {"setActionCommand": "now" ,"addActionListener": actionlistener})
addControl("Button", button2)
dialog.getModel().setPropertyValue("Height", button1["PositionY"]+button1["Height"]+m) # コントロールダイアログの高さを設定。
dialog.createPeer(toolkit, containerwindow) # ダイアログを描画。親ウィンドウを渡す。ノンモダルダイアログのときはNone(デスクトップ)ではフリーズする。Stepを使うときはRoadmap以外のコントロールが追加された後にピアを作成しないとStepが重なって表示される。
if flg: # ノンモダルダイアログにするとき。オートメーションでは動かない。
dialogframe = showModelessly(ctx, smgr, frame, dialog)
dialogframe.addCloseListener(CloseListener(dialog, mouselister, actionlistener)) # CloseListener
else: # モダルダイアログにする。フレームに追加するとエラーになる。
dialog.execute()
dialog.dispose()
class CloseListener(unohelper.Base, XCloseListener): # ノンモダルダイアログのリスナー削除用。
def __init__(self, dialog, mouselister, actionlistener):
self.args = dialog, mouselister, actionlistener
def queryClosing(self, eventobject, getsownership):
dialog, mouselister, actionlistener = self.args
dialog.getControl("Grid1").removeMouseListener(mouselister)
dialog.getControl("Button1").removeActionListener(actionlistener)
def notifyClosing(self, eventobject):
pass
def disposing(self, eventobject):
eventobject.Source.removeCloseListener(self)
class MouseListener(unohelper.Base, XMouseListener):
def __init__(self, doc):
self.args = doc
def mousePressed(self, mouseevent):
if mouseevent.Buttons==MouseButton.LEFT and mouseevent.ClickCount==2: # ダブルクリックの時。
doc = self.args
selection = doc.getCurrentSelection() # シート上で選択しているオブジェクトを取得。
if selection.supportsService("com.sun.star.sheet.SheetCell"): # 選択オブジェクトがセルの時。
source = mouseevent.Source # グリッドコントロールを取得。
griddata = source.getModel().getPropertyValue("GridDataModel") # GridDataModelを取得。
rowdata = griddata.getRowData(source.getCurrentRow()) # グリッドコントロールで選択している行のすべての列をタプルで取得。
selection.setString(" ".join(rowdata)) # 選択セルに書き込む。
def mouseReleased(self, mouseevent):
pass
def mouseEntered(self, mouseevent):
pass
def mouseExited(self, mouseevent):
pass
def disposing(self, eventobject):
eventobject.Source.removeMouseListener(self)
class ActionListener(unohelper.Base, XActionListener):
def actionPerformed(self, actionevent):
cmd = actionevent.ActionCommand
source = actionevent.Source # ボタンコントロールが返る。
context = source.getContext() # コントロールダイアログが返ってくる。
if cmd == "now":
now = datetime.now() # 現在の日時を取得。
context.getControl("Edit1").setText(now.isoformat().replace("T", " ")) # テキストボックスコントロールに入力。
grid1 = context.getControl("Grid1") # グリッドコントロールを取得。
griddata = grid1.getModel().getPropertyValue("GridDataModel") # グリッドコントロールモデルからDefaultGridDataModelを取得。
griddata.addRow(griddata.RowCount, (now.date().isoformat(), now.time().isoformat())) # 新たな行を追加。
accessiblecontext = grid1.getAccessibleContext() # グリッドコントロールのAccessibleContextを取得。
for i in range(accessiblecontext.getAccessibleChildCount()): # 子要素をのインデックスを走査する。
child = accessiblecontext.getAccessibleChild(i) # 子要素を取得。
if child.getAccessibleContext().getAccessibleRole()==AccessibleRole.SCROLL_BAR: # スクロールバーの時。
if child.getOrientation()==ScrollBarOrientation.VERTICAL: # 縦スクロールバーの時。
child.setValue(child.getMaximum()) # 最大値にスクロールさせる。
return
def disposing(self, eventobject):
eventobject.Source.removeActionListener(self)
LibreOffice5(69)Javaの例:GUIをPythonにする その2のshowModelessly()とdialogCreator()の表記は略しています。ダイアログコントロール上のコントロールは隣のコントロールの位置と大きさから、位置を算出しています。
55行目でグリッドコントロールモデルからDefaultGridColumnModelを取得してcreateColumn()メソッドで新規列を作成して、addColumn()メソッドでその列をDefaultGridColumnModelに挿入しています。
列のインデックスはaddColumn()の追加順になっていました。
64行目でグリッドコントロールモデルからDefaultGridDataModelを取得してaddRow()メソッドで行インデックスを指定して、列数と一致した要素のタプルを挿入することでセルに値を代入しています。
gridcontrol.ods
このマクロを埋め込んだCalcドキュメントです。
gridcontrol.odsの実行例
gridcontrol.odsの青紫色が背景のセルをダブルクリックするとノンモダルダイアログが出現します。
ダイアログ内のテーブルがグリッドコントロールです。
ダイアログが開いた時の日時が1行目に入っています。
Nowボタンをクリックするとボタンをクリックしたときの日時が追加されていきます。
すべての行が表示できなくなるとグリッドコントロールに縦スクロールバーが出現します。
デフォルトでは行を追加してもその行が見えるようにスクロールはされませんが、このマクロではスクロールバーを取得して常に追加された行が表示されるようにしています(137行目)。
スクロールバーの取得は、グリッドコントロールにそれを取得するメソッドを見つけられず、 getAccessibleContext()メソッドで取得したAccessibleContextの子要素を走査してAccessibleRoleがSCROLL_BARのものを探しだしています。
スクロールバーのAccessibleContextのサービスとインターフェイスはLibreOffice5(118)コンポーネントウィンドウのサービスとインターフェイスの一覧でみた、コンポーネントウィンドウのものと同じでした(AccessibleContextは常にAccessibleWindow?)。
グリッドコントロールのいずれかの行をダブルクリックするとその行のセルの値を結合して選択したセルに書き込みます。
青紫色が背景のセルのときはノンモダルダイアログなのでダイアログを表示させたままセルを変更できます。
それに対して背景が黄色のセルのときはモダルダイアログなのでダイアログ表示後にセルは変更できません。
グリッドコントロールではCalcのシートと違って1つのセルだけ選択するということはできず、行全体の選択しかできませんでした。




0 件のコメント:
コメントを投稿