我正在研究tkinter中一个简单钟摆的动画。我已将 s 和 r 绑定到动画的stop
和resume
窗口。但是我注意到一个奇怪的怪癖我无法解释:如果我没有停止钟摆时按 r ,它会加速。我无法弄清楚为什么会这样。任何人都可以解释发生了什么以及如何解决它?
这是我的代码:
from math import sqrt, cos, sin, radians
from tkinter import *
class SimplePendulum:
def __init__(self):
# Create a window
win = Tk()
win.title('Pendulum')
# Create a canvas
self.w, self.h = 250, 300
self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
self.canvas.pack()
# Bind keys to the window
win.bind('s', self.stop)
win.bind('S', self.stop)
win.bind('r', self.resume)
win.bind('R', self.resume)
# Pendulum constants
self.g = 1
self.L = 4*self.h/5
self.theta_i = radians(20)
# Initialize time t to 0
self.t = 0
# Start animation
self.isStopped = False
self.speed = 1/50 # initial frequency of oscillation
self.animate()
# Start the event loop
win.mainloop()
def drawPendulum(self):
# Angle of the pendulum (from the vertial) at time t
theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
# The two ends of the cord
x_i, y_i = self.w/2, self.h/10
x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
# Draw the cord and bob of the pendulum
self.canvas.create_line(x_i, y_i, x_f, y_f, tags='cord')
rad = min(self.w, self.h)/20
self.canvas.create_oval(x_f - rad, y_f - rad,
x_f + rad, y_f + rad, fill='red', tags='bob')
def animate(self):
if not self.isStopped:
self.canvas.delete(ALL)
self.drawPendulum()
self.t += 2
self.canvas.after(int(1/self.speed), self.animate)
def stop(self, event):
self.isStopped = True
def resume(self, event):
self.isStopped = False
self.animate()
SimplePendulum()
答案 0 :(得分:2)
与您的问题没有关系,但如果更新画布上的元素而不是清除整个画布并从头开始重绘,则动画会更顺畅:
from math import sqrt, cos, sin, radians
from tkinter import *
class SimplePendulum:
def __init__(self):
# Create a window
win = Tk()
win.title('Pendulum')
# Create a canvas
self.w, self.h = 250, 300
self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
self.canvas.pack()
# Bind keys to the window
win.bind('s', self.stop)
win.bind('S', self.stop)
win.bind('r', self.resume)
win.bind('R', self.resume)
# Pendulum constants
self.g = 1
self.L = 4*self.h/5
self.theta_i = radians(20)
# Initialize time t to 0
self.t = 0
cord, bob = self.calcPendulum()
self.cord = self.canvas.create_line(*cord, tags='cord')
self.bob = self.canvas.create_oval(*bob, fill='red', tags='bob')
# Start animation
self.isStopped = False
self.speed = 1/50 # initial frequency of oscillation
self.animate()
# Start the event loop
win.mainloop()
def calcPendulum(self):
# Angle of the pendulum (from the vertial) at time t
theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
# The two ends of the cord
x_i, y_i = self.w/2, self.h/10
x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
rad = min(self.w, self.h)/20
cord_pos = x_i, y_i, x_f, y_f
bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
return cord_pos, bob_pos
def animate(self):
if not self.isStopped:
cord, bob = self.calcPendulum()
self.canvas.coords(self.cord, *cord)
self.canvas.coords(self.bob, *bob)
self.t += 2
self.canvas.after(int(1/self.speed), self.animate)
def stop(self, event):
self.isStopped = True
def resume(self, event):
self.isStopped = False
self.animate()
SimplePendulum()
此外,没有必要在这里上课。我怀疑你这样做是因为你看到或被告知GUI中的所有内容都需要一个类。但关键是它需要成为GUI小部件的子类。例如,您可以将SimplePendulum类设为Canvas:
from math import sqrt, cos, sin, radians
from tkinter import *
class SimplePendulum(Canvas):
def __init__(self, master=None, **kwargs):
Canvas.__init__(self, master, bg='white', **kwargs)
# Bind keys to the window
master.bind('s', self.stop)
master.bind('S', self.stop)
master.bind('r', self.resume)
master.bind('R', self.resume)
# Pendulum constants
self.g = 1
self.theta_i = radians(20)
# Initialize time t to 0
self.t = 0
cord, bob = self.calcPendulum()
self.cord = self.create_line(*cord, tags='cord')
self.bob = self.create_oval(*bob, fill='red', tags='bob')
# Start animation
self.timer = ''
self.speed = 1/50 # initial frequency of oscillation
self.animate()
def calcPendulum(self):
# Angle of the pendulum (from the vertial) at time t
L = 4*self.winfo_height()/5
theta = self.theta_i * cos(sqrt(self.g/L) * self.t)
# The two ends of the cord
x_i, y_i = self.winfo_width()/2, self.winfo_height()/10
x_f, y_f = x_i + L*sin(theta), y_i + L*cos(theta)
rad = min(self.winfo_width(), self.winfo_height())/20
cord_pos = x_i, y_i, x_f, y_f
bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
return cord_pos, bob_pos
def animate(self):
cord, bob = self.calcPendulum()
self.coords(self.cord, *cord)
self.coords(self.bob, *bob)
self.t += 2
self.timer = self.after(int(1/self.speed), self.animate)
def stop(self, event=None):
self.after_cancel(self.timer)
def resume(self, event=None):
self.stop() # in case it's currently running, stop it
self.animate()
def main():
# Create a window
win = Tk()
win.title('Pendulum')
part = SimplePendulum(win, width=200, height=300)
part.pack(fill=BOTH, expand=True)
win.mainloop() # Start the event loop
if __name__ == '__main__':
main()
现在你可以像我一样在一个小的演示程序中使用你的新小部件,或者将它打包到任何更大的程序中。或者多次使用它。
我还调了几个其他的东西,比如把长度计算放在定时步骤中,这样你就可以调整窗口的大小,并且钟摆也可以调整大小。你的数学使这很有趣,因为它很好地展示了摆长度和频率之间的关系。嗯,现在我们有一个整洁的小部件,我们可以通过在屏幕上放一个短的和一个高的一起轻松演示:
def main():
# Create a window
win = Tk()
win.title('Pendulum')
part = SimplePendulum(win, width=200, height=100)
part.pack(side=LEFT)
part = SimplePendulum(win, width=400, height=600)
part.pack(side=LEFT)
win.mainloop() # Start the event loop
答案 1 :(得分:1)
只需将resume
替换为:
def resume(self, event):
if self.isStopped:
self.isStopped = False
self.animate()
当resume
运行self.isStopped
时,如果animate
标志检查,它运行一个新的animate
,它有自己的自身递归递归,因此它调用线性增加{{1}每次resume
调用没有标志的方法。
答案 2 :(得分:1)
或者,您可以使用after_cancel
取消动画的队列。不确定这是否有优势:
from math import sqrt, cos, sin, radians
from tkinter import *
class SimplePendulum:
def __init__(self):
# Create a window
win = Tk()
win.title('Pendulum')
# Create a canvas
self.w, self.h = 250, 300
self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
self.canvas.pack()
# Bind keys to the window
win.bind('s', self.stop)
win.bind('S', self.stop)
win.bind('r', self.resume)
win.bind('R', self.resume)
# Pendulum constants
self.g = 1
self.L = 4*self.h/5
self.theta_i = radians(20)
# Initialize time t to 0
self.t = 0
self._queue = False
# Start animation
self.isStopped = False
self.speed = 1/50 # initial frequency of oscillation
self.animate()
# Start the event loop
win.mainloop()
def drawPendulum(self):
# Angle of the pendulum (from the vertial) at time t
theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
# The two ends of the cord
x_i, y_i = self.w/2, self.h/10
x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
# Draw the cord and bob of the pendulum
self.canvas.create_line(x_i, y_i, x_f, y_f, tags='cord')
rad = min(self.w, self.h)/20
self.canvas.create_oval(x_f - rad, y_f - rad,
x_f + rad, y_f + rad, fill='red', tags='bob')
def animate(self):
self.canvas.delete(ALL)
self.drawPendulum()
self.t += 2
self._queue = self.canvas.after(int(1/self.speed), self.animate)
def stop(self, event):
if self._queue:
self.canvas.after_cancel(self._queue)
self._queue = False
def resume(self, event):
if not self._queue:
self.animate()
SimplePendulum()