52

I need to get a canvas in tkinter to set its width to the width of the window, and then dynamically re-size the canvas when the user makes the window smaller/bigger.

Is there any way of doing this (easily)?

nbro
  • 15,395
  • 32
  • 113
  • 196
Annonymous
  • 968
  • 1
  • 9
  • 22

3 Answers3

42

I thought I would add in some extra code to expand on @fredtantini's answer, as it doesn't deal with how to update the shape of widgets drawn on the Canvas.

To do this you need to use the scale method and tag all of the widgets. A complete example 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", highlightthickness=0)
    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()
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
ebarr
  • 7,704
  • 1
  • 29
  • 40
  • 8
    A word of caution: using this with `grid` will kill your window manager! – steffen Apr 03 '16 at 19:53
  • 2
    And now: How do I get it to work if the Canvas is not the only widget? In my case, there is a Frame with button below the canvas which gets push out by the canvas... – steffen Apr 03 '16 at 20:03
  • 6
    This is excellent. I just want to stress the importance of `highlightthickness=0` when creating the ResizingCanvas, otherwise the canvas keeps expanding until you kill it. – Plasma May 22 '17 at 08:23
  • Thank you very much! But it seems that all objects are rescaled except images added to the canvas with ```mycanvas.create_image(cur_image_width/2, cur_image_height/2, image=cur_image_bg)```. Do you know a way how to rescale the image with the self.scale function? – schrottulk Jan 26 '19 at 11:53
  • This solution is suboptimal as going through multiple resize event with an array of canvas, the canvas who originally had the same size will all have different size. Also, if you have a line or circle, the line thickness will not change. – norok2 Jul 04 '19 at 19:06
  • 1
    Very nice except remove `bg="red",` which is distracting and makes the red line invisible. – WinEunuuchs2Unix Nov 24 '19 at 13:40
  • This doesnot work for the image widget. If we try mycanvas.create_image, then it doesnot seem to work. Any solution for that? – Lakpa Tamang May 16 '22 at 08:02
  • Will crash if the original scale is 0: https://github.com/python/cpython/issues/101113 – Iuri Guilherme Jan 17 '23 at 23:59
25

You can use the .pack geometry manager:

self.c=Canvas(…)
self.c.pack(fill="both", expand=True)

should do the trick. If your canvas is inside a frame, do the same for the frame:

self.r = root
self.f = Frame(self.r)
self.f.pack(fill="both", expand=True)
self.c = Canvas(…)
self.c.pack(fill="both", expand=True)

See effbot for more info.

Edit: if you don't want a "full sized" canvas, you can bind your canvas to a function:

self.c.bind('<Configure>', self.resize)

def resize(self, event):
    w,h = event.width-100, event.height-100
    self.c.config(width=w, height=h)

See effbot again for events and bindings

martineau
  • 119,623
  • 25
  • 170
  • 301
fredtantini
  • 15,966
  • 8
  • 49
  • 55
3

This does it - in my version of Python at least. The canvas resizes both horizontally (sticky=E+W & rowconfigure) and vertically (sticky=N+S & columnconfigure). I've set the canvas background to a disgusting colour - but at least you can see when it works.

from tkinter import *
root=Tk()
root.rowconfigure(0,weight=1)
root.columnconfigure(0,weight=1)
canv=Canvas(root, width=600, height=400, bg='#f0f0c0')
canv.grid(row=0, column=0, sticky=N+S+E+W)
root.mainloop()