pythontkintertkinter-canvastkinter-button

Tkinter - Struggling to Add a Frame Which is Scrollable


I'm trying to create a system that allows you to add checks into a profile using a gui. I want the checks to be in their own frame that has a scroll wheel because I don't know how many checks a profile will need, but don't want to expand the size of the window. I've been trying to follow an older post about creating a scrollbar just for a grid but can't quite seem to implement their solutions into my own code.

I've also run into a problem where in one configuration, the button to add a check will disappear and the frame containing the checks will appear, or vice versa. I'm unable to have the two displaying at the same time.

Here is the code I've written:

from tkinter import *


gCheckList = []
tCheckList = []

def addCheck(type, homeFrame):
    '''adds a new row to the profile editor which allows the user to input a check'''
    if type == 'g':
        checkLength = 14
        listLength = len(gCheckList)
    elif type == 't':
        checkLength = 9
        listLength = len(tCheckList)
    else:
        checkLength = 0
    
    checkFrame = Frame(homeFrame)
    checkFrame.grid(row=2 + listLength,column=0,padx=10,pady=5)

    checkName = Text(checkFrame, width=10, height=1, font=('Courier',12))
    checkName.grid(row=0, column=0,padx=10,pady=5)

    if type == 'g':
        gCheckList.append(checkFrame)
    elif type == 't':
        tCheckList.append(checkFrame)
    else:
        return
    
    homeFrame.update_idletasks()

    return

#creates root
root = Tk()
root.title = 'Profile Editor'
root.geometry('750x300+250+225')

#creates profile name frame
profileNameFrame = Frame(root)
profileNameFrame.grid(row=0,column=0,padx=10,pady=5)

#Creates a spot to name the profile
nameLabel = Label(profileNameFrame, text='Profile Name: ', font=('Courier',12))
nameLabel.grid(row=0,column=0,padx=10,pady=5)
nameText = Text(profileNameFrame, width=30, height=1, font=('Courier',12))
nameText.grid(row=0, column=1,padx=10,pady=5)

#Frame for title
titleFrame = Frame(root)
titleFrame.grid(row=1,column=0,padx=10,pady=5)
gCheckProfileLabel = Label(titleFrame, text='header', font=('Courier',14))
gCheckProfileLabel.grid(row=0,column=0,padx=10,pady=5)

#Frame for headers
headerFrame = Frame(titleFrame)
headerFrame.grid(row=1,column=0,padx=10,pady=5)
headerLabel = Label(headerFrame, text='Check Name  Indicator  '+''.join(['1  ', '2  ','3  ','4  ','5  ','6  ','7  ','8  ','9  ','10 ','11 ','12 ','13 ','14 ']), font=('Courier',12))
headerLabel.grid(row=0,column=0,padx=10,pady=5)

#frame for canvas
checkFrame = Frame(titleFrame)
checkFrame.grid(row=2,column=0,padx=5,pady=5, sticky='nw')
checkFrame.grid_rowconfigure(0,weight=1)
checkFrame.grid_columnconfigure(0,weight=1)
checkFrame.grid_propagate(False) #don't know if this is needed

#canvas for scrollbar
scrollCanvas = Canvas(checkFrame, bg='yellow')
scrollCanvas.grid(row=0,column=0,sticky='news')

#link scrollbar to canvas
gScrollbar = Scrollbar(checkFrame, orient='vertical',command=scrollCanvas.yview)
gScrollbar.grid(row=0,column=1,sticky='ns')
scrollCanvas.configure(yscrollcommand=gScrollbar.set)

#Create frame to contain checks
gChecksFrame = Frame(scrollCanvas, bg='blue')
scrollCanvas.create_window((0,0),window=gChecksFrame, anchor='nw')
gChecksFrame.grid(row=1,column=1,padx=10,pady=5)

#Creates a new check row to be added into the program
addGCheckButton = Button(titleFrame, text='+', font=('Courier',14), command=lambda: addCheck('g', gChecksFrame))
addGCheckButton.grid(row=0,column=2,padx=1,pady=5)

root.mainloop()

Solution

  • There are following issues in your code:

    Below are the required changes:

    ...
    # don't call .grid_propagate(False)
    #checkFrame.grid_propagate(False)
    ...
    # don't call gChecksFrame.grid(...)
    #gChecksFrame.grid(row=1,column=1,padx=10,pady=5)
    
    # update canvas scrollregion whenever gChecksFrame is resized
    gChecksFrame.bind("<Configure>", lambda e: scrollCanvas.config(scrollregion=scrollCanvas.bbox("all")))
    ...
    

    Suggest to remove root.geometry(...) as well because the window is not tall enough to show all the widgets.