楽して使うTkinter(基本編)
コードを書く目的というのは…ドヤりたいわけではなく、目的の動作をするプログラムを作りたいわけで、毎度毎度似たようなコード書くのも誰かに使ってもらうためにいちいちサポートしなきゃいけないのも面倒だから分かりやすくしたいし難しいこと考えずに使えるようにしたい。とにかく楽したい。
というところでどんだけ手を抜けるようにするかという話です。
mainloop問題
Tkinterで最初に悩んでいたのはこれだったと思いますしこれに関する話題は結構目にします。
画面を表示したいし、その後画面の中の処理をどうにかしたいみたいな、ね。
でもmainloopを呼ぶとそこから処理が帰ってこない。どうすんのこれってな感じ。
そういうところをどうにかするための基本形を自分なりにつくります。例えばこんな感じ。
import tkinter as tk
import threading
# スレッドデコレータ
# 画面が反応なしになっちゃうような処理があるファンクションにつける。
def thread(func):
def run(*args, **kwargs):
t = threading.Thread(target=func, args=args, kwargs=kwargs)
t.start()
return run
class App:
def __init__(self):
self.root = tk.Tk()
def mainloop(self):
self.root.after(500, self.main) #500ミリ秒後に mainを呼ぶ
self.root.mainloop()
@thread
def main(self):
# mainはスレッド処理される。ループ処理を組むときなどは要注意
# whileとかよりはafterで自分自身を呼ぶ方が安全かも。
print("main !!")
if __name__ == "__main__":
app = App()
app.mainloop()
確かに欲しいときもあるんですよね、こういうの。たまーに。
で、たまーにに対応するべくこういうの書いてるといつもの決まりきったパターンを書くのがものすごく面倒になってくる。億劫で世界が滅びる。
手を抜けるようにするにはどうすればいいか、そこで クラス継承 の出番だ。
クラス継承で楽をする
app.py などとしてこんな感じでクラスを作成。
このコードはこのままでは表向きには何もしませんが基本的にどういう風に動くかはすべて組んであるという状態です。
使いやすくするために以下のエントリを用意しました。これらをオーバライドして使います。
- init
- close
- main
initには画面構築とかイベントの定義とか。
closeには画面を閉じようとしたときの処理を書きます。
main は例えばですが、ユーザの指示以外の何やらをトリガーにしてその結果を逐次画面に反映してゆくようなものを作る場合に使えます。IoTだとか機器設定とか監視とか諸々ありますよねー。
あと、画面に配置したウィジットを全部取っ払う為の clear なんていうファンクションも入れてみた。winfo_うにゃららみたいなものの存在について意識すると世界がかわるかもというヒントとして。
import tkinter as tk
import threading
def thread(func):
def run(*args, **kwargs):
t = threading.Thread(target=func, args=args, kwargs=kwargs)
t.start()
return run
class App:
def __init__(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.quit)
def quit(self):
if self.close() != False:
self.root.destroy()
def clear(self):
childs = self.root.winfo_children()
for child in childs:
child.destroy()
def mainloop(self):
self.root.after(200, self._init)
# 待ち時間は最悪無くても大丈夫だけど様子を見て調整する。
self.root.mainloop()
def _init(self):
self.init()
self._main()
@thread
def _main(self):
self.main()
#
# ここからオーバライド用のエントリ
#
def init(self):
pass
def main(self):
pass
def close(self):
# return Falseすると画面は閉じられません。
# 終了確認などに使えます。
pass
で、これは直接いじらずに、外から取り込んで使います。
様々な場面で使いまわせるかどうかがこのクラスにコードを追加するかどうかの指標になります。
app_test.py
app.pyと同じフォルダにapp_test.pyを用意して、app.pyを読み込んで使ってみます。
注目してほしいのはコード量です、これしか書かずに画面が出てくる。記述が少なくて済むというだけではなくすっきりして見やすい。
import app
if __name__ == "__main__":
myapp = app.App()
myapp.mainloop()
でも、これだけだと何も仕事せず役立たずなので実際に働いてもらうためにはもうちょっと書かないといけない。
app_test2.py
app_test.pyと同じくapp.pyと同じフォルダに app_test2.py を用意して使いました。
Appクラスを継承したMyAppクラスを作り、これを使います。
initをオーバライドしてラベルを配置しましたのでhelloと表示されるウィンドウが出来ました。
import app
import tkinter as tk
class MyApp(app.App):
def init(self):
label = tk.Label(self.root, text="Hello")
label.pack(fill="both", expand=True)
if __name__ == "__main__":
myapp = MyApp()
myapp.mainloop()
これはとても簡単な例なのでメリットをあまり感じないかもしれないですけれどもプロジェクトが大きくなってゆけばゆくほど有利になってゆきますから是非こんな感じの実装で(楽々・簡単に)案件を乗り切ってゆきたいところです。
同じものが出来るなら楽な方がいいですしほかに労力が回せるので結果が良くなる傾向にある気がします。
という具合に
よく使うパターンをクラスにまとめて置いて、書き換えたい部分だけをオーバライドできるようにエントリポイントを用意して継承して使うというのが楽々便利な使い方だとわたしは考えます。
今回ベース部分のみですがその他のウィジットなどにも同じ考え方が適用できますのでいろいろ試してみてほしいと思います。
がちがちにやりすぎると逆に使いづらくなったりするので程よくやんわりとラップするくらいの力加減がお気に入りです。
ちなみに・・・
これ見て「分かりづらい」「難しい」って思った人は…それはとても正しい感覚です。
だって自分自身で使いやすく書いたわけじゃない他人のものを見てるんですから分かりづらくて当然です。
自分でこういうのを作ってみたらきっと手放せないものになると思いますよ。