You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Tkinter程序因after()方法引发界面冻结的解决方案求助

Fixing UI Freeze in Tkinter Typing Speed Test

Let's break down why your current code is causing the UI freeze, then fix it properly using Tkinter's built-in tools (no threads needed—they're actually risky here because Tkinter isn't thread-safe).

Why Your Current Code Freezes

  1. The while loop blocks the main thread: Tkinter runs everything in a single main thread. Your while counter < 100 loop runs continuously, even with root.update() and root.after(1000)after(1000) doesn't pause execution, it just schedules a future task, so the loop keeps hogging the thread.
  2. Threading with Tkinter is unsafe: You tried running seconds_counter in a thread, but all Tkinter widget operations (like setting seconds.set() or creating labels) must happen in the main thread. Doing this in a sub-thread leads to unpredictable behavior, including freezes.
  3. The trace triggers the counter repeatedly: Every time the entry text changes, seconds_counter runs again, creating multiple overlapping loops/threads.

Working Solution

We'll use Tkinter's after() method correctly to create a non-blocking timer, and manage the start/stop logic cleanly:

import random
import tkinter as tk
from sentences import sentences
from PIL import ImageTk, Image

root = tk.Tk()
root.title("Speed typing test")
root.geometry('700x700')

# Dynamic variables
entry_text = tk.StringVar()
seconds = tk.IntVar(value=0)
timer_running = False  # Track if the timer is active
total_time = 0

# Pre-create result labels (avoid creating new ones every time)
final_lbl = tk.Label(root, text="")
total_words_lbl = tk.Label(root, text="")

def reset_test():
    """Reset the test to a new sentence and clear results"""
    global sentence, timer_running, total_time
    # Get new sentence
    sentence = random.choice(sentences).strip('.')
    sentence_lbl.config(text=sentence)
    # Reset UI state
    entry.delete(0, tk.END)
    seconds.set(0)
    final_lbl.config(text="")
    total_words_lbl.config(text="")
    timer_running = False
    total_time = 0

def update_timer():
    """Update the timer every second (non-blocking)"""
    global total_time
    if timer_running:
        total_time += 1
        seconds.set(total_time)
        # Schedule next update in 1 second
        root.after(1000, update_timer)

def check_input(*args):
    """Check if the input matches the sentence, start timer if needed"""
    global timer_running
    current_input = entry_text.get()
    
    # Start timer when user begins typing (only once)
    if current_input and not timer_running:
        timer_running = True
        update_timer()
    
    # Check if input matches the target sentence
    if current_input == sentence:
        timer_running = False  # Stop the timer
        # Calculate WPM (standard: words = characters /5, WPM = words / (minutes))
        wpm = (len(current_input) / 5) / (total_time / 60)
        final_lbl.config(text=f"WPM: {round(wpm, 2)}")
        total_words = len(sentence.split())
        total_words_lbl.config(text=f"Total words: {total_words}")
        # Reset for next test after a short delay (optional)
        root.after(2000, reset_test)

# Load widgets
logo = ImageTk.PhotoImage(Image.open('images/logo.png'))
logo_lbl = tk.Label(image=logo)
sentence_lbl = tk.Label(root, text="")
entry = tk.Entry(root, width=80, textvariable=entry_text)  # Reduced width for better layout
seconds_lbl = tk.Label(root, text="Time elapsed: ")
seconds_display = tk.Label(root, textvariable=seconds)
btn_quit = tk.Button(root, text='Quit', command=root.destroy)

# Place widgets
logo_lbl.pack(pady=10)
sentence_lbl.pack(pady=10, padx=20)
entry.pack(pady=10)
# Time display (split into label and variable for clarity)
tk.Frame(root).pack()  # Spacer
seconds_lbl.pack(side=tk.LEFT, padx=(150, 0))
seconds_display.pack(side=tk.LEFT)
# Result labels
final_lbl.pack(pady=5)
total_words_lbl.pack(pady=5)
btn_quit.pack(pady=20)

# Trace input changes to start timer and check completion
entry_text.trace('w', check_input)

# Initialize first test
reset_test()

root.mainloop()

Key Improvements Explained

  1. Non-blocking timer with after(): Instead of a while loop, update_timer() uses root.after(1000, update_timer) to schedule itself every second. This lets Tkinter's main event loop handle other UI actions (like typing) without freezing.
  2. Timer state management: The timer_running flag ensures we only start the timer once when the user begins typing, not every time the text changes.
  3. Reusable widgets: We pre-create result labels and update their text instead of creating new labels each time. This keeps the UI clean and avoids memory bloat.
  4. Safe UI operations: All widget updates happen in the main thread (Tkinter's event loop), which is the only safe way to modify the UI.
  5. Clean reset logic: The reset_test() function handles setting up a new sentence and clearing previous results, making the test reusable without restarting the app.

Why Threads Aren't Needed Here

Tkinter's after() method is designed exactly for this kind of periodic task. Threads introduce complexity and risk because Tkinter's internals aren't thread-safe—even if you get them to work, you might run into race conditions or crashes down the line. The above approach is the standard, recommended way to handle timers in Tkinter.

内容的提问来源于stack exchange,提问作者caviarnetherite

火山引擎 最新活动