Python
GUI
python2.7
Tkinter

PythonのTkinterでGUIアプリを作る

More than 1 year has passed since last update.

PythonでGUIアプリを作ることになって,Tkinter使ったら早かったので,次使うとき用の備忘録です.

雛形

import Tkinter as Tk

class Application(Tk.Frame):
    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.pack()

root = Tk.Tk()
app = Application(master=root)
app.mainloop()

ここに色々なWidgetをくっつけていくことになります.

Widget1

名称 役割
Button ボタン
Canvas 画像を貼り付けたり描画したり
Checkbutton チェックボタン
Entry 1行のテキストエリア.複数行はText
Frame 他のWidgetをまとめるフレーム
Label ラベル.画像も表示できる
Listbox リストボックス
Menu メニューバーの作成
Message 複数行のラベル
OptionMenu コンボボックス
Radiobutton ラジオボタン
Scale スライダー
Scrollbar スクロールバー
Text 複数行のテキストエリア
Toplevel ポップアップウィンドウなどを作る用

Widgetの配置

Widgetの配置にはpack,place,gridメソッドのいずれかを使います.
packはWidgetを一列,または1行に並べていきます.placeは各Widgetの場所を指定して配置します.gridは2次元座標上にWidgetを配置していきます(Excel方眼紙のようなイメージです).
以降は基本的にgridを使います.

    def __init__(self, master=None):
        # 省略
        self.create_widgets()

    def create_widgets(self):
        self.label = Tk.Label(self, text=u'入力ファイル')
        self.entry = Tk.Entry(self)
        self.button = Tk.Button(self, text=u'開く')
        self.check = Tk.Checkbutton(self, text=u'拡張子をtxtに限定')
        self.text = Tk.Text(self)

        self.label.grid(column=0, row=0)
        self.entry.grid(column=1, row=0)
        self.button.grid(column=2, row=0)
        self.check.grid(column=0, row=1)
        self.text.grid(column=0, columnspan=3, row=2)

配置の調整と伸縮への対応

はじめにexpand,fill,anchorを設定し,親フレームを伸縮に対応させます.
anchorや後述のstickyで出てくるTk.XXは位置を東西南北(NSEW)で指定できます.

    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.pack(expand=1, fill=Tk.BOTH, anchor=Tk.NW)
        self.create_widgets()

そして各Widgetをgridで配置するときに,stickyでどこを起点にどう伸縮させるかを指定します.
もう一つ大事なのは,gridでWidgetを配置したときには,columnconfigureとrowconfigureでどの行・列がどの比率で伸縮するのかを指定してやる必要があります.
デフォルトではweightパラメータが0(伸縮しない)になっているので,単純には伸縮させたい行・列のweightを1にしてやります.
以下の例ですと,選択したファイル名が入ることを想定しているEntryのある1列目をTextのある2行目のみが伸縮に対応します.

        self.label.grid(column=0, row=0, sticky=Tk.W)
        self.entry.grid(column=1, row=0, sticky=Tk.EW)
        self.button.grid(column=2, row=0, sticky=Tk.E)
        self.check.grid(column=0, columnspan=2, row=1, sticky=Tk.W)
        self.text.grid(column=0, columnspan=3, row=2, sticky=Tk.NSEW)

        self.columnconfigure(1, weight=1)
        self.rowconfigure(2, weight=1)

Entryの中身やCheckbuttonのチェック状態の取得

Entryの中身やCheckbuttonのチェック状態は(text)variableオプションに変数を設定しておくことで取得できます.

        self.var_entry = Tk.StringVar()
        self.entry = Tk.Entry(self, textvariable=self.var_entry)
        self.var_check = Tk.BooleanVar()
        self.check = Tk.Checkbutton(self, text=u'拡張子をtxtに限定',
                                    variable=self.var_check)

Tk.StringVar()やTk.BooleanVar()の値の設定や取得にはset(),get()を使います.

ボタン押下時のアクション

