Calc(78)グリッドコントロールの行をマウスでコピペする

公開日: 2018年02月18日 更新日: 2019年05月11日

旧ブログ

t f B! P L
LibreOffice5(137)グリッドコントロールを使うマクロの例のグリッドコントロールに追加したMouseListenerで右クリックしたときにコンテクストメニューを出現させ、行をコントロールダイアログ上でコピペできるようにしました。

前の関連記事:Calc(77)コントローラのロックとアクションのロックとリスナー


グリッドコントロールの行をマウスでコピペできるようにしたマクロ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import unohelper  # オートメーションには必須(必須なのはuno)。
from datetime import datetime
from com.sun.star.accessibility import AccessibleRole  # 定数
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.awt import XMenuListener
from com.sun.star.awt import XMouseListener
from com.sun.star.awt import MouseButton  # 定数
from com.sun.star.awt import PopupMenuDirection  # 定数
from com.sun.star.awt import Rectangle  # Struct
from com.sun.star.awt import ScrollBarOrientation  # 定数
from com.sun.star.document import XDocumentEventListener
from com.sun.star.style.VerticalAlignment import MIDDLE  # enum
from com.sun.star.util import XCloseListener
from com.sun.star.view.SelectionType import MULTI  # enum
def macro(documentevent=None):  # 引数は文書のイベント駆動用。import pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True)
    ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
    smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
    doc = XSCRIPTCONTEXT.getDocument()  # マクロを起動した時のドキュメントのモデルを取得。  
    controller = doc.getCurrentController()  # コントローラの取得。
    enhancedmouseclickhandler = EnhancedMouseClickHandler(controller, ctx, smgr, doc)
    controller.addEnhancedMouseClickHandler(enhancedmouseclickhandler)  # EnhancedMouseClickHandler
    doc.addDocumentEventListener(DocumentEventListener(enhancedmouseclickhandler))  # DocumentEventListener。ドキュメントのリスナーの削除のため。
