6

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()
Community
  • 1
  • 1
ebarr
  • 7,704
  • 1
  • 29
  • 40
  • 1
    The canvas adds four extra pixels in the `highlightthickness` option, so event.width/height is trying to resize to catch up. See: http://stackoverflow.com/questions/11974710/why-does-the-tkinter-canvas-request-4-extra-pixels-for-the-width-and-height – atlasologist Apr 03 '14 at 13:27
  • You should put that as an answer, as it solved the problem to set `highlightthickness=0`. It is still not entirely clear to me why this happens though, as I would assume that `config` would be called only once to set this parameter and this would cause a single resizing. – ebarr Apr 03 '14 at 13:53

2 Answers2

5

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.

atlasologist
  • 3,824
  • 1
  • 21
  • 35
  • 1
    I am 98% sure that this is at least 96% correct. Your explanation makes sense as to why the window keeps expanding at the beginning, but it doesn't explain why it never tries to do it again on a resize. Either way, good answer. Thanks. – ebarr Apr 03 '14 at 21:43
  • I think the difference is that when the size is changed in the code, it just changes the widget, and the highlightthickness becomes a conflict, but when the window is manually resized, the widget and highlightthickness are changed together. I don't know if that makes any sense. – atlasologist Apr 03 '14 at 22:00
  • It's plausible. I don't think I will fully understand it without digging into the Tkinter source code (which I'm not about to do). – ebarr Apr 03 '14 at 22:05
0

I think the original problem is the recursive behaviour of on_resize() which is binding to Configure event, and resize() also calls "self.config(width=self.width, height=self.height)" - which will trigger the Configure event again. Remove "self.config(width=self.width, height=self.height)" will fix the problem.

Apparently setting "highlightthickness=0" also fix the problem - I am guess the window layout manager has some magic detecting there is no change in the window size hence stop calling on_resize() recursively