在Tkinter应用中运行循环

3 投票
1 回答
5189 浏览
提问于 2025-04-16 19:12

我是一名高中编程学生,有个小问题想请教一下。我正在用Tkinter写一个简单的游戏,游戏里有一根冰柱从天花板上掉下来,你需要用鼠标避开它。听起来挺简单的。但是,我遇到了一些问题。每当我在Tkinter应用程序中运行一个循环时,窗口都不会打开。我试过用一个每0.5秒暂停一次的for循环,使用time.sleep(),结果窗口在循环结束后才打开。请问在Tkinter中运行循环有什么特别的要求吗?

from Tkinter import *
import time
import random

class App:
    def __init__(self, parent):
        self.frame = Frame(root, bg= '#1987DF', width=800, height=800)
        self.frame.bind("<Motion>", self.motionevent)
        self.frame.pack()
        #self.run()
    def randhex(self):
        b = "#"
        for i in range(1, 7):
            a = random.randint(0, 15)
            if a == 10:
                a = "A"
            elif a == 11:
                a = "B"
            elif a == 12:
                a = "C"
            elif a == 13:
                a = "D"
            elif a == 14:
                a = "E"
            elif a == 15:
                a = "F"
            b = b+str(a)
        return b

    def motionevent(self, event):
        xpos, ypos, bg = event.x, event.y, self.randhex()
        str1 = "X : %d  Y : %d BG : %s" % (xpos, ypos, bg)
        root.title(str1)
        x,y, delta = 100, 100, 10
        self.frame.config(bg=bg)

    def run(self):
        for i in range(0, 10):
            time.sleep(.5)
            print 'i'
            self.frame.config(bg=self.randhex())

root = Tk()
app = App(root)
root.mainloop()

现在这个程序的功能只是当鼠标移动时改变背景颜色。当我取消注释init中的self.run()那一行时,它会打印'i' 10次,然后窗口才会打开。有人能帮帮我吗?

1 个回答

6

写一个基于事件的程序和写传统程序是完全不同的。因为在后台已经有一个无限循环在运行,就像任何无限循环一样,如果你在里面再放一个循环,外面的循环就得等里面的循环完成才能继续。外面的循环负责刷新屏幕和处理事件,所以内部的循环会让你的应用程序在完成之前“冻结”。

既然已经有一个循环在运行,你就不需要再创建一个新的循环。你只需要一个一个地把小任务添加到事件队列中。每个任务实际上就是你内部循环的一次迭代。

比如说,如果你想写一个这样的内部循环:

for i in range(10):
    print "i:", i

...你应该把一个事件添加到事件队列中,每次事件循环运行(更准确地说,每次它处理完其他事件后)就会执行你循环的一次迭代。你可以这样做:

def do_one_iteration(i):
    print "i:", i
    if i < 9:
        root.after_idle(do_one_iteration, i+1)

然后,在你第一次调用 do_one_iteration 之后,它会把下一个迭代放到事件队列中,并继续这样做,直到它决定完成。

通常情况下,你会在用户按下按钮(比如“开始”按钮)时调用 do_one_iteration。调用一次,它就会做一点工作(比如把冰柱向下移动几个像素),然后重新安排自己。

在游戏开发中,你可能会有一个叫 update_display 的函数,负责重新绘制所有东西。比如,它可以把每个冰柱的Y坐标减去1。你可以为左右箭头添加绑定来移动玩家(通过增加或减少X坐标),然后你的更新函数会用这些新的坐标来重新绘制玩家。

顺便说一下,你可以通过使用 after 而不是 after_idle 来让你的程序变慢,这样可以在稍微延迟后调用函数。这个延迟可以用来控制帧率。例如,假设更新几乎是瞬间完成的(在你的情况下可能确实如此),调用 after 并传入41(毫秒)作为参数,帧率大约是24帧每秒(24帧乘以41毫秒等于984毫秒,差不多是每秒24帧)。

听起来可能有点复杂,但实际上只要做一两次就会发现其实很简单。

撰写回答