本文总结如何暂停或继续 Tkinter
多线程以及多文件间的调用。
Update: 2022 / 11 / 19
Python | GUI | Tkinter - 4. 多线程以及文件间调用
- Tkinter 多线程
- 启用子线程
- 子线程的暂停与重启
- Tkinter 文件之间调用
- 参考链接
Tkinter 多线程
参考这里 1
为什么要使用多线程?——
单线程下,主线程需要运行窗口,如果这个时候点击“确定”按钮,主线程就会去执行 event
方法,而如果 event
方法占用主线程,则原先的运行窗口就会出现 无响应
状态。
比如,按照以下示例运行,
import tkinter as tk
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Title')
self.root.geometry("600x300+550+300")
self.interface()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
def event(self):
'''按钮事件,一直循环'''
a = 0
while True:
a += 1
self.w1.insert(1.0, str('1') + '\n')
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
运行效果如下,
如果要原先的运行窗口正常显示,那我们就需要用到多线程 ( threading
)。
启用子线程
将上面的示例改写,改写为主线程去执行 start
方法,而如果 start
方法中启用 self.T
子线程,子线程调用 event
方法。self.T
子线程作为守护进程,即主进程结束(或者说,运行窗口被关闭)后此子线程也结束,否则主进程结束子进程不结束,如下所示:
import tkinter as tk
import threading
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Title')
self.root.geometry("600x300+550+300")
self.interface()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.start)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
def event(self):
'''按钮事件,一直循环'''
a = 0
while True:
a += 1
self.w1.insert(1.0, str(a) + '\n')
def start(self):
self.T = threading.Thread(target=self.event) # 多线程
self.T.setDaemon(True) # 线程守护,即主进程结束后,此线程也结束。否则主进程结束子进程不结束
self.T.start() # 启动
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
运行效果如下所示:
子线程的暂停与重启
除了结束主进程以结束守护线程外,还可以通过 threading.event()
的相关方法来实现守护线程的暂停和继续。改写为如下所示的代码:
import tkinter as tk
import threading
import time
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Title')
self.root.geometry("600x300+550+300")
self.interface()
self.flag = threading.Event()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="启动", command=self.start)
self.Button0.grid(row=0, column=0)
self.Button1 = tk.Button(self.root, text="暂停", command=self.stop)
self.Button1.grid(row=0, column=1)
self.Button2 = tk.Button(self.root, text="继续", command=self.conti)
self.Button2.grid(row=0, column=2)
self.w1 = tk.Text(self.root, width=70, height=10)
self.w1.grid(row=1, column=0, columnspan=3)
def event(self):
'''按钮事件,一直循环'''
while True:
time.sleep(1)
self.flag.wait() # 只有在internal flag为true时,继续运行子线程
self.w1.insert(1.0, '运行中' + '\n')
def start(self):
self.flag.set() # 将internal flag设为true,运行子线程
self.T = threading.Thread(target=self.event)
self.T.setDaemon(True)
self.T.start()
def stop(self):
self.flag.clear() # 将internal flag设为false,子线程被block
self.w1.insert(1.0, '暂停' + '\n')
def conti(self):
self.flag.set() # 将internal flag设为true,运行子线程
self.w1.insert(1.0, '继续' + '\n')
if __name__ == '__main__':
gui = GUI()
gui.root.mainloop()
运行效果如下所示:
Tkinter 文件之间调用
-
a.py
文件 - - 界面逻辑 + 线程 -
b.py
文件 - - 业务逻辑
以上文件在同一个目录下
- 方法
以下面的代码块为例,
a.py
的代码如下所示:
import tkinter as tk
import threading
from b import logic
global flag, input, output
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('example')
self.root.geometry("500x300+500+150")
self.interface()
self.flag = threading.Event()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="start", command=self.start, bg="#7bbfea")
self.Button0.place(x=50, y=15, width=70, height=30)
self.Button1 = tk.Button(self.root, text="stop", command=self.stop, bg="#7bbfea")
self.Button1.place(x=150, y=15, width=70, height=30)
self.Button2 = tk.Button(self.root, text="continue", command=self.conti, bg="#7bbfea")
self.Button2.place(x=250, y=15, width=70, height=30)
self.Button3 = tk.Button(self.root, text="clear", command=self.clear, bg="#7bbfea")
self.Button3.place(x=350, y=15, width=70, height=30)
label = tk.Label(text='Input')
label.place(x=50, y=70)
self.entry00 = tk.StringVar()
self.entry00.set("Please give a number here")
self.entry0 = tk.Entry(self.root, textvariable=self.entry00)
self.entry0.place(x=100, y=70, width=300, height=30)
self.entry0.bind('<Button-1>', self.delete)
label = tk.Label(text='Output')
label.place(x=50, y=180)
self.w1 = tk.Text(self.root)
self.w1.place(x=100, y=120, width=300, height=170)
self.output = self.w1
def seal(self):
self.input = self.entry00.get()
logic(self.flag, self.input, self.output).event()
def clear(self):
self.w1.delete('0.0', 'end')
def start(self):
'''
set internal flag to True and start threading
:return:
'''
self.flag.set()
self.T = threading.Thread(target=self.seal)
self.T.setDaemon(True)
self.T.start()
def stop(self):
logic(self.flag, self.input, self.output).stop()
def conti(self):
logic(self.flag, self.input, self.output).conti()
def delete(self, event):
self.entry0.delete(0, tk.END)
if __name__ == '__main__':
a = GUI()
a.root.mainloop()
b.py
的代码如下所示:
import threading
import time
class logic:
def __init__(self, flag, input, output):
self.flag = flag
self.input = input
self.output = output
def main(self, x):
'''
block calling until timeout occurs or internal flag is set to True
:return:
'''
while True:
self.flag.wait()
y = int(self.input)+int(x)
self.output.insert(1.0, threading.current_thread().name + ': ' + str(y) + '\n')
time.sleep(1)
x += 1
def stop(self):
'''
reset internal flag to False -> threading block calling wait()
:return:
'''
self.flag.clear()
self.output.insert(1.0, f'stopped? {threading.current_thread().is_alive()}' + '\n') #
def conti(self):
'''
set internal flag to True -> calling wait()
:return:
'''
self.flag.set()
self.output.insert(1.0, f'continued? {threading.current_thread().is_alive()}' + '\n')
def event(self):
'''main所调用的方法'''
x = 1
self.main(x)
运行效果如下所示:
2个文件中的变量或者函数的相互引用,需要注意避免
circular import
。详情参考 2’ 3。
参考链接
- python之Tkinter使用详解 ↩︎
- ImportError: cannot import name ‘…’ from partially initialized module ‘…’ (most likely due to a circular import) ↩︎
- 【ModuleNotFoundError 与 ImportError】之 most likely due to a circular import ↩︎