如何与 Tkinter 的事件循环一起运行您自己的代码?

2024-11-25 08:49:00
admin
原创
160
摘要:问题描述:我弟弟刚刚开始​​学习编程,他正在为他的科学展项目做一个模拟天空中一群鸟儿的项目。他已经写好了大部分代码,而且运行良好,但鸟儿需要时刻移动。然而,Tkinter 占用了它自己的事件循环的时间,所以他的代码不会运行。Doingroot.mainloop()运行、运行、继续运行,它唯一运行的就是事件处理...

问题描述:

我弟弟刚刚开始​​学习编程,他正在为他的科学展项目做一个模拟天空中一群鸟儿的项目。他已经写好了大部分代码,而且运行良好,但鸟儿需要时刻移动。

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

有没有办法让他的代码与主循环一起运行(不使用多线程,这会令人困惑,而且应该保持简单),如果可以,那么它是什么?

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


解决方案 1:

在对象上使用该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."""

解决方案 2:

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

问题似乎是,app.start()由于 app 包含 Tk 元素,因此将其视为对 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)

解决方案 3:

当编写自己的循环时,就像在模拟中一样(我假设),您需要调用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 的事件循环。

解决方案 5:

这是 GPS 阅读器和数据呈现器的第一个工作版本。tkinter 非常脆弱,错误消息太少。它不会显示内容,而且大多数时候也不会说明原因。对于一个优秀的所见即所得表单开发人员来说,这非常困难。无论如何,它每秒运行 10 次小程序,并在表单上显示信息。花了一段时间才实现。当我尝试将计时器值设为 0 时,表单从未出现。我的头现在很痛!每秒 10 次或更多对我来说已经足够了。我希望它能帮助别人。Mike Morrow

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()

解决方案 6:

对于首次使用 tkinter 的程序员和新手 Python 程序员来说,这是一个非常常见的问题,他们要么在这里卡住并放弃,要么以“有效”但错误的方式实现他们的代码并导致问题。我知道,因为我是一个新手,刚刚克服了这个障碍。

首先,阅读一些关于 mainloop() 在 tkinter 中实际如何工作的优秀文档,这也解释了为什么将 root.update() 放在代码循环中是一个糟糕的主意,特别是对于长时间运行的脚本(想想 24x7 监控工具等)并且会导致它们最终中断。

接下来,阅读有关线程和类的所有内容,然后尝试实现线程,结果才意识到线程并不是 tkinter 真正想要的架构,而且无论文档怎么说,tkinter 都不是线程安全的。

因此,作为只需要一个具体示例即可入门的新手,请参阅以下最基本的代码(无类、无多余内容,剪切并粘贴到您的 Python IDE 中并按“Go”按钮):

import tkinter as tk

def main(args):
    
    # Build the UI for the user
    root = tk.Tk()
    buttonShutDown = tk.Button(root, text="PUSH ME!")#, command=sys.exit(0))
    buttonShutDown.pack()
    
    # Go into the method that looks at inputs and outputs. Must be run BEFORE you get to root.mainloop()
    monitorInputsAndActionOutputs(root)
    
    # The main tkinter loop. This loop is blocking - ie once you call it your python script stays here forever, hence why you can't do other stuff in your while(blah) loop that you also want to run
    root.mainloop()

def monitorInputsAndActionOutputs(root): 
    
    # This is where your code does something that you want to do regularly and not have blocked by the root.mainloop(). You could make this big or small as you desire.
    print ("checked the room temperature")
    print ("adjusted the airconditioning to make comfy")
    
    # This line causes this method to add a "future event" to the root.mainloop() queue in a manner of speaking. This means that the root.mainloop() method in which your script will be stuck forever will in approx 100 miliseconds time execute this method again for you.
    # Because this method contains this "put myself back in the queue to be run again" trigger, this method will run every 100 miliseconds until you quit your GUI with root.destroy() or sys.exit() or something similar.
    root.after(100, monitorInputsAndActionOutputs, root) 

    return None

# Don't worry about this bit it's not related to your GUI / loop
if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

这是一个非常基本的空调监控脚本示例,其 GUI 上只有一个按钮 - 该按钮可以启用或禁用 AC 监控系统或其他功能。

无论如何,你明白了......这段代码是实现 tkinter 的架构上适当的方式,带有某种额外的运行循环,不需要用户与 GUI 交互并按下按钮或类似操作。

这个例子

  1. 没有实现线程

  2. 是线程安全的(假设您没有在代码中的其他地方自己实现线程)

  3. 是非阻塞的(如果你把你的东西放在那个 monitorInputsAndActionsOutputs() 方法中

  4. 将导致你的脚本在一段随机的时间后不会崩溃

  5. 对新手程序员是否友好(没有课程,没有难以理解的自我架构)

  6. 可扩展 - 你可以添加 100 个额外的方法,使用相同的 after() 样式回调,只要你的处理器有足够的马力,它就会工作 - 也许不要在其中放入 1k 个带有 after() 的方法,然后尝试在 5 年前的 Raspberry Pi 上运行它,同时观看高清视频

此代码不是什么:

  1. 最佳实现(见上面的链接)

  2. 易于大规模管理

但是,它可能足够有用,可以让新手走上正确的道路,并帮助他们避免陷入我所犯的 root.update() 陷阱。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1590  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1361  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   18  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   18  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   19  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用