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.
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.
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.
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.
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.
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()
This is the execution result. It is now possible to confirm that the GUI program is controlled as designed.
Recommended Posts