Python tkinter frame canvas resize

Question:

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()
Asked By: David Collins

||

Answers:

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.

Answered By: Ethan Field

I hope below answer will help your need. If my understanding is different, please reach back, will try to help you more.

Corrections to be made:

Line 7: Added parameter "window" for frame input to the class.

Line 15, 23, 31: Added code line to configure column to expand as the window expands.
(If you add more column need to add the same command with column index to make that column expand)
(Applies to row too, if row need to be expanded)

Since Canvas, Frame are used in other methods (configure methods) declared as class variable using self

Line 48: While creating window inside canvas and assigning to the frame, it should be saved in a variable.
And this variable should be used in itemconfigure function (Line 65)

Line 81, 88, 96: Here same Frame column is expanded as per the window size using the column index.
(If you add more column need to add the same command with column index to make that column expand)
(Applies to row too, if row need to be expanded)

Below is the corrected code:

import tkinter as tk


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

        label_1 = tk.Label(self.win,
                           text="""Riding""",
                           justify=tk.CENTER,
                           width=20,
                           padx=10)
        label_1.grid(row=4, column=0, pady=5, sticky=tk.NSEW)
        self.win.columnconfigure(0, weight=1)

        label_2 = tk.Label(self.win,
                           text="""Winning Candidate""",
                           justify=tk.CENTER,
                           width=20,
                           padx=10)
        label_2.grid(row=4, column=1, pady=5, sticky=tk.NSEW)
        self.win.columnconfigure(1, weight=1)

        label_3 = tk.Label(self.win,
                           text="""Percent of Vote""",
                           justify=tk.CENTER,
                           width=10,
                           padx=10)
        label_3.grid(row=4, column=2, pady=5, sticky=tk.NSEW)
        self.win.columnconfigure(2, weight=1)

        mybox = tk.LabelFrame(self.win, padx=5, pady=4)
        mybox.grid(row=5, column=0, columnspan=3, sticky=tk.NSEW)
        mybox.columnconfigure(0, weight=1)
        self.win.rowconfigure(5, weight=1)

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

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

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

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

        self.fillWindow(self.frame)

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

    def FrameWidth(self, event):
        canvas_width = event.width
        canvas_height = event.height
        self.canvas.itemconfig(self.frame_id, width=canvas_width, height=canvas_height)

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

    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.NSEW)
            frame.columnconfigure(0, weight=1)

            ShowSystem = tk.Label(frame,
                                  text="""%s """ % i[1],
                                  width=20,
                                  )
            ShowSystem.grid(row=placement, column=1, sticky=tk.NSEW)
            frame.columnconfigure(1, weight=1)

            PercentVotes = i[2] * 100
            ShowVotes = tk.Label(frame,
                                 text="""%3.1f""" % PercentVotes,
                                 width=10,
                                 )
            ShowVotes.grid(row=placement, column=2, sticky=tk.NSEW)
            frame.columnconfigure(2, weight=1)
            placement += 1


if __name__ == "__main__":
    root = tk.Tk()
    main = Selections(root)
    root.mainloop()
Answered By: Kannan venkatraman