#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Location
    California, USA
    Posts
    76
    Rep Power
    2

    Creating a tkinter label but can't delete it.


    I am using a class that creates labels, adds the text according
    to what button was pressed. I mistakenly thought that I was
    destroying the old label first, then re-recreating the new label
    using the same label name. I checked my try...except code
    when doing the destroy() and found that it is taking the exception path.

    The labels are working for me, but what is going on here? Are
    the new labels with the same names replacing the old ones?
    When I put a line to destroy a label without the try/except, it
    gives me the error:
    AttributeError: 'App' object has no attribute 'LabelA'
    Code:
    try:
                self.LabelA.destroy()
            except:
                pass
    and after that without using try/except:
    Code:
    self.LabelA = Label(root, bg='White', etc.
    This issue came up when I added another 2 labels that I wanted
    to show when a checkbox is checked, and remove when the
    checkbox is unchecked. No problem adding it, I just can't
    delete them. I do have the checkbox widget calling a function
    to destroy the labels, but I get this error when done outside
    of the try/except:
    AttributeError: 'NoneType' object has no attribute 'destroy'
    Thanks for your advice.

    Edit. Sorry about the code indentation above. That's the way
    it came out with a copy & paste.
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Location
    California, USA
    Posts
    76
    Rep Power
    2
    Here is minimal code to demonstrate the problem.
    I can't get the destroy() function to work because of my
    lack of experience. Knowing how to do this will be a great
    help in my quest to learn Python (3.3).

    My question is: how can a label be destroyed and have it
    disappear from the window?
    Code:
    from tkinter import *
    
    root = Tk()
    
    root.geometry('400x300+20+20')
    
    class App:
        def __init__(self, root):
            self.root = root
            
            row,col = 0,0
    
            self.buttons = ["dummy"]
            self.btn_text= ['','Add Labels', 'Remove Labels']
               
            for index in range(1,3):   #add 2 buttons
                self.buttons.append(
                    Button(root, text=self.btn_text[index], width=13,
                    font=('Helvetica', 12, 'normal'),  
                    command=lambda i=index:self.change(i)))
                self.buttons[-1].grid(row=row, column=col)
                row+=1   
            
        def change(self,index):
            """
            add labels if #1 button clicked, destroy labels
            if #2 button clicked
            """
            
            if index == 1:
                self.LabelA = Label(root, bg='white', fg='blue', \
                    font=('Helvetica', 16), \
                    text="Hello", width=10).place(x=100, y = 100)
            
                self.LabelB = Label(root, bg='orange', fg='black', \
                    font=('Helvetica', 16), \
                    text="World", width=10).place(x=100, y = 130)    
            else:
                try:
                    self.LabelA.destroy()
                except:
                    print("\nself.LabelA.destroy() failed. Button index= ", index)
                try:
                    self.LabelB.destroy()
                except:
                    print("\nself.LabelB.destroy() failed. Button index= ", index)
                
    app = App(root)
    
    root.mainloop()
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2010
    Posts
    153
    Rep Power
    5
    It's very hard to debug a program if you silence the actual errors using try/except.

    In this case, when I took your code and moved the .destroy() call out of the try/except structure, I get this exception:
    Code:
      File "test.py", line 39, in change
        self.LabelA.destroy()
    AttributeError: 'NoneType' object has no attribute 'destroy'
    That tells me that for some reason, self.LabelA is "None", rather than a widget handle. Now why would that be?

    Spoilers Alert! :-)

    You create self.LabelA and self.LabelB like so:
    Code:
                self.LabelA = Label(root, bg='white', fg='blue', \
                    font=('Helvetica', 16), \
                    text="Hello", width=10).place(x=100, y = 100)
            
                self.LabelB = Label(root, bg='orange', fg='black', \
                    font=('Helvetica', 16), \
                    text="World", width=10).place(x=100, y = 130)
    What gets put in each is not the return value of Label() (which would be a handle for the Label widget), but rather the return value of the last method called in the expression, which in this case is place().

    place() returns: None

    So the solution is to call place() on a separate line after you've created the Labels and stored their handles in self.LabelA and self.LabelB, like so:
    Code:
                self.LabelA = Label(root, bg='white', fg='blue', \
                    font=('Helvetica', 16), \
                    text="Hello", width=10)
                self.LabelA.place(x=100, y = 100)
            
                self.LabelB = Label(root, bg='orange', fg='black', \
                    font=('Helvetica', 16), \
                    text="World", width=10)
                self.LabelB.place(x=100, y = 130)
    Voila! Your code works just as expected!

    The moral of the story is Don't silence errors with try/except, especially not during development. Tracebacks are your friends!

  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    May 2009
    Posts
    509
    Rep Power
    33
    You send the id of self.LabelA to the place() manager which returns None/nothing, so both label variables contain None. See the following code with LabelA modified to contain an actual reference. Woops, admoore posted while I was composing so I guess this is just a confirmation of that post.
    Code:
    root = Tk()
    
    root.geometry('400x300+20+20')
    
    class App:
        def __init__(self, root):
            self.root = root
            
            row,col = 0,0
    
            self.buttons = ["dummy"]
            self.btn_text= ['','Add Labels', 'Remove Labels']
               
            for index in range(1,3):   #add 2 buttons
                self.buttons.append(
                    Button(root, text=self.btn_text[index], width=13,
                    font=('Helvetica', 12, 'normal'),  
                    command=lambda i=index:self.change(i)))
                self.buttons[-1].grid(row=row, column=col)
                row+=1   
            
        def change(self,index):
            """
            add labels if #1 button clicked, destroy labels
            if #2 button clicked
            """
            
            if index == 1:
                self.LabelA = Label(root, bg='white', fg='blue', \
                    font=('Helvetica', 16), \
                    text="Hello", width=10)
                self.LabelA.place(x=100, y = 100)
                print "self.LabelA =", self.LabelA
            
                self.LabelB = Label(root, bg='orange', fg='black', \
                    font=('Helvetica', 16), \
                    text="World", width=10).place(x=100, y = 130)    
                print "self.LabelB =", self.LabelB
                print "-" * 50
            else:
                print "trying to destroy Labels"
                try:
                    self.LabelA.destroy()
                    self.LabelB.destroy()
                except:
                    import traceback
                    traceback.print_exc()
                    raise
    
    index= ", index)
                
    app = App(root)
    
    root.mainloop()
    Last edited by dwblas; August 25th, 2013 at 10:52 PM.
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Location
    California, USA
    Posts
    76
    Rep Power
    2
    My thanks to you both for explaining and showing
    the correct way in setting up labels so they can be
    removed with the destroy() function, and also how
    to handle error conditions.
    Jerry
  10. #6
  11. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,894
    Rep Power
    481
    changing the labels you've got is probably a better approach than destroy/create.

    Most of the widgets have a config method which with key words will make the change.

    Note that canvas items (line, rectangle, whatever) use the item_config method (or itemconfig, look it up!) along with an item identification since these aren't actual widgets. I also recently learned of a "move" method for canvas items.
    [code]Code tags[/code] are essential for python code and Makefiles!
  12. #7
  13. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Feb 2013
    Location
    California, USA
    Posts
    76
    Rep Power
    2
    B4...
    For my early level of Python coding, is it okay to just
    move the widgets out of sight like I've done in the code
    below by changing the y position to -100?

    I hope I can get a 2fer out of this. Do you know the reason
    why the 2 command statements I've commented out
    failed to execute, and there was no error generated?
    A print statement added to the called function showed
    that the function was not called. I don't understand what
    command=lambda means, but that one works.

    Thanks.
    Code:
    #labels are hidden by moving them above the window
    from tkinter import *
    
    root = Tk()
    
    root.geometry('400x300+20+20')
    
    class App:
        def __init__(self, root):
            self.root = root
            
            row,col = 0,0
    
            self.frame = Frame(root, width = 50, borderwidth = 2, relief = GROOVE)
            self.frame.pack()
            self.frame.place(x=120, y=50)
            
            self.LabelA = Label(self.frame, bg='white', fg='blue', \
                    font=('Helvetica', 16), text="Hello", width=14)
            self.LabelA.pack()
            
            self.LabelB = Label(self.frame, bg='orange', fg='black', \
                    font=('Helvetica', 16), text="World.", width=14)
            self.LabelB.pack()
            
            self.btn1 = Button(root, text="Show Labels", padx=4, pady=2, \
                #command=self.btn_clicked(1))
                command=lambda i=1:self.btn_clicked(i))     
            self.btn1.pack()
            self.btn1.place(x=150, y = 200)
    
            self.btn2 = Button(root, text="Hide Labels", padx=6, pady=2, \
                #command=self.btn_clicked(2))
                command=lambda i=2:self.btn_clicked(i))                  
            self.btn2.pack()
            self.btn2.place(x=150, y = 235)
    
        def btn_clicked(self,index):
            """
            Show labels if #1 button clicked,
            hide labels if #2 button clicked.
            """
            
            if index == 1:
                self.frame.place(y=50) 
            else:
                self.frame.place(y=-100)
                
    app = App(root)
    
    root.mainloop()
  14. #8
  15. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2010
    Posts
    153
    Rep Power
    5
    "command" needs to be set to a function handle -- that is, the reference to the actual function object.

    if I have a function:
    Code:
    def myfunc():
        pass
    "myfunc" refers to my function as an object, whereas "myfunc()" gets evaluated and will refer to the return value of myfunc (which is "None").

    "lambda" is a special keyword that lets you create anonymous functions, which are function objects that don't have names. The reason you use it here is because you want to call your function with arguments. Therefore, you can't just set command to the handle for your function (since no arguments would be passed), you have to create a wrapper function that calls your function with those arguments, and then give the handle for the wrapper function to "command".

    Clear as mud?
  16. #9
  17. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    May 2009
    Posts
    509
    Rep Power
    33
    Personal preference is to use partial instead of lambda as it is more straight forward. Also, there are several ways to remove a widget; the following uses pack_forget(), but you could also use iconify or withdraw.
    Code:
    from tkinter import *
    from functools import partial
    
    
    class App:
        def __init__(self):
            self.root = Tk()
            self.root.geometry('400x300+20+20')
            
            #row,col = 0,0
    
            self.frame = Frame(self.root, width = 50, borderwidth = 2, relief = GROOVE)
            self.frame.pack()
            self.frame.place(x=120, y=50)
            
            self.LabelA = Label(self.frame, bg='white', fg='blue', \
                    font=('Helvetica', 16), text="Hello", width=14)
            self.LabelA.pack()
            
            self.LabelB = Label(self.frame, bg='orange', fg='black', \
                    font=('Helvetica', 16), text="World.", width=14)
            self.LabelB.pack()
            
            self.btn1 = Button(self.root, text="Show Labels", padx=4, pady=2, \
                command=partial(self.btn_clicked, 1))
            self.btn1.pack()
    #        self.btn1.place(x=150, y = 200)
    
            self.btn2 = Button(self.root, text="Hide Labels", padx=6, pady=2, \
                command=partial(self.btn_clicked, 2))
            self.btn2.pack()
            self.btn2.place(x=150, y = 235)
    
            self.root.mainloop()
    
        def btn_clicked(self,index):
            """
            Show labels if #1 button clicked,
            hide labels if #2 button clicked.
            """
            print index        
            if index == 1:
                self.LabelA.pack()
                self.LabelB.pack()
            else:
                ##self.frame.place(y=-100)
                self.LabelA.pack_forget()
                self.LabelB.pack_forget()
                
    app = App()

    Comments on this post

    • Dietrich agrees : very nice way
    • admoore agrees : Agreed; very pythonic.
    Last edited by dwblas; August 29th, 2013 at 10:53 AM.

IMN logo majestic logo threadwatch logo seochat tools logo