Search code examples
pythontkinterttkttkwidgetstkinter-label

tkinter (ttk) leaving artifacts when updating widget


When updating a value in a widget using tkinter there's traces of the old widget's state on screen, and I was wondering if there's any fix to this. I'm assuming tkinter just re-draws whatever it needs to, not updating the old widget/old widget's state. Minimal reproducible example follows.

from tkinter import *
from tkinter.ttk import *

window=Tk()
ttk.Style().configure("Info.TLabel",foreground="white", background="#1e2124",relief="sunken")

def update_label(currvar):
    current_var_levels = current_var.get()
    var_label=ttk.Label(window,text=f'{current_var_levels}'+'%',style="Info.TLabel")
    var_label.grid(row=0,column=1)

current_var=IntVar()
scale_bar = ttk.Scale(window, from_=0, to=100, length=200, variable=current_var, command=update_label)
current_var.set(100)
scale_bar.grid(row=0,column=0)

# Initialize scale's display label. Works without this, but it shows everything from the start
var_label=ttk.Label(window,text=f'{current_var.get()}'+'%',style="Info.TLabel")
var_label.grid(row=0,column=1)

window.mainloop()

I've tried conf_label.grid_forget() and then re-grid-ing it on the update function, but that didn't work. I've also tried making 2 separate labels (global/local) ones, although I may be thinking too linearly. I think I'm looking for a "screen refresh" but I don't know what that looks like. This also happens without the fore/background colors, or even without a relief altogether; removing the Style config and the style assignment from the label(s) will still leave a small part of % visible, once part if the current value is a double digit number, and 2 parts if it's a single digit number. A viable botch would be to force length by padding with whitespace, but this is a good learning opportunity. Thanks for reading!


Solution

  • tkinter doesn't remove old Label but it puts new Label above old Label - so you have two labels in one place.

    You could use grid_forget() or destroy() to remove old Label.

    But this needs to keep var_label as global variable.

    And this can make flickering widget. It shows gray background after removing old Label and before adding new Label.

    def update_label(value):
        global var_label   # inform function that new label has to be assigned to global variable `var_label`, instead of local `var_label`
    
        current_var_levels = current_var.get()
        #current_var_levels = int(float(currvar))
    
        var_label.destroy()     # remove old label from screen and from memory
        #var_label.grid_forget()  # remove old label from screen but not from memory
        
        var_label = ttk.Label(window, text=f'{current_var_levels}%', style="Info.TLabel")
        var_label.grid(row=0, column=1)
    

    Or you should create Label only once and later replace text in existing Label

    def update_label(currvar):
        current_var_levels = current_var.get()
        #current_var_levels = int(float(currvar))
    
        var_label.config(text=f'{current_var_levels}%')  # replace text
        #var_label['text'] = f'{current_var_levels}%'    # replace text
    

    Full working code which I used for tests:

    import tkinter as tk       # `PEP8: `import *` is not preferred
    import tkinter.ttk as ttk  # `PEP8: `import *` is not preferred
    
    # --- functions ---  # PEP8: all functions before main code
    
    def update_label_version_1(value):
        global var_label   # inform function that new label has to be assigned to global variable `var_label`, instead of local `var_label`
    
        current_var_levels = current_var.get()
        #current_var_levels = int(float(currvar))
    
        var_label.destroy()     # remove old label from screen and from memory
        #var_label.grid_forget()  # remove old label from screen but not from memory
    
        var_label = ttk.Label(window, text=f'{current_var_levels}%', style="Info.TLabel")
        var_label.grid(row=0, column=1)
        
    def update_label_version_2(value):
        current_var_levels = current_var.get()
        #current_var_levels = int(float(value))
            
        var_label.config(text=f'{current_var_levels}%')  # replace text
        #var_label['text'] = f'{current_var_levels}%'    # replace text
        
    # --- main ---
    
    window = tk.Tk()
    
    ttk.Style().configure("Info.TLabel", foreground="white", background="#1e2124", relief="sunken")
    
    current_var = tk.IntVar()
    
    #update_label = update_label_version_1   # test version 1
    update_label = update_label_version_2   # test version 2
    
    scale_bar = ttk.Scale(window, from_=0, to=100, length=200, variable=current_var, command=update_label)
    current_var.set(100)
    scale_bar.grid(row=0,column=0)
    
    # Initialize scale's display label. Works without this, but it shows everything from the start
    var_label = ttk.Label(window, text=f'{current_var.get()}%', style="Info.TLabel")
    var_label.grid(row=0, column=1)
    
    window.mainloop()
    

    PEP 8 -- Style Guide for Python Code