Tkinter: прокручиваемый фрейм в Canvas: ошибка привязки автоматического изменения размера

Этот скрипт при запуске открывает окно, которое разделено на 3 кадра:

  • Большой кадр, в который будут отображаться данные (метки виджетов).
  • Меньшая рамка под ней с виджетами пользовательского ввода.
  • Небольшая рамка в правом нижнем углу с виджетом для текстового поля.

В большом фрейме будет много данных (= label-widgets), поэтому мне нужно, чтобы его можно было прокручивать (по вертикали). Это я сделал, создав виджет холста рядом с виджетом полосы прокрутки. На холсте размещен виджет рамки.

Кажется, все работает, но моя функция изменения размера - нет. Мой виджет рамки не обновляется! Это, вероятно, из-за ошибки, которую я не могу исправить.

Основной вопрос:

Скрипт выдает ошибку по команде "lambda: resize_frame (self)" в строке 43. Как мне это исправить?

Примечание: моя проблема, вероятно, больше связана с неправильной привязкой к виджету canvas. Потому что я не уверен, что хотел дать достаточно контекста (сценарий).

Спасибо заранее.

from Tkinter import *
import math

class Processing(Toplevel):
    def __init__(self, master, *args, **kwargs):
        Toplevel.__init__(self, master)
        self.master = master
        self.title("Process Window")
        for r in range(6):
            self.rowconfigure(r, weight = 1)    
        for c in range(4):
            self.columnconfigure(c, weight = 1)

        ### WINDOW size and position definitions ###
        ScreenSizeX = master.winfo_screenwidth()
        ScreenSizeY = ( master.winfo_screenheight() - 75 ) #about 75pixels for taskbar on bottom of screen (Windows)
        ScreenRatio = 0.9
        FrameSizeX  = int(ScreenSizeX * ScreenRatio)
        FrameSizeY  = int(ScreenSizeY * ScreenRatio)
        FramePosX   = (ScreenSizeX - FrameSizeX)/2
        FramePosY   = (ScreenSizeY - FrameSizeY)/2
        self.geometry("%sx%s+%s+%s"%(FrameSizeX,FrameSizeY,FramePosX,FramePosY))

        ### Creating 3 "sub-frames" ###
        # Frame 1 - canvas container with scrollbar#
        self.Canvas1 = Canvas(self, bg = "white")
        self.Canvas1.grid(row = 0, column = 0, rowspan = 5, columnspan = 4, sticky = N+E+S+W)
        self.Canvas1.rowconfigure(1, weight = 1)
        self.Canvas1.columnconfigure(1, weight = 1)
        self.myscrollbar=Scrollbar(self, orient = "vertical", command = self.Canvas1.yview)
        self.Canvas1.configure(yscrollcommand = self.myscrollbar.set)
        self.myscrollbar.grid(row = 0, column = 4, rowspan = 5, sticky = N+S)

        # Frame 1 - Frame widget in canvas #
        self.Frame1 = Frame(self.Canvas1, bg = "white")
        self.Frame1.rowconfigure(0, weight = 1)    
        for c in range(2):
            self.Frame1.columnconfigure(1 + (2 * c), weight = 1)#1,3 - columns for small icons in the future
        for cb in range(3):
            self.Frame1.columnconfigure((cb * 2), weight = 9)#0,2,4 - columns for data

        self.CFrame1 = self.Canvas1.create_window(0, 0, window = self.Frame1, width = FrameSizeX, anchor = N+W)
        self.Canvas1.bind("<Configure>", lambda: resize_frame(self)) # !!!!! Doesn't work & gives error !!!!!! #
        self.Frame1.bind("<Configure>", lambda: scrollevent(self))

        self.Canvas1.config(scrollregion=self.Canvas1.bbox("all"))

        # Frame 2 #
        self.Frame2 = Frame(self, bg= "yellow")
        self.Frame2.grid(row = 5, column = 0, rowspan = 1, columnspan = 3, sticky = W+E+N+S)
        for r in range(3):
            self.Frame2.rowconfigure(r, weight=1)    
        for c in range(3):
            self.Frame2.columnconfigure(c, weight = 1)
        # Frame 3 #
        self.Frame3 = Frame(self)
        self.Frame3.grid(row = 5, column = 3, rowspan = 1, columnspan = 2, sticky = W+E+N+S)
        self.Frame3.rowconfigure(0, weight = 1)
        self.Frame3.columnconfigure(0, weight = 1)

        # Propagation #
        #self.grid_propagate(False)        # All widgets (the 3 subframes) need to fit in Toplevel window. Minimal window size will be implemented later.
        self.Canvas1.grid_propagate(False) # canvas works with scrollbar, widgets dont need to fit in window size.
        #self.Frame1.grid_propagate(False) # Frame1 should resize to hold all data (label-widgets)
        self.Frame2.grid_propagate(False)  # fixed frame dimensions
        self.Frame3.grid_propagate(False)  # fixed textbox dimensions
        self.Frame1.update_idletasks() # just to make sure

        ### Widgets for the multiple frames ###
        # Frame1 - further populated by button command in frame 2#
        self.lblaa = Label(self.Frame1, bg="white", text = "Processing...", justify = "left")
        self.lblaa.grid(row = 0, column = 0, sticky = N+W)
        self.LSlabelsr = []
        self.LSlabelsa = []
        self.LSlabelsb = []
        # Frame 2 #
        self.Wbuttontest=Button(self.Frame2, text="Start listing test", command = lambda: refresh(self))
        self.Wbuttontest.grid(row = 0, column = 0, columnspan = 3)
        self.Wentry = Entry(self.Frame2)
        self.Wentry.grid(row = 2, column = 0, columnspan = 3, sticky = E+W, padx = 10)
        self.Wentry.delete(0, END)
        self.Wentry.insert(0, "user input here")
        # Frame3 #
        self.Wtext = Text(self.Frame3)
        self.Wscrollb = Scrollbar(self.Frame3)
        self.Wscrollb.config(command = self.Wtext.yview)
        self.Wtext.config(yscrollcommand = self.Wscrollb.set)
        self.Wtext.grid(row = 0, column = 0, sticky = N+E+W+S)        


        ### Test-Lists ### Last character in the left column is "ez" !! ###
        self.LSa = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
        self.LSb = [1, 2, 3, 4, 66, 6, 7, 8, 9, 67, 11, 12, 13, 14, 68]
        self.LSr = []
        ib = 0
        prefix = ""
        for i in range(104):
            if ib > 25:
                prefix = chr(ord("a") + (i/ib - 1))
                ib = 0
            else:
                pass
            self.LSr.append(prefix + chr(97+ib))
            ib += 1

        ### FUNCTIONS ###
        def resize_frame(self, event):
            self.Canvas1.itemconfig(self.CFrame1, width = e.width) #height of frame should depend on the contents.

        def scrollevent(event):
            self.Canvas1.configure(scrollregion=self.Canvas1.bbox("all"),width=200,height=200)

        def refresh(self): ### Button-command: data will be shown ###
            if self.lblaa.winfo_exists() == 1:
                self.lblaa.destroy()
            for i in range(len(self.LSr)):
                self.Frame1.rowconfigure(i, weight = 0)
            del self.LSlabelsr[:] # remove any previous labels from if the callback was called before
            del self.LSlabelsa[:] # remove any previous labels from if the callback was called before
            del self.LSlabelsb[:] # remove any previous labels from if the callback was called before
            Vlabelheight = 1
            # Left List #
            for i in range(len(self.LSr)):
                self.LSlabelsr.append(Label(self.Frame1, text = str(self.LSr[i]), bg = "LightBlue", justify = "left", height = Vlabelheight))
                self.LSlabelsr[i].grid(row = i, column = 0, sticky = E+W)
            # Middle List #
            for i in range(len(self.LSa)):
                self.LSlabelsa.append(Label(self.Frame1, text = str(self.LSa[i]), bg = "LightBlue", fg = "DarkViolet", justify = "left", height = Vlabelheight))
                self.LSlabelsa[i].grid(row = i, column = 2, sticky = E+W)
            # Right List #
            for i in range(len(self.LSb)):
                self.LSlabelsb.append(Label(self.Frame1, text = str(self.LSb[i]), bg = "LightBlue", fg = "DarkGreen", justify = "left", height = Vlabelheight))
                self.LSlabelsb[i].grid(row = i, column = 4, sticky = E+W)
            self.Frame1.update()
            self.Frame1.update_idletasks()
            print("done")

