CTkTextbox fails to update properly during programmatic text insertion with .after()
When trying to create a typewriter effect by inserting characters with delays using .after(), the CTkTextbox widget doesn't update visually until the entire operation completes. Manual .update() calls are required to force refresh, which shouldn't be necessary.
Steps to Reproduce:
- Create CTkTextbox widget
- Schedule character-by-character insertion using .after()
- Observe text appears all at once instead of incrementally
Expected Behavior: Text should appear character-by-character with specified delays without requiring manual .update() calls.
Actual Behavior: Text only becomes visible after all insertions complete unless .update() is called after each insertion.
Workaround:
Requires manual GUI updates:
self.text_widget.insert("end", char)
self.text_widget.update() # Shouldn't be necessary
Minimal Reproducible Example:
import customtkinter as ctk
class App:
def __init__(self):
self.root = ctk.CTk()
self.textbox = ctk.CTkTextbox(self.root)
self.textbox.pack()
self.root.after(100, self.type_text)
self.root.mainloop()
def type_text(self):
text = "Hello World!"
for i, char in enumerate(text):
self.textbox.insert("end", char)
# Text won't appear without .update()
self.textbox.after(50 * i)
self.textbox.see("end")
App()
System Info: CustomTkinter Version: 5.2.2
Python Version: 3.12.5
OS: Windows 11
Additional Context: This works as expected in standard Tkinter's Text widget. The issue appears specific to CTkTextbox's update mechanism.
@TheRiddler2 When executing a time-consuming task within a loop, we need to use threading in order to make the GUI responsive. In Tkinter, the method after() is an effective way for handling recursive functions without the need for a separate thread. Plus, to guarantee that widgets are drawn successfully, the event "Map" should be used.
Here is the updated code:
import customtkinter as ctk
class App:
def __init__(self):
self.root = ctk.CTk()
self.textbox = ctk.CTkTextbox(self.root)
self.textbox.pack()
self.text = "Hello World!"
self.textbox.bind("<Map>", self._on_redraw)
self.root.mainloop()
def _on_redraw(self, e=None):
self.type_text()
def type_text(self, index=0):
if index < len(self.text):
self.textbox.insert("end", self.text[index])
self.textbox.after(200, lambda: self.type_text(index + 1))
else:
self.textbox.see("end")
App()
Output: https://github.com/user-attachments/assets/c1aef767-7b3f-4df4-896b-28c0581ffd04
Regards.
@TheRiddler2 When executing a time-consuming task within a loop, we need to use threading in order to make the GUI responsive. In Tkinter, the method
after()is an effective way for handling recursive functions without the need for a separate thread. Plus, to guarantee that widgets are drawn successfully, the event "Map" should be used. Here is the updated code:import customtkinter as ctk class App: def __init__(self): self.root = ctk.CTk() self.textbox = ctk.CTkTextbox(self.root) self.textbox.pack() self.text = "Hello World!" self.textbox.bind("<Map>", self._on_redraw) self.root.mainloop() def _on_redraw(self, e=None): self.type_text() def type_text(self, index=0): if index < len(self.text): self.textbox.insert("end", self.text[index]) self.textbox.after(200, lambda: self.type_text(index + 1)) else: self.textbox.see("end") App()Output: https://github.com/user-attachments/assets/c1aef767-7b3f-4df4-896b-28c0581ffd04
Regards.
I appreciate your concern regarding this issue. However, you didn't understand the point I was trying make. So using chatgpt to help me is pointless.