我正在尝试制作一个有趣的小程序,其中每小时左右就会有一只毛茸茸的动物走过屏幕。我有一个主窗口,它启动一个循环,每小时左右播放一次动画/声音,但是在口袋妖怪第一次完成行走后,整个程序就会结束。我认为这可能与我设置 tkinter 窗口的方式有关,但我无法弄清楚。
我认为在这里包含整个代码是不合适的,但我是新的我不知道我在做什么,所以请耐心等待。
from tkinter import *
from PIL import Image
from random import randint
from pydub import AudioSegment
from pydub.playback import play
import threading
import time
class MyFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.status = StringVar(self, 'Not Walking')
self.walking = False
self.showAnimation = None
self.createWidgets()
def createWidgets(self):
self.mainLabel = Label(self, text= "<>FURRET WALK<>")
self.statusLabel = Label(self, textvariable= self.status)
self.startButton = Button(self, text="START", command= self.starter)
self.endButton = Button(self, text="END", command= self.endWalk)
self.mainLabel.pack(expand=True)
self.statusLabel.pack(expand=True)
self.startButton.pack(expand=True)
self.endButton.pack(expand=True)
def playSong(self):
num = randint(1,6)
path = ""
match num:
case 1:
path = "1-17. Accumula Town.wav"
case 2:
path = "1-32. The Dreamyard.wav"
case 3:
path = "1-51. Castelia City.wav"
case 4:
path = "1-61. Bicycle.wav"
case 5:
path = "2-02. Driftveil City.wav"
case 6:
path = "2-08. Mistralton City.wav"
song = AudioSegment.from_wav(path)
audio = song[:43*1000]
play(audio)
def animation(self, extraWindow, count, imageObject, gifLabel, frames):
newImage = imageObject[count]
try:
gifLabel.configure(image=newImage)
except:
pass
count += 1
if count == frames:
count = 0
try:
self.showAnimation = extraWindow.after(20, lambda: self.animation(extraWindow, count, imageObject, gifLabel, frames))
except:
pass
def moveWindow(self, extraWindow,moveX,moveY,startX,startY,dirX,dirY,width,yCumulitive,avgY):
extraWindow.geometry(f"128x72+{moveX+startX}+{moveY+startY}")
if dirY:
yCumulitive += avgY
if yCumulitive > 1:
yCumulitive = 0
moveY += 1
else:
yCumulitive += avgY
if yCumulitive > 1:
yCumulitive = 0
moveY -= 1
if dirX:
moveX += 2
if moveX > width+128:
extraWindow.destroy()
else:
moveX -= 2
if moveX < (width+128)*-1:
extraWindow.destroy()
time.sleep(0.05)
try:
extraWindow.after(150, self.moveWindow(extraWindow,moveX,moveY,startX,startY,dirX,dirY,width,yCumulitive,avgY))
except:
pass
def createWindow(self):
extraWindow = Toplevel()
count = 0
moveX = 0
moveY = 0
width = extraWindow.winfo_screenwidth()
height = extraWindow.winfo_screenheight()
dirX = True
dirY = True
startX = randint(-128, -127)
startY = randint(0, height)
if startX == -127:
dirX = False
startX = width
yTarget = startY
if randint(1,3) > 1:
yTarget = (float(randint(0, width))*0.25)*2
if randint(1,2) == 2:
dirY = False
if (startY - yTarget) < 0:
dirY = True
if (startY + yTarget) > height:
dirY = False
tics = width/2
yDistance = yTarget - startY
avgY = yDistance/tics
extraWindow.geometry(f"128x72+{startX}+{startY}")
extraWindow.wm_attributes("-topmost", True)
extraWindow.overrideredirect(True)
extraWindow.attributes("-transparentcolor", "white")
if dirX:
gifImage = "furret.gif"
else:
gifImage = "furret-right-to-left.gif"
openImage = Image.open(gifImage)
frames = openImage.n_frames
imageObject = [PhotoImage(file=gifImage, format=f"gif -index {i}") for i in range(frames)]
gifLabel = Label(extraWindow, image="")
gifLabel.place(x=0,y=0,width=128,height=72)
t1 = threading.Thread(target=self.playSong)
t1.start()
self.animation(extraWindow, count, imageObject, gifLabel, frames)
self.moveWindow(extraWindow,moveX,moveY,startX,startY,dirX,dirY,width,0,avgY)
def startWalk(self):
self.walking = True
self.status.set("Walking!")
while(self.walking):
self.createWindow()
add = randint(1800, 3600)
time.sleep(3600)
def starter(self):
ts = threading.Thread(target=self.startWalk, args=())
ts.start()
def endWalk(self):
self.walking = False
self.status.set("Not Walking")
def main():
root = Tk()
root.title("Furret Walk!")
root.geometry("300x100")
frame = MyFrame(root)
frame.pack()
root.mainloop()
main()
我尝试更改应该保持程序运行的 while 循环的位置,但它只会破坏一切。
基本上, main创建一个小窗口,其中填充了 createWidgets() 函数生成的小部件。我按下开始按钮,它运行 starter(),它为 startWalk() 函数创建一个线程。 startWalk() 应该继续播放,直到我按下结束按钮,这会触发 endWalk() ,从而将 while 循环条件变为 false。但如上所述,程序在毛茸茸第一次走过屏幕后结束。
在乱搞之后,我认为这与我设置的线程更相关,就像 t1 线程(它的存在是为了播放音乐)一样,播放完歌曲,程序结束。我不确定这有什么关系。
问题不在于 Tkinter 窗口不断关闭,而在于你的
startWalk
函数在第一次调用
createWindow
后结束,从而过早地停止了漫步过程。这是因为
while self.walking
循环在
createWindow
返回后立即检查
self.walking
的值,此时
createWindow
及其所有子进程(动画、移动、音乐)可能仍在运行。
你需要修改代码以使
startWalk
函数在
createWindow
创建的动画完成之前不会结束。
与其试图管理循环和睡眠时间,更简洁的解决方案是使用
after
方法来安排窗口的下一个“步骤”。以下是如何重构代码以实现此目的:
import tkinter as tk
from PIL import Image, ImageTk
from random import randint
from pydub import AudioSegment
from pydub.playback import play
import threading
class MyFrame(tk.Frame):
def __init__(self, root):
super().__init__(root)
self.status = tk.StringVar(self, 'Not Walking')
self.walking = False
self.createWidgets()
def createWidgets(self):
self.mainLabel = tk.Label(self, text="<>FURRET WALK<>")
self.statusLabel = tk.Label(self, textvariable=self.status)
self.startButton = tk.Button(self, text="START", command=self.startWalk)
self.endButton = tk.Button(self, text="END", command=self.endWalk)
self.mainLabel.pack(expand=True)
self.statusLabel.pack(expand=True)
self.startButton.pack(expand=True)
self.endButton.pack(expand=True)
def playSong(self):
# ... (song playing logic remains the same)
def createWindow(self):
extraWindow = tk.Toplevel()
extraWindow.overrideredirect(True)
extraWindow.attributes("-transparentcolor", "white")
extraWindow.wm_attributes("-topmost", True)
width = extraWindow.winfo_screenwidth()
height = extraWindow.winfo_screenheight()
# ... (rest of your createWindow logic for positioning, etc.)
self.animate_and_move(extraWindow, width, height, 0, moveX, moveY, dirX, dirY, 0, avgY)
def animate_and_move(self, window, screen_width, screen_height, frame_count, x, y, dir_x, dir_y, y_cumulative, avg_y):
if not self.walking or not window.winfo_exists():
return # Stop if walking is stopped or window closed
# ... (Your animation and movement logic here)
# Schedule the next frame after a delay
window.after(
150,
self.animate_and_move,
window,
screen_width,
screen_height,
frame_count + 1,
x,
y,
dir_x,
dir_y,
y_cumulative,
avg_y
)
def startWalk(self):
if not self.walking:
self.walking = True
self.status.set("Walking!")
self.createWindow()
def endWalk(self):
self.walking = False
self.status.set("Not Walking")
def main():
root = tk.Tk()
root.title("Furret Walk!")
root.geometry("300x100")
frame = MyFrame(root)
frame.pack()
root.mainloop()
if __name__ == "__main__":
main()
在这个改进的版本中:
-
我们将动画和移动逻辑合并到一个函数
animate_and_move
中。 -
animate_and_move
使用window.after
方法安排自身在 150 毫秒后再次调用,从而有效地创建了一个动画循环。 -
createWindow
函数现在只初始化窗口和动画,然后animate_and_move
接管。 -
startWalk
函数现在只设置self.walking
标志并调用一次createWindow
。 -
animate_and_move
函数在每次调用开始时都会检查self.walking
标志。如果标志为 False(表示endWalk
被调用),则函数返回,从而停止动画。
这种方法可以让你更简洁地控制动画,并且不会依赖于可能不准确的
time.sleep
调用。