Ken Oh
Ken Oh

Reputation: 157

Getting Tkinter pack_forget to work only on unique objects

Ubuntu Linux 11.04 / Python 2.7 / Tkinter

I'm a novice at Python GUIs and I'm having trouble making single objects disappear and reappear. pack_forget kind of works for me, but the way I'm doing it makes all the objects blink in and out, when I only want one at a time. In the boiled-down example below, I'm trying to only make object "a" appear and then disappear and then do the same for object "b".

I would be very grateful for any help, plus some better docs on this would be cool too. The stuff I've been able to find has been light on examples.

from Tkinter import *
import time

class Box:
    def __init__(self, canvas):
            self.canvas = canvas
    def type(self, type):
        if type == "1":
            self.if = self.canvas.create_rectangle(0, 0, 50, 50, fill="red")
        elif type == "2":
            self.if = self.canvas.create_rectangle(50, 50, 100, 100, fill="blue")
    def hide(self):
        self.canvas.pack_forget()
        self.canvas.update_idletasks()
        root.update()
    def unhide(self):
        self.canvas.pack()
        self.canvas.update_idletasks()
        root.update()

root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()
root.update()

a = Box(canvas)
a.type("1")
b = Box(canvas)
b.type("2")

a.unhide()
time.sleep(.5)
a.hide()
time.sleep(1)

b.unhide()
time.sleep(.5)
b.hide()
time.sleep(1)

Upvotes: 0

Views: 1762

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385970

There are many things wrong or curious about your code. For one, you should never call sleep in a program that has an event loop. When you call that, your whole program will freeze. If you want something to happen after a fixed period of time, use the after method to schedule a function to run in the future.

Second, there is no point in calling root.update after calling self.canvas.update_idletasks. For one, assume it is a rule written in stone that you should never call update. It's OK to call update if you know the ramifications of that, but since you are just learning and don't know, assume it's unsafe to call it. Plus, update does the same work as update_idletasks and more, so if you do choose to call update, calling update_idletasks is unnecessary.

As to your real problem. You seem to be wanting to create two distinct "Box" objects, and want to hide and show them independently (?). However, both boxes are rectangles that are drawn on the same canvas. When you call pack_forget, that affects the whole canvas so both of these objects will disappear and then reappear.

It isn't clear what your intention is. If each instance of "Box" is just a rectangle on the canvas, you don't want to use pack_forget because that works on widgets, not canvas objects.

If you just want the rectangles to appear and disappear you have several choices. You can destroy and recreate them each time, or you can use the canvas move or coords method to move the item to a non-visible portion of the canvas. You could also manipulate the stacking order to raise or lower an object above or below any or all other objects.

Here's a quick example that uses the trick of moving the items outside the visible area of the canvas.

from Tkinter import *
import time

class Box:
    def __init__(self, canvas, type):
        self.canvas = canvas
        if type == "1":
            self.rect = canvas.create_rectangle(0, 0, 50, 50, fill="red")
        elif type == "2":
            self.rect = canvas.create_rectangle(50, 50, 100, 100, fill="blue")
        self.coords = canvas.coords(canvas, self.rect)

    def hide(self):
        # remember where this object was
        self.coords = canvas.coords(self.rect)
        # move it to an invisible part of the canvas
        self.canvas.move(self.rect, -1000, -1000)
    def unhide(self):
        # restore it to where it was
        self.canvas.coords(self.rect, *self.coords)

root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()

a = Box(canvas, type="1")
b = Box(canvas, type="2")

root.after(1000, a.hide)
root.after(2000, a.unhide)
root.after(3000, b.hide)
root.after(4000, b.unhide)

root.mainloop()

Finally, if what you really want is for an object to blink continuously, you can have the hide method automatically call the unhide method and visa versa:

    def hide(self):
        # remember where this object was
        self.coords = canvas.coords(self.rect)
        # move it to an invisible part of the canvas
        self.canvas.move(self.rect, -1000, -1000)
        # unhide after a second
        self.canvas.after(1000, self.unhide)

    def unhide(self):
        # restore it to where it was
        self.canvas.coords(self.rect, *self.coords)
        # re-hide after a second
        self.canvas.after(1000, self.hide)

Upvotes: 4

Related Questions