This question already has answers here:
Adding a scrollbar to a group of widgets in Tkinter
(3 answers)
Closed 4 months ago.
I have a bunch of checkboxes instantiated in Tkinter, and I would like to scroll through the window because all of them cannot be fit in one frame. I see the scrollbar, but there is no effect on using it, and the window stays stationary. How can I fix it? This is my code -
root = Tk()
scroll = Scrollbar(root)
scroll.pack(fill=Y,side=RIGHT)
l = []
for checkBoxName in all_files:
var = IntVar()
c = Checkbutton(root, text=checkBoxName, variable=var, onvalue=1, offvalue=0)
c.pack()
l.append(var)
root.mainloop()
After some searches from the internet, I found what you need.
Note: Everything below is not mine, all the source I will put below.
So, this is what you need:
# Create A Main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand=1)
# Create Frame for X Scrollbar
sec = Frame(main_frame)
sec.pack(fill=X, side=BOTTOM)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
# Add A Scrollbars to Canvas
scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
# Configure the canvas
my_canvas.configure(yscrollcommand=scrollbar.set)
my_canvas.bind("<Configure>", lambda e: my_canvas.config(scrollregion=my_canvas.bbox(ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0, 0), window=second_frame, anchor="nw")
and the full code is:
from tkinter import *
from tkinter import ttk
all_files = [str(i) for i in range(100)] # your list
root = Tk()
# Create A Main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand=1)
# Create Frame for X Scrollbar
sec = Frame(main_frame)
sec.pack(fill=X, side=BOTTOM)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
# Add A Scrollbars to Canvas
scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
# Configure the canvas
my_canvas.configure(yscrollcommand=scrollbar.set)
my_canvas.bind("<Configure>", lambda e: my_canvas.config(scrollregion=my_canvas.bbox(ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0, 0), window=second_frame, anchor="nw")
#==========
l = []
for checkBoxName in all_files:
var = IntVar()
c = Checkbutton(second_frame, text=checkBoxName, variable=var, onvalue=1, offvalue=0)
c.pack()
l.append(var)
#==========
root.mainloop()
There is also an another shorter way:
from tkinter import *
from tkinter.tix import *
all_files = [str(i) for i in range(100)] # your list
root = Tk()
frame = Frame(width="500",height="500")
frame.pack()
swin = ScrolledWindow(frame, width=500, height=500)
swin.pack()
win = swin.window
#==========
l = []
for checkBoxName in all_files:
var = IntVar()
c = Checkbutton(win, text=checkBoxName, variable=var, onvalue=1, offvalue=0)
c.pack()
l.append(var)
#==========
root.mainloop()
To be honest, I'm not too good at tkinter, and I just have a little experience about it, so I don't really understand how they could make this code.
Sources:
First Way
Video Tutorial (1st way)
Another Way
Related
My app displays data from a sqlite database. After displaying the initial data, say 50 records when my program destroys the widgets associated with that initial display and repopulates based on a search function the frame container no longer adapts to the size of the new widgets placed in it.
To make this simpler I've created smaller simpler version of the problem. My initial version of this post was from that actual app and difficult understand. Lesson learned. Running this code defaults to showin 50 rows initially, Then try entering 100 in the search field and click the button and see that the window does not expand to fit. Then can then try entering 40 and see that the window doesn't shrink. I'm using Python v3.11.
Here's the simplified code that captures the essence of my problem:
from tkinter import *
from tkinter import ttk
def displayData():
for widgets in second_frame.winfo_children():
widgets.destroy()
Count = Search.get()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,100), window=second_frame, anchor='nw')
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayData()
root.mainloop()
```
FYI: my actual app is displaying database records based on search parameters. The programs works fine except for the fact once the initial size of 'second_frame' is set up it never changes. So if a search happens to display more records than that initial display, those records will be hidden. e.g. Initial display shows 50 records, if a search asks to display 75 records, 25 of them will not be visible...So the second_frame doesn't resize to show the added widgets in the search.
My workaround for now is just to initially display more records then I anticipate most searches will need to display.
How can I make 'second_frame' adapt to new amounts of widgets on new searches? The simplified code above emulates my issue.
Yahoo, found a solution to this issue. Essentially one must first destroy and recreate the canvas used to display the records. Some complications due to the scrollbar widget being linked to the frame containing the canvas. I solved this by creating an intermediate frame to contain the frame referenced by the scrollbar and the used the methods winfo_childred() and destroy() to delete the frames/canvas/widgets before recreating them anew to display the new data.
Here's the modified example code that now works correctly:
from tkinter import *
from tkinter import ttk
def makeCanvas():
# Create a Canvas
global my_canvas
global second_frame
global my_scrollbar
global main_frame
mid_frame = Frame(main_frame)
mid_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(mid_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(mid_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,0), window=second_frame, anchor='nw')
def displayData():
global displayCnt
print("displayData entered with displayCnt of " + str(displayCnt))
if displayCnt != 0:
for widgets in main_frame.winfo_children():
widgets.destroy()
Count = Search.get()
makeCanvas()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
displayCnt += 1
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
#makeCanvas()
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayCnt = 0
displayData()
root.mainloop()
I ended up creating a separate function makeCanvas() to recreate the frame/canvas structure upon each new display of data. For this much simplified example the data consists simply of numbered Label widgets.
Hi guys i'm having trouble with a scrollbar, in the photo you can see the grey box that is a simple listbox, how to put the scrollbar in that red specific position, and not in the right or bottom end of the screen just like te normal scollbar? Thanks!
adding code:
from tkinter import *
from ctypes import windll
def inserisci_spesa():
global lista_spese
global testo_nuova_spesa
if testo_nuova_spesa.get() != "":
lista_spese.insert(0,testo_nuova_spesa.get())
def invio_aggiungi_spesa(e):
window.bind("<Return>",illumina_aggiungi_spesa)
def illumina_aggiungi_spesa(e):
bottone_inserisci_spesa.config(bg="#052b4d")
window.after(200,illumina_aggiungi_spesa2)
def illumina_aggiungi_spesa2():
bottone_inserisci_spesa.config(bg="#1e476b")
window.after(0,inserisci_spesa)
def invio_descrivi_spesa(e):
window.bind("<Return>",illumina_descrivi_spesa)
def illumina_descrivi_spesa(e):
global bottone_inserisci_descrizione
bottone_inserisci_descrizione.config(bg="#052b4d")
window.after(200,illumina_descrivi_spesa2)
def illumina_descrivi_spesa2():
global bottone_inserisci_descrizione
bottone_inserisci_descrizione.config(bg="#1e476b")
windll.shcore.SetProcessDpiAwareness(1)
window = Tk()
frame = Frame (window)
frame.pack(padx=150, pady=150)
window.geometry("1500x770")
window.title ("Gestione spese")
window.call('wm', 'iconphoto', window._w, PhotoImage(file="trasparente.png"))
sfondo = PhotoImage(file="soldi.png")
etichetta_sfondo = Label(window,image=sfondo)
etichetta_sfondo.place(x=0,y=0)
testo_nuova_spesa = Entry(window,borderwidth=5,font=("Ink Free",20),width=9,bg="#f2f2f2")
testo_nuova_spesa.place(x=36,y=80)
testo_nuova_spesa.bind("<FocusIn>",invio_aggiungi_spesa)
descrizione_testo_nuova_spesa = Label(window,text="Nuova spesa",bg="#64d981",font=("Ink Free",19),relief="solid",borderwidth=1)
descrizione_testo_nuova_spesa.place(x=40,y=28)
testo_descrivi_spesa = Entry(window,borderwidth=5,font=("Ink Free",20),width=22,bg="#f2f2f2")
testo_descrivi_spesa.place(x=300,y=80)
testo_descrivi_spesa.bind("<FocusIn>",invio_descrivi_spesa)
descrizione_testo_descrivi_spesa = Label(window,text="Descrizione",bg="#64d981",font=("Ink Free",19),relief="solid",borderwidth=1)
descrizione_testo_descrivi_spesa.place(x=304.5,y=28)
bottone_inserisci_spesa = Button(window,text="Inserisci",font=("Ink Free",15),bg="#1e476b",fg="white",activebackground="#052b4d",activeforeground="white",command=inserisci_spesa)
bottone_inserisci_spesa.place(x=36,y=140)
bottone_inserisci_descrizione = Button(window,text="Inserisci",font=("Ink Free",15),bg="#1e476b",fg="white",activebackground="#052b4d",activeforeground="white")
bottone_inserisci_descrizione.place(x=300,y=140)
lista_spese = Listbox(frame)
lista_spese.pack(side=LEFT)
lista_spese.configure(font=('Courier 20 '), width=21, height=9, bg="#4a4a4a", fg="#dedede",relief="solid",borderwidth=4)
etichetta_lista_spese = Label(window,text="Lista delle spese",bg="#64d981",font=("Ink Free",19),relief="solid",borderwidth=1)
etichetta_lista_spese.place(x=720,y=270)
scrollbar = Scrollbar(frame,command=lista_spese.yview)
scrollbar.pack(side=RIGHT,fill=Y)
lista_spese.config(yscrollcommand=scrollbar.set)
window.mainloop()
Simply put both (Listbox and Scrollbar) in the same Frame to group them.
Minimal working code
import tkinter as tk
root = tk.Tk()
root['bg'] = '#ff8080'
# - create -
frame = tk.Frame(root)
frame.pack(padx=150, pady=150)
listbox = tk.Listbox(frame)
listbox.pack(side='left', fill='both', expand=True)
scrollbar = tk.Scrollbar(frame, orient='vertical', command=listbox.yview)
scrollbar.pack(side='right', fill='y')
#scrollbar.pack(side='left', fill='y') # `left` also works
listbox.config(yscrollcommand=scrollbar.set)
# - add some values to listbox for scrolling -
for i in range(50):
listbox.insert('end', str(i))
# - start -
root.mainloop()
Result:
EDIT:
You may also use Frame to create own widget ScrolledListbox and then you can reuse it many times.
import tkinter as tk
class ScrolledListbox(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listbox = tk.Listbox(self)
self.listbox.pack(side='left', fill='both', expand=True)
self.scrollbar = tk.Scrollbar(self, orient='vertical', command=self.listbox.yview)
self.scrollbar.pack(side='right', fill='y')
#self.scrollbar.pack(side='left', fill='y') # `left` also works
self.listbox.config(yscrollcommand=self.scrollbar.set)
# - main -
root = tk.Tk()
root['bg'] = '#ff8080'
# - create -
lb1 = ScrolledListbox(root)
lb1.pack(side='left', fill='both', expand=True, padx=25, pady=25)
lb2 = ScrolledListbox(root)
lb2.pack(side='left', fill='both', expand=True, padx=25, pady=25)
lb3 = ScrolledListbox(root)
lb3.pack(side='left', fill='both', expand=True, padx=25, pady=25)
# - add some values to listbox for scrolling -
for i in range(50):
lb1.listbox.insert('end', str(i))
lb2.listbox.insert('end', str(i+100))
lb3.listbox.insert('end', str(i+200))
# - start -
root.mainloop()
I am trying to create a GUI where left hand side is a Listbox (contained inside a frame f2) that displays employee ID's and right side is another frame second_frame (contained inside canvas and outer frame f3) that shows transaction details of each selected employee in the form of labels.
Each employee can have multiple transactions. So, The number of labels had to be dynamic, i.e. for first selected item in listbox, there could be two labels and for second selected item in listbox, it could be hundred. For every selection, I am calling two functions to destroy old labels and create new labels. While the code works fine, I am having trouble resizing the scrollbar according to the selected listbox entry. I am new to Tkinter, Please advise. Below is my code.
Also note, the test() function when called from outside any function displays the scroll bar, but does not display anything when called from within any function.
# -*- coding: utf-8 -*-
from tkinter import *
'''def test():
for i in range(0,50):
for j in range (0,7):
Label(second_frame, text=f'{i}{j}', width=20).grid(row=i, column=j, pady=5,padx=5)
'''
# --- function ---
def destroy_frame():
#f1.grid_forget()
print("destroying frame")
for label in second_frame.winfo_children():
label.destroy()
def create_frame(val):
print("creating new frame")
for i in range(0,val):
for j in range (5):
Label(second_frame, text=f'{i} {j} ', relief=GROOVE, width=10).grid(row=i, column=j, pady=5,padx=5)
def on_selection(event):
# here you can get selected element
print('previous:', listbox.get('active'))
print(' current:', listbox.get(listbox.curselection()))
# or using `event`
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print (listbox.get(listbox.curselection()))
if (listbox.get(listbox.curselection()) == "Eid 1"):
destroy_frame()
create_frame(100)
elif (listbox.get(listbox.curselection()) == "Eid 2"):
destroy_frame()
create_frame(200)
print('---')
root = Tk()
root.geometry('800x500')
#Create base Frames
f1 = Frame(width=800, height=50, bg="yellow", colormap="new")
f1.grid(row=0, columnspan=2)
f1.grid_propagate(False)
f2 = Frame(width=200, height=425, bg="light blue", colormap="new")
f2.grid(row=1, column=0)
f2.grid_propagate(False)
f3 = Frame(width=600, height=425, bg="light green", colormap="new")
f3.grid(row=1, column=1)
f3.grid_propagate(False)
#Create header Label
l1_f1 = Label(f1, text="Employee Purchase Entries:", bg="yellow")
l1_f1.grid(row=0, column=0)
#Create Listbox
listbox = Listbox(f2, bg="light blue", width=40, height=400)
listbox.grid(row=0, column=0)
#Add Scrollbar to ListBox
list_scrollbar = Scrollbar(f2)
list_scrollbar.grid(row=0, column=1, sticky=NSEW)
#Enter Listbox Data
listbox.insert(1, 'Eid 1')
listbox.insert(2, 'Eid 2')
listbox.bind('<<ListboxSelect>>', on_selection)
#configure the Listbox and Scrollbar
listbox.config(yscrollcommand = list_scrollbar.set)
list_scrollbar.config(command = listbox.yview)
#Create a Canvas
my_canvas = Canvas(f3, width=580, height=425, bg="light green")
#my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.grid(row=0, column=0)
#Add a Scrollbar to the canvas
my_scrollbar = Scrollbar(f3, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.grid(row=0, column=1, sticky=NSEW)
#configure the canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
#Create another frame inside the canvas
second_frame = Frame(my_canvas)
#Add the new frame to a window in the canvas
my_canvas.create_window((0,0), window=second_frame, anchor="nw")
#test()
root.mainloop()
Your canvas isnt trigger the configure event when you add widgets to your frame. Instead your frame is been triggerd.
So you need to put this line:
second_frame.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
after creating second_frame
There is only one frame in my GUI, and it resizes itself to the size of the window. The frame has a child label, and I want the label to always be 1/3 the height of the frame and 1/1.5 the width of the frame. The code below tries to do that but the label always resizes itself to the size of the frame.
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10)
def callback(event):
f.config(height=tk.winfo_height(), width=tk.winfo_width())
l.config(width=int(f.winfo_width()/1.5), height=int(f.winfo_height()/3))
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5)
l.pack(side="bottom")
tk.bind("<Configure>", callback)
tk.mainloop()
The width and height of the label are in characters. In order to use pixels, you need to add an empty image to the label:
img = tkinter.PhotoImage() # an image of size 0
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5,
image=img, compound='center')
Actually you don't need to resize the frame in the callback if you add fill="both", expand=1 into f.pack(...):
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10, fill="both", expand=1)
def callback(event):
l.config(width=int(f.winfo_width()/1.5), height=int(f.winfo_height()/3))
#l.config(width=event.width*2//3, height=event.height//3) # same as above line if bind on frame
img = tkinter.PhotoImage()
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5,
image=img, compound='center')
l.pack(side="bottom")
f.bind("<Configure>", callback) # bind on frame instead of root window
tk.mainloop()
Given your precise specifications, the best solution is to use place since it lets you use relative widths and heights. However, if you plan to have other widgets in the window, place is rarely the right choice.
This example will do exactly what you asked: place the label at the bottom with 1/3 the height and 1/1.5 the width. There is no need to have a callback for when the window changes size.
Note: I had to change the call to pack for the frame. The text of your question said it would expand to fill the window but the code you had wasn't doing that. I added the fill and expand options.
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10, fill="both", expand=True)
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5)
l.place(relx=.5, rely=1.0, anchor="s", relheight=1/3., relwidth=1/1.5)
tkinter.mainloop()
My code has a Frame, a Canvas inside the Frame, and an inner Frame inside the Canvas. I want to put Entry boxes inside the inner Frame and fit them to the inner Frame via pack_propagate(0) to avoid pixel/font width conversions with the Entry widget's width option. However, this breaks the inner Frame's scroll functionality. I want to add Entry widgets dynamically above and below the first and last Entry widgets in the inner Frame, which I am currently doing using pack(before=). So I would like to stick with the packer if possible.
How can I get the scrollbar working again? The following minimum working example has frame.pack_propagate(0) commented out, so the Entry widgets are not sized to the column correctly:
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
myframe = ttk.Frame(root)
myframe.pack()
sb = ttk.Scrollbar(myframe)
sb.pack(side=tk.RIGHT, fill=tk.Y, expand=1)
canvas = tk.Canvas(myframe, width=200, height=300,
scrollregion=(0, 0, 200, 300), yscrollcommand=sb.set)
canvas.pack()
frame = ttk.Frame(canvas, width=200, height=300) # inner Frame
canvas.create_window((0, 0), window=frame, anchor='nw')
sb.config(command=canvas.yview)
frame.bind("<Configure>", lambda event: canvas.configure(
scrollregion=canvas.bbox(tk.ALL)))
# frame.pack_propagate(0) # How to enable this and ensure scrollbar works?
s = tk.StringVar()
s.set("I'm a box")
for _ in range(100):
eb = ttk.Entry(frame, textvariable=s)
eb.pack()
root.mainloop()
Edit1: Added StringVar() and changed eb.grid() to eb.pack()
See example which resize inner Frame to Canvas - it uses
self._canvas.bind('<Configure>', self.inner_resize)
and inside method inner_resize()
self._canvas.itemconfig(self._window, width=event.width)
Full example
import tkinter as tk
import tkinter.ttk as ttk
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
# canvas for inner frame
self._canvas = tk.Canvas(self, bg='red')
self._canvas.grid(row=0, column=0, sticky='news') # changed
# create right scrollbar and connect to canvas Y
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
# create bottom scrollbar and connect to canvas X
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
# inner frame for widgets
self.inner = tk.Frame(self._canvas)
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
# autoresize inner frame
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
# resize when configure changed
self.inner.bind('<Configure>', self.resize)
# resize inner frame to canvas size
self.resize_width = False
self.resize_height = False
self._canvas.bind('<Configure>', self.inner_resize)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
def inner_resize(self, event):
# resize inner frame to canvas size
if self.resize_width:
self._canvas.itemconfig(self._window, width=event.width)
if self.resize_height:
self._canvas.itemconfig(self._window, height=event.height)
# --- main ----
root = tk.Tk()
sf = ScrolledFrame(root)
sf.resize_width = True # it will resize frame to canvas
sf.pack(fill='both', expand=True)
s = tk.StringVar()
s.set("I'm a box")
for _ in range(100):
eb = ttk.Entry(sf.inner, textvariable=s)
eb.pack(fill='x', expand=True)
root.mainloop()