LoginSignup
4
8

More than 3 years have passed since last update.

[Tkinter] GUIの応答性をよくする

Last updated at Posted at 2020-07-31

1.はじめに

今日はGUIアプリケーションを作るとき、Threadingを利用して、その応答性をよくする方法について説明します。

2.やりたいこと

下記の図のようなシンプルなGUI画面を設計します。上部のボタン(Button)がクリックされると、下部に0から9まで順番に表示されるものです。
image.png
早速、Tkinterでコードを書いてみましょう。

3.応答性が悪いコード

まず、下記のようなコードを書いてみました。
ボタンを押すと、_main_func()を実行する構造となっています。_main_func()は、For文を利用して0から9まで表示する関数です。

結果の表示には、Tkinterのラベルと、print文の2種類を用いました。

3.1.プログラミングコード(1)

tkinter_wo_threading.py
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プログラムでの一つのイベント処理が終わるまで、ほかのイベントがスタートすらできなくなる応答性が悪くなります。
with_thread.gif

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()

全体のコードです。

tkinter_with_threading.py
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を利用したコードの実行結果です。
without_thread.gif

最初に意図した通りにプログラムが動くことが確認できました。プログラムの反応性もよくなり、ほかのイベント処理も可能となりました。

まとめ

スレッドを利用し、GUIプログラムの応答性をよくすることができました。

参考資料

1.python#Threading
2.Pythonのthreadingとmultiprocessingを完全理解
3.[Python] スレッドで実装する

4
8
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8