class DocumentEventListener(unohelper.Base, XDocumentEventListener):
    def __init__(self, enhancedmouseclickhandler):
        self.args = enhancedmouseclickhandler
    def documentEventOccured(self, documentevent):  # ドキュメントのリスナーを削除する。
        enhancedmouseclickhandler = self.args
        if documentevent.EventName=="OnUnload"
            source = documentevent.Source
            source.removeEnhancedMouseClickHandler(enhancedmouseclickhandler)
            source.removeDocumentEventListener(self)
    def disposing(self, eventobject):
        eventobject.Source.removeDocumentEventListener(self)
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==0x6666FF# 背景が青色の時。
                        createDialog(ctx, smgr, doc, True# ノンモダルダイアログにする。
                        return False  # セル編集モードにしない。
                    elif cellbackcolor==0xFFFF99# 背景が黄色の時。
                        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": 118, "Height": 100, "ShowColumnHeader": True, "ShowRowHeader": True, "SelectionModel": MULTI, "VScroll": True, "ShowRowHeader": False# グリッドコントロールの基本プロパティ。
    label = {"Height": 12, "NoLabel": True, "Align": 2, "VerticalAlign": MIDDLE}  # ラベルフィールドコントロールの基本プロパティ。
    textbox = {"Height": label["Height"], "VerticalAlign": MIDDLE}  # テクストボックスコントロールの基本プロパティ。
    button = {"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)  # コントロールダイアログの作成。
    actionlistener = ActionListener()
    grid1 = addControl("Grid", grid)  # グリッドコントロールの取得。マウスリスナーはメニューリスナー作成後に追加する。
    menulistener = MenuListener(grid1)  # ポップアップメニューにつけるメニューリスナーを取得。
    items = ("~Cut", 0, {"setCommand": "cut"}),\
            ("Cop~y", 0, {"setCommand": "copy"}),\
            ("~Paste Above", 0, {"setCommand": "pasteabove"}),\
            ("P~aste Below", 0, {"setCommand": "pastebelow"}),\
            (),\
            ("~Delete Selected Rows", 0, {"setCommand": "delete"})  # グリッドコントロールにつける右クリックメニュー。
    popupmenu = menuCreator(ctx, smgr)("PopupMenu", items, {"addMenuListener": menulistener})  # 右クリックでまず呼び出すポップアップメニュー。 
    mouselister = MouseListener(doc, popupmenu, menulistener)
    grid1.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()  # 現在の日時を取得。
    d = now.date().isoformat()
    t = now.time().isoformat().split(".")[0]
    griddata.addRow(0, (d, t))  # グリッドに行を挿入。
    label["PositionY"] = textbox["PositionY"] = YHeight(grid, m)
    label1, label2 = [label.copy() for dummy in range(2)]
    textbox1, textbox2 = [textbox.copy() for dummy in range(2)]
    label1["Label"] = "Date: "
    label1["PositionX"] = m
    label1["Width"] = 18
    textbox1["PositionX"] = XWidth(label1)
    textbox1["Width"] = 42
    textbox1["Text"] = d
    label2["Label"] = "Time: "
    label2["PositionX"] = XWidth(textbox1, m)
    label2["Width"] = 18
    textbox2["PositionX"] = XWidth(label2)
    textbox2["Width"] = 34
    textbox2["Text"] = t
    addControl("FixedText", label1)
    addControl("Edit", textbox1) 
    addControl("FixedText", label2)
    addControl("Edit", textbox2) 
    button["PositionY"= YHeight(label, m)
    button1, button2, button3 = [button.copy() for dummy in range(3)]
    button1["Label"] = "~Now"
    button1["PositionX"] =  controldialog["Width"] - (button["Width"] + m) * 3
    button2["Label"] = "~Insert"
    button2["PositionX"] = XWidth(button1, m)
    button3["Label"] = "~Close"
    button3["PositionX"] = XWidth(button2, m)
    button3["PushButtonType"] = 2  # CANCEL 
    addControl("Button", button1, {"setActionCommand": "now" ,"addActionListener": actionlistener}) 
    addControl("Button", button2, {"setActionCommand": "insert" ,"addActionListener": actionlistener}) 
    addControl("Button", button3) 
    dialog.getModel().setPropertyValue("Height", YHeight(button1, m))  # コントロールダイアログの高さを設定。
    dialog.createPeer(toolkit, containerwindow)  # ダイアログを描画。親ウィンドウを渡す。ノンモダルダイアログのときはNone(デスクトップ)ではフリーズする。Stepを使うときはRoadmap以外のコントロールが追加された後にピアを作成しないとStepが重なって表示される。
    if flg:  # ノンモダルダイアログにするとき。オートメーションでは動かない。
        dialogframe = showModelessly(ctx, smgr, frame, dialog) 
        args = popupmenu, menulistener, dialog, mouselister, actionlistener
        dialogframe.addCloseListener(CloseListener(args))  # CloseListener。ノンモダルダイアログのリスナー削除用。
    else# モダルダイアログにする。フレームに追加するとエラーになる。
        dialog.execute() 
        dialog.dispose() 
def XWidth(props, m=0):  # 左隣のコントロールからPositionXを取得。mは間隔。
    return props["PositionX"] + props["Width"] + m  
def YHeight(props, m=0):  # 上隣のコントロールからPositionYを取得。mは間隔。
    return props["PositionY"] + props["Height"] + m
class CloseListener(unohelper.Base, XCloseListener):  # ノンモダルダイアログのリスナー削除用。
    def __init__(self, args):
        self.args = args
    def queryClosing(self, eventobject, getsownership):
        popupmenu, menulistener, dialog, mouselister, actionlistener = self.args
        popupmenu.removeMenuListener(menulistener)
        dialog.getControl("Grid1").removeMouseListener(mouselister)
        dialog.getControl("Button1").removeActionListener(actionlistener)
        eventobject.Source.removeCloseListener(self)
    def notifyClosing(self, eventobject):
        pass
    def disposing(self, eventobject): 
        eventobject.Source.removeCloseListener(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.date().isoformat())  # テキストボックスコントロールに入力。
            context.getControl("Edit2").setText(now.time().isoformat().split(".")[0])  # テキストボックスコントロールに入力。
        elif cmd=="insert":
            d = context.getControl("Edit1").getText()
            t = context.getControl("Edit2").getText()  
            gridcontrol = context.getControl("Grid1"# グリッドコントロールを取得。
            griddata = gridcontrol.getModel().getPropertyValue("GridDataModel"# グリッドコントロールモデルからDefaultGridDataModelを取得。
            selectedrows = gridcontrol.getSelectedRows()  # 選択行インデックスのタプルを取得。
            if not selectedrows:  # 選択行がない時。
                selectedrows = griddata.RowCount-1# 最終行インデックスを選択していることにする。
            insertRows(gridcontrol, griddata, selectedrows, 1, ((d, t),))  # 選択行の下に行を挿入する。
    def disposing(self, eventobject):
        eventobject.Source.removeActionListener(self)
class MouseListener(unohelper.Base, XMouseListener): 
    def __init__(self, doc, popupmenu, menulistener):
        self.args = doc, popupmenu, menulistener
    def mousePressed(self, mouseevent):  # グリッドコントロールをクリックした時。
        doc, popupmenu, menulistener = self.args
        gridcontrol = mouseevent.Source  # グリッドコントロールを取得。
        if mouseevent.Buttons==MouseButton.LEFT and mouseevent.ClickCount==2# ダブルクリックの時。
            selection = doc.getCurrentSelection()  # シート上で選択しているオブジェクトを取得。
            if selection.supportsService("com.sun.star.sheet.SheetCell"):  # 選択オブジェクトがセルの時。
                griddata = gridcontrol.getModel().getPropertyValue("GridDataModel"# GridDataModelを取得。
                rowdata = griddata.getRowData(gridcontrol.getCurrentRow())  # グリッドコントロールで選択している行のすべての列をタプルで取得。
                selection.setString(" ".join(rowdata))  # 選択セルに書き込む。
        elif mouseevent.PopupTrigger:  # 右クリックの時。
            rowindex = gridcontrol.getRowAtPoint(mouseevent.X, mouseevent.Y)  # クリックした位置の行インデックスを取得。該当行がない時は-1が返ってくる。
            if rowindex>-1# クリックした位置に行が存在する時。
                popupmenu.enableItem(3, True# Pasteメニューを表示する。
                popupmenu.enableItem(4, True# Pasteメニューを表示する。
                if not gridcontrol.isRowSelected(rowindex):  # クリックした位置の行が選択状態でない時。
                    gridcontrol.deselectAllRows()  # 行の選択状態をすべて解除する。
                    gridcontrol.selectRow(rowindex)  # 右クリックしたところの行を選択する。
                rows = gridcontrol.getSelectedRows()  # 選択行インデックスを取得。
                rowcount = len(rows)  # 選択行数を取得。
                if rowcount>1 or not menulistener.rowdata:  # 複数行を選択している時または保存データがない時 。
                    popupmenu.enableItem(3, False# Pasteメニューを表示しない。
                    popupmenu.enableItem(4, False# Pasteメニューを表示しない。
                pos = Rectangle(mouseevent.X, mouseevent.Y, 0, 0# ポップアップメニューを表示させる起点。
                popupmenu.execute(gridcontrol.getPeer(), pos, PopupMenuDirection.EXECUTE_DEFAULT)  # ポップアップメニューを表示させる。引数は親ピア、位置、方向  
    def mouseReleased(self, mouseevent):
        pass
    def mouseEntered(self, mouseevent):
        pass
    def mouseExited(self, mouseevent):
        pass
    def disposing(self, eventobject):
        eventobject.Source.removeMouseListener(self)
class MenuListener(unohelper.Base, XMenuListener):
    def __init__(self, gridcontrol):
        self.gridcontrol = gridcontrol
        self.rowdata = None
    def itemHighlighted(self, menuevent):
        pass
    def itemSelected(self, menuevent):  # PopupMenuの項目がクリックされた時。
        gridcontrol = self.gridcontrol
        griddata = gridcontrol.getModel().getPropertyValue("GridDataModel"# GridDataModelを取得。
        selectedrows = gridcontrol.getSelectedRows()  # 選択状態の行インデックスのタプルを取得。
        cmd = menuevent.Source.getCommand(menuevent.MenuId)
        if cmd=="cut"# 選択行のデータを取得してその行を削除する。
            self.rowdata = [griddata.getRowData(r) for r in selectedrows]  # 選択行のデータを取得。
            [griddata.removeRow(r) for r in selectedrows]  # 選択行を削除。
        elif cmd=="copy"# 選択行のデータを取得する。 
            self.rowdata = [griddata.getRowData(r) for r in selectedrows]  # 選択行のデータを取得。
        elif cmd=="pasteabove"# 行を選択行の上に挿入。
            insertRows(gridcontrol, griddata, selectedrows, 0, self.rowdata)
        elif cmd=="pastebelow"# 空行を選択行の下に挿入。 
            insertRows(gridcontrol, griddata, selectedrows, 1, self.rowdata)
        elif cmd=="delete"# 選択行を削除する。 
            [griddata.removeRow(r) for r in selectedrows]  # 選択行を削除。
    def itemActivated(self, menuevent):
        pass
    def itemDeactivated(self, menuevent):
        pass  
    def disposing(self, eventobject):
        eventobject.Source.removeMenuListener(self)
def insertRows(gridcontrol, griddata, selectedrows, position, datarows):  # positionは0の時は選択行の上に挿入、1で下に挿入。
    c = len(datarows)  # 行数を取得。
    griddata.insertRows(selectedrows[0]+position, ("", )*c, datarows)  # 行を挿入。
    gridcontrol.deselectAllRows()  # 行の選択状態をすべて解除する。
    gridcontrol.selectRow(selectedrows[0]+position)  # 挿入した行の最初の行を選択する。
LibreOffice5(72)Javaの例:GUIをPythonにする その5のmenuCreator()、LibreOffice5(69)Javaの例:GUIをPythonにする その2のshowModelessly()とdialogCreator()を使っています。

XGridDataModelインターフェイスのgetRowData()メソッドで1行の列の値がタプルで取得できるので、切り取りやコピーはそのタプルをリストにして取得しています(219行目と222行目)。

ペーストはXMutableGridDataModelインターフェイスのinsertRows()メソッドを使って複数行を一度に挿入しています。

1行ずつ行を挿入するinsertRow()メソッドもありますが、insertRows()メソッドでも1行を挿入できるのでinsertRows()メソッドだけで事足ります。

addRow()やaddRows()メソッドで行を追加すると行ヘッダに1から順に空き番号が自動的に振られますが、insertRow()やinsertRows()メソッドでは行ヘッダが自動的に割り当てられません。

なのでこのマクロでは行ヘッダを空文字にして行を挿入し、グリッドコントロールモデルのShowRowHeaderプロパティをFalseにして行ヘッダを非表示にしています。

複数行を選択できるようにするためにはSelectionModelプロパティにenum SelectionTypeにMULTIを指定しないといけません。

Calcドキュメントのシートと違ってダブルクリックするとセルの編集モードになるというのはないので、Insertボタンでテキストボックスコントロールからデータを取得して新規行に挿入します。

GridControl2.ods

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

GridControl2.odsの実行結果



青色のセルをダブルクリックするとノンモダルダイアログ、黄色のセルをダブルクリックするとモダルダイアログとしてコントロールダイアログが出現します。

NowボタンをクリックするとテキストボックスコントロールのDateとTimeに現在の日付と時刻が入力されます。


InsertボタンをクリックするとテキストボックスコントロールのDateとTimeの文字列をグリッドコントロールの最下行に追加します。


NowボタンとInsertボタンを交互にクリックするとNowボタンをクリックしたときの日時がグリッドコントロールに追加されていきます。


グリッドコントロールの行を右クリックするとコンテクストメニューが出現します。


行をCutかCopyをしたあとに右クリックするとPaste Above、Paste Belowの項目が追加されます。

Paste Aboveは選択行の上にペースト、Paste Belowは選択行の下にペーストします。


ShiftキーやCtrlキーで複数行を選択すると複数行について切り取りやコピー、削除ができます。

複数行を切り取りやコピーしたときはまとめてペーストします。

次の関連記事:Calc(79)NamedRangesのサービスとインターフェイス一覧

ブログ検索 by Blogger

Translate

«
Feb. 2018
»
Sun
Mon
Tue
Wed
Thu
Fri
Sat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Created by Calendar Gadget

QooQ