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