[PYTHON] [Tkinter] Control threads with Event object

Introduction

In Previous article, I introduced how to improve responsiveness by using threads in Tkinter GUI.

This time, I will show you how to control the behavior of this thread.

Thing you want to do

Take a look at the figure below. I would like to create a GUI program that counts numbers when I press the start button. If you press the stop button at the bottom on the way, you will be asked to stop counting the numbers. Also, by pressing the start button at the top, the number count is restarted.

image.png

A thread has a Start but no Stop.

At first, I thought about creating a function that counts numbers using a While statement, starting a thread by pressing the start button, and stopping the thread by pressing the ** stop button **.

Here is the code I first thought of. Thread-intensive users may get angry when they see this code.

def _start_func(self):
    self.thread_main=threading.Thread(target=self._main_func)
    self.thread_main.start()

def _stop_func(self):
    self.thread_main=threading.Thread(target=self._main_func)
    self.thread_main.stop()

The first _start_func () is not wrong. But the second _stop_func () is a big mistake. That's right. The thread has a .start () method but no **. stop () method. ** ** Threads are not designed to be forced down in the middle. ** **

Then what should I do?

At that time, you would launch a thread and ** temporarily suspend it ** or ** resume ** it. The hero that appears here is the ** Event ** object.

Use of Event object

The Event object manages thread behavior by the value of internal flags. If the value of the internal flag is True, run the thread. If False, stop the thread. That's all. The important methods for controlling its internal flags are organized in the table below.

Event related methods Description Remarks
wait() Make a thread wait(=Temporarily stop. ) Make the thread wait until the internal flag is True.
set() Run the thread Set the value of the internal flag to True.
clear() Stop the thread. Set the value of the internal flag to False.
is_set() Returns the value of the internal flag Used when judging the operating status of a thread.

It may be difficult to understand at first because the name of the method is set () instead of begin (), but it is easy to understand if you think that you set the internal flags to True and False and control the operation. I think.

Program structure

Using the Event object, write the structure of the program of what you want to do as follows.

First, when the program starts, the thread is also started with the start method at the same time. Next, prepare started as an instance of the event. This started internal flag will control the behavior of the thread.

Put ** started.wait () ** at the beginning of the function _main_func () that will be the target of the thread. _main_func () is made up of a while statement, and the while statement is controlled by alive. (Alive is used when the program is finally launched, but here it is only shown in the code and the explanation is omitted.)

Bind the .set () method that sets the internal flag to True to the GUI start button, and the clear () method that sets the internal flag to False to the GUI stop button.

For the internal structure of the While statement of _main_func (), first check the state of the internal flag by the .is_set () method. If the value of the internal flag is True, do what you want the thread to do. If the internal flag is found to be False, the .wait () method causes the thread to wait.

image.png

image.png

image.png

Program code

I will post the entire program code.

GUI_Contol_Thread_with_Event_Object.py


import tkinter as tk
from tkinter import ttk
from tkinter import font
import threading


class Application(tk.Frame):
    def __init__(self,master):
        super().__init__(master)
        self.pack()

        self.master.geometry("300x300")
        self.master.title("Tkinter GUI with Event")

        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()
    #--------------------------------------------
    # Setup Threading Start
    #--------------------------------------------
        self.started = threading.Event() # Event Object
        self.alive = True #Loop condition
        self._start_thread_main()


    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_func)
        self.btn_Start.grid(column = 0, row = 0, padx=10, pady = 10,sticky='NESW' )

        # Stop Button
        self.btn_Stop = ttk.Button(self.main_frame)
        self.btn_Stop.configure(text = 'Stop')
        self.btn_Stop.configure(command = self._stop_func)
        self.btn_Stop.grid(column = 0, row = 1, padx=10, pady = 10,sticky='NESW')

        # Label
        self.lbl_result = ttk.Label(self.main_frame)
        self.lbl_result.configure(text = 'Threading Result Shown Here')
        self.lbl_result.grid(column = 0, row = 2, padx= 30, pady=10,sticky='NESW')

        # Kill Button
        self.btn_Kill = ttk.Button(self.main_frame)
        self.btn_Kill.configure(text = 'Kill Thread')
        self.btn_Kill.configure(command = self._kill_thread)
        self.btn_Kill.grid(column=0, row=3, padx = 10, pady=20,sticky='NESW')

    #--------------------------------------------------
    # Callback Function
    #--------------------------------------------------
    def _start_func(self):
        self.started.set()
        print("Threading Begin")
        print( 'Thread status', self.thread_main )


    def _stop_func(self):
        self.started.clear()
        print("\n Threading Stopped")
        print( 'Thread status', self.thread_main )


    def _start_thread_main(self):
        self.thread_main = threading.Thread(target=self._main_func)
        self.thread_main.start()
        print('main function Threading Started')
        print('Thread status', self.thread_main)

    def _kill_thread(self):
        if self.started.is_set() == False:
            self.started.set()
            self.alive = False
            self.thread_main.join()
        else:
            self._stop_func()
            self.started.set()
            self.alive = False
            #self.thread_main.join()
        print("Thread was killed.")
        print( 'Thread status', self.thread_main )

      

    def _main_func(self):
        i = 0
        self.started.wait()
        while self.alive:
            if self.started.is_set() == True:
                i = i + 1
                print( "{}\r".format( i ), end="" )
                self.lbl_result.configure( text=i ,font = self.font_lbl_big )

            else:
                self.lbl_result.configure( text= 'Stopped' ,font = self.font_lbl_big)
                self.started.wait()

        pass

def main():
    root = tk.Tk()
    app = Application(master=root)#Inherit
    app.mainloop()

if __name__ == "__main__":
    main()

Execution result

This is the execution result. It is now possible to confirm that the GUI program is controlled as designed.

bandicam-2020-07-31-19-01-28-328.gif

Summary

  1. Use threads to improve the responsiveness of the Tkinter GUI.
  2. Use the Event object to control the behavior of threads.
  3. Threads can be controlled by using four methods that control the internal flags of Events.

References

  1. Python official document Event object
  2. [Tkinter] Improve GUI responsiveness
  3. Your threading.Event is used incorrectly
  4. Simple sample of stopping and resuming Python threads

Recommended Posts

[Tkinter] Control threads with Event object
MVC with Tkinter
Become Santa with Tkinter
Control scripts with exceptions
Programming with Python and Tkinter
Getting Started with Tkinter 2: Buttons
Working with tkinter and mouse
Visualize 2ch threads with WordCloud-Scraping-
Control error wording with nginx
Screen switching / screen transition with Tkinter
Create Image Viewer with Tkinter
Let's make dice with tkinter
Run Label with tkinter [Python]
I measured BMI with tkinter
Control multiple robots with jupyter-lab