How to get tkinter canvas to dynamically resize to window width?

Question:

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)?

Asked By: Annonymous

||

Answers:

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

Answered By: fredtantini

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()
Answered By: ebarr

To resize the canvas object to fit the new window size just use the tkinter geometry managers properly.

With pack theres the expand and fill options, with grid you will need to use the columnconfigure and rowconfigure methods of the canvas master (the widget that contains the canvas) and with place manager theres the relwidth and relheight options.
On the other hand, this only resizes the canvas dimensions without changing the dimensions of its objects. To resize the canvas objects you will need to use the scale method as has been suggested. Another thing to take into consideration is that some canvas objects like text will not be affected by the scale canvas method.

Here is the response code changed a little bit:

import tkinter as tk


# a subclass of Canvas for dealing with resizing of windows
class ResizingCanvas(tk.Canvas):
    def __init__(self, parent, **kwargs):
        tk.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 = event.width/self.width
        hscale = event.height/self.height
        self.width = event.width
        self.height = event.height
        # rescale all the objects
        self.scale("all", 0, 0, wscale, hscale)


def main():
    root = tk.Tk()
    myframe = tk.Frame(root)
    myframe.pack(fill=tk.BOTH, expand=tk.YES)
    mycanvas = ResizingCanvas(myframe,width=850, height=400, bg="light blue")#, highlightthickness=0)
    mycanvas.pack(fill=tk.BOTH, expand=tk.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
    root.mainloop()

if __name__ == "__main__":
    main()
Answered By: Daniel Casasampera

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()
Answered By: Pete Faraday