ボタン押下時に実行するメソッドはcommandオプションで指定します.

    def create_widgets(self):
        self.var_entry = Tk.StringVar()
        self.entry = Tk.Entry(self, textvariable=self.var_entry)
        self.button = Tk.Button(self, text=u'開く', command=self.button_pushed)

    def button_pushed(self):
        self.var_entry.set(u'ボタンが押されました.')

ファイルダイアログ

ファイルダイアログを開くには,tkFileDialogをimportして,askopenfilenameを使います.filetypesオプションを使うことでファイルタイプの指定ができます.
tkFileDialogには他に,askopenfile,askopenfiles,askopenfilenames,asksaveasfile,asksaveasfilename,askdirectoryなどがあります.

import tkFileDialog as tkFD

    def button_pushed(self):
        ft = [('text files', '.txt')] if self.var_check.get() else []
        self.var_entry.set(tkFD.askopenfilename(filetypes=ft))

変数の変化に応じたアクション

変数を監視して変化した時にメソッドを呼ぶには,traceメソッドを使います.

    def create_widgets(self):
        self.var_entry = Tk.StringVar()
        self.var_entry.trace('w', self.entry_changed)

    def entry_changed(self, *args):
        if os.path.exists(self.var_entry.get()):
            self.text.delete('1.0', Tk.END)
            self.text.insert('1.0', open(self.var_entry.get()).read())

var_entryの値が書き換えられた時にentry_changedが呼ばれます.
最初にTextの内容を全部消去してから,ファイルの内容を表示します.

スクロールバー

Textにスクロールバーをつけることができます.2
垂直方向のスクロールバーが最初から付いているScrolledTextというWidgetもあるようです.

        self.text = Tk.Text(self, wrap=Tk.NONE)
        self.yscroll = Tk.Scrollbar(self, command=self.text.yview)
        self.xscroll = Tk.Scrollbar(self, command=self.text.xview,
                                    orient=Tk.HORIZONTAL)
        self.text['yscrollcommand'] = self.yscroll.set
        self.text['xscrollcommand'] = self.xscroll.set

        self.text.grid(column=0, columnspan=3, row=2, rowspan=2, sticky=Tk.NSEW)
        self.yscroll.grid(column=2, row=2, sticky=Tk.NS + Tk.E)
        self.xscroll.grid(column=0, columnspan=3, row=3, sticky=Tk.EW + Tk.S)

画像の貼り付け

Tkinterでは基本的にgifを想定しているようです.一方で例えばmatplotlib(pyplot)のsavefigではgifでの画像保存ができません(よね?).そこで私は以下のように変換してから貼り付けているのですが,もう少しスマートな方法ないでしょうか.

from PIL import Image
    def create_widgets(self):
        self.canvas = Tk.Canvas(self)
        self.canvas.grid(column=0, columnspan=3, row=4, sticky=Tk.NSEW)

    def set_image(self):
        img = Image.open('foo.png')
        img.save('_tmp.gif')
        self.image_data = Tk.PhotoImage(file='_tmp.gif')
        self.canvas.create_image(200, 100, image=self.image_data)

標準出力のリダイレクト

stackoverflowからのコピペですが...
標準出力の内容をTextなどに出力させることができます.

import sys

    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.pack(expand=1, fill=Tk.BOTH, anchor=Tk.NW)
        self.create_widgets()
        sys.stdout = self.StdoutRedirector(self.text)
        sys.stderr = self.StderrRedirector(self.text)

    class IORedirector(object):
        def __init__(self, text_area):
            self.text_area = text_area

    class StdoutRedirector(IORedirector):
        def write(self, st):
            self.text_area.insert(Tk.INSERT, st)

    class StderrRedirector(IORedirector):
        def write(self, st):
            self.text_area.insert(Tk.INSERT, st)

sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__

Python3対応

Tkinterはtkinter,tkFileDialogはfiledialogに名前が変わっているようです.


  1. 全てをカバーしているわけではありません 

  2. スクロールバーの交点,もう少しやりようがある気がします