144

我的小弟弟刚刚开始​​编程,为了他的科学博览会项目,他正在模拟天空中的一群鸟。他已经编写了大部分代码,并且运行良好,但是鸟儿需要每时每刻都在移动。

然而,Tkinter 为自己的事件循环占用了时间,因此他的代码不会运行。运行root.mainloop(),运行,并保持运行,它运行的唯一东西是事件处理程序。

有没有办法让他的代码在主循环旁边运行(没有多线程,这很混乱,应该保持简单),如果是这样,它是什么?

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

4

5 回答 5

168

在对象上使用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."""
于 2009-01-19T20:55:36.543 回答
70

Bjorn 发布的解决方案导致我的计算机(RedHat Enterprise 5,python 2.6.1)上出现“RuntimeError: Calling Tcl from different appartment”消息。Bjorn 可能没有收到此消息,因为根据我检查的一个地方,使用 Tkinter 错误处理线程是不可预测的并且依赖于平台。

问题似乎是app.start()对 Tk 的引用,因为 app 包含 Tk 元素。我通过用 inside 替换来解决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)
于 2009-12-02T18:55:46.127 回答
26

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

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

while 1:
   task()  
于 2011-01-29T09:35:20.837 回答
8

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

于 2009-02-11T20:14:55.167 回答
3

这是 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()
于 2016-10-25T07:12:18.297 回答