TkinterでMVC


はじめに

この記事でのMVC(Model-View-Controller)は、以下の方針で機能を切り分けることを指します。
Model: ViewもControllerも知らない。ただ実直に計算を行う。
View: ModelもControllerも知らない。美を追求するのみ。
Controller: ModelとViewの両方を知っている神様的ポジション。ModelとViewの橋渡しをする。

何番煎じか分からないですが、
意外と上の方針に則るMVCの情報が少ないと思ったため記事にします。

いきなりコード

import tkinter as tk


class Model:
    def __init__(self):
        self.val = tk.IntVar(0)

    def add_one(self):
        self.val.set(self.val.get() + 1)

    def reset(self):
        self.val.set(0)


class View(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent
        self.value_label = tk.Label(self)
        self.add_one_btn = tk.Button(self, text="ADD ONE")
        self.reset_btn = tk.Button(self, text="RESET")

        self.value_label.pack()
        self.add_one_btn.pack()
        self.reset_btn.pack()
        self.pack()


class Controller:
    def __init__(self, root):
        self.model = Model()
        self.view = View(root)

        # Binding
        self.view.value_label.config(textvariable=self.model.val)

        # Callback
        self.view.add_one_btn.config(command=self.model.add_one)
        self.view.reset_btn.config(command=self.model.reset)


if __name__ == "__main__":
    root = tk.Tk()
    app = Controller(root)
    root.mainloop()

完成品

ADD ONEボタンを押すと上の数字が1ずつ増えていきます。
RESETボタンを押すと上の数字が0に戻ります。

解説

どうなってんの?

Model
tk.IntVar()というのがすごい便利なやつです。これを通常のintの代わりに使っています。
ほかにもデータ型によってtk.DoubleVar(), tk.BooleanVar(), tk.StringVar()があります。
値のやりとりにget()/set()を使う必要がある点にだけ注意して、淡々と計算をさせます。

View
Viewはただ見た目だけ気にしてコンポーネントを作っていきます。
変数になる部分(ラベルのテキスト)はここでは何も設定しません。

Controller
ControllerはModelとViewの両方を知っているのでやりたい放題です。
self.view.value_label.config(textvariable=self.model.val)
ここで、ModelのもつIntVar()を、Viewのテキストを空にしていたラベルコンポーネントに割り当てています。
こうすることで、ModelとViewの変数が同期するようになり、Modelで値を変更すると、自動的にViewに反映されます。
また、各ボタンを押した時の処理もControllerで割り当てます。

知らないってどういうこと?

「ModelがViewもControllerも知らない」というのは、簡単に言うとModelがViewやControllerのインスタンスを持たないことです。

例えば以下のようにModelがViewを持つような実装をしたとします。

class Model:
    def __init__(self, view):
        self.val = 0
        self.view = view

こうすれば、Modelがもつ値が変わったとき、その変更をViewに教えることができます。
例えばこう

class Model:
    #・・・
    def add_one(self):
        self.val += 1
        self.view.label["text"] = str(self.val)  # viewに値を反映

すごく簡単そうで何だか最初のコードよりも良さそうに思うかもしれません。
でも実はModelを実装する人の負担がめちゃ増えています><
この実装だとModel担当の人は、こんな足し算するだけの処理のために、次のことを知っている必要があります。

  • Viewにはラベルコンポーネントがあること
  • その名前はlabelということ
  • ラベルコンポーネントのテキスト更新の方法

などなど

また、ModelがViewを持つことで、Viewを破壊する危険もあります。
ていうか一発で破壊できます。

class Model:
    #・・・
    def balse(self):
        self.view = None

大いなる力には大いなる責任が伴います。
なるべく力は持たないようにしたいです。

ということで
MVCで役割分担を明確にして、負担も責任も最小にしましょう。


余談

GUI作成の経験があんまりないので、いまいちどのライブラリが良いのか分からないです。
pythonだとtkinterしか使ったことがないのですが、
wxpythonとかkivyとかは、どこが優れているのか教えて頂けると助かります。