TkinterによるPythonのGUIプログラミング

趣旨

  • たまに使おうとするとウェブを漁りまくることになるので,注意点だけまとめておく。

ドキュメント

tkとttk

  • テーマを設定すれば一括してウィジットの見た目を変更できるのがttk。
  • 多くのウィジットが同名のウィジットがあるけれど,canvas, menu, messageはttkにはないので注意する。

ttkテーマの設定

style = ttk.Style()
available_themes = style.theme_names() # 利用可能テーマの確認
style.theme_use('clam')  # テーマを指定する。

ウィジット配置

  • 3つのマネージャーが用意されているが,座標指定のplaceはほぼ使わない。1行または1列に並ぶだけの場合にはpackマネージャーがが有用。ただし,多くの場合はpackでは済まないので,gridを使うことが基本になる。グリッドと言っても,columnspan/rowspanで複数行列に跨ることは可能なので,大概は問題ない。
  • 適切な配置をするためにexpand/anchor/fill/padxy/ipadxyのオプションについて理解しておく。
    • packのオプション
      • expand:親ウィジットの拡大縮小に応じてリサイズするかどうか。
      • anchor:左寄せ,上寄せなど,スペースに空きがある場合にどこに寄せるか。東西南北(EWSN)により8方向の中から指定する。例えば,右上は'NE'
      • fill:スペースがある場合にウィジットを拡大する。x, y, bothでどちらに拡大するかを指定する。
      • padxy:外側のスペース。
      • ipadxy:内側のスペース。
    • gridのオプション
      • row/column:行列を指定。0から開始。
      • row/columnspan:ぶち抜く場合には幅を指定する。
      • padxy,ipadxy:packと同じ。
      • sticky:packのfill+anchorのオプション。sticky=tk.Eだと右寄せ。fillする場合は'+'でつなぐ。例えば,横に伸ばす場合にはsticky=tk.W+tk.E,全体はsticky=tk.W+tk.E+tk.N+tk.S,みたいな要領。

ウィンドウの拡大縮小

  • 親フレームをexpand, fill, anchorでrootと連動して拡大縮小するようにpackしておく。
  • 子ウィジットはgridで設置する際に,拡大縮小したいウィジットをのcolumnconfigure, rowconfigureのweightを1にする。デフォルトでは0になっている。よって,例えば,画面サイズに依存せず同じ大きさで良いボタンなどはデフォルトのまま(0)にしておいて,textやentryなどを1にしておくと,そのウィジットだけ拡大縮小される。
parent_frame.columnconfigure(1, weight=1)  # 列方向は1列目が拡大縮小される。
parent_frame.rowconfigure(2, weight=1)     # 行方向は2行目が拡大縮小される。

値の取得方法

  • textやentryなどの値は,ウィジット生成時にtextvariableにtk.Variableを指定しておけば自動的にその変数に値が格納される。
  • tk.Variableは型ごとにIntVar, StringVar, BooleanVarなどが用意されていて,変換もしてくれる。
  • 値を取得する場合にはget(),設定する場合はset()。生の変数(StringVarなど)はそのままでは値ではないので注意する。
entry_var = Tk.StringVar()
entry = Tk.Entry(self, textvariable=entry_var)

コールバックの設定(command/bind)について

  • ボタンを押した場合などの処理をcommandにて設定することが可能である。
  • 注意としては,commandに渡す関数は引数を取れないことである。よって,関数生成時に引数をクロージャで包み込んでおいて,それをcommandに渡すようにする。

textウィジット

  • 文字列の書き込みはinsertを使う。第一引数で書き込み開始位置を,第2引数で文字列を渡す。
  • 現在の最後尾がtk.ENDで指定できる。
  • 書き込むだけだと,表示位置は動いてくれないので,seeで表示位置も動かす。
txt_wdgt.insert(tk.END, (''hogehoge))  # 文字列はなぜかカッコで囲む。
txt_wdgt.see(tk.END)

イベント処理

