Tkinter scrollbar for frame
"Am i doing it right?Is there better/smarter way to achieve the output this code gave me?"
Generally speaking, yes, you're doing it right. Tkinter has no native scrollable container other than the canvas. As you can see, it's really not that difficult to set up. As your example shows, it only takes 5 or 6 lines of code to make it work -- depending on how you count lines.
"Why must i use grid method?(i tried place method, but none of the labels appear on the canvas?)"
You ask about why you must use grid. There is no requirement to use grid. Place, grid and pack can all be used. It's simply that some are more naturally suited to particular types of problems. In this case it looks like you're creating an actual grid -- rows and columns of labels -- so grid is the natural choice.
"What so special about using anchor='nw' when creating window on canvas?"
The anchor tells you what part of the window is positioned at the coordinates you give. By default, the center of the window will be placed at the coordinate. In the case of your code above, you want the upper left ("northwest") corner to be at the coordinate.
tkinter scrollbar (frame/canvas) in application that create SubFrames
It is the canvas that has the ability to scroll, so your self.groupOfFrames
needs to go inside the canvas. And since you want the scrollbar and canvas to appear as a single complex object, they should go in a frame.
You need to make sure that when the window is resized, the frame inside the canvas is resized as well. And you also need to make sure that when you add something to self.groupOfFrames
you also update the scrollregion. These can be done by binding to the <Configure>
event of each widget.
Thus, I would create the FrameStack like the following. Please note that self.topFrame
and self.bottomFrame
are children of self
rather than root
. That is a mistake I didn't catch in the code I gave you in your previous question.
class FrameStack(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.subframes = []
self.topFrame = tk.Frame(self)
self.bottomFrame = tk.Frame(self)
self.topFrame.pack(side="top", fill="x")
self.bottomFrame.pack(side="bottom", fill="both", expand=True)
self.canvas = tk.Canvas(self.bottomFrame, bd=0)
vsb = tk.Scrollbar(self.bottomFrame, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.groupOfFrames = tk.Frame(self.canvas)
self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames, tags=("inner",))
self.canvas.bind("<Configure>", self._resize_inner_frame)
self.groupOfFrames.bind("<Configure>", self._reset_scrollregion)
def _reset_scrollregion(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def _resize_inner_frame(self, event):
self.canvas.itemconfig("inner", width=event.width)
Vertical scrollbar for frame in Tkinter, Python
See again link: https://stackoverflow.com/a/3092341/7432
It shows how to create scrolled frame - and then you can add all widgets in this frame.
import tkinter as tk
def on_configure(event):
# update scrollregion after starting 'mainloop'
# when all widgets are in canvas
canvas.configure(scrollregion=canvas.bbox('all'))
root = tk.Tk()
# --- create canvas with scrollbar ---
canvas = tk.Canvas(root)
canvas.pack(side=tk.LEFT)
scrollbar = tk.Scrollbar(root, command=canvas.yview)
scrollbar.pack(side=tk.LEFT, fill='y')
canvas.configure(yscrollcommand = scrollbar.set)
# update scrollregion after starting 'mainloop'
# when all widgets are in canvas
canvas.bind('<Configure>', on_configure)
# --- put frame in canvas ---
frame = tk.Frame(canvas)
canvas.create_window((0,0), window=frame, anchor='nw')
# --- add widgets in frame ---
l = tk.Label(frame, text="Hello", font="-size 50")
l.pack()
l = tk.Label(frame, text="World", font="-size 50")
l.pack()
l = tk.Label(frame, text="Test text 1\nTest text 2\nTest text 3\nTest text 4\nTest text 5\nTest text 6\nTest text 7\nTest text 8\nTest text 9", font="-size 20")
l.pack()
# --- start program ---
root.mainloop()
Create resizable Tkinter frame inside of scrollable canvas
Is there a simple way to allow the frame inside the canvas to resize itself without breaking the scrollbar?
Simple? I guess that depends on your definition of simplicity. It's possible, but it requires a few extra lines of code.
The scrollbars only work if you add the frame to the canvas with create_window
and only when you let the frame be as big as it needs to be to hold all of its children and then set the canvas bbox accordingly. When the window resizes you need to force the frame to be bigger than it wants to be if it is smaller than the canvas, but you need to let the frame be its preferred size if the frame is bigger than the canvas.
The solution looks something like the following example, off the top of my head. Notice the use of a tag to make it easy to find the inner frame. You could just as easily store the id returned by create_window
and use that instead. This also takes advantage of the fact that the event
object has width
and height
attributes for the canvas.
def get_vertically_scrollable_frame(parent_frame: tk.Frame or tk.Tk) -> tk.Frame:
...
canvas.create_window((0, 0), window=canvas_frame, anchor="nw", tags=("canvas_frame",))
canvas.bind('<Configure>', handle_resize)
...
def handle_resize(event):
canvas = event.widget
canvas_frame = canvas.nametowidget(canvas.itemcget("canvas_frame", "window"))
min_width = canvas_frame.winfo_reqwidth()
min_height = canvas_frame.winfo_reqheight()
if min_width < event.width:
canvas.itemconfigure("canvas_frame", width=event.width)
if min_height < event.height:
canvas.itemconfigure("canvas_frame", height=event.height)
canvas.configure(scrollregion=canvas.bbox("all"))
Scrollbar in Canvas doesn't work - Tkinter Python
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()
tkinter Canvas Scrollbar with Grid?
The height of your scrollbar didn't match the buttons frame height because you did't tell it to stick North and South .grid(..., sticky='ns')
Then, the scrolling behavior you want to achieve is described here: Adding a Scrollbar to a group of widgets
See also @martineau's answer for a more general object-oriented solution with 2D scrolling (horizontal & vertical)
import tkinter as tk
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="gray")
frame_main.grid(sticky='news')
label1 = tk.Label(frame_main, text="Label 1", fg="green")
label1.grid(row=0, column=0, pady=(5, 0), sticky='nw')
label2 = tk.Label(frame_main, text="Label 2", fg="blue")
label2.grid(row=1, column=0, pady=(5, 0), sticky='nw')
label3 = tk.Label(frame_main, text="Label 3", fg="red")
label3.grid(row=3, column=0, pady=5, sticky='nw')
# Create a frame for the canvas with non-zero row&column weights
frame_canvas = tk.Frame(frame_main)
frame_canvas.grid(row=2, column=0, pady=(5, 0), sticky='nw')
frame_canvas.grid_rowconfigure(0, weight=1)
frame_canvas.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow 5-by-5 buttons resizing later
frame_canvas.grid_propagate(False)
# Add a canvas in that frame
canvas = tk.Canvas(frame_canvas, bg="yellow")
canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
vsb = tk.Scrollbar(frame_canvas, orient="vertical", command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
# Create a frame to contain the buttons
frame_buttons = tk.Frame(canvas, bg="blue")
canvas.create_window((0, 0), window=frame_buttons, anchor='nw')
# Add 9-by-5 buttons to the frame
rows = 9
columns = 5
buttons = [[tk.Button() for j in range(columns)] for i in range(rows)]
for i in range(0, rows):
for j in range(0, columns):
buttons[i][j] = tk.Button(frame_buttons, text=("%d,%d" % (i+1, j+1)))
buttons[i][j].grid(row=i, column=j, sticky='news')
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
frame_buttons.update_idletasks()
# Resize the canvas frame to show exactly 5-by-5 buttons and the scrollbar
first5columns_width = sum([buttons[0][j].winfo_width() for j in range(0, 5)])
first5rows_height = sum([buttons[i][0].winfo_height() for i in range(0, 5)])
frame_canvas.config(width=first5columns_width + vsb.winfo_width(),
height=first5rows_height)
# Set the canvas scrolling region
canvas.config(scrollregion=canvas.bbox("all"))
# Launch the GUI
root.mainloop()
Related Topics
Python Subprocess/Popen with a Modified Environment
Handling Very Large Numbers in Python
How to Switch to New Window in Selenium for Python
How to Check for Palindrome Using Python Logic
Scope of Python Variable in for Loop
Python Module for Converting PDF to Text
How to Execute a Program from Python? Os.System Fails Due to Spaces in Path
"For Line In..." Results in Unicodedecodeerror: 'Utf-8' Codec Can't Decode Byte
Cannot Find Module Cv2 When Using Opencv
From List of Integers, Get Number Closest to a Given Value
Drf: Simple Foreign Key Assignment with Nested Serializers
Add a Prefix to All Flask Routes
Output to the Same Line Overwriting Previous Output
Can't Set Attributes on Instance of "Object" Class
How to Get Multiline Input from the User