如何逐个字符更新自定义tkinter标签,中间有定时延迟?

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

我正在尝试使用 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 实现它。它只是一直说命令没有关闭。

python tkinter customtkinter
3个回答
0
投票

这是纯 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。这一切都基于快速浏览文档以及与许多其他事件驱动工具包的相似性,因此这可能不是最优雅的解决方案。


0
投票
@drmuelr

的详细回复,我会通过创建一个自己的 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()



0
投票
我如何正确地编写customtkinter语法以便它允许我打印 ctk标签的输出缓慢。

问题可以解决。只需一小段代码即可解决。

只有 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()

© www.soinside.com 2019 - 2024. All rights reserved.