Home > Mobile >  Scrollbar in Canvas doesn't work - Tkinter Python
Scrollbar in Canvas doesn't work - Tkinter Python

Time:01-13

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

CodePudding user response:

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

CodePudding user response:

Getting scrollbars to work can be awkward with tkinter, especially when also accounting for things like grid weight (I asked a question about that myself recently).

I've created a minimal version of your code below but replaced the layout of the entries & button canvas with a frame for simplicity, as well as also minimizing the file opener function, which now just needs the filepath to be added in the code below to run.

I've added some comments to the canvas/scrollbar gui section just so its easier to visualise placement of the elements and how they relate to one another.

import tkinter
from tkinter import *
import pandas as pd


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

        window.columnconfigure(0, weight=1)
        window.rowconfigure(1, weight=1)

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

        # === GUI for entries & button ===

        # Main frame
        self.frame = tkinter.Frame(window)
        self.frame.grid(row=0, column=0, columnspan=2, sticky='w')
        # label User
        self.user_label = tkinter.Label(self.frame, text='USERNAME')
        self.user_label.grid(row=0, column=0)
        # entry User
        self.user_name = tkinter.Entry(self.frame)
        self.user_name.grid(row=0, column=1)
        # label Client
        self.client_label = tkinter.Label(self.frame, text='CLIENT NAME')
        self.client_label.grid(row=1, column=0)
        # Entry Client
        self.client_name = tkinter.Entry(self.frame)
        self.client_name.grid(row=1, column=1)
        # Button
        self.button1 = tkinter.Button(self.frame, text='Find Client', command=self.file_opener)
        self.button1.grid(row=2, column=1)

        # === GUI for canvas and scrollbar ===

        # 1) Create a frame (may not be necessary depending on grid method used)
        self.frame1 = tkinter.Frame(window)
        self.frame1.grid(row=1, column=0, sticky='nsew')
        self.frame1.columnconfigure(0, weight=1)
        self.frame1.rowconfigure(1, weight=1)

        # 2) Canvas goes inside frame
        self.canvas = tkinter.Canvas(self.frame1)
        self.canvas.grid(row=1, column=0, sticky='nsew')
        self.canvas.columnconfigure(0, weight=1)

        # 3) Scroll Sidebar also goes inside this frame in next column
        self.scroll_y = tkinter.Scrollbar(self.frame1, orient='vertical', command=self.canvas.yview)
        self.scroll_y.grid(row=1, column=1, sticky='ns')
        self.scroll_y.columnconfigure(0, weight=1)

        # 4) Sub-frame goes inside of the canvas
        self.canvas_sub_frame = tkinter.Frame(self.canvas)
        self.canvas_sub_frame.grid(row=1, column=0)
        self.canvas_sub_frame.rowconfigure(0, weight=1)
        # Column Header population
        self.column_list = ['CLIENT', 'Column2', 'Column3', 'Column4', 'Column5']
        for i in range(len(self.column_list)):
            tkinter.Label(self.canvas_sub_frame, text=self.column_list[i]).grid(row=0, column=i, sticky='ew')
            self.canvas_sub_frame.columnconfigure(i, weight=1)

        # 5) Create Scroll Y & table expansion events
        self.canvas.create_window(0, 0, anchor='nw', window=self.canvas_sub_frame, tag='window')
        self.canvas_sub_frame.bind('<Configure>', self.config_frame)  # configure to allow scrolling
        self.canvas.bind('<Configure>', self.canvas_config)  # configures to allow expansion of window

    # 5) Scroll bar function
    def config_frame(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox('all'), yscrollcommand=self.scroll_y.set)

    # 5) Resizing function
    def canvas_config(self, event):
        canvas_width = event.width
        event.widget.itemconfig('window', width=canvas_width)

    def file_opener(self):
        xl = pd.read_excel(r'filepath\file.xlsx')

        for x in range(len(xl)):
            for y in range(len(xl.columns)):
                textbox = Text(self.canvas_sub_frame, width=15, height=2)
                textbox.grid(row=x 1, column=y, sticky='ew')
                textbox.insert(END, xl.iloc[x, y])
            checkbox = Checkbutton(self.canvas_sub_frame)
            checkbox.grid(row=x 1, column=6)


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