Pythonでマクロ(5):コントロールを制御する

Pythonでマクロ(4):ダイアログ(フォーム)の配置で配置したダイアログ上にある部品(コントロール)を制御する。

このダイアログには「テキストボックス」と「ボタン」の二つのコントロールが配置されている。

このダイアログの意図していることは
『ユーザが「テキストボックス」に語句を入力し、「ボタン」をクリックするとこの語句が別の窓に表示される』という簡単なものである。
マクロは以下のようになる:


#coding: utf-8
import uno
import screen_io as ui
import unohelper
from com.sun.star.awt import XActionListener

class MyActionListener(unohelper.Base, XActionListener):
  def __init__(self, ctrl):
    self.ctrl = ctrl
  def actionPerformed(self, evnt):
    ui.Print(self.ctrl.Text)

def createDialog():
  ctx = XSCRIPTCONTEXT.getComponentContext()
  smgr = ctx.getServiceManager()
  dp = smgr.createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)
  dialog = dp.createDialog("vnd.sun.star.script:Standard.Dialog2?location=application")
#コントロールの登録
  txtf1 =  dialog.getControl("TextField1")
  cmdbtn1 = dialog.getControl("CommandButton1") 
#エヴェント監視(ボタンが押されたとき)
  btn1_listener = MyActionListener(txtf1)
  cmdbtn1.addActionListener(btn1_listener)
  dialog.execute()
  dialog.dispose()

ダイアログ自体の登録は以前紹介したマクロと同じである。新たに登録したものはテキストボックス(名称:TextField1)とボタン(名称:CommandButtam1)の二つのコントロールである。

クラスMyActionListenerは親クラスunohelper.Baseと XActionListenerを継承している。。この新たなクラスは親クラスXActionListenerで定義されているメソッドactionPerformedを再定義するためのものである。メソッドactionPerformedはコントロール(今の場合はボタン)をマウスの左ボタンでクリックしたときのイヴェントを処理するメソッドである。コントロール「ボタン」で起こり得るイヴェントはこれ以外にも沢山ある(15個)。例えばmouseEntered 。これはマウスがそのコントロールに入ったときのイヴェントで、親クラスはXMouseListenerである。詳細はここで。

メソッドactionPerformedではクラスMyActionListenerのインスタンスbtn1_listenerの引数として渡されたコントロールtxtf1の属性Textの中身をPrint文で表示する。各コントロールが持つ属性の詳細はここで。

 

 

Pythonでマクロ(4):ダイアログの配置

作成したダイアログをLibreOffice:Calcのシート画面上に配置するだけのマクロである。

【ダイアログの作成】

シート画面のプロダウンメニュ「ツール」->「ダイアログの管理」と進む。管理画面で配置ツリーから「マイダイアログ」-> 「Standard」と進み、それをクリックすると連番号のダイアログ名(今はDialog2)が現れる。「新規作成」のボタンを選ぶとマッサラなダイアログ画面がでる。

この画面にいろんな部品(コントロールと呼ぶ)を配置する。

作例では「テキストボックス」(名称:TextField1)と「ボタン」(名称:CommandButtom1)を配置した。プレビューで実際のダイアログの画面が現れる。作例の画面を示す:

上のコントロールが「テキストボックス」で、下のコントロールが「ボタン」である。このダイアログはユーザが「テキストボックス」に字句を入力して「ボタン」を押すとその字句がプログラムで使える(例えばその字句をCalcの表に表示する)。

【配置】

このダイアログをCalcのシート上に配置するマクロを作ってみる。マクロの全体は以下のようになる:


#coding: utf-8

import uno
import unohelper

def dialog_example(*args):
     ctx = XSCRIPTCONTEXT.getComponentContext()
     smgr = ctx.getServiceManager()
     dp = smgr.createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)
     dialog = dp.createDialog("vnd.sun.star.script:Standard.Dialog2?location=application")
     dialog.execute()
     dialog.dispose()

Basicのマクロ記述に比較すると結構複雑な記述になる。Transfer from Basic to Pythonに詳しい説明がある。

Pythonでマクロ(3):セルの属性(文字の色等)を変える

表計算(Calc)のシート画面で条件にしたがって文字の色を変えるマクロである。

10X10のセルには0から100までの数値が書かれている。これは教課目の試験の点数だ。これに対してユーザは及第点を入力する(ui.InputBox関数を使う)。マクロではこの及第点と点数を比較して及第点に及ばないときはこのセルの中の文字を赤にする。

マクロ全体を示す:


#coding: utf-8
import uno
import screen_io as ui
import random

def level_judge_macro( *args):
  doc = XSCRIPTCONTEXT.getDocument()
  sheet = doc.Sheets[0]
  for  i in range(10):
    for j in range(10):
      sheet.getCellByPosition(i,j).Value=random.randrange(101)
#
  level = ui.InputBox('及第点を入力してください')
  level_v = int(level)
  for  i in range(10):
    for j in range(10):
      cell = sheet.getCellByPosition(i, j )
      point_v =int(cell.Value)
      if point_v < level_v:
        cell.CharColor = 0xff0000

  return

セルの属性(文字の色)は
cell.CharColor = 0xff0000
で16進定数で与えた。

InputBoxの実行イメージは:

とラベルの付いた入力窓である。

Pythonでマクロ(2):マクロ実行ボタンを作る

LibreOfficeでマクロを実行するときに便利なマクロ実行ボタンを配置することにする。

LibreOfficeのCalcの表計算のシート上にマクロ実行ボタンを付けることにする。

ボタンはフォーム機能が持つ部品の一つである。Calcの画面でプルダウンメニュー「表示」-> 「ツールバー」-> 「フォームコントロール」を選択し、フォーム作成のためのツール一覧を出しておく。

まず一覧窓の上部にあるフォーム機能の切り替えをデザイン・モードする。様々な部品が使えるようになる。その中の「ボタン」選択し、シートの適当な場所にドラックして配置する。

設置したボタンアイコンを右クリックしてメニューから「コントロールのプロパティー」を選択する。「全般」ではボタンの表面にあるタイトルを変えたり(例えば「マクロの実行」)、ボタンの背景色を変えたりできる。「イベント」ではこのボタンで起こり得るイベントとそのイベント処理プログラム(イベント・ハンドラ)を繋げることをする。今の場合は「ボタンが押されたとき」に問題のマクロが実行されるように繋ぐ(マクロに実行関数は引数なしでよいが、イベントハンドラーでは不定個数の引数を取れるようにする。つまりmacro()  -> macro(*arg))。

これで準備ができた。デザイン・モードをオフにしてボタンを押してみる。マクロが実行されることを確認する。

フォーム実装例

【重要】このまま表を保存すると再度この表を開いたときにはフォームはデザインモードになっている。これは開発中であればよいがこのフォームをユーザが使うときは都合が悪い。表を開いたときにフォームのデザインモードがオフではじめたい。そのためにはこの表を保存するまえに

プルダウンメニュ「ツール」-> 「フォーム」-> 「フォームナビゲーター」選択。そのナビゲーター画面で「フォーム」を右クリックしデザインモードを解除した後に保存する。

Pythonでマクロ(1):誕生日から今日まで日数を知る

LibreOfficeのマクロをpythonで作ってみた。

標題のように「誕生日から今日まで日数を知る」ためのマクロである。表の誕生日データ(年、月、日)を使い目的の日数を表に記入する。

【例】1950/12/05生まれの人は今日(2021/04/29)までで25713日経過している。

暦計算ではユリウス日(Julian Day)が使われる。これは紀元前4713年1月1日からの連続した通し番号の日数である。この連続した日付けを使うと誕生日から今日までに経過した日数を知ることができる。Pythonではdatetimeモジュールでこれらの機能を提供している。これをマクロで使う。

プログラムの全体は以下のようになる:


#coding: utf-8
import uno
import screen_io as ui
import time
from datetime import date

class Birthday:
    def __init__(self, mydate):
        self.year= mydate[0]
        self.month=mydate[1]
        self.day= mydate[2]
    def get_date(self):
        return date(self.year, self.month, self.day)
    def get_days(self):
        today = date.today()
        my_birthday=date(self.year, self.month, self.day)
        time_to_birthday=abs(today-my_birthday)
        return time_to_birthday.days
    def get_years_days(self):
        my_birthday=date(self.year, self.month, self.day)
        today = date.today()
        today_year=today.year
        my_last_birthday=date(today.year-1,self.month, self.day)
        lastday=date(today_year-1,12,31)
        my_birthday_thisyear=date(today.year, self.month, self.day)
        daydiff_today=abs(today-lastday)
        daydiff_birthday=abs(my_birthday_thisyear-lastday)
        if daydiff_today > daydiff_birthday:
            yearspan=today.year - my_birthday.year
            dayspan=abs(today-my_birthday_thisyear)
        else:
            yearspan=today.year - my_birthday.year-1
            dayspan=abs(today - my_last_birthday)
        return (yearspan, dayspan.days)
        

