速度类型测试应用程序的 Tkinter 线程问题

问题描述 投票:0回答:1

所以我读了很多关于 Tkinter 的线程和事件管理器的不同问题,但我找不到解决我的问题的方法。所以现在我知道我的代码很混乱,我还不担心 tkinter 组织,但我成功创建了计时器并在倒计时时将其显示在 Canvas 上,但我无法实时输入,因为我的条目正在等待计时器显示前整整一秒,造成了延迟。

我的代码中具体做错了什么?我以为我把线索弄对了。

#timer_display.py
from tkinter import *
from english_words import get_english_words_set


class App(Tk):
    def __init__(self):
        super().__init__()
        self.timer = 60
        self.score = 0

    def countdown(self, parent):
        self.timecanvas = Canvas(parent, bg="black", width=500, height=100)
        self.timecanvas.pack()

    def tick(self):
        self.timecanvas.delete('all')
        self.timecanvas.create_text(145, 45, text=f"{self.timer}", fill="green", font=('Helvetica 55 bold'))
        self.timer -= 1
        self.timecanvas.update()

#main.py

import threading
from tkinter import *
import PIL
from PIL import ImageTk, Image, ImageDraw, ImageFont
from timer_display import App
from threading import *
def start_timer():
    while root.timer != 0:
        root.timecanvas.after(1000, root.tick())

def time_thread():
    thread = threading.Thread(target=start_timer,daemon=True)
    thread.start()


def begin():
    global canvas, start_button
    canvas.destroy()
    frame = Frame(root,bg="white",height=100,width=500)
    frame2 = Frame(root,bg="white",height=100,width=500)
    root.countdown(frame2)
    start_button.destroy()
    start_button = Button(root, bg="white", image=start_but, command=time_thread)
    start_button.pack()
    input = Entry(root, bg="white", width=50, fg="black")
    input.pack()
    frame2.pack()

root = App()
root.title("Typing Speed App")
root.config(padx=10, pady=10)
root.minsize(500,500)
root.maxsize(900,900)
root.geometry("900x900")
display = PIL.Image.open('racetrack.jpg', mode='r').resize((800, 800))
trackpic = ImageDraw.Draw(display)
font = ImageFont.load_default(20)
trackpic.text((75,0),"Welcome to Type Timer App!",font=font,fill=(0,0,0),font_size=250)
trackpic.text((305,700),"Click Start to Begin!",font=font,fill=(255,255,255))
canvas = Canvas(root,bg="white")
img = ImageTk.PhotoImage(display)
start_but = ImageTk.PhotoImage(Image.open('start_button.jpg', mode='r').resize((150, 150)))
canvas.config(height=800,width=800)
start_button = Button(root,bg="white",image=start_but,command=begin)
start_button.config(height=150,width=150)
start_button.place(x=350,y=520)
canvas.create_image(0,0,image=img,anchor=NW)
canvas.pack()

root.mainloop()
python multithreading tkinter event-handling
1个回答
0
投票

after()
(类似于
Button(command=...)
Thread(target=...)
root.bind(...)
)需要函数名称而不带
()
like

.after(1000, root.tick)

tkinter
稍后将使用
()
来运行此函数。

你的代码的工作原理就像

result = root.tick() 
....after(1000, result)

因为

tick()
不使用 return 所以它返回
None
并且你有

.after(1000, None)

其他问题可能是

while
循环

while root.timer != 0:
    root.timecanvas.after(1000, root.tick())

因为这个循环一直运行,所以它每秒可以运行数百次,并且可能会启动数百次

tick()
- 并且它可能会阻塞您的所有代码。

但你根本不需要

threading

您的

tick()
不需要很长时间,因此它不会阻塞
tkinter
,因此它可以在没有
threading

的情况下运行

您应该在

after(..., tick())
中运行
tick()
再次执行它。

    def tick(self):

        # ... code ...
        
        if self.timer > 0:  # to makes sure it is not negative
            self.timecanvas.after(1000, self.tick)

这是完整的工作代码,没有

threading
,但代码更清晰。

我没有你的图像,所以我使用

Image.new
来生成图像,并且我使用一些方法自动居中文本。

# [Text anchors - Pillow (PIL Fork) 10.3.0 documentation](https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#text-anchors)

import tkinter as tk
from PIL import ImageTk, Image, ImageDraw, ImageFont

# --- classes ---

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.timer = 10
        self.score = 0
        self.timer_started = False
        self.timer_text_id = None
        
    def countdown(self, parent):
        self.timecanvas = tk.Canvas(parent, bg="black", width=500, height=100)
        self.timecanvas.pack()
        
        # create empty text
        if not self.timer_text_id:
            self.timer_text_id = self.timecanvas.create_text(250, 50, fill="green", font=('Helvetica 55 bold'), anchor='center')
            
    def tick(self):
        # replace text without deleting all elements
        self.timecanvas.itemconfigure(self.timer_text_id, text=self.timer)
        
        self.timer -= 1
        
        if self.timer >= 0:  # to makes sure it is not negative, use >= instead of > to display 0
            self.timecanvas.after(1000, self.tick)

    def start_timer(self):
        if not self.timer_started:  # stop running again
            self.timer_started = True
            self.tick() # run at once
        
# --- functions ---

def begin():
    global canvas, start_button
    
    # remove previous content
    canvas.destroy()
    start_button.destroy()

    # create new content
    start_button = tk.Button(root, bg="white", image=start_button_image, command=root.start_timer)
    start_button.pack()
    
    input = tk.Entry(root, bg="white", width=50, fg="black")
    input.pack()

    root.countdown(root)

# --- main ---

root = App()
root.title("Typing Speed App")
root.config(padx=10, pady=10)
root.minsize(500,500)
root.maxsize(900,900)
root.geometry("900x900")

font = ImageFont.load_default(20)

# ----

image_width  = 150
image_height = 150

#start_button_pil = Image.open('start_button.jpg').resize((image_width, image_height))
start_button_pil = Image.new('RGB', (image_width, image_height), color='red')
start_button_draw = ImageDraw.Draw(start_button_pil)

# center text on image
pos_x = image_width//2
pos_y = image_height//2
anchor = 'mm'  # width-height: middle-middle
start_button_draw.text((pos_x, pos_y), "START", font=font, fill=(0,0,0), anchor=anchor)

start_button_image = ImageTk.PhotoImage(start_button_pil)
start_button = tk.Button(root, bg="white", image=start_button_image, command=begin, width=image_width, height=image_height)
start_button.place(x=350, y=520)

# ----

image_width  = 800
image_height = 400

#racetrack_pil = Image.open('racetrack.jpg').resize((image_width, image_height))
racetrack_pil = Image.new('RGB', (image_width, image_height), color='green')
racetrack_draw = ImageDraw.Draw(racetrack_pil)

# center text on image
pos_x = image_width//2
pos_y = 5
anchor = 'mt'  # width-height: top-middle
racetrack_draw.text((pos_x, pos_y), "Welcome to Type Timer App!", font=font, fill=(0,0,0), anchor=anchor)

# center text on image
pos_x = image_width//2
pos_y = image_height//2
anchor = 'mm'  # width-height: middle-middle
racetrack_draw.text((pos_x, pos_y), "Click Start to Begin!", font=font, fill=(255,255,255), anchor=anchor)

canvas = tk.Canvas(root, bg="white", width=image_width, height=image_height)
canvas.pack()

img = ImageTk.PhotoImage(racetrack_pil)
canvas.create_image(0, 0, image=img, anchor='nw')

# ----

root.mainloop()
© www.soinside.com 2019 - 2024. All rights reserved.