When I say a dynamic grid I'm saying a grid that adjusts the number of columns(like bootstrap) according to the window width, so it must adjust the "cards" accordingly, and when I say scrollable... well... easier to understand.
I've tried 2 aproachs:
I've failed in both ways! I've found out that for being scrollable the grid can't be in a simple frame, it must be in a Canvas. And for the canvas I'm having a hard time making it dynamic.
Down here is my dynamic grid code
class DynaGrid(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.columns = None
self.bind('<Configure>', self.re_grid)
def re_grid(self, event=None):
grid_width = self.winfo_width()
slaves = self.grid_slaves()
slaves_width = slaves[1].winfo_width()
cols = grid_width // slaves_width
if (cols == self.columns) | (cols == 0):
return
for i, slave in enumerate(reversed(slaves)):
slave.grid_forget()
slave.grid(row=i // cols, column=i % cols)
self.columns = cols
class CardFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, bd=1, relief=tk.RAISED, **kwargs)
tk.Label(self, text="Hello").pack()
def main():
root = tk.Tk()
frame = DynaGrid(root)
frame.pack(fill=tk.BOTH, expand=True)
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
CardFrame(frame).grid()
root.mainloop()
if __name__ == '__main__':
main()
I wont waste other people's time posting my messy canvas code here instead I've got one from https://blog.tecladocode.com/tkinter-scrollable-frames/ which I've made the change to use my "cards" instead of labels.
import tkinter as tk
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
class TestFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, bd=5, relief=tk.RAISED, **kwargs)
tk.Label(self, text="name").pack(pady=10)
root = tk.Tk()
frame = ScrollableFrame(root)
for i in range(50):
TestFrame(frame.scrollable_frame).grid()
frame.pack()
root.mainloop()
To make the dynamic scrollable canvas the tricky part here is use the re_grid function inside the canvas. I'm lost in how I'll get the window width correctly like I did in the dynaGrid code.
In the end I want a mash up of these two codes; a Class that is some sort of frame with dynamic grid with lateral scroll.
Your canvas is a frame inside your class, so, what you need to do is bind your re-grid function to your master frame and keep the scrollable frame config callback as it is. Then alter the grid, inside your canvas, instead of your 'dynamic grid' from your re-grid function . Simple as that! And I think it was easier to say: I want to create a window that behaves like a File Explorer
import tkinter as tk
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
def _init_(self, container, *args, **kwargs):
super()._init_(container, *args, **kwargs)
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
self.columns=0
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
self.bind('<Configure>', self.regrid)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
def regrid(self, event=None):
print(type(self))
grid_width = self.winfo_width()
slaves = self.scrollable_frame.grid_slaves()
print(len(slaves))
slaves_width = slaves[1].winfo_width()
cols = grid_width // slaves_width
if (cols == self.columns) | (cols == 0): # if the column number has not changed, abort
return
for i, slave in enumerate(reversed(slaves)):
slave.grid_forget()
slave.grid(row=i // cols, column=i % cols)
self.columns = cols
class TestFrame(tk.Frame):
def _init_(self, master=None, **kwargs):
tk.Frame._init_(self, master, bd=5, relief=tk.RAISED, **kwargs)
tk.Label(self, text="name").pack(pady=10)
tk.Label(self, text=" info ........ info ").pack(pady=10)
root = tk.Tk()
frame = ScrollableFrame(root)
for i in range(10):
TestFrame(frame.scrollable_frame).grid()
TestFrame(frame.scrollable_frame).grid()
frame.pack(side="left", fill=tk.BOTH, expand=True)
root.mainloop()