Python | GUI | Tkinter - 4. 多线程以及文件间调用

5 篇文章 1 订阅

本文总结如何暂停或继续 Tkinter 多线程以及多文件间的调用。

Update: 2022 / 11 / 19



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。详情参考 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 ↩︎

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值