您如何在Tkinter的事件循环中运行自己的代码?


119

我的弟弟刚刚开始​​编程,对于他的Science Fair项目,他正在模拟天空中一群鸟。他得到了大部分他写的代码,它工作得很好,但鸟儿需要移动的每一刻

但是,Tkinter浪费了自己的事件循环的时间,因此他的代码无法运行。这样root.mainloop()运行,运行,并保持运行,并且它运行的唯一事情是事件处理程序。

有没有一种方法可以让他的代码与mainloop一起运行(没有多线程,这很令人困惑,应该保持简单),如果这样,那是什么?

现在,他想出了一个丑陋的方法,将其move()功能绑定到<b1-motion>,这样,只要按住按钮并摇动鼠标,它就可以工作。但是必须有一个更好的方法。

Answers:


140

afterTk对象上使用方法:

from tkinter import *

root = Tk()

def task():
    print("hello")
    root.after(2000, task)  # reschedule event in 2 seconds

root.after(2000, task)
root.mainloop()

这是该after方法的声明和文档:

def after(self, ms, func=None, *args):
    """Call function once after given time.

    MS specifies the time in milliseconds. FUNC gives the
    function which shall be called. Additional parameters
    are given as parameters to the function call.  Return
    identifier to cancel scheduling with after_cancel."""

29
如果将超时指定为0,则任务将在完成后立即将其自身放回到事件循环中。这不会阻止其他事件,同时仍会尽可能频繁地运行您的代码。
弥敦道

在将我的头发拉了几个小时之后,当单击[X]按钮时,试图使opencv和tkinter正常工作并干净地关闭,这与win32gui.FindWindow(None,'window title')一起成功了!我真是个
菜鸟

这不是最佳选择。尽管在这种情况下可以使用,但对于大多数脚本来说,它并不好(它仅每2秒运行一次),并且按照@Nathan的建议将超时设置为0,因为它仅在tkinter不忙时运行(可以导致某些复杂程序出现问题)。最好坚持使用该threading模块。
匿名的

59

Bjorn发布解决方案在我的计算机(RedHat Enterprise 5,python 2.6.1)上显示“ RuntimeError:从不同的公寓呼叫Tcl”消息。Bjorn可能没有得到此消息,因为据我检查过的一个地方,使用Tkinter处理线程是不可预测的且依赖于平台。

问题似乎是可以将其app.start()视为对Tk的引用,因为app包含Tk元素。我通过替换app.start()self.start()inside来解决此问题__init__。我也这样做了,以便所有Tk引用都在调用mainloop()函数内,或者在调用的函数所调用的函数内mainloop()(这对于避免“不同单元”错误很关键)。

最后,我添加了带有回调的协议处理程序,因为如果没有此程序,当用户关闭Tk窗口时,程序将退出并出现错误。

修改后的代码如下:

# Run tkinter code in another thread

import tkinter as tk
import threading

class App(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.start()

    def callback(self):
        self.root.quit()

    def run(self):
        self.root = tk.Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.callback)

        label = tk.Label(self.root, text="Hello World")
        label.pack()

        self.root.mainloop()


app = App()
print('Now we can continue running code while mainloop runs!')

for i in range(100000):
    print(i)

您如何将参数传递给run方法?我似乎不知道该怎么

5
通常,您会将参数传递给__init__(..),进行存储并在self其中使用run(..)
Andre Holzner

1
根本没有显示根目录,发出警告:`警告:NSWindow拖动区域仅应在主线程上无效!这将抛出在未来`一个例外
鲍勃Bobster

1
这个评论值得更多的认可。惊人。
丹尼尔·雷哈尼安

这可以节省生命。如果您一旦退出gui便无法退出python脚本,则GUI外部的代码应检查tkinter线程是否仍然有效。像while app.is_alive(): etc
m3nda

20

在编写自己的循环时,就像在模拟中一样(我假设),您需要调用执行update功能的函数mainloop:使用所做的更改来更新窗口,但是您需要在循环中进行操作。

def task():
   # do something
   root.update()

while 1:
   task()  

