#1.はじめに
今日はGUIアプリケーションを作るとき、Threadingを利用して、その応答性をよくする方法について説明します。
#2.やりたいこと
下記の図のようなシンプルなGUI画面を設計します。上部のボタン(Button)がクリックされると、下部に0から9まで順番に表示されるものです。
早速、Tkinterでコードを書いてみましょう。
#3.応答性が悪いコード
まず、下記のようなコードを書いてみました。
ボタンを押すと、_main_func()を実行する構造となっています。_main_func()は、For文を利用して0から9まで表示する関数です。
結果の表示には、Tkinterのラベルと、print文の2種類を用いました。
##3.1.プログラミングコード(1)
import tkinter as tk
from tkinter import ttk
from tkinter import font
import time
class Application(tk.Frame):
def __init__(self,master):
super().__init__(master)
self.pack()
self.master.geometry("300x300")
self.master.title("Tkinter Freezes after clicking Buttons")
self.font_lbl_big = font.Font( family="Meiryo UI", size=30, weight="bold" )
self.font_lbl_middle = font.Font( family="Meiryo UI", size=15, weight="bold" )
self.font_lbl_small = font.Font( family="Meiryo UI", size=12, weight="normal" )
self.create_widgets()
def create_widgets(self):
# Frame
self.main_frame = tk.LabelFrame(self.master, text ='', font = self.font_lbl_small)
self.main_frame.place(x=25,y=25)
self.main_frame.configure(height = 250, width=250)
self.main_frame.grid_propagate(0)
self.main_frame.grid_columnconfigure(0, weight = 1)
# Start Button
self.btn_Start = ttk.Button( self.main_frame)
self.btn_Start.configure( text='Start' )
self.btn_Start.configure( command=self._main_func)
self.btn_Start.grid( column=0, row=0, pady=10 , sticky='NESW')
# Label Title
self.lbl_title = ttk.Label( self.main_frame)
self.lbl_title.configure( text='Calculation Results Shown Here' )
self.lbl_title.grid( column=0, row=1, padx = 20, pady=20 ,sticky='EW')
# Label Result
self.lbl_result = ttk.Label( self.main_frame )
self.lbl_result.configure( text='' )
self.lbl_result.grid( column=0, row=2, padx = 100, pady=10 ,sticky='EW')
def _main_func(self):
for i in range(10):
print(i)
self.lbl_result.configure(text = i, font = self.font_lbl_big)
time.sleep(0.1)
def main():
root = tk.Tk()
app = Application(master=root)#Inherit
app.mainloop()
if __name__ == "__main__":
main()
##3.2.実行結果
このコードを実行してみます。
ボータンを押すと、for文のループが終わるまで、プログラムが応答しなくなります。print文で結果を連続に表示してくれますが、GUIは止まったままです。そして、for文のループが終わると最後の数字だけ表示してくれます。最初に期待したものと違いますね。
これはPythonがコードを1行ずつ逐次実行することが原因です。このままだと、GUIプログラムでの一つのイベント処理が終わるまで、ほかのイベントがスタートすらできなくなる応答性が悪くなります。
#4.Threadingを利用し、応答性をよくしたコード
この時、Threadingを利用します。Threadingに関する内容は、参考資料を参照にしました。
##4.1.応答性を改善したプログラミングコード(1)
まず、threadingを導入します。
import threading
順番として、ボタン(Button)のコールバック関数を_start_thread()に変更します。
その_start_thread()に、_main_func()をスレッドとしてstartする機能を入れておきます。ターゲットとして指定し、スタートを書けるイメージです。
def _start_thread(self):
self.thread_main = threading.Thread(target = self._main_func)
self.thread_main.start()
全体のコードです。
import tkinter as tk
from tkinter import ttk
from tkinter import font
import time
import threading
class Application(tk.Frame):
def __init__(self,master):
super().__init__(master)
self.pack()
self.master.geometry("300x300")
self.master.title("Tkinter Freezes after clicking Buttons")
self.font_lbl_big = font.Font( family="Meiryo UI", size=30, weight="bold" )
self.font_lbl_middle = font.Font( family="Meiryo UI", size=15, weight="bold" )
self.font_lbl_small = font.Font( family="Meiryo UI", size=12, weight="normal" )
self.create_widgets()
def create_widgets(self):
# Frame
self.main_frame = tk.LabelFrame(self.master, text ='', font = self.font_lbl_small)
self.main_frame.place(x=25,y=25)
self.main_frame.configure(height = 250, width=250)
self.main_frame.grid_propagate(0)
self.main_frame.grid_columnconfigure(0, weight = 1)
# Start Button
self.btn_Start = ttk.Button( self.main_frame)
self.btn_Start.configure( text='Start' )
self.btn_Start.configure( command=self._start_thread)
self.btn_Start.grid( column=0, row=0, pady=10 , sticky='NESW')
# Label Title
self.lbl_title = ttk.Label( self.main_frame)
self.lbl_title.configure( text='Calculation Results Shown Here' )
self.lbl_title.grid( column=0, row=1, padx = 20, pady=20 ,sticky='EW')
# Label Result
self.lbl_result = ttk.Label( self.main_frame )
self.lbl_result.configure( text='' )
self.lbl_result.grid( column=0, row=2, padx = 100, pady=10 ,sticky='EW')
#-----------------------------------------------------------
# Start Thread
# -----------------------------------------------------------
def _start_thread(self):
self.thread_main = threading.Thread(target = self._main_func)
self.thread_main.start()
def _main_func(self):
for i in range(10):
print(i)
self.lbl_result.configure(text = i, font = self.font_lbl_big)
time.sleep(0.1)
def main():
root = tk.Tk()
app = Application(master=root)#Inherit
app.mainloop()
if __name__ == "__main__":
main()
##4.2.実行結果
Threadingを利用したコードの実行結果です。
最初に意図した通りにプログラムが動くことが確認できました。プログラムの反応性もよくなり、ほかのイベント処理も可能となりました。
#まとめ
スレッドを利用し、GUIプログラムの応答性をよくすることができました。
#参考資料
1.python#Threading
2.Pythonのthreadingとmultiprocessingを完全理解
3.[Python] スレッドで実装する