使用Tkinter开发节拍器调用after_cancel()触发TypeError报错求助
Hey there, let's break down what's causing that error and get your metronome working smoothly.
First, the TypeError Explained
The after_cancel() method only needs one argument: the unique ID returned by the after() call you want to cancel. Right now you're passing two arguments (1, run_), which is why you're seeing TypeError: after_cancel() takes 2 positional arguments but 3 were given (the first argument is always screen itself, so your two extra args make 3 total).
But there are a few other issues in your code that are going to cause problems beyond this error—like using sleep() which freezes your Tkinter GUI, and a loop logic that won't work as intended. Let's fix all of this step by step.
Corrected Code
Here's a revised version of your metronome with explanations of the key changes:
from tkinter import * import os import sys # Global variable to track the after() task ID after_id = None times1 = 0 def quit_(): screen.destroy() sys.exit() def stop_metronome(): global after_id if after_id is not None: screen.after_cancel(after_id) after_id = None def run(): global times1, after_id # Stop any existing metronome loop before starting a new one stop_metronome() times1 = 0 try: bpm = int(bpm_.get()) if bpm <= 0: raise ValueError("BPM must be a positive number") except ValueError as e: entry.delete(0, END) entry.insert(0, str(e)) return # Calculate interval in milliseconds (60 seconds = 60000 ms) interval = int(60000 / bpm) def run_(): global times1, after_id # Play the sound (using afplay, adjust if you're on Windows/Linux) os.system("afplay metronome.wav&") times1 += 1 # Schedule the next tick after_id = screen.after(interval, run_) # Start the first tick run_() def main(): global bpm_, entry bpm_ = StringVar() Label(screen, text="").pack() Label(screen, text="Enter a BPM").pack() entry = Entry(screen, textvariable=bpm_) entry.pack() Label(screen, text="").pack() Button(screen, text="Start/Update BPM", command=run).pack() Label(screen, text="").pack() Button(screen, text="Quit", command=quit_).pack() screen = Tk() screen.title("Metronome") main() screen.mainloop()
Key Changes Made:
- Fixed
after_cancel()usage: We now store the ID returned byscreen.after()inafter_id, and pass only that ID toafter_cancel()when stopping the loop. - Removed
sleep():sleep()blocks Tkinter's event loop, which makes your GUI unresponsive. Instead, we calculate the interval in milliseconds and useafter()to schedule each tick—this keeps the GUI alive. - Added stop logic: The
stop_metronome()function safely cancels any running metronome loop, so clicking "Start/Update BPM" multiple times won't create overlapping loops. - Input validation: We added a try/except block to handle invalid BPM inputs (like non-numbers or zero/negative values) so your app doesn't crash unexpectedly.
- Simplified loop logic: The
run_()function now schedules itself repeatedly usingafter(), with the correct interval based on the BPM.
How to Adjust BPM Now
Just enter a new BPM and click "Start/Update BPM"—the existing loop will stop automatically, and a new one with the updated BPM will start right away.
内容的提问来源于stack exchange,提问作者Leonardo Cremona