10
这种编程必须非常小心。如果导致任何事件task被调用,您将最终陷入嵌套事件循环,这很糟糕。除非您完全了解事件循环的工作方式,否则应update不惜一切代价避免调用。
布莱恩·奥克利

我曾经使用过这种技术-可以正常工作,但是根据您的操作方式,UI中可能会有一些错位。
jldupont

6

另一个选择是让tkinter在单独的线程上执行。一种方法是这样的:

import Tkinter
import threading

class MyTkApp(threading.Thread):
    def __init__(self):
        self.root=Tkinter.Tk()
        self.s = Tkinter.StringVar()
        self.s.set('Foo')
        l = Tkinter.Label(self.root,textvariable=self.s)
        l.pack()
        threading.Thread.__init__(self)

    def run(self):
        self.root.mainloop()


app = MyTkApp()
app.start()

# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')

但是要小心,多线程编程很难,而且很容易使自己陷入困境。例如,当您更改上面的示例类的成员变量时,请务必小心,以免打断Tkinter的事件循环。


3
不确定是否可以正常工作。刚刚尝试类似的东西,我得到“ RuntimeError:主线程不在主循环中”。
jldupont

5
jldupont:我收到“ RuntimeError:从不同的位置调用Tcl”(可能在不同的版本中存在相同的错误)。解决方法是在run()中而不是在__init __()中初始化Tk。这意味着,你正在初始化Tk的在同一个线程中调用在主循环()。
mgiuca

2

这是GPS读取器和数据演示器的第一个工作版本。tkinter是一件非常脆弱的事情,错误消息很少。它不会塞满东西,也无法说明为什么要花费很多时间。一个好的WYSIWYG表单开发者非常困难。无论如何,这将运行一个小的例程,每秒10次,并在表格上显示信息。花了一段时间才能实现。当我尝试将计时器值设置为0时,表单就永远不会出现。我的头现在好痛!每秒10次或更多次对我来说足够了。我希望它可以帮助其他人。迈克·莫罗

import tkinter as tk
import time

def GetDateTime():
  # Get current date and time in ISO8601
  # https://en.wikipedia.org/wiki/ISO_8601 
  # https://xkcd.com/1179/
  return (time.strftime("%Y%m%d", time.gmtime()),
          time.strftime("%H%M%S", time.gmtime()),
          time.strftime("%Y%m%d", time.localtime()),
          time.strftime("%H%M%S", time.localtime()))

class Application(tk.Frame):

  def __init__(self, master):

    fontsize = 12
    textwidth = 9

    tk.Frame.__init__(self, master)
    self.pack()

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             text='Local Time').grid(row=0, column=0)
    self.LocalDate = tk.StringVar()
    self.LocalDate.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             textvariable=self.LocalDate).grid(row=0, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             text='Local Date').grid(row=1, column=0)
    self.LocalTime = tk.StringVar()
    self.LocalTime.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             textvariable=self.LocalTime).grid(row=1, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             text='GMT Time').grid(row=2, column=0)
    self.nowGdate = tk.StringVar()
    self.nowGdate.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             textvariable=self.nowGdate).grid(row=2, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             text='GMT Date').grid(row=3, column=0)
    self.nowGtime = tk.StringVar()
    self.nowGtime.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             textvariable=self.nowGtime).grid(row=3, column=1)

    tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)

    self.gettime()
  pass

  def gettime(self):
    gdt, gtm, ldt, ltm = GetDateTime()
    gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
    gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'  
    ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
    ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]  
    self.nowGtime.set(gdt)
    self.nowGdate.set(gtm)
    self.LocalTime.set(ldt)
    self.LocalDate.set(ltm)

    self.after(100, self.gettime)
   #print (ltm)  # Prove it is running this and the external code, too.
  pass

root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)

w = 200 # width for the Tk root
h = 125 # height for the Tk root

# get display screen width and height
ws = root.winfo_screenwidth()  # width of the screen
hs = root.winfo_screenheight() # height of the screen

# calculate x and y coordinates for positioning the Tk root window

#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)

#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35  # -35 fixes it, more or less, for Win10

#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))

root.mainloop()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.