【问题标题】:Python Tkinter loop cannot operate UI interfacePython Tkinter循环无法操作UI界面
【发布时间】:2022-01-11 16:25:30
【问题描述】:

我是 tkinter 的新手。当我尝试创建诸如for i in range(100) 之类的循环时,GUI '崩溃',这意味着我无法操作 GUI,直到循环结束(即使我没有在文本中插入任何内容)。在我检查了一些信息后,我尝试了线程。它确实有效。但是……

from tkinter import *
import time
from threading import Thread

class Application(Frame):
    def __init__(self):
        self.root = Tk()
        self.root.title('tkinter')
        self.root.geometry('400x300+400+100')
        
        self.createWidget()
        self.root.mainloop()
        
    def createWidget(self):
        self.text = Text(self.root, width=40, height=15, relief=SOLID, borderwidth=1)
        self.text.grid(row=0, column=0)

        self.button = Button(self.root, text="click", relief=SOLID, borderwidth=1, command=self.button_click)
        self.button.grid(row=1, column=0)
        pass

    def button_click(self):
        t1 = Thread(target=self.loop)
        t1.start()            
        
    def loop(self):
        for i in range(100):
            self.text.insert(END, chars=str(i)+'\n')
            time.sleep(1)
            self.root.update()
            self.text.see(END)


if __name__=="__main__":
    Application()

但是每次遇到循环都不可能使用线程(太难了,在某些情况下必须顺序执行)。有什么有用/简单的方法来处理它?

【问题讨论】:

  • 尝试删除time.sleep(1)。如果您需要减慢文本插入速度,请尝试使用after
  • 您不能在事件处理程序中运行循环。与所有 GUI 框架一样,Tkinter 是事件驱动的。当您创建或修改窗口时,不会绘制任何内容。所做的只是向控件发送消息。当您的代码返回主循环时,即是获取和处理消息的时间。您的处理程序必须快速运行并返回,以便处理消息。因此,您必须使用root.after 在一秒钟内请求回调。您在那一秒钟内进行更新,再次调用root.after,然后返回。
  • 此外,所有 Tkinter 更新必须在主线程中完成。您不能在辅助线程中进行 UI 更新。所有的 GUI 框架也是如此。

标签: python loops tkinter


【解决方案1】:

如果您正在寻找一种以给定速度插入文本的方法,则可以使用 tkinter after 来实现。

代码试图阐明after的用法。

可以通过按 Escape 键中断该过程。这将cancel_after 并立即完成文本插入。

import tkinter as tk

data = dir(tk) # or str.split('\n')

root = tk.Tk()
text = tk.Text(root, undo = 1, wrap = tk.NONE)
text.pack()

def insert_text():
    global delay

    d = data.pop(0)
    text.insert(tk.END, f"{d}\n")
    if len(data) > 0:
        delay = root.after(500, insert_text)

# cancel timed input
def stop(event):
    
    root.after_cancel(delay)
    for d in data:
        text.insert(tk.END, f"{d}\n")

root.bind("<Escape>", stop)

# start output
delay = root.after(50, insert_text)

root.mainloop()

【讨论】:

    【解决方案2】:

    我已经找到了一种更好的方法来解决接口不更新由其他循环或 tkinter 循环实践中的多线程引起的问题。只要窗口类MainWindows中的函数操作函数是用子线程打开的,无论子线程打开的这个函数中有多少循环或多线程(线程池),都不会影响GUI更新,并且可以实时滚动日志。

    from tkinter import *
    import time
    import requests
    from threading import Thread
    from concurrent.futures import ThreadPoolExecutor
    class MainWindows(Frame):
        def __init__(self, master):
            super().__init__(master=master)
            self.master=master
            self.createWidget()
            self.pack()
            pass
    
        def createWidget(self):
            self.text = Text(self, width=50, height=20, relief=SOLID, borderwidth=1, fg="gray")
            self.text.pack()
    
            self.button = Button(self, text="click", command=self.thread)
            self.button.pack()
            pass
    
        def thread_show_info(self, i):
            self.text.insert(END, chars=str(i)+'\n')
            self.text.see(END)
            time.sleep(1)
    
        def threadpool_show_info(self):
            with ThreadPoolExecutor(10) as t:
                for q in range(1, 1000):
                    t.submit(self.thread_show_info, i=q)
    
        def thread(self):
            t1 = Thread(target=self.threadpool_show_info,daemon=True)
            t1.start()
    
    
    if __name__=="__main__":
        root = Tk()
        root.title('test')
        root.geometry('400x300+450+100')
        a = MainWindows(master=root)
        root.mainloop()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-11
      • 1970-01-01
      • 2020-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-15
      • 2012-06-27
      相关资源
      最近更新 更多