所以我读了很多关于 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()
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()