'Sizing the Canvas widget with other widgets on the GUI

I'm creating a GUI that has a spreadsheet-like interface which is wrapped with entry method to allow the person that uses the program to enter it as if it is on a spreadsheet. This allows me much greater control over all the data and manipulate it for data analysis later. However, I can't restrict the size of the canvas to allow the scrolling bar to take effect. I.E. if I change the number of rows, the canvas will resize to that (along with column(s) changes too). I have other widgets within the GUI that isn't shown in the code but I'm just focusing on trying to force a size on the Canvas widget itself.

Is there a way for me to force the Canvas to stay within "width and height" size without having the rows and columns controlling the size of the Canvas?

import tkinter as tk
from tkinter import *
from textwrap import fill
from datetime import date

#Instantiate the GUI 
class StartApp(tk.Tk): 
def __init__(self):
    tk.Tk.__init__(self)
    t1 = ExcelTable(self)
    t1.grid(row = 0, columnspan=2, sticky = "N")
    t2 = ProductWidget()
    t2.grid(row=1, column=0,sticky="SW")
    t3 = TotalTrucks()
    t3.grid(row=1, column=1, sticky="nsew")
    
 #Instantiate the layout for the excel-like table entry for the top 
part of the GUI
class ExcelTable(tk.Frame):

def __init__(self, parent, width=500, height=500):
    
    rows=20
    columns=10
    #label for each column in the table        colHeaders = ["#", "Driver", "Tare", "Ticket #", "Location", "Product", "Gross", "Net", "Tons", "Date"]
  
# use black background so it "peeks through" to 
# form grid lines
    #tk.Frame.__init__(self, parent, background="black", width=150, height=200)

    #attempt to create a scrollbar within a canvas
    canvas = tk.Canvas(parent)
    scrollbar = tk.Scrollbar(parent, orient="vertical", command=canvas.yview)
    
    #contain the Frame within the canvas
    tk.Frame.__init__(self, canvas)

    #creates the widgets to behave like excel-like table for data entry
    self._widgets = []
    for row in range(rows):
        current_row = []
        for column in range(columns):
            if row == 0: #create the headers for the spreadsheet widget, using the colHeaders array
                if column == 0:
                    label = tk.Label(self, text = colHeaders[column], borderwidth=0, width = 4)
                else:
                    label = tk.Label(self, text = colHeaders[column], borderwidth=0, width=10)
            else:
                if column == 0:
                    label = tk.Label(self, text = (row))
                else:   
                    label = tk.Entry(self, text="")
                    label.bind
            label.grid(row=row, column=column, sticky="nsew", padx=1, pady=1)
            current_row.append(label)
        self._widgets.append(current_row)

    for column in range(columns):
        self.grid_columnconfigure(column, weight=1)
    
    canvas.create_window(0,0,anchor='nw', window=self)
    canvas.update_idletasks()

    canvas.configure(scrollregion=parent.bbox('all'))
    canvas.grid(row=0,column=0)
    scrollbar.grid(row=0, column=1, sticky="ns")

def set(self, row, column, value):
    widget = self._widgets[row][column]
    widget.configure(text=value)

def key(event):
    print ("key: {}".format(event.char))

#obtain and store values that are entered in the ExcelTable
def find_in_grid(frame, row, column):
    for children in frame.children.values():
        info = children.grid_info()
        #note that rows and column numbers are stored as string                                                                         
        if info['row'] == str(row) and info['column'] == str(column):
            return children
    return None

class ProductWidget(tk.Frame):
    def __init__(self):
        tk.Frame.__init__(self, background="white")
        self._widgets = []
        label1 = tk.Label(self, text="Product")
        label1.grid(row=0,column=0, sticky="nsew")
        label2 = tk.Label(self, text="Total Tons")
        label2.grid(row=0, column=1, sticky="nsew")

class TotalTrucks(tk.Frame):
    def __init__(self):
        tk.Frame.__init__(self, background="white" )
        self._widgets = []
        label1 = tk.Label(self, text="Total Trucks")
        label1.grid(row=0, rowspan=2, column=0, sticky="nsew")
        label2 = tk.Label(self, text="Today: ")
        label2.grid(row=1, column=0, stick="nsew")
        label3 = tk.Label(self, text="Last Week: ")
        label3.grid(row=2, column=0, sticky="nsew")
        label4 = tk.Label(self, text="Overall")
        label4.grid(row=3, column=0, sticky="nsew")

if __name__ == "__main__":
    currYear = date.today().year
    startGUI = StartApp()
    startGUI.title("Truck Log " + str(currYear))

    startGUI.mainloop()


Solution 1:[1]

Since ExcelTable is the internal frame inside canvas and is already put inside canvas using canvas.create_window(...), so you should not call t1.grid(...) inside StartApp.__init__().

Also you set the scrollregion of canvas wrong in the following line:

canvas.configure(scrollregion=parent.bbox('all'))

It should be:

canvas.configure(scrollregion=canvas.bbox('all'))

Finally you forgot to configure yscrollcommand option of canvas.

Below is the changes required:

...

class StartApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        t1 = ExcelTable(self)
        ### should not call t1.grid(...) since t1 has already put in canvas using canvas.create_window(...)
        #t1.grid(row = 0, columnspan=2, sticky = "N")
        t2 = ProductWidget()
        t2.grid(row=1, column=0,sticky="SW")
        t3 = TotalTrucks()
        t3.grid(row=1, column=1, sticky="nsew")
...

class ExcelTable(tk.Frame):
    def __init__(self, parent, width=500, height=500):
        ...
        #canvas.configure(scrollregion=parent.bbox('all'))
        canvas.configure(scrollregion=canvas.bbox('all'), width=self.winfo_width(),
                         yscrollcommand=scrollbar.set)
        ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1