可滚动框架无法使用 tkinter 正确调整大小

Scrollable Frame does not resize properly using tkinter

基于 Dynamically changing scrollregion of a canvas in Tkinter 中的示例,我正在尝试实现一个框架,您可以在其中使用 tkinter 在可滚动框架中添加和删除条目。我的问题是 Frame 持有项目在删除条目后不会调整大小。添加条目时,它会正确调整大小。我在这两种情况下都调用 update_layout()

from tkinter import *

class ScrollableContainer(Frame):
    """A scrollable container that can contain a number of messages"""

    def __init__(self, master, **kwargs):
        Frame.__init__(self, master, **kwargs) #holds canvas & scrollbars
        # configure row and col to take additional space if there is some
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        # create canvas
        self.canv = Canvas(self, bd=0, highlightthickness=0)

        # create scrollbars
        self.hScroll = Scrollbar(self, orient='horizontal',
                                 command=self.canv.xview)
        self.hScroll.grid(row=1, column=0, sticky='we')
        self.vScroll = Scrollbar(self, orient='vertical',
                                 command=self.canv.yview)
        self.vScroll.grid(row=0, column=1, sticky='ns')

        # set postiotion of canvas in (self-)Frame
        self.canv.grid(row=0, column=0, sticky='nsew')
        self.canv.configure(xscrollcommand=self.hScroll.set,
                            yscrollcommand=self.vScroll.set)

        # create frame to hold messages in canvas
        self.frm = Frame(self.canv, bd=2, bg='gray') #holds messages
        self.frm.grid_columnconfigure(0, weight=1)

        # create empty tkinter widget (self.frm) on the canvas
        self.canv.create_window(0, 0, window=self.frm, anchor='nw', tags='inner')

        # update layout
        self.update_layout()

        # on change of size or location this event is fired. The event provides new width an height to callback function on_configure
        self.canv.bind('<Configure>', self.on_configure)

        self.widget_list = []

    # update and resize layout
    def update_layout(self):
        print('update')
        self.frm.update_idletasks()
        self.canv.configure(scrollregion=self.canv.bbox('all'))
        self.size = self.frm.grid_size()

    # resize canvas and scroll region depending on content
    def on_configure(self, event):
        print('on_configure')
        # get new size of canvas
        w,h = event.width, event.height
        # get size of frm required to display all content
        natural = self.frm.winfo_reqwidth()
        self.canv.itemconfigure('inner', width= w if w>natural else natural)
        self.canv.configure(scrollregion=self.canv.bbox('all'))

    # add new entry and update layout
    def add_message(self, text):
        print('add message')
        # create var to represent states
        int_var = IntVar()

        cb = Checkbutton(self.frm, text=text, variable=int_var)
        cb.grid(row=self.size[1], column=0, padx=1, pady=1, sticky='we')
        self.widget_list.append(cb)

        self.update_layout()

    # delete all messages
    def del_message(self):
        print('del message')
        for it in self.widget_list:
            it.destroy()
        self.update_layout()


root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2, bg='black')
sc.grid(row=0, column=0, sticky='nsew')


def new_message():
    test = 'Something Profane'
    sc.add_message(test)


def del_message():
    sc.del_message()

b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')

del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')

root.mainloop()

我正在做类似的事情,所以我把我的代码和你的代码合并起来作为答案。

这是一个 scrollingFrame class,它将添加滚动条并在调整框大小时删除滚动条。然后还有第二个 class 用于您的消息列表,它将告诉滚动框架在项目为 added/deleted.

时根据需要重新调整自身
class scrollingFrame(Frame):
    def __init__(self, parentObject, background):
        Frame.__init__(self, parentObject, background = background)
        self.canvas = Canvas(self, borderwidth=0, background = background, highlightthickness=0)
        self.frame = Frame(self.canvas, background = background)

        self.vsb = Scrollbar(self, orient="vertical", command=self.canvas.yview, background=background)
        self.canvas.configure(yscrollcommand=self.vsb.set)
        self.vsb.grid(row=0, column=1, sticky=N+S)

        self.hsb = Scrollbar(self, orient="horizontal", command=self.canvas.xview, background=background)
        self.canvas.configure(xscrollcommand=self.hsb.set)
        self.hsb.grid(row=1, column=0, sticky=E+W)

        self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
        self.window = self.canvas.create_window(0,0, window=self.frame, anchor="nw", tags="self.frame")

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.frame.bind("<Configure>", self.onFrameConfigure)
        self.canvas.bind("<Configure>", self.onCanvasConfigure)


    def onFrameConfigure(self, event):
        #Reset the scroll region to encompass the inner frame
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def onCanvasConfigure(self, event):
        #Resize the inner frame to match the canvas
        minWidth = self.frame.winfo_reqwidth()
        minHeight = self.frame.winfo_reqheight()

        if self.winfo_width() >= minWidth:
            newWidth = self.winfo_width()
            #Hide the scrollbar when not needed
            self.hsb.grid_remove()
        else:
            newWidth = minWidth
            #Show the scrollbar when needed
            self.hsb.grid()

        if self.winfo_height() >= minHeight:
            newHeight = self.winfo_height()
            #Hide the scrollbar when not needed
            self.vsb.grid_remove()
        else:
            newHeight = minHeight
            #Show the scrollbar when needed
            self.vsb.grid()

        self.canvas.itemconfig(self.window, width=newWidth, height=newHeight)

class messageList(object):
    def __init__(self, scrollFrame, innerFrame):
        self.widget_list = []
        self.innerFrame = innerFrame
        self.scrollFrame = scrollFrame

        # Keep a dummy empty row if the list is empty
        self.placeholder = Label(self.innerFrame, text=" ")
        self.placeholder.grid(row=0, column=0)

    # add new entry and update layout
    def add_message(self, text):
        print('add message')
        self.placeholder.grid_remove()
        # create var to represent states
        int_var = IntVar()

        cb = Checkbutton(self.innerFrame, text=text, variable=int_var)
        cb.grid(row=self.innerFrame.grid_size()[1], column=0, padx=1, pady=1, sticky='we')
        self.widget_list.append(cb)

        self.innerFrame.update_idletasks()
        self.scrollFrame.onCanvasConfigure(None)

    # delete all messages
    def del_message(self):
        print('del message')
        for it in self.widget_list:
            it.destroy()

        self.placeholder.grid()
        self.innerFrame.update_idletasks()
        self.scrollFrame.onCanvasConfigure(None)

deviceBkgColor = "#FFFFFF"
root = Tk() # Makes the window
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.wm_title("Title") # Makes the title that will appear in the top left
root.config(background = deviceBkgColor)

myFrame = scrollingFrame(root, background = deviceBkgColor)
myFrame.grid(row=0, column=0, sticky=N+S+E+W)

msgList = messageList(myFrame, myFrame.frame)

def new_message():
    test = 'Something Profane'
    msgList.add_message(test)


def del_message():
    msgList.del_message()

b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')

del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')

root.mainloop() #start monitoring and updating the GUI