Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
like image 912
ebarr Avatar asked Apr 03 '14 12:04

ebarr


1 Answers

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.

like image 76
atlasologist Avatar answered Oct 11 '22 03:10

atlasologist