1

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:

  1. create a dynamic grid and then make it scrollable.
  2. create a scrollable grid and then make it dynamic.

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.

7
  • Please don't ask a question and then post someone else's code that isn't doing what your code is doing. Take the time to create a proper minimal reproducible example for your question. Also, please post code that is syntactically correct. Your first block of code has indentation errors. Commented Jan 7, 2021 at 13:37
  • Do you literally want to create a grid with equal width columns, or is it good enough to just wrap the contents of a row when they don't fit? Commented Jan 7, 2021 at 13:48
  • ident fixed.. sorry.. yes my grid will have equal width columns. in the production code it will be a property from conf classs Commented Jan 7, 2021 at 15:02
  • it doesnt work, this way the scroll does not match the rows... the inverte way of you said around the canvas becomes "a tile" Commented Jan 7, 2021 at 15:26
  • 1
    I don't know what you mean by "the scroll does not match the rows". If you have a frame that behaves the way you want, you can simply add that frame to a canvas so that you can use the scrolling ability of the canvas. Commented Jan 7, 2021 at 16:28

1 Answer 1

1

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()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.