Search code examples
pythoncanvastkinterresizescrollbar

Python tkinter frame canvas resize


I'm attempting to resize a canvas so the canvas fills the entire window when the window is resized by the user employing a 'click and drag' on the window edge, but I am not successful.

I have looked at the following other questions:

Question #1 (previously posted, but not helping here)

This one sent me in what I thought was the correct direction because I bind a Configure on canvas. However, I get an error that " 'canvas' is not defined in FrameWidth."

Tkinter: How to get frame in canvas window to expand to the size of the canvas?

If I modify the function call and the function, then I get the error that "TypeError: FrameWidth() missing 1 required positional argument: 'canvas' " The modification is

canvas.bind('<Configure>', self.FrameWidth(canvas))

def FrameWidth(self, event, canvas):

Question #2 (previously posted, but not helping here)

I also looked at this question:

Python Tkinter Scrollbar Shaky Scrolling

But this question is addressing an erratically behaving scrollbar.

Question #3 (previously posted, but not helping here)

I have also looked at this questions and put in weight=1, but that is not helping:

Python TKinter: frame and canvas will expand horizontally, but not vertically, when window is resized

Any advice would be appreciated.

Here's my MWE:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Nov  3 04:39:43 2017

@author: davidc
"""

import tkinter as tk


class Selections(tk.Frame): 
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        self.FifthLabelLeft = tk.Label(self,
                         text="""Riding""",
                         justify = tk.CENTER,
                         width=20,
                         padx = 10).grid(row=4, column = 0, pady=5)

        self.FifthLabelCenter = tk.Label(self,
                         text="""Winning Candidate""",
                         justify = tk.CENTER,
                         width=20,
                         padx = 10).grid(row=4, column = 1, pady=5)

        self.FifthLabelRight = tk.Label(self,
                         text="""Percent of Vote""",
                         justify = tk.CENTER,
                         width=10,
                         padx = 10).grid(row=4, column = 2, pady=5)

        mybox = tk.LabelFrame(self, padx=5, pady=4)
        mybox.grid(row=5, column=0, columnspan=3)

        canvas = tk.Canvas(mybox, borderwidth=5, background="#70ff33")
        frame = tk.Frame(canvas, background="#33f4ff")
        vsb = tk.Scrollbar(mybox, orient="vertical", command=canvas.yview)
        canvas.configure(yscrollcommand=vsb.set, width=450, heigh=30)       

        mybox.grid_columnconfigure(5,weight=1)
        mybox.grid_rowconfigure(5,weight=1)
        frame.grid_columnconfigure(5,weight=1)
        frame.grid_rowconfigure(5,weight=1)

        vsb.pack(side="right", fill="y")
        canvas.pack(side="left", fill="both", expand=True)
        canvas.create_window((4,4), window=frame, anchor="nw", tags="frame")

        # be sure that we call OnFrameConfigure on the right canvas
        frame.bind("<Configure>", lambda event: self.OnFrameConfigure(canvas))
        canvas.bind('<Configure>', self.FrameWidth)

        self.fillWindow(frame)

        self.QuitButton = tk.Button(self, 
                                    text="QUIT", 
                                    command=root.destroy, 
                                    padx=25, pady=0)
        self.QuitButton.grid(column = 0, columnspan=3)


    def fillWindow(self, frame): 
        PartyWinnersList = [['Some list of places', "Somebody's Name", 0.37448599960838064], 
                            ['A shorter list', 'Three Long Names Here', 0.52167817821240514],
                            ['A much longer, longer entry', 'Short Name', 0.41945832387008858]]
        placement = 2
        for i in PartyWinnersList:
            ShowYear = tk.Label(frame,
                                      text="""%s """ % i[0],
                                      width=20,
                                      )
            ShowYear.grid(row=placement, column = 0, sticky=tk.S)
            ShowSystem = tk.Label(frame,
                                      text="""%s """ % i[1],
                                      width=20,
                                      )
            ShowSystem.grid(row=placement, column = 1, sticky=tk.N)
            PercentVotes = i[2]*100
            ShowVotes = tk.Label(frame,
                                      text="""%3.1f""" % PercentVotes,
                                      width=10,
                                      )
            ShowVotes.grid(row=placement, column = 2, sticky=tk.N)
            placement += 1

    def FrameWidth(self, event):
        canvas_width = event.width
        canvas.itemconfig(self.canvas_frame, width = canvas_width)

    def OnFrameConfigure(self, canvas):
        canvas.configure(scrollregion=canvas.bbox("all"))


if __name__ == "__main__": 
    root = tk.Tk()
    main = Selections(root)
    main.pack(side="top", fill="both", expand=True)
    root.mainloop()

Solution

  • No need to reinvent the wheel here. Canvas() is a tkinter widget, and like all tkinter widgets it has to be drawn in the window using a geometry manager. This means that we can manipulate how it appears in the window.

    Example program below modified from an example found on this page.

    If we use .pack() then we can do something like the below:

    from tkinter import *
    
    root = Tk()
    
    w = Canvas(root, width=200, height=100)
    w.pack(fill="both", expand=True)
    
    w.create_line(0, 0, 200, 100)
    w.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
    
    w.create_rectangle(50, 25, 150, 75, fill="blue")
    
    root.mainloop()
    

    Where the combination of fill="both" and expand=True tells the widget to eat up all the extra space it's given in the window and expand to fill it.

    If we're using .grid() then we have to do something slightly different:

    from tkinter import *
    
    root = Tk()
    
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    
    w = Canvas(root, width=200, height=100)
    w.grid(sticky=N+S+E+W)
    
    w.create_line(0, 0, 200, 100)
    w.create_line(0, 100, 200, 0, fill="red", dash=(4, 4))
    
    w.create_rectangle(50, 25, 150, 75, fill="blue")
    
    root.mainloop()
    

    Here we use .rowconfigure() and .columnconfigure to tell row 0 and column 0 (where our canvas is) to have a higher weight than any other row or column, meaning they get given more of the free space in the window than the others (In this case all of it seeing as how no other rows or columns exist), we then need to tell the widget to actually expand with the "cell" it resides in, which we do by specifying sticky=N+S+E+W, which tells the widget to stick to all 4 edges of the cell when they expand, thus expanding the widget.