【Python GUI】「tkinter」で関数実行中にウィンドウがフリーズしてしまう問題を「threading」を使って回避する方法
tkinterでGUIを作ると関数実行中に固まってしまう……
PythonのGUI実装ライブラリはいくつか存在しますが、その中でも「tkinter」はPythonに最初から入っている最もシンプルなライブラリです。
今回は「tkinterで実装したGUIのボタンクリックで動作する処理がフリーズしてしまう問題」を「threading」を使うことで回避するサンプルコードをご紹介したいと思います。
想定するアプリの動作
今回は「処理開始」ボタンをクリックすると、「待機中」と表示されているラベルの表示が「1..2..」という風に更新され、10秒後に最初の状態に戻る動作を想定してみます。
↓↓
問題が起きるコード
import tkinter import time #### # ボタンクリック後の動作 def button_clicked(): # ボタン非表示化 button.pack_forget() # カウント処理 for i in range(10): time.sleep(1) var.set(i+1) var.set("待機中") # 最後にボタンを復元 button.pack() #### # Windowの設定 root = tkinter.Tk() root.title("tkinterサンプル") root.geometry("300x150") # ラベル表示 var = tkinter.StringVar() var.set("待機中") label = tkinter.Label(root, textvariable=var) label.pack() # ボタン表示 button = tkinter.Button(root, text='処理開始',command=button_clicked) button.pack() root.mainloop()
上記のコードは一見動きそうですが「処理開始」ボタンを押すとウィンドウがフリーズし、処理が全て終わるまで固まったままラベルの更新も行われません。
そこでこの問題を「threading」を用いることで回避します。
threadingでプロセスを分ける
ここではthreadingを用いることで「表示を更新する処理」と「数え上げる処理」を別々のプロセスに分けます。
なお、threadingは最初からPythonに入っているのでそのままimportできます。
import threading
動作するサンプルコード
import tkinter import time import threading #### # ボタンクリック後の動作 def button_clicked(): # ボタン非表示化 button.pack_forget() # スレッディングでフリーズしないカウントダウン処理 thread1 = threading.Thread(target=work1) thread1.start() # スレッドでラベル表示を更新 def work1(): global var # カウント for i in range(10): time.sleep(1) var.set(str(i+1)) var.set("待機中") # 最後にボタンを復元 button.pack() #### # Windowの設定 root = tkinter.Tk() root.title("tkinterサンプル") root.geometry("300x150") # ラベル表示 var = tkinter.StringVar() var.set("待機中") label = tkinter.Label(root, textvariable=var) label.pack() # ボタン表示 button = tkinter.Button(root, text='処理開始',command=button_clicked) button.pack() root.mainloop()
コードの解説
button = tkinter.Button(root, text='処理開始',command=button_clicked)
ここで「button」をクリックすることで、button_clicked()が実行される様にしています。
# ボタンクリック後の動作 def button_clicked(): # ボタン非表示化 button.pack_forget() # スレッディングでフリーズしないカウントダウン処理 thread1 = threading.Thread(target=work1) thread1.start()
ここではボタンを一旦非表示にしたあと、threadingによって「def work1()」を別プロセスとして開始しています。
# スレッドでラベル表示を更新 def work1(): global var # カウント for i in range(10): time.sleep(1) var.set(str(i+1)) var.set("待機中") # 最後にボタンを復元 button.pack()
ここでは「var」をグローバル変数化し、「var.set("")」で順次その中身を入れ替えることでラベルの表示を更新するように指定しています。