可滚动框架在Python

时间:2015-08-18 05:01:27

标签: python tkinter scrollbar tkinter-canvas

基于Dynamically changing scrollregion of a canvas in Tkinter的示例,我试图实现一个Frame,您可以使用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()

1 个答案:

答案 0 :(得分:2)

我正在做类似的事情,所以我把我的代码与你的代码合并以获得答案。

这是一个scrollingFrame类,它将添加滚动条并在调整框大小时删除它们。然后,您的消息列表会有第二个类,它会告诉scrollingFrame在添加/删除项目时根据需要重新调整。

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