キーイベント

  • ウィジット毎にバインドできる。当然rootウィンドウにもバインドできる。
  • callbackは1引数としてevent情報を取る関数。(クラス内ならselfも入れて2引数)
  • イベントには下記のプロパティが設定されている。
    • x,y: マウス位置
    • num: マウスボタンの番号(1:左,2:真ん中,3:右)
    • time: イベント時刻
    • char: キー文字
    • keysym: キーに対する名前
# widget.bind(<Event Sequence>, callback)の書式
# Event Sequence = <modifier>-<modifier>-<type>-<detail>の書式
#  i.e.) <Control-Shift-A>, <Buttun-1-KeyPress>, <Button-1-KeyPress-Motion>
root.bind('<Right>', move_right)  # arrow key:[Right, Left, Up, Down]
root.bind('<Escape>', move_right)  # arrow key:[Right, Left, Up, Down]

def move_right(e):
    print(e.x)

ウィジット情報の取得と設定

  • cgetで取得し,configureで設定する。ただし,canvasの項に記載の通り,ウィジットの大きさに関してはcgetは初期値の設定情報のようで,リアルタイムな値はwinfo_xxxを確認する。
  • ウィジットはそれぞれwinfo_xxx(widget information)を持っている。
  • xxxとしてはgeometory, width, height, x, y, rootx, rootyがある。x,yは親ウィジット内での位置,rootx,yはディスプレイ上の位置。
  • ウィジットが生成された直後に出力してもwinfo_xxxが正しく取れない場合があるので注意。その場合はupdate_idletasksをして実行させるか,他の処理で時間を消費してからアクセスする。

Canvasウィジット

色の指定

  • '#RGB'で各色16進(0〜F)で指定する。各色2バイトにも出来る(0〜FF)。

オブジェクト操作

  • Canvas内のオブジェクト(Rectangle)などはそれぞれIDを持っており,move, deleteなどIDを介して操作が可能。
  • scale関数もあるので,ウィンドウのリサイズに応じてオブジェクトをリサイズすることも可能。

ウィンドウのリサイズに伴うオブジェクトのリサイズ

  • Canvasウィジット自体はpackならexpandに,gridならcolumnconfigureなどで自動でリサイズできる。
  • ただし,Canvas内の描画オブジェクトはリサイズされないので別途対処が必要。
  • ウィンドウ(rootウィジット)のりサイズはにて検出する。そして,検出イベントのコールバックとして各図形をリサイズする。
  • キャンバスのりサイズ後の大きさはcget(configure get)では得られない。cgetでは初期設定値が返ってくる。代わりに,winfo_width(), winfo_height()にて取得可能。
  • ウィジットが生成された直後に出力してもwinfo_xxxが正しく取れない場合があるので注意。その場合はupdate_idletasksをして実行させるか,他の処理で時間を消費してからアクセスする。
root.bind('<Configure>', rescale_objects)

クラスとして書く

  • フラットに書かれている例が多いが,クラス化しておくと見通しも良いので,クラスの書き方テンプレートをもとに拡張する。
  • フラットに書いてあるものと基本は同じ。
import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        master.title("My application")
        self.pack(expand=1, fill=tk.BOTH, anchor=tk.NW)
        self.create_widgets()

    def create_widgets(self):
        self.label = tk.Label(self, text='Input file')
        self.label.grid(column=0, row=0)
        
root = tk.Tk()
app = Application(master=root)
app.mainloop()

その他

バージョン確認

  • Python3.6付属のTkinter8.0以降は見た目がきれいになっている。
$ python -c "import tkinter;print(tkinter.TkVersion)"

ウィジットのプロパティへのアクセス

  • 各ウィジットのプロパティ一覧はconfigure()にて取得できる。
w = tk.WidgetXXX()
print(w.configure())

スクロールについて

  • textなどにおいて,スクロールバーは付けなくてもどんどん延長は自動でしてくれる。記述が煩雑で見た目もきれいではないので付けなくても良い気がする。