'Tkinter Scroll Window Does Not Scroll

Probably an easy question for a Tkinter veteran but why can't I scroll in my window? If you run this code and add multiple data ports with the button, you will see what I mean. Can anyone modify my code to make the scroll window work please? I am not very well versed in Tkinter yet as I am with other similar tools in other languages so I'm sure there's going to be a lot to edit here.

from tkinter import *

fields = 'Buffer Depth \n(# of max-sized packets)', 'Payload Size \n(Max=1500bytes) ', 'Duplicate Quantity'
entries =[]

root = Tk()
root.title(string= 'Buffer Testing Tool')#create window title
root.geometry('500x750')#main window dimensions
root.configure(bg= 'crimson')

canvas = Canvas(root, height = 1000)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
frame = Frame()

options = ["Transmit", "Receive"]

def makeform(root, fields):#this will organize and align the labels of the entries plus the entry fields
    numberCheck =0
    
    for field in fields: 
        row = Frame(canvas)
        if (numberCheck==0):
            firstES = Label(row, text= "Data Port 1", anchor = 'center', fg = "blue")#first ES label  
            firstES.pack() 
            DPtype = Label(row, text = 'Type', width=19, anchor = 'nw')
            DPtype.pack(side= LEFT)
            clicked= StringVar()
            clicked.set(options[0])
            drop = OptionMenu(row, clicked, *options) 
            drop.pack(side = LEFT, padx=5, pady= 5)
            numberCheck=1
            row.pack(side=TOP, fill=X, padx=5, pady= 5)
            entries.append((field, clicked))
            rowNew= Frame(canvas)
            lab = Label(rowNew, width=20, text=field, anchor = 'w')
            lab.pack(side=LEFT)
            ent = Entry(rowNew)
            ent.pack(side=RIGHT, expand = YES, fill=X)
            rowNew.pack(side=TOP, fill=X, padx=5, pady= 5)
            entries.append((field, ent))
        else:
            lab = Label(row, width=20, text=field, anchor = 'w')
            ent = Entry(row)
            row.pack(side=TOP, fill=X, padx=5, pady= 5)
            lab.pack(side=LEFT)
            ent.pack(side=RIGHT, expand = YES, fill=X)
            entries.append((field, ent))
    #return entries
    
def addEntryField():
        next_row = int(len(entries)/4)+1
        numberCheck =0
        for field in fields:
            row = Frame(canvas)
            if (numberCheck==0):
                nextES = Label(row, text= ('Data Port ' +str(next_row)), anchor = 'center', fg = "blue")
                nextES.pack()
                DPtype = Label(row, text = 'Type', width=19, anchor = 'nw')
                DPtype.pack(side= LEFT)
                clicked= StringVar()
                clicked.set(options[0])
                drop = OptionMenu(row, clicked, *options) 
                drop.pack(side = LEFT, padx=5, pady= 5)
                numberCheck=1
                row.pack(side=TOP, fill=X, padx=5, pady= 5)
                entries.append((field, clicked))
                rowNew= Frame(canvas)
                lab = Label(rowNew, width=20, text=field, anchor = 'w')
                lab.pack(side=LEFT)
                ent = Entry(rowNew)
                ent.pack(side=RIGHT, expand = YES, fill=X)
                rowNew.pack(side=TOP, fill=X, padx=5, pady= 5)
                entries.append((field, ent))
            else:
                lab = Label(row, width=20, text=field, anchor = 'w')
                ent = Entry(row)
                row.pack(side=TOP, fill=X, padx=5, pady= 5)
                lab.pack(side=LEFT)
                ent.pack(side=RIGHT, expand = YES, fill=X)
                entries.append((field, ent))
        #return entries

if __name__=='__main__':
    b1 = Button(root, text = 'Generate XML File', command=root.quit, anchor = 'center')
    b1.pack(side= BOTTOM, padx=5, pady=5)
    b2 = Button(root, text = 'Run TTE-Build', command=root.quit, anchor = 'center')
    b2.pack(side= BOTTOM, padx=5, pady=5)
    b3 = Button(root, text = 'Run TTE-Plan', command=root.quit, anchor = 'center')
    b3.pack(side= BOTTOM, padx=5, pady=5)

    addEntryButton = Button(root, text= 'Add Data Port', fg = "Blue", command = addEntryField)
    addEntryButton.pack(side= BOTTOM, anchor = 'n', padx=5, pady= 5)
    
    ents = makeform(root, fields)#the creation of the window happens here
    root.bind('<Return>', (lambda event, e=ents: fetch(e)))#after hitting 'Enter' this will collect the entered data from the user
    
    canvas.create_window(0, 0, anchor= 'nw', window=frame)
    canvas.update_idletasks()

    canvas.configure(scrollregion=canvas.bbox("all"), yscrollcommand=scroll_y.set)

    canvas.pack(fill='both', expand=True, side='left')
    scroll_y.pack(fill='y', side='right')
    root.mainloop()

def fetch(entries):#this will print what what the entry fields are named plus what is written down in the entry fields (regardless of the # of fields)
    for entry in entries:
        field = entry[0]
        text = entry[1].get()#collect the entry for the fields
        print('%s: "%s"' % (field, text))

def Message1():
    XMLdone = Label(root, text= "XML File Generated!!")
    XMLdone.pack()

def Message2():
    XMLdone = Label(root, text= "TTE-Plan has been completed!!")
    XMLdone.pack()

def Message3():
    XMLdone = Label(root, text= "TTE-Build has been completed!!")
    XMLdone.pack()

#Note: The Payload Size must always be greater than or equal to 18 bytes less than the corresponding VL.


Solution 1:[1]

There are several problems with the code. The first is that the frame you're adding to the canvas isn't a child of the canvas, but it needs to be. You should also give this window a more descriptive name than just frame.

So, create the frame like this:

canvas = Canvas(root, height = 1000)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
inner_frame = Frame(canvas)
canvas.create_window(0, 0, anchor= 'nw', window=inner_frame)

The second problem is that you're not updating the scrollregion of the canvas with the inner frame changes. This is typically done by adding a binding to the <Configure> event of the inner frame:

inner_frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))

The third problem is that you're adding rows to the canvas rather than to the frame. You need to make your rows children of this inner frame.

    for field in fields:
        row = Frame(inner_frame)
        ...
            rowNew= Frame(inner_frame)
        ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Bryan Oakley