LibreOffice5(137)グリッドコントロールを使うマクロの例

2018-01-29

旧ブログ

t f B! P L
グリッドコントロールはまずDefaultGridColumnModelで列を作成してグリッドコントロールに挿入し、その列にDefaultGridDataModelで行を一連のデータ単位として挿入するという手順になります。

前の関連記事: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つのセルだけ選択するということはできず、行全体の選択しかできませんでした。

次の関連記事:LibreOffice5(138)スクロールバーのサービスとインターフェイスの一覧

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