[Python] How can I make my customtkinter app render all widgets smoothly at once instead of...

Discussão em 'Python' iniciado por Stack, Julho 14, 2025.

  1. Stack

    Stack Membro Participativo

    i'm working on a Python application using customtkinter and CTkTable to display a paginated table with dynamic resizing and various widgets (frames, labels, buttons, comboboxes, etc.). The code below creates a dashboard-like interface with a table, pagination controls, and metrics cards. However, when the window is resized or the application is initialized, the widgets render slowly, often appearing one at a time, which creates a noticeable lag and a suboptimal user experience.

    I've implemented debouncing for the resize event to limit excessive updates, but the rendering of widgets (especially during initialization or pagination) still feels sluggish. I'm looking for ways to optimize the rendering speed so that all widgets appear smoothly and simultaneously, rather than rendering incrementally.

    Here's the relevant code

    from customtkinter import *
    from CTkTable import CTkTable
    from PIL import Image
    import time
    import threading
    import queue

    # Initialize the main application window
    app = CTk()
    app.geometry("1280x720") # Set initial window size
    app.resizable(True, True) # Allow resizing
    set_appearance_mode("light")

    # Get initial screen dimensions
    screen_width = app.winfo_screenwidth()
    screen_height = app.winfo_screenheight()

    # Variables for debouncing and pagination
    last_resize_time = 0
    resize_delay = 0.5 # Increased to 500ms for better performance
    last_width = screen_width
    last_height = screen_height
    rows_per_page = 5 # Limit to 5 rows per page
    current_page = 0
    resize_enabled = True # Flag to control resize handling

    # Thread-safe queue for initial table load
    update_queue = queue.Queue()

    # Table data
    table_data = [
    ["Order ID", "Item Name", "Customer", "Address", "Status", "Quantity"],
    ['3833', 'Smartphone', 'Alice', '123 Main St', 'Confirmed', '8'],
    ['6432', 'Laptop', 'Bob', '456 Elm St', 'Packing', '5'],
    ['2180', 'Tablet', 'Crystal', '789 Oak St', 'Delivered', '1'],
    ['5438', 'Headphones', 'John', '101 Pine St', 'Confirmed', '9'],
    ['9144', 'Camera', 'David', '202 Cedar St', 'Processing', '2'],
    ['7689', 'Printer', 'Alice', '303 Maple St', 'Cancelled', '2'],
    ['1323', 'Smartwatch', 'Crystal', '404 Birch St', 'Shipping', '6'],
    ['7391', 'Keyboard', 'John', '505 Redwood St', 'Cancelled', '10'],
    ['4915', 'Monitor', 'Alice', '606 Fir St', 'Shipping', '6'],
    ['5548', 'External Hard Drive', 'David', '707 Oak St', 'Delivered', '10'],
    ['5485', 'Table Lamp', 'Crystal', '808 Pine St', 'Confirmed', '4'],
    ['7764', 'Desk Chair', 'Bob', '909 Cedar St', 'Processing', '9'],
    ['8252', 'Coffee Maker', 'John', '1010 Elm St', 'Confirmed', '6'],
    ['2377', 'Blender', 'David', '1111 Redwood St', 'Shipping', '2'],
    ['5287', 'Toaster', 'Alice', '1212 Maple St', 'Processing', '1'],
    ['7739', 'Microwave', 'Crystal', '1313 Cedar St', 'Confirmed', '8'],
    ['3129', 'Refrigerator', 'John', '1414 Oak St', 'Processing', '5'],
    ['4789', 'Vacuum Cleaner', 'Bob', '1515 Pine St', 'Cancelled', '10']
    ]

    # Precompute paginated data
    paginated_data_cache = []
    total_pages = max(1, (len(table_data) - 1 + rows_per_page - 1) // rows_per_page)
    for page in range(total_pages):
    start_idx = page * rows_per_page + 1
    end_idx = min(start_idx + rows_per_page, len(table_data))
    paginated_data_cache.append([table_data[0]] + table_data[start_idx:end_idx])

    # Main view with dynamic sizing
    main_view = CTkFrame(master=app, fg_color="#F7F7F7", width=screen_width, height=screen_height, corner_radius=0)
    main_view.pack_propagate(0)
    main_view.pack(fill="both", expand=True)

    # --- Title Frame ---
    title_frame = CTkFrame(master=main_view, fg_color="transparent")
    title_frame.pack(anchor="n", fill="x", padx=20, pady=(20, 0))

    # --- Title Label (Top Row) ---
    title_label = CTkLabel(title_frame, text="Orders", font=("Arial Black", 20), text_color="#FFFFFF")
    title_label.pack(anchor="w", padx=10, pady=(10, 0))

    # --- Breadcrumbs + Buttons Row ---
    top_bar_frame = CTkFrame(master=title_frame, fg_color="transparent")
    top_bar_frame.pack(fill="x", padx=10, pady=(5, 10))

    # --- Breadcrumbs (Left Side) ---
    breadcrumb_frame = CTkFrame(master=top_bar_frame, fg_color="transparent")
    breadcrumb_frame.pack(side="left")

    CTkLabel(breadcrumb_frame, text="Home", text_color="#AAAAAA").pack(side="left")
    CTkLabel(breadcrumb_frame, text=" > ", text_color="#AAAAAA").pack(side="left")
    CTkLabel(breadcrumb_frame, text="Orders", text_color="#AAAAAA").pack(side="left")
    CTkLabel(breadcrumb_frame, text=" > ", text_color="#AAAAAA").pack(side="left")
    CTkLabel(breadcrumb_frame, text="New Order", text_color="#FFFFFF", font=("Arial", 13, "bold")).pack(side="left")

    # --- Button Frame (Right Side) ---
    button_frame = CTkFrame(master=top_bar_frame, fg_color="transparent")
    button_frame.pack(side="right")

    # --- Refresh Button ---
    refresh_button = CTkButton(
    master=button_frame,
    text="�是一件 Refresh",
    font=("Arial Black", 15),
    text_color="#FFFFFF",
    fg_color="#2A8C55",
    hover_color="#207244",
    corner_radius=8,
    )
    refresh_button.pack(side="right", padx=(5, 0))

    # --- Print Button ---
    print_button = CTkButton(
    master=button_frame,
    text=" Print",
    font=("Arial Black", 15),
    text_color="#FFFFFF",
    fg_color="#2A8C55",
    hover_color="#207244",
    corner_radius=8,
    )
    print_button.pack(side="right", padx=(5, 0))

    # --- Export Button ---
    export_button = CTkButton(
    master=button_frame,
    text="Export ▼",
    font=("Arial Black", 15),
    text_color="#FFFFFF",
    fg_color="#2A8C55",
    hover_color="#207244",
    corner_radius=8,
    )
    export_button.pack(side="right", padx=(5, 0))

    # --- New Order Button ---
    new_order_button = CTkButton(
    master=button_frame,
    text="+ New Order",
    font=("Arial Black", 15),
    text_color="#FFFFFF",
    fg_color="#2A8C55",
    hover_color="#207244",
    corner_radius=8,
    )
    new_order_button.pack(side="right", padx=(5, 0))

    # --- Export Menu ---
    from tkinter import Menu

    export_menu = Menu(app, tearoff=0,
    background="#2A8C55",
    foreground="#FFFFFF",
    activebackground="#207244",
    activeforeground="#FFFFFF",
    font=("Arial", 13),
    borderwidth=0,
    relief="flat")

    export_menu.add_command(label="Export as PDF", command=lambda: print("Exporting to PDF..."))
    export_menu.add_command(label="Export as Excel", command=lambda: print("Exporting to Excel..."))

    def show_export_menu_below(event):
    x = export_button.winfo_rootx()
    y = export_button.winfo_rooty() + export_button.winfo_height()
    export_menu.tk_popup(x, y)

    export_button.bind("<Button-1>", show_export_menu_below)

    # Metrics frame
    metrics_frame = CTkFrame(master=main_view, fg_color="transparent")
    metrics_frame.pack(anchor="n", fill="x", padx=20, pady=(30, 0))

    # Metric widths
    metric_width = int((screen_width - 60) / 3.5)
    orders_metric = CTkFrame(master=metrics_frame, fg_color="#2A8C55", width=metric_width, height=60)
    orders_metric.grid_propagate(0)
    orders_metric.pack(side="left", padx=(0, 10))

    logistics_img_data = Image.open("logistics_icon.png")
    logistics_img = CTkImage(light_image=logistics_img_data, dark_image=logistics_img_data, size=(43, 43))
    CTkLabel(master=orders_metric, image=logistics_img, text="").grid(row=0, column=0, rowspan=2, padx=(12, 5), pady=10)
    CTkLabel(master=orders_metric, text="Orders", text_color="#FFFFFF", font=("Arial Black", 15)).grid(row=0, column=1, sticky="sw")
    CTkLabel(master=orders_metric, text="123", text_color="#FFFFFF", font=("Arial Black", 15), justify="left").grid(row=1, column=1, sticky="nw", pady=(0, 10))

    shipped_metric = CTkFrame(master=metrics_frame, fg_color="#2A8C55", width=metric_width, height=60)
    shipped_metric.grid_propagate(0)
    shipped_metric.pack(side="left", expand=True, anchor="center", padx=10)

    shipping_img_data = Image.open("shipping_icon.png")
    shipping_img = CTkImage(light_image=shipping_img_data, dark_image=shipping_img_data, size=(43, 43))
    CTkLabel(master=shipped_metric, image=shipping_img, text="").grid(row=0, column=0, rowspan=2, padx=(12, 5), pady=10)
    CTkLabel(master=shipped_metric, text="Shipping", text_color="#FFFFFF", font=("Arial Black", 15)).grid(row=0, column=1, sticky="sw")
    CTkLabel(master=shipped_metric, text="91", text_color="#FFFFFF", font=("Arial Black", 15), justify="left").grid(row=1, column=1, sticky="nw", pady=(0, 10))

    delivered_metric = CTkFrame(master=metrics_frame, fg_color="#2A8C55", width=metric_width, height=60)
    delivered_metric.grid_propagate(0)
    delivered_metric.pack(side="right", padx=(10, 0))

    delivered_img_data = Image.open("delivered_icon.png")
    delivered_img = CTkImage(light_image=delivered_img_data, dark_image=delivered_img_data, size=(43, 43))
    CTkLabel(master=delivered_metric, image=delivered_img, text="").grid(row=0, column=0, rowspan=2, padx=(12, 5), pady=10)
    CTkLabel(master=delivered_metric, text="Delivered", text_color="#FFFFFF", font=("Arial Black", 15)).grid(row=0, column=1, sticky="sw")
    CTkLabel(master=delivered_metric, text="23", text_color="#FFFFFF", font=("Arial Black", 15), justify="left").grid(row=1, column=1, sticky="nw", pady=(0, 10))

    # Search container
    search_container = CTkFrame(master=main_view, height=50, fg_color="#F0F0F0")
    search_container.pack(fill="x", pady=(30, 0), padx=20)

    search_width = int((screen_width - 60) * 0.5)
    combo_width = int((screen_width - 60) * 0.2)
    search_entry = CTkEntry(master=search_container, width=search_width, placeholder_text="Search Order", border_color="#2A8C55", border_width=2, font=("Arial", 14))
    search_entry.pack(side="left", padx=(10, 5), pady=10)

    date_combo = CTkComboBox(master=search_container, width=combo_width, values=["Date", "Most Recent Order", "Least Recent Order"], button_color="#2A8C55", border_color="#2A8C55", border_width=2, button_hover_color="#207244", dropdown_hover_color="#207244", dropdown_fg_color="#2A8C55", dropdown_text_color="#FFFFFF", font=("Arial", 14))
    date_combo.pack(side="left", padx=5, pady=10)

    status_combo = CTkComboBox(master=search_container, width=combo_width, values=["Status", "Processing", "Confirmed", "Packing", "Shipping", "Delivered", "Cancelled"], button_color="#2A8C55", border_color="#2A8C55", border_width=2, button_hover_color="#207244", dropdown_hover_color="#207244", dropdown_fg_color="#2A8C55", dropdown_text_color="#FFFFFF", font=("Arial", 14))
    status_combo.pack(side="left", padx=(5, 0), pady=10)

    # Table frame with pagination
    table_frame = CTkScrollableFrame(master=main_view, fg_color="transparent")
    table_frame.pack(expand=True, fill="both", padx=20, pady=20)

    # Function to update table with cached paginated data
    def update_table():
    global resize_enabled
    resize_enabled = False # Disable resize during table update
    try:
    if 0 <= current_page < len(paginated_data_cache):
    table.configure(values=paginated_data_cache[current_page])
    page_label.configure(text=f"Page {current_page + 1} of {total_pages}")
    finally:
    resize_enabled = True # Re-enable resize after update

    # Initialize table with first page of data
    table = CTkTable(master=table_frame, values=paginated_data_cache[0], colors=["#E6E6E6", "#EEEEEE"], header_color="#2A8C55", hover_color="#B4B4B4")
    table.edit_row(0, text_color="#FFFFFF", hover_color="#2A8C55")
    table.pack(expand=True, fill="both")

    # Pagination controls
    pagination_frame = CTkFrame(master=main_view, fg_color="transparent")
    pagination_frame.pack(fill="x", padx=20, pady=10)

    page_label = CTkLabel(master=pagination_frame, text=f"Page {current_page + 1} of {total_pages}", font=("Arial", 14))
    page_label.pack(side="left")

    def prev_page():
    global current_page
    if current_page > 0:
    current_page -= 1
    update_table()

    def next_page():
    global current_page
    if current_page < len(paginated_data_cache) - 1:
    current_page += 1
    update_table()

    CTkButton(master=pagination_frame, text="Previous", font=("Arial", 14), fg_color="#2A8C55", hover_color="#207244", command=prev_page).pack(side="left", padx=5)
    CTkButton(master=pagination_frame, text="Next", font=("Arial", 14), fg_color="#2A8C55", hover_color="#207244", command=next_page).pack(side="left", padx=5)

    # Bind resize event with debouncing
    def on_resize(event):
    global last_resize_time, last_width, last_height, screen_width, screen_height, metric_width, search_width, combo_width
    if not resize_enabled:
    return

    current_time = time.time()
    if current_time - last_resize_time < resize_delay:
    return

    new_width = app.winfo_width()
    new_height = app.winfo_height()
    if abs(new_width - last_width) < 20 and abs(new_height - last_height) < 20: # Increased threshold
    return

    last_resize_time = current_time
    last_width = new_width
    last_height = new_height
    screen_width = new_width
    screen_height = new_height

    # Update main view
    main_view.configure(width=screen_width, height=screen_height)

    # Update metrics
    metric_width = int((screen_width - 60) / 3.5)
    for frame in [orders_metric, shipped_metric, delivered_metric]:
    frame.configure(width=metric_width)

    # Update search and comboboxes
    search_width = int((screen_width - 60) * 0.5)
    combo_width = int((screen_width - 60) * 0.2)
    search_entry.configure(width=search_width)
    date_combo.configure(width=combo_width)
    status_combo.configure(width=combo_width)

    app.bind("<Configure>", on_resize)

    # Allow exiting full-screen with Escape key
    def exit_fullscreen(event):
    if app.attributes('-fullscreen'):
    app.attributes('-fullscreen', False)
    try:
    fullscreen_button.configure(text="Maximize")
    except NameError:
    pass # fullscreen_button may not exist yet

    app.bind("<Escape>", exit_fullscreen)

    app.mainloop()


    How can I optimize the rendering performance of this customtkinter application to ensure all widgets render smoothly and simultaneously, rather than appearing one at a time?

    Continue reading...

Compartilhe esta Página