def elapsed_days_macro():
    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.Sheets[0]
    today=date.today()
    msg = "今日は"+str(today)+"です。"
    ui.Print( msg)
    #データのある行数を調べる
    sRange = sheet.getCellRangeByName("A1")
    sCursor=sheet.createCursorByRange(sRange)
    sCursor.collapseToCurrentRegion()
    MaxDataRow = sCursor.Rows.Count-1
    for  i in range(MaxDataRow):
        year = int(sheet.getCellByPosition(0, i+1 ).Value)
        month =int(sheet.getCellByPosition(1, i+1 ).Value)
        day =  int(sheet.getCellByPosition(2, i+1 ).Value)
        mydate=(year, month, day)
        my_birthday=Birthday(mydate)
        sheet.getCellByPosition(3,i+1).Value=my_birthday.get_days()
        sheet.getCellByPosition(4,i+1).Value=my_birthday.get_years_days()[0]
        sheet.getCellByPosition(5,i+1).Value=my_birthday.get_years_days()[1]
    return

LIbreOfficeのPythonマクロで使える関数・定数などの情報は「マクロの森」が詳しい。また、ASOPのデバッガやコンソールはプログラミングに結構役に立つことが分った、

 

ASOP(Alternative Script Organizer for Python)を使ってみる

LibreOfficeのマクロをPythonで作るときに使えるユーティリティーにASOPというLibreOffice用の拡張機能がある。

ASOPの詳しい説明はここにある。

【準備】

早速最新版のapso.oxtファイルをダウンロードする。

LibreOfficeへの登録は

  • libreOfficeのアプリケーションの一つを開く。プルダウンメニューの「ツール」->「拡張機能マネジャー」
  • 登録済みの機能が表示される。
  • 追加ボタンを押してダウンロードしたaop.oxtファイルを指名する。
  • 登録が終わるとASOPが登録済みとなる。この窓の左下のあるオプションボタンを選択して、コードを記述するためのエディターを登録する(terapadを登録してみた)。

【実行】

早速ASOPを使ってみる。

Calcの「ツール」->「マクロ」->「Pythonスクリプトの管理」を選択する。ASOPの管理窓がでるので実行したいマクロを選択する。管理窓の「メニュー」から「デバッグ」を選ぶと以下のようなデバッガの画面がでる:

デバッグ画面

この画面で「端末」ボタンを押すとPythonコンソールの別画面が出る。「次へ」のボタンでプログラムの一行毎の実行と変数の値等が表示される。apso.oxtのインストールと同様にMRI.oxtをインストールしておくとこのデバッガの画面で変数の名前空間の性質も調べられる。「編集」ボタンで登録したエディタでマクロの編集ができる。「再スタート」ボタンを押すと編集の結果が反映される。「終了」ボタンを押すとデバッガは終了する(このデバッガはかなり有効に使える感じである)。またマクロにprint文があるとASOP管理下ではコンソールその出力がでる(これはprint文の多用したデバッグには便利)。

Pythonでマクロ(0):LibreOfficeのマクロを書いてみる

久ぶりのPythonの話題である。

LibreOfficeはそのマクロをPythonで書くことができるが、pythonが元々機能として持っていたprint文などがそのままでは使えない。このままではユーザインタフェースやデバッグ機能に欠けた状況でPythonマクロはあまり魅力的ではない。

調べてみるとprint文などに対応するユーザフェース機能をマクロが使うモジュールとして用意することできることがわかった。少し面白くなった。まずマクロのサンプルを示す:


import uno
import screen_io as ui


def my_first_macro_calc():
    doc = XSCRIPTCONTEXT.getDocument()
    for num, column in enumerate(['A1', 'A2', 'A3']):
        cell = doc.Sheets[0][column]
        cell.Value = num 
        ui.Print('OK?')
    return

このマクロの保存場所は(windowsの場合)

C:\Users\~~\AppData\Roaming\LibreOffice\4\user\python

である。

これはLibreOfficeの表計算ソフトCalcのシート番号0のシートの’A1’欄に数値0、’A2’欄に数値1、’A3’欄の数値2を入れる簡単なマクロである。

ui.Print(‘OK’)は数値を書き込む毎に警告窓を出す。ここではデバッグ機能として使っている。この関数Printはscreen_ioモジュールで定義されていて、import screen_io as uiとしてインポートされてされている。import unoがシステムモジュールのインポートであるのに対してimport screen_ioはユーザモジュールのインポートである。このモジュールのの実体はここにある。このモジュールの保存場所は

