Scrollbar in Canvas doesn't work - Tkinter Python

I have been struggling to add a working scrollbar to my canvas widget for days. I'm working on a small app to show excel data. I have tried so many things but couldn't achieve a working result :/

I populate canvas2 with "file_opener" function, and I would like to add the scrollbar to the 7th column of canvas2. However, in my previous attempts the scrollbar was appearing only in row 0 and without the functionality.

I would greatly appreciate your help as I'm a self-learning beginner.

This is how it looks: "https://ibb.co/W2d674g"

Here is my code:

import tkinter
from tkinter import *
import pandas as pd

class App:
    def __init__(self, window):
        self.window = window

        window.title("Excel Magician")
        window.geometry("800x800")

        self.canvas1 = tkinter.Canvas(window, width = 720, height = 200)
        self.canvas1.grid(row=0, column=0)

        self.frame1 = tkinter.Frame(window, width = 720,height = 20, bg = '#0ca274')
        self.frame1.grid(row=1, column=0, pady=4)

        self.canvas2 = tkinter.Canvas(window, width = 720,height = 300, bg = '#0ca274')
        self.canvas2.grid(row=2, column=0, pady=1)

        self.column_list = ['CLIENT','Column2','Column3','Column4','Column5']
        for i in range(len(self.column_list)):
            tkinter.Label(self.frame1, text= self.column_list[i], font=('Bahnschrift',10)).grid(row= 0, column= i, sticky='e', ipadx=50)

        self.user_label = tkinter.Label(self.canvas1, text='USERNAME', font=('Bahnschrift',10))
        self.canvas1.create_window(600, 60, window=self.user_label)

        self.user_name = tkinter.Entry (self.canvas1) 
        self.canvas1.create_window(600, 80, window=self.user_name)

        self.client_label = tkinter.Label(self.canvas1, text='CLIENT NAME', font=('Bahnschrift',10))
        self.canvas1.create_window(600, 100, window=self.client_label)

        self.client_name = tkinter.Entry (self.canvas1) 
        self.canvas1.create_window(600, 120, window=self.client_name)  

        self.button1 = tkinter.Button(text='Find Client',font=('Bahnschrift',10), command=self.file_opener)
        self.canvas1.create_window(600, 150, window=self.button1)

    def file_opener(self):
        self.name = self.user_name.get()
        self.xl= pd.read_excel(f"C:/Users/leven\Desktop/{self.name}'s Portfolio.xlsm", sheet_name='CM DATA')
        self.client = self.client_name.get()
        self.result = self.xl[self.xl.Client.str.contains(self.client, regex=False, case=False)][['Client','Column2','Column3','Column4','Column5']]
        self.client_name.delete(0, 'end')
        self.active_state=[]
        for widget in self.canvas2.winfo_children():
            widget.destroy()
        for x in range(len(self.result)):
            for y in range(len(self.result.columns)):
                textbox = Text(self.canvas2, width=20, height=2,font=('Bahnschrift',10))
                textbox.grid(row=x,column=y, padx=2, pady=2)
                textbox.insert(END, self.result.iloc[x,y])
            var = tkinter.IntVar()
            self.checkbox = Checkbutton(self.canvas2,variable=var, onvalue=1, offvalue=0, relief=SUNKEN)
            self.checkbox.grid(row=x, column=6, padx=2, pady=2, ipadx=2, ipady=2)
            self.active_state.append(var)

if __name__ == "__main__":
    root = Tk()
    my_gui = App(root)
    root.mainloop()


Solution 1:

As putting widgets into a canvas using grid() or pack() does not change the scrollregion, so the scrollbar linked to the canvas will not be activated.

You need to create a frame and put it into the canvas using .create_window(...) and then put those Text and Checkbutton widgets into this frame. Also you need to update the scrollregion of the canvas when the frame is resized, so that the attached scrollbar can be activated.

Below is a modified code based on yours:

import tkinter
import pandas as pd

class App:
    def __init__(self, window):
        self.window = window

        window.title("Excel Magician")
        window.geometry("800x800")

        self.canvas1 = tkinter.Canvas(window, width = 720, height = 200)
        self.canvas1.grid(row=0, column=0)

        self.frame1 = tkinter.Frame(window, width = 720,height = 20, bg = '#0ca274')
        self.frame1.grid(row=1, column=0, pady=4)

        self.canvas2 = tkinter.Canvas(window, width = 720,height = 300, bg = '#0ca274')
        self.canvas2.grid(row=2, column=0, pady=1, sticky='ew') # added sticky='ew'

        self.column_list = ['CLIENT','Column2','Column3','Column4','Column5']
        for i in range(len(self.column_list)):
            tkinter.Label(self.frame1, text= self.column_list[i], font=('Bahnschrift',10)).grid(row= 0, column= i, sticky='e', ipadx=50)

        self.user_label = tkinter.Label(self.canvas1, text='USERNAME', font=('Bahnschrift',10))
        self.canvas1.create_window(600, 60, window=self.user_label)

        self.user_name = tkinter.Entry (self.canvas1)
        self.canvas1.create_window(600, 80, window=self.user_name)

        self.client_label = tkinter.Label(self.canvas1, text='CLIENT NAME', font=('Bahnschrift',10))
        self.canvas1.create_window(600, 100, window=self.client_label)

        self.client_name = tkinter.Entry (self.canvas1)
        self.canvas1.create_window(600, 120, window=self.client_name)

        self.button1 = tkinter.Button(text='Find Client',font=('Bahnschrift',10), command=self.file_opener)
        self.canvas1.create_window(600, 150, window=self.button1)

        # create the scrollable frame and the scrollbar
        self.internal = tkinter.Frame(self.canvas2)
        self.internal.bind('<Configure>', lambda e: self.canvas2.config(scrollregion=self.canvas2.bbox('all')))
        self.canvas2.create_window(0, 0, window=self.internal, anchor='nw')

        self.scrollbar = tkinter.Scrollbar(window, command=self.canvas2.yview)
        self.scrollbar.grid(row=2, column=1, sticky='ns')
        self.canvas2.config(yscrollcommand=self.scrollbar.set)

    def file_opener(self):
        self.name = self.user_name.get()
        self.xl= pd.read_excel(f"C:/Users/leven/Desktop/{self.name}'s Portfolio.xlsm", sheet_name='CM DATA')
        self.client = self.client_name.get()
        self.result = self.xl[self.xl.Client.str.contains(self.client, regex=False, case=False)][['Client','Column2','Column3','Column4','Column5']]
        self.client_name.delete(0, 'end')
        self.active_state=[]
        # clear existing widgets in self.internal
        for widget in self.internal.winfo_children():
            widget.destroy()
        for x in range(len(self.result)):
            for y in range(len(self.result.columns)):
                # created inside self.internal
                textbox = tkinter.Text(self.internal, width=20, height=2,font=('Bahnschrift',10))
                textbox.grid(row=x,column=y, padx=2, pady=2)
                textbox.insert(tkinter.END, self.result.iloc[x,y])
            var = tkinter.IntVar()
            # created inside self.internal
            self.checkbox = tkinter.Checkbutton(self.internal,variable=var, onvalue=1, offvalue=0, relief=tkinter.SUNKEN)
            self.checkbox.grid(row=x, column=6, padx=2, pady=2, ipadx=2, ipady=2)
            self.active_state.append(var)

if __name__ == "__main__":
    root = tkinter.Tk()
    my_gui = App(root)
    root.mainloop()