我正在尝试使用 CustomTkinter 创建一款游戏,我想让 CTkLabel 中的文本慢慢打印出来,一次一个字符。我希望看到的文本效果就像是一个人实时输入的一样。我找到的大部分资源都是 Tkinter 的,所以我不知道如何做到这一点。
这是我正在努力解决的代码的一部分:
dialogue_var = ("Hello User")
dialogue_var = ("Ready To Protect It")
dialogue = customtkinter.CTkLabel(app, width=350, height=80, text=dialogue_var)
dialogue.pack(padx=1, pady=1)
DirkS 在
Raspberry Pi 论坛中发布了以下
print_slow
功能:
from time import sleep def print_slow(txt): # cycle through the text one character at a time for x in txt: # print one character, no new line, flush buffer print(x, end='', flush=True) sleep(0.3) # go to new line print() print_slow("Hello. I'm feeling a bit slow today")
但我不知道如何用 CTkLabel 实现它。它只是一直说命令没有关闭。
这是纯 Tkinter 中的解决方案。根据我对文档的理解,您应该能够将 Tkinter 中的类名替换为 CTkinter。
如果您不熟悉事件驱动的应用程序,TkDocs 教程 提供了快速介绍:
与大多数用户界面工具包一样,Tk 运行一个“事件循环”来接收来自操作系统的事件。这些操作包括按下按钮、击键、移动鼠标、调整窗口大小等。 通常,Tk 会为您管理此事件循环。它将找出该事件适用于哪个小部件(用户是否单击了此按钮?如果按下了某个键,哪个文本框获得了焦点?),并相应地调度它。各个小部件知道如何响应事件;例如,当鼠标移到按钮上时,按钮可能会改变颜色,并在鼠标离开时恢复原状。
换句话说,类似
dialogue = ""
buffer = "Ready To Protect It"
for x in buffer:
dialogue.append(x)
sleep(0.3)
不起作用,因为
sleep
调用会反复暂停执行,从而阻止事件循环执行其操作;该应用程序几乎完全没有响应。
相反,我们需要建立可以基于计时器以小而离散的步骤工作的结构。首先,导入一些:import tkinter as tk
from collections import deque
现在我们定义一个自定义的
StringVar
类。这将让我们逐个字符地添加到字符串中。每次调用
step
方法时,都会将一个字符从缓冲区移动到显示字符串的末尾:class TeletypeVar(tk.StringVar):
"""StringVar that appends characters from a buffer one at a time.
Parameters
----------
value : string, optional
Initial string value.
buffer : string, optional
Initial buffer content.
"""
def __init__(self, *args, **kwargs):
buffer = kwargs.pop("buffer", "")
super().__init__(*args, **kwargs)
self.buffer = deque(buffer)
def clear(self):
"""Clear contents of the string and buffer."""
self.set("")
self.buffer.clear()
def enqueue(self, str):
"""Add the given string to the end of the buffer."""
self.buffer.extend(str)
def step(self, _event=None):
"""Move 1 character from the buffer to the string."""
if len(self.buffer) > 0:
self.set(self.get() + self.buffer.popleft())
现在我们定义一个类,它将以稳定的速度重复调用像
TeletypeVar.step
这样的回调函数。它还有一个
step
方法,该方法调用指定的回调函数,并为自己的下一步启动计时器:class Clock():
"""A clock that calls ``cb`` every ``T`` milliseconds.
Parameters
----------
T : int
Delay between calls, in milliseconds.
cb : function
Callback function to be repeatedly called.
"""
def __init__(self, T, cb):
self.T = T
self.cb = cb
self.after = root.after
def step(self):
"""Called every T milliseconds."""
self.cb()
self.after(self.T, self.step)
def start(self):
"""Start running the clock."""
self.after(self.T, self.step)
这就完成了准备工作。创建框架并启动主循环应该看起来很熟悉:
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack()
# create our custom StringVar pre-populated with "Hello User"
# and "_Ready To Protect It" waiting in the buffer
self.tt_dynamic = TeletypeVar(value="Hello User",
buffer="_Ready To Protect It")
# create our clock, but don't start it yet
# this replaces a line of code like `sleep(0.150)`
self.clk = Clock(150, self.tt_dynamic.advance)
# create a TkLabel; same as using a CTkLabel
# using `textvariable` instead of `text` lets the label
# know that the string is a dynamic property
dialogue = tk.Label(self, width=35, height=8,
textvariable=self.tt_dynamic)
dialogue.pack(padx=1, pady=1)
# call self.delayed_cb1() after 2 seconds
# note how there is no delay between the two print statements
print("before after")
root.after(2_000, self.delayed_cb1)
print("after after")
def delayed_cb1(self):
# start the clock defined in `__init__`
self.clk.start()
# call self.delayed_cb2() after a further 5 seconds
root.after(5_000, self.delayed_cb2)
def delayed_cb2(self):
# clear out the teletype string
self.tt_dynamic.clear()
# speed up the clock
self.clk.T = 75
# and queue up more text
self.tt_dynamic.enqueue("_And Protect It Some More")
root = tk.Tk()
myapp = App(root)
myapp.mainloop()
我写这篇文章的方式希望可以让你轻松地尝试不同的部分,看看一切是如何运作的。如果有任何不清楚的地方,请告诉我。免责声明:我以前没有使用过 Tkinter 或 CustomTkinter。这一切都基于快速浏览文档以及与许多其他事件驱动工具包的相似性,因此这可能不是最优雅的解决方案。
的详细回复,我会通过创建一个自己的 Label
类来实现,并赋予它键入文本而不是
StringVar
的能力。另外,他的 Clock
类主要只是 after
方法的功能。这是我的方法:
from customtkinter import CTk, CTkLabel, CTkButton, StringVar
class TypedLabel(CTkLabel):
""" Label that can slowly type the provided text """
def __init__(self, master, text, type_delay):
self.delay = type_delay
self.display_text = StringVar(master, value='')
self.text_buffer = text
super().__init__(master, textvariable=self.display_text)
def change_delay(self, new_delay):
"""change the speed for the typing by setting lower millisecond intervalls"""
self.delay = new_delay
def set_buffer(self, text):
"""Change buffer text"""
self.text_buffer = text
def append_buffer(self, text, newline=True):
"""append the buffer with new text. Default will print a newline before the appended text"""
if newline:
text = '\n' + text
self.text_buffer += text
def clear_text(self):
"""reset both bufffer and display text to empty string"""
self.text_buffer = ''
self.display_text.set('')
def type_text(self):
if len(self.text_buffer) > 0:
self.display_text.set(self.display_text.get() + self.text_buffer[0]) # append first character from buffer
self.text_buffer = self.text_buffer[1:] # remove first character from buffer
self.after(self.delay, self.type_text) # type next char after given delay
class TestGui(CTk):
def __init__(self):
super().__init__()
self.type_text_lbl = TypedLabel(self, 'Test', 300)
self.type_text_lbl.pack()
test_btn = CTkButton(self, text='new test', command=self._new_test) # test buffer append
test_btn.pack()
self.after(2000, self.type_text_lbl.type_text) # type text 2 sec after instantiation of the GUI
def _new_test(self):
self.type_text_lbl.clear_text()
self.type_text_lbl.set_buffer('Test 2')
self.type_text_lbl.append_buffer('succesfull!')
self.type_text_lbl.type_text()
if __name__ == '__main__':
gui = TestGui()
gui.mainloop()
问题可以解决。只需一小段代码即可解决。
只有 33 行。
添加按钮。
import tkinter as tk
import customtkinter as ctk
app = tk.Tk()
def clicked() -> None:
n = 0
txt = r"Hello User. Ready To Protect It"
showChar(n, txt)
def showChar(n: int, txt: str) -> int and str:
n += 1
diaglogue.configure(text = txt[:n])
if n < len(txt):
app.after(1000, lambda: showChar(n, txt))
diaglogue = ctk.CTkLabel(master=app, width=350,
height=80,
text_color="#000",
)
#diaglogue_var = ("Hello User")
#diaglogue_var = ("Ready To Protect It")
diaglogue.pack(padx=1, pady=1)
button = ctk.CTkButton(master=app, text="Click Me",
command=clicked)
button.pack()
app.mainloop()