0

I'm trying to build a function which opens a file browser and lists the selected files in a scrollable frame. I tried using a scrollable frame class I found on the web which works when I apply it to a very simple case, but does not work when applied inside my file browsing function. The scrollable frame class is written as follows:

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.grid(row=0, column=0)
        scrollbar.grid(row = 0, column=1)

and then is applied by:

import os
from tkinter import filedialog,Label,Tk,Entry,Frame,ttk
import tkinter as tk

root = Tk()
browse_frame = Frame(root)
browse_frame.grid(row=0, column=0, columnspan=2)

all_files = []

def browse_files():
    #function which opens a file browser and adds selected files to all_files list.
    
    #opens file browser 
    filename_list = list(filedialog.askopenfilenames(initialdir = '/',
                                                     title = "Select Files",
                                                     filetypes = (("text files","*.txt"),
                                                                  ("csv files","*.csv"),
                                                                  ("all files","*.*"))))
    
    #adds selected files to all_files list
    for i in filename_list:
        all_files.append(i)
        
    if len(all_files) >=1:
        #build scrollable frame for file names
        files = ScrollableFrame(root)
        files.grid(column=0, row=3, padx=10, pady=10)
        
        file_label = Label(files, text="Selected Files:", font=("arial", 13))
        file_label.grid(column=0, row=0, padx=80, pady=10)
        
        u = 0
        while u < len(all_files):  
            #adds each file name to scrollable frame  
            file_name = Label(files, text=str(os.path.basename(all_files[u])))
                
            file_name.grid(column=0, row=u+1, padx=5, pady=5)
                           
            u+=1  
  
    return

browse = ttk.Button(browse_frame, text="browse files", command=browse_files)
browse.grid(column=1, row=0, rowspan=2, padx=50, pady=10)

root.mainloop()

which produces the following result where the file list continues beyond the span of the window and the scrollbar is nonfunctional and small.

image of tkinter window with scrollable frame

Any help is greatly appreciated.

Madmem
  • 7
  • 2
  • Use `canvas.grid(row=0, column=0, sticky="news")` and `scrollbar.grid(row=0, column=1, sticky="ns")`. Also you need to put all of your widgets that you want to scroll inside the `self.scrollable_frame` so instead of `Label(files, ...)` use `Label(files.scrollable_frame, ...)` – TheLizzard Jul 02 '21 at 19:14
  • Why not use a listbox? That would be considerably easier – Bryan Oakley Jul 03 '21 at 03:36
  • @TheLizzard, thank you this seems to have resolved the issue! – Madmem Jul 05 '21 at 13:40
  • @BryanOakley what's the difference between a listbox and a frame in tkinnter? – Madmem Jul 05 '21 at 13:41
  • @Madmem a frame is an empty container for other widgets. A listbox is a scrollable widget specifically for displaying a list of strings. – Bryan Oakley Jul 05 '21 at 15:41

1 Answers1

0

Your class is flawed in 1 big way. When you use:

import tkinter as tk


class ScrollableFrame(tk.Frame):
    def __init__(self, container, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        # Note the `bg="red"`. It's just to show where the canvas is.
        # Remove it later
        canvas = tk.Canvas(self, bg="red")
        scrollbar = tk.Scrollbar(self, orient="vertical", command=canvas.yview)
        self.scrollable_frame = tk.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.grid(row=0, column=0, sticky="news")
        scrollbar.grid(row=0, column=1, sticky="ns")


root = tk.Tk()

frame = ScrollableFrame(root)
frame.pack()

label = tk.Label(frame, text="Test")
label.grid()

root.mainloop()

Notice how the label is outside of the canvas. This is because you are actually putting it in the frame that contains the canvas not the frame that is inside the canvas. To fix it just use tk.Label(frame.scrollable_frame, text="Test"). That will force the label to go inside of the frame that is inside the canvas not the frame that wraps everything.

If you want an improved class, look at this answer that I wrote a while back.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31
  • Thank you this helps tremendously! I have one follow up question, how can I change the size of the canvas/scrollable region? I have another very similar code with entries to the right of each file, but when I try applying this the frame cuts off some part of the entries. – Madmem Jul 06 '21 at 15:25
  • @Madmem For that you will need to save the canvas in `self.canvas` so that you can call `self.canvas.config(width=, height=)`. All `tkinter` widgets have the `.config` method so you can use it to change any of their parameters. You might also need to call `self.canvas.configure(scrollregion=self.canvas.bbox("all"))` – TheLizzard Jul 06 '21 at 15:33