if __name__ == "__main__":
    root = Tk()
    root.title("Invisible")
    root.resizable(FALSE,FALSE)
    root.withdraw()
    app = Processing(root)
    root.mainloop()

Рабочая версия после предложений от R4PH4EL:

  • Правильный отступ: функции были определены как часть init.
  • Некоторые настройки лямбда-команд в строке 43/107 и 44/110.

    from Tkinter import *
    import math
    
    class Processing(Toplevel):
        def __init__(self, master, *args, **kwargs):
            Toplevel.__init__(self, master)
            self.master = master
            self.title("Process Window")
            for r in range(6):
                self.rowconfigure(r, weight = 1)    
            for c in range(4):
                self.columnconfigure(c, weight = 1)
    
            ### WINDOW size and position definitions ###
            ScreenSizeX = master.winfo_screenwidth()
            ScreenSizeY = ( master.winfo_screenheight() - 75 ) #about 75pixels for taskbar on bottom of screen (Windows)
            ScreenRatio = 0.9
            FrameSizeX  = int(ScreenSizeX * ScreenRatio)
            FrameSizeY  = int(ScreenSizeY * ScreenRatio)
            FramePosX   = (ScreenSizeX - FrameSizeX)/2
            FramePosY   = (ScreenSizeY - FrameSizeY)/2
            self.geometry("%sx%s+%s+%s"%(FrameSizeX,FrameSizeY,FramePosX,FramePosY))
    
            ### Creating 3 "sub-frames" ###
            # Frame 1 - canvas container with scrollbar#
            self.Canvas1 = Canvas(self, bg = "white")
            self.Canvas1.grid(row = 0, column = 0, rowspan = 5, columnspan = 4, sticky = N+E+S+W)
            self.Canvas1.rowconfigure(1, weight = 1)
            self.Canvas1.columnconfigure(1, weight = 1)
            self.myscrollbar=Scrollbar(self, orient = "vertical", command = self.Canvas1.yview)
            self.Canvas1.configure(yscrollcommand = self.myscrollbar.set)
            self.myscrollbar.grid(row = 0, column = 4, rowspan = 5, sticky = N+S)
    
            # Frame 1 - Frame widget in canvas #
            self.Frame1 = Frame(self.Canvas1, bg = "white")
            self.Frame1.rowconfigure(0, weight = 1)    
            for c in range(2):
                self.Frame1.columnconfigure(1 + (2 * c), weight = 1)#1,3 - columns for small icons in the future
            for cb in range(3):
                self.Frame1.columnconfigure((cb * 2), weight = 9)#0,2,4 - columns for data
    
            self.CFrame1 = self.Canvas1.create_window(0, 0, window = self.Frame1, width = FrameSizeX, anchor = N+W)
            self.Canvas1.bind("<Configure>", lambda event: self.resize_frame(event)) # !!!!! Doesn't work & gives error !!!!!! #
            self.Frame1.bind("<Configure>", lambda event: self.scrollevent(event))
    
            self.Canvas1.config(scrollregion=self.Canvas1.bbox("all"))
    
            # Frame 2 #
            self.Frame2 = Frame(self, bg= "yellow")
            self.Frame2.grid(row = 5, column = 0, rowspan = 1, columnspan = 3, sticky = W+E+N+S)
            for r in range(3):
                self.Frame2.rowconfigure(r, weight=1)    
            for c in range(3):
                self.Frame2.columnconfigure(c, weight = 1)
            # Frame 3 #
            self.Frame3 = Frame(self)
            self.Frame3.grid(row = 5, column = 3, rowspan = 1, columnspan = 2, sticky = W+E+N+S)
            self.Frame3.rowconfigure(0, weight = 1)
            self.Frame3.columnconfigure(0, weight = 1)
    
            # Propagation #
            #self.grid_propagate(False)        # All widgets (the 3 subframes) need to fit in Toplevel window. Minimal window size will be implemented later.
            self.Canvas1.grid_propagate(False) # canvas works with scrollbar, widgets dont need to fit in window size.
            #self.Frame1.grid_propagate(False) # Frame1 should resize to hold all data (label-widgets)
            self.Frame2.grid_propagate(False)  # fixed frame dimensions
            self.Frame3.grid_propagate(False)  # fixed textbox dimensions
            self.Frame1.update_idletasks() # just to make sure
    
            ### Widgets for the multiple frames ###
            # Frame1 - further populated by button command in frame 2#
            self.lblaa = Label(self.Frame1, bg="white", text = "Processing...", justify = "left")
            self.lblaa.grid(row = 0, column = 0, sticky = N+W)
            self.LSlabelsr = []
            self.LSlabelsa = []
            self.LSlabelsb = []
            # Frame 2 #
            self.Wbuttontest=Button(self.Frame2, text="Start listing test", command = lambda: self.refresh())
            self.Wbuttontest.grid(row = 0, column = 0, columnspan = 3)
            self.Wentry = Entry(self.Frame2)
            self.Wentry.grid(row = 2, column = 0, columnspan = 3, sticky = E+W, padx = 10)
            self.Wentry.delete(0, END)
            self.Wentry.insert(0, "user input here")
            # Frame3 #
            self.Wtext = Text(self.Frame3)
            self.Wscrollb = Scrollbar(self.Frame3)
            self.Wscrollb.config(command = self.Wtext.yview)
            self.Wtext.config(yscrollcommand = self.Wscrollb.set)
            self.Wtext.grid(row = 0, column = 0, sticky = N+E+W+S)        
    
    
            ### Test-Lists ### Last character in the left column is "ez" !! ###
            self.LSa = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
            self.LSb = [1, 2, 3, 4, 66, 6, 7, 8, 9, 67, 11, 12, 13, 14, 68]
            self.LSr = []
            ib = 0
            prefix = ""
            for i in range(104):
                if ib > 25:
                    prefix = chr(ord("a") + (i/ib - 1))
                    ib = 0
                else:
                    pass
                self.LSr.append(prefix + chr(97+ib))
                ib += 1
    
        ### FUNCTIONS ###
        def resize_frame(self, e):
            self.Canvas1.itemconfig(self.CFrame1, width = e.width) #height of frame should depend on the contents.
    
        def scrollevent(self, event):
            self.Canvas1.configure(scrollregion=self.Canvas1.bbox("all"),width=200,height=200)
    
        def refresh(self): ### Button-command: data will be shown ###
            if self.lblaa.winfo_exists() == 1:
                self.lblaa.destroy()
            for i in range(len(self.LSr)):
                self.Frame1.rowconfigure(i, weight = 0)
            del self.LSlabelsr[:] # remove any previous labels from if the callback was called before
            del self.LSlabelsa[:] # remove any previous labels from if the callback was called before
            del self.LSlabelsb[:] # remove any previous labels from if the callback was called before
            Vlabelheight = 1
            # Left List #
            for i in range(len(self.LSr)):
                self.LSlabelsr.append(Label(self.Frame1, text = str(self.LSr[i]), bg = "LightBlue", justify = "left", height = Vlabelheight))
                self.LSlabelsr[i].grid(row = i, column = 0, sticky = E+W)
            # Middle List #
            for i in range(len(self.LSa)):
                self.LSlabelsa.append(Label(self.Frame1, text = str(self.LSa[i]), bg = "LightBlue", fg = "DarkViolet", justify = "left", height = Vlabelheight))
                self.LSlabelsa[i].grid(row = i, column = 2, sticky = E+W)
            # Right List #
            for i in range(len(self.LSb)):
                self.LSlabelsb.append(Label(self.Frame1, text = str(self.LSb[i]), bg = "LightBlue", fg = "DarkGreen", justify = "left", height = Vlabelheight))
                self.LSlabelsb[i].grid(row = i, column = 4, sticky = E+W)
            self.Frame1.update()
            self.Frame1.update_idletasks()
            print("done")
    
    if __name__ == "__main__":
        root = Tk()
        root.title("Invisible")
        root.resizable(FALSE,FALSE)
        root.withdraw()
        app = Processing(root)
        root.mainloop()
    

1 ответ

Решение

Либо ваш отступ неправильный, либо вы вообще что-то делаете неправильно.

Все ваши функции определены внутри вашего __init__ функция

Второе: если вы хотите вызвать функцию класса, вы вызываете ее obj.function

Ваша ошибка на lambda: resize(self) может произойти как и должно быть lambda: self.resize,

Дайте ему шанс с этим и попробуйте. И, пожалуйста, убедитесь, что ваши отступы верны.

Я полностью согласен с Брайаном в этом вопросе - опуская лямбды, было бы (по моему личному мнению) легче читать и отчасти "лучше" в смысле более структурированного и прагматичного стиля кодирования.

Другие вопросы по тегам