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

问题描述 投票:103回答:5

我的小弟弟正在进入编程,而在他的科学博览会项目中,他正在模拟天空中的一群鸟。他已经完成了大部分代码编写工作,并且工作得很好,但是鸟类需要每时每刻都在移动。

然而,Tkinter占用了自己的事件循环的时间,所以他的代码不会运行。做root.mainloop()运行,运行并继续运行,它运行的唯一事情是事件处理程序。

有没有办法让他的代码与mainloop一起运行(没有多线程,这很混乱,这应该保持简单),如果是这样,它是什么?

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

python events tkinter
5个回答
122
投票

after对象上使用Tk方法:

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."""

44
投票

solution posted by Bjorn导致我的计算机上出现“RuntimeError:从不同的公寓调用Tcl”消息(RedHat Enterprise 5,python 2.6.1)。 Bjorn可能没有得到这个消息,因为根据one place I checked,错误处理与Tkinter的线程是不可预测和平台相关的。

问题似乎是app.start()算作对Tk的引用,因为app包含Tk元素。我通过用app.start()中的self.start()替换__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)

17
投票

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

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

while 1:
   task()  

4
投票

另一个选择是让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的事件循环。


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()
© www.soinside.com 2019 - 2024. All rights reserved.