"BOKU"のITな日常

62歳・文系システムエンジニアの”BOKU”は日々勉強を楽しんでます

python標準の「tkinter」を使って、とりあえず試す用のGUI画面を作る

コマンドではなく、GUI画面から入力して動かしたくなるケースがあります。

今回、おそらく、もっとも手軽な選択肢「tkinter」の話題です。

f:id:arakan_no_boku:20200627132450p:plain

 

はじめに

 

tkinterは、Python標準のGUIライブラリで、 Tcl/Tk の Python インタフェースです。

ja.wikipedia.org

GUIは古く感じるデザインです。

なので、これで本番アプリケーションを作ろうとは思いません。

設計が古いせいか、ちょっと癖がありますしね。

でも、「ちょこっと試す」ためのGUIを構築するには便利なライブラリです。

今回は、よく使う以下のWidgetに絞って自分の使い方を紹介しようと思います。

  • Entry : 1行テキスト入力(テキストボックス)
  • Text  : 複数行テキスト入力(テキストエリア)
  • OptionMenu : プルダウンメニュー
  • CheckButton : チェックボックス
  • Button : サブミットボタン

他にもいろいろありますが、正直、ちょっと試す程度にしか使わないので、今のところ使ってません。(笑)

必要になったら、こちらのドキュメントとかを参考にして追加しますけどね。

effbot.org

 

tkinterで自分がめんどくさいと思うこと

 

tkinterを素で使うと、レイアウトがめんどくさいです。

GUIの設計画面があるわけもありませんし、なにより、配置する座標をX・Yの座標で指定してやって、表示して微調整・・が面倒です。

自分的には、ちゃちゃっと試すのに使いたいだけなので、こんなことに神経をつかいたくはありません。

ということで。

汎用性を犠牲にして、部品を1行ずつペタペタ書いていけば、それなりの画面になるっていうように使い方をするための、こんな感じのクラスを作ってます。

gui_templete.py

import tkinter as tk
from tkinter import messagebox


class Gui:
    def __init__(self, title='Demo window'):
        self.window_width = 640
        self.window_height = 480
        self.left_space = 20
        self.right_space = 30
        self.top_space = 20
        self.row_span = 50
        self.col_span = 10
        self.label_width = 80
        self.x = self.left_space
        self.y = self.top_space
        self.root = tk.Tk()
        self.root.title(title)
        self.root.geometry(str(self.window_width) +
                           "x" + str(self.window_height))
        self.parts_list = []

    def text_box(self, label):
        x = self.x
        y = self.y
        w = self.label_width
        pw = 80
        lb = tk.Label(text=label)
        lb.place(x=x, y=y)
        box = tk.Entry(width=pw)
        box.place(x=x + w + self.col_span, y=y)
        self.y = self.y + self.row_span
        return box
    
    def text_area(self, label):
        x = self.x
        y = self.y
        w = self.label_width
        pw = 68
        lb = tk.Label(text=label)
        lb.place(x=x, y=y)
        box = tk.Text(width=pw, height=6)
        box.place(x=x + w + self.col_span, y=y)
        self.y = self.y + self.row_span + 50
        return box

    def check_box(self, label, var):
        x = self.x
        y = self.y
        w = self.label_width
        box = tk.Checkbutton(text=label, variable=var)
        box.place(x=x + w + self.col_span, y=y)
        self.y = self.y + self.row_span
        return box

    def combo_box(self, label, values, select):
        x = self.x
        y = self.y
        w = self.label_width
        lb = tk.Label(text=label)
        lb.place(x=x, y=y)
        optionList = values
        variable = tk.StringVar(self.root)
        variable.set(optionList[0])
        box = tk.OptionMenu(self.root, variable, *optionList, command=select)
        box.place(x=x + w + self.col_span, y=y)
        self.y = self.y + self.row_span
        return box

    def button(self, label, command):
        button = tk.Button(text=label, command=command)
        button.place(x=self.x, y=self.y)
        self.y = self.y + self.row_span
        return button

    def show(self):
        self.root.mainloop()

    def msgbox(self, msg1, msg2):
        messagebox.showinfo(msg1, msg2)
    
    def textindex1(self):
        return '1.0'

    def textindex2(self):
        return 'end -1c'

