Skip to content

CTkEntry.destroy() does not remove trace from self._textvariable #2743

@the-sabin

Description

@the-sabin

Description
In CTkEntry.__init__, a trace_add callback is attached to self._textvariable:

if not (self._textvariable is None or self._textvariable == ""):
    self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback)

However, in CTkEntry.destroy(), this trace is never removed.
If the tkinter.Variable outlives the widget, the callback can still fire after the widget is destroyed, causing TclError: invalid command name when _textvariable_callback tries to access self._entry.

Example with a top-level dialog:

import customtkinter as ctk
import tkinter as tk

def open_dialog():
    dialog = ctk.CTkToplevel(root)
    dialog.title("Dialog with CTkEntry")

    # Reuse the same StringVar every time
    entry = ctk.CTkEntry(dialog, textvariable=shared_var, placeholder_text="Type here...")
    entry.pack(padx=20, pady=20)

    ctk.CTkButton(dialog, text="Close", command=dialog.destroy).pack(pady=10)

root = ctk.CTk()
root.geometry("300x200")

# This variable persists beyond the lifetime of any CTkEntry
shared_var = tk.StringVar(value="Default text")

ctk.CTkButton(root, text="Open Dialog", command=open_dialog).pack(pady=50)

root.mainloop()

Steps to reproduce issue:

  1. Open dialog, type in entry: this works fine.
  2. Close dialog.
  3. Open dialog again.
  4. Typing at the end of existing text works fine (no errors).
  5. Select all text in the entry and type something new. Now observing this error inside the terminal:
_tkinter.TclError: invalid command name ".!ctktoplevel.!ctkentry.!entry"

Note: The issue is more visible when reusing the same StringVar across dialogs.
Appending text works fine, but replacing all text (select all + type) causes the error because it triggers the placeholder logic on a destroyed widget.

Proposed fix: destroy() should remove the trace from self._textvariable before destroying the widget.

def destroy(self):
    if self._textvariable and self._textvariable_callback_name:
        try:
            self._textvariable.trace_remove("write", self._textvariable_callback_name)
        except Exception:
            pass
        self._textvariable_callback_name = ""

    if isinstance(self._font, CTkFont):
        self._font.remove_size_configure_callback(self._update_font)

    super().destroy()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions