本文总结如何暂停或继续 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()

运行效果如下,

python tkinter 多选择下拉框 tkinter选择多个文件_多线程


如果要原先的运行窗口正常显示,那我们就需要用到多线程 ( 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()

运行效果如下所示:

python tkinter 多选择下拉框 tkinter选择多个文件_子线程_02



子线程的暂停与重启

除了结束主进程以结束守护线程外,还可以通过 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()

运行效果如下所示:

python tkinter 多选择下拉框 tkinter选择多个文件_开发_03


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)

运行效果如下所示:

python tkinter 多选择下拉框 tkinter选择多个文件_开发_04

2个文件中的变量或者函数的相互引用,需要注意避免 circular import。详情参考 23


参考链接


  1. python之Tkinter使用详解 ↩︎
  2. ImportError: cannot import name ‘…’ from partially initialized module ‘…’ (most likely due to a circular import) ↩︎
  3. 【ModuleNotFoundError 与 ImportError】之 most likely due to a circular import ↩︎