45

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
  • 13,796
  • 25
  • 99
  • 185
Annonymous
  • 888
  • 1
  • 9
  • 20

4 Answers4

40

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
  • 20,148
  • 23
  • 110
  • 201
ebarr
  • 7,476
  • 1
  • 25
  • 38
  • 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
22

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
  • 112,593
  • 23
  • 157
  • 280
fredtantini
  • 14,608
  • 7
  • 46
  • 54
1

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()
-1

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()
  • "Here is the response code changed a little bit"? How has it been changed? What value is your answer adding beyond what's already in the accepted answer? – martineau Mar 21 '21 at 16:23