ebarr ebarr - 2 months ago 23
Python Question

Tkinter canvas resizing automatically

So in the process of answering this question, I came across some odd behaviour from Tkinter. I have a class that resizes a

Canvas
instance and any widgets drawn on it. However when I run the code, regardless of the initial window dimensions, the window expands continuously until it fills the entire screen. After this happens, the window behaves exactly as expected, resizing the objects properly. The window only expands to fill the screen on launch.

From reading the Tkinter docs I could believe that this may be platform specific (I don't have any proof though).

My question is: Why does this happen? How can I stop it?

The code is below:

from Tkinter import *

# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(Canvas):
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()

def on_resize(self,event):
# determine the ratio of old width/height to new width/height
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)

def main():
root = Tk()
myframe = Frame(root)
myframe.pack(fill=BOTH, expand=YES)
mycanvas = ResizingCanvas(myframe,width=850, height=400, bg="red")
mycanvas.pack(fill=BOTH, expand=YES)

# add some widgets to the canvas
mycanvas.create_line(0, 0, 200, 100)
mycanvas.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
mycanvas.create_rectangle(50, 25, 150, 75, fill="blue")

# tag all of the drawn widgets
mycanvas.addtag_all("all")
root.mainloop()

if __name__ == "__main__":
main()

Answer

We've established that highlightthickness, an option of Canvas, is the culprit for this behavior, and setting it to 0 fixes the issue.

Here's why (I think) it happens:

From http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm

Configure The widget changed size (or location, on some platforms). The new size is provided in the width and height attributes of the event object passed to the callback.

This is the stripped down version of the Canvas subclass:

class ResizingCanvas(Canvas):
    def __init__(self,parent,**kwargs):
        Canvas.__init__(self,parent,**kwargs)
        print self.winfo_reqwidth(),self.winfo_reqheight() #>>>854, 404
        self.bind("<Configure>", self.on_resize)

    def on_resize(self,event):
        self.width = event.width   #>>>854
        self.height = event.height #>>>404
        self.config(width=self.width, height=self.height)

So, <Configure> should operate like this:

  1. Detect resize
  2. Call function
  3. Set Canvas to new size

But it's doing this:

  1. Detect resize
  2. Call function
  3. Set Canvas to new size
  4. Detect resize, repeat

What's happening between 3 & 4? Well, the Canvas is being set to a new size (its previous size + 4), but after that, the highlightthickness changes the actual size to +4 of that, which triggers <Configure> in an endless loop until the screen width is hit and it breaks.

At that point, normal resizing can occur, because it's got just one size to work off of (the combined highlight and canvas size), and it functions normally. If you added a button that resized the canvas and pressed it after the canvas stopped expanding, it would resize, then get weird again and start expanding.

I hope that kind of explained it. I'm not 100% sure that this is 100% correct, so if anyone has corrections, feel free.

Comments