簡単に保続します。 

Windowsサイズとか、左右トップの各マージンとかは固定にしてます。

self,xとself.yに「次に描画する部品の座標」を保持するように、部品の描画後に更新するようにしています。

あと、複数行テキストの「Text」の場合、1行目から最終行までをとりだす場合の指定の仕方がちょっと独特です。

text.get('1.0','end-1c')

なんともはや。

こんな書き方・・たいてい使うときには忘れてます。

なので、それを「textindex1」「textindex2」メソッドで指定できるようにしました。

あと。

optionMenuを使うプルダウンリスト(combo_boxという名前にしてますが(;^_^A)の表示データの渡し方で、「*optionList」みたいにポインタ渡しが必要だったりするところとか。

checkbuttonもそうですが、価の取得のためのコールバック関数名を渡してやらないといけないところとか・・。

特徴的な部分で、わかりづらいのは・・こんなもんですかね。

 

このクラスを使うサンプルです

 

パーツをぺたぺたのひとつずつ配置し、ボタンを押したときに入力値を取得して、メッセージボックスに表示してみます。

ソースコードはこんな感じです。

import gui_templete as gt
import tkinter as tk


def button_click():
    s = t1.get() + "\n" + selected + "\n" + str(chkValue.get()) + \
        "\n" + t4.get(wdw.textindex1(), wdw.textindex2())
    wdw.msgbox("クリックイベント", s)


def select(value):
    global selected
    selected = value


wdw = gt.Gui()
t1 = wdw.text_box(label='入力部品1')
oplist = [
    "選択するもの1",
    "選択するもの2"
]
selected = oplist[0]
chkValue = tk.BooleanVar()
chkValue.set(True)
t2 = wdw.combo_box(label='入力部品2', values=oplist, select=select)
t3 = wdw.check_box(label='入力部品3', var=chkValue)
t4 = wdw.text_area(label='入力部品4')
b1 = wdw.button(label='実行ボタン', command=button_click)
wdw.show()

各部品をひとつずつ配置してます。 

実行すると、以下のような画面を表示します。

f:id:arakan_no_boku:20200628144444p:plain

適当に入力したり、選択しなおしてみます。

f:id:arakan_no_boku:20200628144748p:plain

実行ボタンを押してみます。

f:id:arakan_no_boku:20200628144915p:plain

入力値は、取得できているみたいです。

 

データの取得について補足です

 

 ikinterのwidgetでデータを取得する方法を簡単にまとめておきます。

まず、1行入力のテキストボックス(Entry)は簡単です。

t1.get()

のように、get()を呼ぶだけです。

これが複数行テキスト入力(Text)になると、get()の引数に、開始行(index1)と終了行(index2)の指定が必要なのは、前に書きましたので飛ばします。

プルダウンリスト(OptionMenu)は、状態が変化したときに呼び出されるコールバック関数を用意して、その中でglobal変数に値をセットして、それを参照するみたいなことをする必要があります。

上記のソースなら、この部分です。

def select(value):
    global selected
    selected = value

なのでボタンクリックで参照しているのは、selectedなのです。 

チェックボックス(CheckButton)の場合は、状態をセットする参照用の変数を定義してやる必要があります。

それがこの部分です。

chkValue = tk.BooleanVar()
chkValue.set(True)
t3 = wdw.check_box(label='入力部品3', var=chkValue)

この場合だと、チェックボックスが選択されたり、解除されたりしたときに、chkValueの値が変化します。

なので。

chkValue.get()

でとりだせているわけです。

この変数定義しないといけないところが、クラスのインタフェースとしてはイマイチだなあ・・と思ってはいますが、まあいいかな・・と放置(笑)してます。

かなり、いい加減なクラスですが、自分の用途(ちょっとお試しで画面を使いたいときの使い捨て画面作成)には十分かなとわりきってます。

今回はこんなところで。

ではでは。