C:\Users\~~\AppData\Roaming\LibreOffice\4\user\python\pythonpath

である。

このモジュールはbasic言語で作ったモジュールのラッパーらしい。このbasic言語のモジュールの実体もここにある。このモジュールも保存する。保存する場所はLibreOfficeのプルダウン・メニュのマクロで

マイマクロ->standard->module1と進み、編集ボタンで編集画面になるので実体をペーストし、保存する(モジュール名をuiScriptsとすることを忘れないで)。

以上でPrint関数を含むマクロを実行できる環境ができあがった。

最後にPrint関数の実行結果の画面を示す:

【蛇足】pythonでマクロを書くときの注意:

  • indentはTabキーではダメで半角スペースを使う。
  • Pythonでよく使うprint文は使えない
  • 登録しようとしているマクロに構文エラーがあるとマクロの登録すらできない。しかもその構文エラーを知る手掛りがない

 

 

ディジタル化:センサー・マニピュレータ・小規模ネットワーク・ビッグデータ

盛んに「ディジタル化」が叫ばれている。

この「ディジタル化」は国民の生活の向上と生産活動の効率化に資するものでなければならない。

それに必要なものがセンサー・マニピュレータ・小規模ネットワーク・ビッグデータ処理技術である。

例えば農業で温室の管理を「ディジタル化」しようとする。

センサー:温室管理には温室内に設置された様々なセンサーからの情報が必要となる。温度・湿度・雨量・日照・酸素濃度など温室内の多くの場所に設置したセンサーが時系列にデータ吐き出す。

マニピュレータ:センサーからの情報に従って換気、水遣り装置を操作する多数のマニピュレータが必要となる。

小規模ネットワーク:センサー類及びマニピュレータは小規模のネットワークとして組織化される必要がある。

ビッグデータ処理技術:大量の(時系列)データが得られるがこの膨大なデータから必要な情報を取り出す技術も不可欠である。

これらの技術の集積で温室の自動管理が可能となる。このように国民生活・生産活動の多くの分野でディジタル技術を適用することが「ディジタル化」である。

Python備忘録:ジェネレータの実際

ジェネレータはnext関数を使って呼び出す。この関数の呼び出し毎にジェネレータは一定の長さのデータを吐き出す。この機構を使って動画ファイルをフレーム毎に読みこむことにする。このような例をimageioモジュールで見てみる。このモジュールは様々な画像ファイル(動画ファイルを含む)を処理することができる。今回の例は動画ファイルを読み込みヴィデオ表示するPythonプログラムである。

プログラム例


#coding: utf-8
import tkinter as tk, threading
import imageio
from PIL import Image, ImageTk

video_name = "test.mp4" #ここにヴィデオ・ファイル名を書く
video = imageio.get_reader(video_name)
gen = video.iter_data()
def stream(label):
    def showlabel():
        global count
        try:
            image=next(gen)
        except: 
            return
        frame_image = ImageTk.PhotoImage(Image.fromarray(image))
        label.config(image=frame_image)
        label.image = frame_image
        label.after(14, showlabel) #フレーム・レイトの調整
    showlabel()

if __name__ == "__main__":

    root = tk.Tk()
    root.title('TKinterでヴィデオを見る')
    my_label = tk.Label(root)
    my_label.pack()
    thread = threading.Thread(target=stream, args=(my_label,))
    thread.daemon = 1
    thread.start()
    root.mainloop()

赤い文字の部分がジェネレータに関連する部分で、video = imageio.get_reader(video_name)はファイルを読み込む機構の設定で、video.iter_data()でその機構にジェネレータの性格を付与している。

iter_data()
Iterate over all images in the series. (Note: you can also iterate over the reader object.)

と説明がある。
image=next(gen)でフレーム毎にデータを読み込みそれをimageに代入している。フレーム毎にデータを読みこみデータが尽きるとエラーになるのでエラー処理をしている。

Python備忘録:関数でジェネレータをつくる

関数でジェネレータをつくることもできる。


def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

この関数ではreturn文の代わりにyield文を使う。yield文がfor文の内部にあることに注意。yield文に達するとこの関数は一時停止し値を返すが、この関数のローカル変数などは保持されたままである。この一時停止の解除はこの関数を呼んだプログラムの要請による(同期している)。
上の関数の実行例を示す。


>>> s = reverse('golf')
>>> s
<generator object reverse at 0x0000000002ED07C8>
>>> next(s)
'f'
>>> next(s)
'l'
>>> next(s)
'o'
>>> next(s)
'g'
>>> next(s)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in 
    next(s)
StopIteration
>>>