PythonでGUIを使って処理を行うと、重い処理だとその処理中にGUIが触れなくなり、困ります。また処理中にボタンとかラベルに「処理中ですよぉ」とか、終わったら「終了しましたよぉ」とか表示させたいです。
これらが最初思ったように記述できなかったので、その覚書を残しておきます。やりたいことはシンプルで、処理中にGUIのなにがしか表示を変え、終わったら戻す、そんな動きをさせることです。
結果としてThreadを使ってそれらを解決させました。
ボタン表示の変更
ボタン表示とかGUIとして、Tkinterを使うことを想定します。ただ別のUIライブラリでもいいとは思います。
import tkinter as tk
import tkinter.filedialog
こんな感じでインポートして、ボタンはファイル選択ボタンと、処理開始ボタンの2つをレイアウトします。
####################################
## main
if __name__ == '__main__':
root = tk.Tk()
root.geometry('200x90')
button1 = tk.Button(text="選択",command=button1_clicked,width=20)
button1.place(x=30,y=110)
button2 = tk.Button(text="実行",command=button2_clicked,width=20)
button2.place(x=30,y=150)
root.mainloop()
こんな感じ。見た目は超シンプル。

ここで、実行ボタンで動くプログラムが超ヘビーなので、処理中に実行ボタンの表示を「実行中…」とか表示させておきたい、そんなユースケースです。
で短絡的にこんなコードを書いてみます。heavy_process()が処理時間がかかる重い処理。その処理前にボタン表示を変え、終わったら戻すよう書いているつもりですが、思ったようにいきません。やりたいことはなんとなく伝わりますかね?ただこの記述ではボタン表示が何も変わりませんでした。(つまり以下はダメコード)
dirName = ""
dstName = ""
def heavy_process():
global dirName
global dstName
very_hevy(dirName,dstName)
button2["text"] = "実行"
def button1_clicked():
global dirName
dirName = tk.filedialog.askdirectory()
def button2_clicked():
global dstName
dstName = tk.filedialog.asksaveasfilename(filetypes = [("JPEG file", ".jpg")])
if dstName != "":
button2["text"] = "実行中..."
heavy_process()
はじめはどうしたものか手立てが分かりませんでした。button2[“text”]を変更した後に、再表示系のイベントを起こせばいいのかな?と色々やってみたのですが、うまい方法が見つからず、結局重たい処理本体を、Thread使って別プロセスにすることで解決しました。(ほかにいい方法あったら教えてください。)
まずは、threadのimportをします。
import threading
標準で入ってそうです。何もライブラリを足さずにできました。これに合わせて、コードは以下の様に書き換えます。threadにしたので、処理中にボタンを押せないようにしたかったのですが、いったん押しても何も反応しないように対応させました。
nowprocessというフラグを使って、処理中はTrueに、終わったらFalseにするようにしました。あまりスレッドセーフな感じの記述じゃないなぁ、とは思いながらも簡単で、必要十分だったので。
でそのコード。これは成功コードです。
dirName = ""
dstName = ""
nowprocess = False
def heavy_process():
global dirName
global dstName
global nowprocess
nowprocess = True
very_hevy(dirName,dstName)
button2["text"] = "実行"
nowprocess = False
def button1_clicked():
if nowprocess:
return
global dirName
dirName = tk.filedialog.askdirectory()
def button2_clicked():
if nowprocess:
return
global dstName
dstName = tk.filedialog.asksaveasfilename(filetypes = [("JPEG file", ".jpg")])
if dstName != "":
button2["text"] = "実行中..."
thread = threading.Thread(target=heavy_process)
thread.start()
スレッド2行ですよ。
thread = threading.Thread(target=heavy_process)
thread.start()
これだけ。あってんのか?と思いましたが動いてます。スレッドの定番として引数なしの関数ポインタ渡し。というお作法にさえ乗ってしまえば簡単にこれら記述できたので、いったんこれでOKとします。
こうすることで、処理中にはボタン表示が変わって、処理が終わると元に戻る。また処理中はボタン操作が何も効かない。という思惑通りの動きをしてくれるようになりました。
まとめ
PythonのGUIコードにて、重たい処理を走らせている最中に、GUIのなにがしか表示を替える手段として、Threadを使ってみました。これにより、重たい処理をしている間に表示(今回はボタンの文字列)が変わり、終わると戻るというシンプルな動きをさせることができるようになりました。
threadとは仰々しいかなとは思ったのですが、2行で書けたのでまぁよしとします。ほかにもっと手軽な方法があれば教えてほしいです。
コメント