Tkinter Frame won't destroy() correctly

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • jinsue
    New Member
    • Oct 2007
    • 9

    Tkinter Frame won't destroy() correctly

    Hi all,
    For the life of me I cannot work out why this is occuring but whenever I try to .destroy() this widget (or its parent) my program hangs - it doesn't crash, it just stops responding so I have to force kill it.
    Python 2.6.5, Windows 7

    Code:
            def generate_list(self):
                """
                Clears off current list and runs through the entire contact list again,
                makes a new 'user' object for each
                """
                for each in self.contacts_made:
                    each.clear()
                    #Can't get beyond here
    
                contacts_made = []
                global user_count
                user_count = 1
                
                for user in client_flags.contacts:
                    process_user = self.create(user)
                    process_user.produce()
                    self.contacts_made.append(process_user)
                self.update()
    
            def create(self, user_info):
                return self.user(self.inner_frame, user_info)
    
            def update(self):
                """
                For ensuring the the scrollbar knows the correct/current dimensions of
                the active user list - it will shrink and expand with more and less users
                online
                """
                self.UListBox.configure(scrollregion=(0, 0, self.inner_frame.winfo_width(), self.inner_frame.winfo_height()))
    
            class user:
                def __init__(self, parent, user_info):
                    self.root = parent
                    self.id = user_info[0]
                    self.nick = user_info[1]
                    self.status = user_info[2]
                    
                    self.container = Frame(parent, bg='white', bd=1, relief=RAISED)
                    global user_count
                    self.container.grid(row=(user_count + 1), column=0, sticky=(E,W))
                    user_count += 1 #need to go down too
    
                def clear(self):
                    self.container.destroy() #<-Hangs here
                    #Can't get to here
    
                def produce(self):
                    global statusGreen, statusRed, verd11
                    self.greenCircle = Label(self.container, image=statusGreen, bg="white")
                    self.redCircle = Label(self.container, image=statusRed, bg="white")
                    lNick = Label(self.container, text=self.nick, font=verd11, bg="white", anchor=W)#, font=fFont_Nick)
                    bConnect = Button(self.container, text="C", font=verd11, command=self.user_connect)#having an image would be cooler than "C"
    
                    if self.status == "AVAILABLE":
                        self.greenCircle.grid(row=0, column=0, pady=5)#, sticky=(N,S,W))
                    else:
                        self.redCircle.grid(row=0, column=0, pady=5)#, sticky=(N,S,W))
    
                    lNick.grid(row=0, column=1, sticky=(N,S,E,W), padx=(5,0), pady=5)
                    bConnect.grid(row=0, column=2, sticky=(N,S,E), pady=3)
    
                    self.container.columnconfigure(0, minsize=18, weight=0)
                    self.container.columnconfigure(1, minsize=150, weight=0)
                    self.container.columnconfigure(2, minsize=18, weight=0)
                    self.container.rowconfigure(0, minsize=34, weight=0)
    These 'user' classes are being grided under each other as they're created (tracked by user_count). Then I want to 'refresh' the list of users; which involves destroying the old objects and making new ones.

    There is something going wrong on what must be a basic level of what I've created or my understanding of widget destruction.

    Does anything jump out here?
    Cheers
    Last edited by jinsue; Jul 14 '10, 01:57 AM. Reason: Added more code, namely the create function
  • bvdet
    Recognized Expert Specialist
    • Oct 2006
    • 2851

    #2
    What does method self.create() return? self.contacts_m ade should be a list of user instances.

    destroy() is a universal widget method. It should work. Without having all the code for testing, it's sometimes hard to see a problem.

    Comment

    • jinsue
      New Member
      • Oct 2007
      • 9

      #3
      Ah, my bad. Would make sense to include the create function... :)

      I also added in the produce function in case that is making some odd settings.

      Thanks

      Comment

      • bvdet
        Recognized Expert Specialist
        • Oct 2006
        • 2851

        #4
        jinsue,

        I don't see anything that sticks out, but I have no way to test the code.
        You should capitalize the name of the class User. This is recommended for naming classes.

        Try calling destroy() directly on the widget instead of in an instance method. This can be accomplished by inheriting from Tkinter.Frame. Example:
        Code:
        class User(Frame):
            def __init__(self, parent, user_info):
                Frame.__init__(self, parent)
        Then you should be able to call destroy() directly on the instance as in:
        Code:
        for each in self.contacts_made:
            each.destroy()
        In my experience with Tkinter, things that you might think would work just don't work.

        Comment

        • jinsue
          New Member
          • Oct 2007
          • 9

          #5
          I'm not big on how inheritance works (never needed it), but I understand what you mean.

          In adding in that line to have:
          Code:
          def __init__(self, parent, user_info):
                          Frame.__init__(parent)
                          self.root = parent
                          self.id = user_info[0]
                          self.nick = user_info[1]
                          self.status = user_info[2]
           
                          self.container = Frame(parent, bg='white', bd=1, relief=RAISED)
                          global user_count
                          self.container.grid(row=(user_count + 1), column=0, sticky=(E,W))
                          user_count += 1 #need to go down too
          When the call goes through to:
          Code:
                          process_user = self.create(user)
                          process_user.produce()
          Nothing, visually occurs, so I'm not sure what its altering to cause incorrect grid'ing or some such.
          On your train of thought though I tried changing self.clear() to self.container. destroy() but with no success.

          In my experience with Tkinter, things that you might think would work just don't work.
          I agree entirely with that >.>

          Comment

          • bvdet
            Recognized Expert Specialist
            • Oct 2006
            • 2851

            #6
            When done this way:
            Code:
            class User(Frame):
                def __init__(self, parent, user_info):
                    Frame.__init__(self, parent)
            the instance itself is the Frame object. There is no need in creating another Frame object assigned to self.container.

            Here's a simple example of creating a Frame in this way inside a Tk window:
            Code:
            import Tkinter
            from Tkconstants import *
            import time
            
            textFont1 = ("Arial", 16, "bold")
            
            class Clock(Tkinter.Frame):
                def __init__(self, master):
                    Tkinter.Frame.__init__(self, master)
                    self.pack(fill=BOTH, expand=1)
                    menubar = Tkinter.Menu()
                    master.config(menu=menubar)
                    optionsMenu = Tkinter.Menu(tearoff=0)
                    menubar.add_cascade(label="Options", menu=optionsMenu)
                    optionsMenu.add_command(label='Print', command=self.print_value)
                    optionsMenu.add_command(label='Quit', command=master.destroy)
            
                    self.clockVar = Tkinter.StringVar()
                    self.clockLabel = Tkinter.Label(self, textvariable=self.clockVar,
                                                    relief="raised", font=textFont1,
                                                    bd=3,
                                                    bg='#ffffff000',
                                                    fg="#000000fff",
                                                    activebackground = "#000000fff",
                                                    activeforeground = "#ffffff000",
                                                    takefocus=1,
                                                    padx=3,
                                                    pady=3)
                    self.clockLabel.pack(fill=BOTH, expand=1)
                    self.run()
            
                def run(self):
                    timeStr = time.strftime("%A, %b %d, %Y\n%I:%M:%S %p\n%Z")
                    # 24 hour clock
                    #timeStr = time.strftime("%A, %b %d, %Y\n%X\n%Z")
                    if timeStr != self.clockVar.get():
                        self.clockVar.set(timeStr)
                    self.after(200, self.run)
            
                def print_value(self):
                    print self.clockVar.get()
            
            if __name__ == "__main__":
                root = Tkinter.Tk()
                app = Clock(root)
                root.mainloop()
            Change the command master.destroy to self.destroy and the Tk window will remain when Quit is selected but the clock frame will vanish. The Tk window is still active. There must be a way to make yours work.

            Comment

            • jinsue
              New Member
              • Oct 2007
              • 9

              #7
              Ok, I modified the appropriate sections and everything came out like normal which is good, but upon calling each.destroy() the same problem occurs - the process hangs.

              At this point I think more information would be good about the specifics of what is making it choke up.

              Speculation on my part is now that Tkinter's grid manager might be unhappy with destroying a frame and then the other frames could be trying to all take its place or something odd. I know when you pack and grid something in Tkinter you often get the 'impossible balance' problem where it will stall forever trying to make both managers happy. It could be something odd like that.

              However; how would I go about running a more detailed trace into the .destroy() method as that may render some clues as to why it becomes unhappy.

              Thanks for your input thus far bvdet.

              Comment

              • jinsue
                New Member
                • Oct 2007
                • 9

                #8
                Upon further testing I have encountered something interesting.
                If in the first run of generate_list() , you build the User objects and then destroy all of anyone of them straight after, it works perfectly. The other frames push together nicely and execution continues happily. When you run it the second time (refreshing the list) and it goes through self.contacts_m ade to destroy anything in it, it hangs.

                Code:
                        def generate_list(self):
                            if self.contacts_made <> []: #Only destroy if it actually has things in it
                                for each in self.contacts_made:
                                    each.destroy() #<-Fails here second time through
                                
                            contacts_made = []
                            global user_count
                            user_count = 1
                
                            self.process_user1 = self.create([1, "Jane", "AVAILABLE"])
                            self.process_user1.produce()
                            self.process_user2 = self.create([2, "Bob", "AVAILABLE"])
                            self.process_user2.produce()
                            self.process_user3 = self.create([3, "Alex", "UNAVAILABLE"])
                            self.process_user3.produce()
                            self.process_user4 = self.create([4, "Tim", "AVAILABLE"])
                            self.process_user4.produce()
                
                            self.contacts_made.append(self.process_user1)
                I think the solution is close now, but I am unsure as to the implications of this data. Please advise.

                Comment

                • bvdet
                  Recognized Expert Specialist
                  • Oct 2006
                  • 2851

                  #9
                  Unrelated to your problem, but this line:
                  Code:
                  if self.contacts_made <> []:
                  is unnecessary. When the for loop sees an empty list, it never does anything.

                  In addition, if you are going to compile a list of contacts, don't create all the extra variables self.process_us er1, self.process_us er2, and so on. Example:
                  Code:
                              userList = [[1, "Jane", "AVAILABLE"],
                                          [2, "Bob", "AVAILABLE"],
                                          [3, "Alex", "UNAVAILABLE"],
                                          [4, "Tim", "AVAILABLE"]]
                              for user in userList:
                                  new = self.create(user)
                                  new.produce()
                                  self.contacts_made.append(new)
                  Try this suggestion. Create your frame object holding the contacts and destroy the frame only. You may need another frame to hold the contacts frame to save it's place if you have other widgets on the master. Using the Clock example again:
                  Code:
                  import Tkinter
                  from Tkconstants import *
                  import time
                  
                  textFont1 = ("Arial", 16, "bold")
                  
                  class Clock(Tkinter.Frame):
                      def __init__(self, master):
                          self.master = master
                          menubar = Tkinter.Menu()
                          master.config(menu=menubar)
                          optionsMenu = Tkinter.Menu(tearoff=0)
                          menubar.add_cascade(label="Options", menu=optionsMenu)
                          optionsMenu.add_command(label='Print', command=self.print_value)
                          optionsMenu.add_command(label='Stop', command=self.destroy)
                          optionsMenu.add_command(label='Restart', command=self.start)
                          optionsMenu.add_command(label='Quit', command=master.destroy)
                          self.start()
                  
                      def start(self):
                          Tkinter.Frame.__init__(self, self.master)
                          self.pack(fill=BOTH, expand=1)
                          self.clockVar = Tkinter.StringVar(self.master)
                          self.clockLabel = Tkinter.Label(self, textvariable=self.clockVar,
                                                          relief="raised", font=textFont1,
                                                          bd=3,
                                                          bg='#ffffff000',
                                                          fg="#000000fff",
                                                          activebackground = "#000000fff",
                                                          activeforeground = "#ffffff000",
                                                          takefocus=1,
                                                          padx=3,
                                                          pady=3)
                          self.clockLabel.pack(fill=BOTH, expand=1)
                          self.run()
                  
                      def run(self):
                          timeStr = time.strftime("%A, %b %d, %Y\n%I:%M:%S %p\n%Z")
                          # 24 hour clock
                          #timeStr = time.strftime("%A, %b %d, %Y\n%X\n%Z")
                          if timeStr != self.clockVar.get():
                              self.clockVar.set(timeStr)
                          self.after(200, self.run)
                  
                      def print_value(self):
                          print self.clockVar.get()
                  
                  if __name__ == "__main__":
                      root = Tkinter.Tk()
                      app = Clock(root)
                      root.mainloop()
                  Notice the frame is destroyed when "Stop" is selected. Any widgets in that frame will also be destroyed. When "Restart" is selected, the frame and its contents are recreated.

                  Comment

                  • bvdet
                    Recognized Expert Specialist
                    • Oct 2006
                    • 2851

                    #10
                    As an afterthought, if you can't get destroy() to work, you could always try grid_forget().

                    Comment

                    • jinsue
                      New Member
                      • Oct 2007
                      • 9

                      #11
                      Ahah! So close now. Ok I didn't mention in the OP but the secondary (and thus the call that hangs) call is run from a thread which from a quick google search shows that this is a huge no-no /facepalm. At least now I know.

                      So I'm pretty certain that what I have will work as intended, provided I call it from the mainloop()'s thread.
                      That makes the million dollar question: how do I get the main (GUI) thread to make the call? It seems a common enough occurrence that this can be done but obviously I'm not in the know.

                      Similar problem:


                      Third dot point from the bottom, up from 'Resources':


                      The key point from the last link; "use 'after' from the main loop to poll a 'threading Queue' that your thread writes"

                      That makes sense but I do not understand where in the main loop I would put this command - if its a specific location I will probably be better off simply doing a condition check for this specific case and not complicating my project with the use of Queues (although they do look quite nice).

                      Please tell me you know where I should be putting this bvdet or that I'm being an idiot for realising it already :D.

                      Comment

                      • bvdet
                        Recognized Expert Specialist
                        • Oct 2006
                        • 2851

                        #12
                        Try calling widget.after() from your main thread to check the value of some variable that you set in the child thread. See my Clock example.

                        Comment

                        • jinsue
                          New Member
                          • Oct 2007
                          • 9

                          #13
                          It works perfectly now! Thanks so much bvdet for helping with this. I now understand the true significance of your clock example.
                          I could die happily right now that I have that solved...

                          To who may care, I ended up making a flag that gets checked like this:
                          Code:
                                  def --the setup section--
                                      self.UListBox.after(5000, self.check_to_update_list)            
                          
                                  def check_to_update_list(self):
                                      """Allows process to be called based on variables from non-Mainloop threads safely"""
                                      if client_flags.contacts_update == True:
                                          self.generate_list()
                                          client_flags.contacts_update = False
                                          
                                      self.UListBox.after(2000, self.check_to_update_list)
                          So it makes the call to self.generate_l ist() based on the variable set in the child thread and Tkinter is all happy.

                          Comment

                          • bvdet
                            Recognized Expert Specialist
                            • Oct 2006
                            • 2851

                            #14
                            Originally posted by jinsue
                            It works perfectly now! Thanks so much bvdet for helping with this. I now understand the true significance of your clock example.
                            I could die happily right now that I have that solved...

                            To who may care, I ended up making a flag that gets checked like this:
                            Code:
                                    def --the setup section--
                                        self.UListBox.after(5000, self.check_to_update_list)            
                            
                                    def check_to_update_list(self):
                                        """Allows process to be called based on variables from non-Mainloop threads safely"""
                                        if client_flags.contacts_update == True:
                                            self.generate_list()
                                            client_flags.contacts_update = False
                                            
                                        self.UListBox.after(2000, self.check_to_update_list)
                            So it makes the call to self.generate_l ist() based on the variable set in the child thread and Tkinter is all happy.
                            You have made my day. It is gratifying to solve problems such as this. I have learned from your problem also.

                            Comment

                            Working...