python

pythonでグラフ描画GUIを作ってみたお話③

前回の記事はこちら

概要

tkinterでwidgetを配置する際、”widgetを定義”し、”pack・grid・placeで配置”するのはコードが冗長になるので改善したい。そのために、各widgetのclassを継承し、myclassとして定義して見栄えを良くしてみる。

典型的なwidget配置方法

まずは、様々なサイトで紹介されているwidgetの定義と配置の例を記載しておく。

import tkinter as tk

class App(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.grid(row=0, column=0)
        self.createbutton()
   
    def createbutton(self):
        self.button = tk.Button(self, text="hoge")
        self.button.grid(row=0, column=0)

root = tk.Tk()
app = App(master=root)
app.mainloop()

Appのメンバ関数”createbutton”ではボタンの定義と配置でそれぞれ1行ずつ使っている。これを大量に書くのはとても冗長なので1行にまとめたい。その方法の一つとして以下の方法がある。

def createbutton(self):
    tk.Button(self, text="hoge").grid(row=0, column=0)

さて、これで解決したと思いきやそういうわけにもならない。なぜなら、この作成したボタンはメンバ関数である“createbutton”の外からはアクセスができない。では、どうすればいいか?一つ考えられるのは、最初に示したコードのメンバ変数”self.button”をグローバル変数にすることである。これなら、どこからでもアクセスできる。ただし、グローバル変数になると同一名称の変数を使えない(※1)。さらに、グローバル変数を定義するために1行とメンバ関数内でグローバル変数をいじれるように”global [変数名]”で1行の計2行を要する。つまり、一つのwidgetを配置するために計3行を要するので本末転倒である。

※1 同一名称の変数をローカル変数として定義することは可能だが、後に混乱するので避けるべき

解決方法

クラスである”tk.Button”を継承し、新たなクラス”mybutton”を作成すればよい。その方法を以下に示す。

import tkinter as tk

def new_kw(kw1, list):
    kw2 = {}
    for t in list:
        try:
            kw2 = {**kw2, **{t: kw1.pop(t)}}
        except:
            continue
    return kw1, kw2

class myButton(tk.Button):
    def __init__(self, master=None, cnf={}, **kw):
        kw1, kw2 = new_kw(kw, ["row","column"])
        tk.Button.__init__(self, master, cnf, **kw1)
        self.grid(**kw2)
    
class App(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.grid(row=0, column=0)
        self.createbutton()
   
    def createbutton(self):
        self.button = myButton(self, text="hoge", row=0, column=0)

root = tk.Tk()
app = App(master=root)
app.mainloop()

コードの解説をしていく。メンバ関数”createbutton”には1行のみ書いてあり、これだけでボタンの定義と配置ができている。新たに定義した”myButton”の引数は可変長引数で、self(tk.Frameのこと)とtextとrowとcolumnが渡されている。

“myButton”は”tk.Button”を継承しているので、“tk.Button”で使われる引数はすべて使用できる。それに追加でどこに配置するか(rowとcolumn)を引数で渡している。渡されたすべての引数は辞書”kw”に格納されているので、”kw”から”tk.Button”の引数とrowおよびcolumnを分離する必要がある。この分離を行う関数が”new_kw”である。

“new_kw”は辞書”kw1″から”list”で指定された項目をpopで取り除き、取り除いた項目を”kw2″に追加する。”kw1″は”tk.Button”の引数、”kw2″は”grid”の引数であり、これを戻り値とする。分離して得たそれぞれの引数を用いて、ボタンの定義と配置を行う。

ところで、”new_kw”関数にはtry-excpet構文が存在する。先に示したコードではtry-exceptは特に何も役割がないので取り除いてもよい。しかし、”grid”の他の引数”rowspan”や”columnspan”など指定してもよいが省略できる引数を使う場合はあるととても便利である。念のため、これらの引数を追加したコードも記載する。

import tkinter as tk

def new_kw(kw1, list):
    kw2 = {}
    for t in list:
        try:
            kw2 = {**kw2, **{t: kw1.pop(t)}}
        except:
            continue
    return kw1, kw2

class myButton(tk.Button):
    def __init__(self, master=None, cnf={}, **kw):
        kw1, kw2 = new_kw(kw, ["row","column","rowspan","columnspan"])
        tk.Button.__init__(self, master, cnf, **kw1)
        self.grid(**kw2, sticky="ew")
        self.__myconfigure()
        self.configure(**kw1)
    
    def __myconfigure(self):
        self.configure(fg="black", bg="white")


class App(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.grid(row=0, column=0)
        self.createbutton()
   
    def createbutton(self):
        self.button = myButton(self, text="hoge", row=0, column=0, columnspan=2)

root = tk.Tk()
app = App(master=root)
app.mainloop()

このコードで作成されるボタンはrowspan=1でcolumnspan=2である。このように引数を省略して見やすくする場合はtry-exceptは記載しておくとよい。

ちなみに、”myButton”にローカルの関数として”__myconfigure”を追加している。実際のGUI作成では文字の色や背景などのデザインは一つのアプリ内で統一されることがしばしばあるので、”__myconfigure”を追加しておくとより便利になるだろう。

また、このボタンだけ別のデザインにしたいというときもあると思う。そういったときは、”myButton”内の”self.configure(**kw1)”が役に立つ。今、ボタンを配置すると文字が黒で背景が白のボタンが生成されるが、逆にしたい場合は

def createbutton(self):
    self.button = myButton(self, text="hoge", fg="white", bg="black", row=0, column=0)

と書けばよい。

まとめ

一部サイトでtkinterはコードが冗長になるので、他のライブラリでGUIを作成した方が良いという記述を見かけたことがあるが、クラスを継承すれば冗長さをある程度緩和できる。今回はボタンのみを例として取り上げたが、ラベルやチェックボタンなど様々なwidgetを同様に継承してmyclassとして定義できる。実は2020年8月21日現在、グラフ描画GUIはクラス継承を利用しておらず、今後暇なときに書き直そうと思っている。

ちなみに、どれくらい冗長であるかというとこれぐらい(ひどすぎる)。

ABOUT ME
POM_AK
ガバガバで動けばヨシ!程度のプログラムを書きます