customtkinter

customtkinter raises error and has conflict between buttons


I am writing a dictionary program using customtkinter library. There is one button and a switch. When I press the right button, the programm destroys first ten sets of widgets and creates next 10 so it looks like I turned a page. So if I turn on the switch before hitting the right button, there are no errors. But if I first click the rightbutton and then click the switcher, it raises errors. I understand that there are some logical conflicts at this point, but I can't find out where the mistake in the code is. The buttons which the switcher activates all at once will be used for acces to editing menus. Here is a simplified reproducible code:

import customtkinter

customtkinter.set_appearance_mode('light')
customtkinter.set_default_color_theme('dark-blue')

root = customtkinter.CTk()
root.geometry('750x800')
root.title('My dictionary')
root.resizable(width=False, height=False)


database_example = (
    [1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4], [5, 5, 5], [6, 6, 6], [7, 7, 7], [8, 8, 8], [9, 9, 9],
    [10, 10, 10], [11, 11, 11], [12, 12, 12], [13, 13, 13], [14, 14, 14], [15, 15, 15], [16, 16, 16],
    [17, 17, 17], [18, 18, 18], [19, 19, 19], [20, 20, 20], [21, 21, 21], [22, 22, 22], [23, 23, 23],
    [24, 24, 24], [25, 25, 25], [26, 26, 26], [27, 27, 27], [28, 28, 28], [29, 29, 29], [30, 30, 30],
    [31, 31, 31], [32, 32, 32], [33, 33, 33], [34, 34, 34], [35, 35, 35], [36, 36, 36], [37, 37, 38],
    [39, 39, 39], [40, 40, 40]
)

start_pos = 0
end_pos = 10


class WordBox:
    button_container = []
    instances = []

    def __init__(self,
                 word='',
                 translation='',
                 context='',
                 ):
        self.instances.append(self)
        word_fontsize = 23
        translation_fontsize = 23

        self.outer_frame = customtkinter.CTkFrame(master=scroll_frame, width=700, height=150, fg_color='#AED5D5')
        self.outer_frame.pack(pady=12, padx=10, side='top')

        self.inner_LabelWord = customtkinter.CTkLabel(master=self.outer_frame,
                                                      text=word,
                                                      fg_color='#C9D1D8',
                                                      text_color='#0B7BDE',
                                                      font=('Arial Black', word_fontsize),
                                                      width=450, height=50, anchor='nw')
        self.inner_LabelWord.grid(row=0, column=0)

        self.inner_LabelTranslation = customtkinter.CTkLabel(master=self.outer_frame,
                                                             text=translation,
                                                             fg_color='#C9D1D8',
                                                             text_color='black',
                                                             font=('Yu Gothic UI Semibold', translation_fontsize),
                                                             width=450, height=50, anchor='nw')
        self.inner_LabelTranslation.grid(row=1, column=0)
        self.inner_LabelContext = customtkinter.CTkLabel(master=self.outer_frame,
                                                         text=context,
                                                         fg_color='#908D8B',
                                                         text_color='black',
                                                         font=('Dubai', 14),
                                                         width=450, height=50, anchor='nw')
        self.inner_LabelContext.grid(row=2, column=0)
        self.inner_PicFrame = customtkinter.CTkFrame(master=self.outer_frame, fg_color='#48E5E0', width=150, height=150)
        self.inner_PicFrame.grid(row=0, column=1, rowspan=3)
        self.inner_PicLabel = customtkinter.CTkLabel(master=self.inner_PicFrame, text='PICTURE')
        self.inner_PicLabel.pack()

        self.inner_Button = customtkinter.CTkButton(master=self.outer_frame, width=50, height=150, text='O',
                                                    font=('', 20), fg_color='#C9D1D8', hover_color='#FFA60B',
                                                    state='disabled')
        self.inner_Button.grid(row=0, column=2, rowspan=3)
        self.button_container.append(self.inner_Button)


def switcher():
    if switch_var.get() == 'on':
        for i in WordBox.button_container:
            i.configure(state='normal')
    if switch_var.get() == 'off':
        for i in WordBox.button_container:
            i.configure(state='disabled')


def start_programm(db):
    for word in db[:10]:
        WordBox(
            word=word[0],
            translation=word[1],
            context=word[2],
        )


def rightbutton():
    global start_pos
    global end_pos
    start_pos += 10
    end_pos += 10
    for item in WordBox.button_container:
        del item
    for instance in WordBox.instances:
        instance.inner_Button.destroy()
        instance.outer_frame.destroy()
        instance.inner_PicFrame.destroy()
        instance.inner_LabelContext.destroy()
        instance.inner_LabelTranslation.destroy()
        instance.inner_LabelWord.destroy()
        del instance

    for word in database_example[start_pos:end_pos]:
        WordBox(
            word=word[0],
            translation=word[1],
            context=word[2],
        )


switch_var = customtkinter.StringVar(value="off")
switch = customtkinter.CTkSwitch(master=root, text='', command=switcher,
                                 variable=switch_var, onvalue="on", offvalue="off",
                                 progress_color='#E5611F')
switch.place(relx=0.862, rely=0.26)

scroll_frame = customtkinter.CTkScrollableFrame(master=root, width=700, height=500)
scroll_frame.place(relx=0.01, rely=0.3)


right_button = customtkinter.CTkButton(master=root, text='>>', command=rightbutton, width=35, height=30,
                                       corner_radius=17, fg_color='#817A7A', hover_color='#E5611F')
right_button.place(relx=0.12, rely=0.26)


start_programm(database_example)

root.mainloop()

Oh, I've found the solution. For some reason del operator wasn't working the way it should (or I thought it should). Instead of using del, I have rewritten the rightbutton function, using clear() method to empty button_container and instances containers of WordBox class. And now it works. Here is the rewritten function:

def rightbutton():
    global start_pos
    global end_pos
    start_pos += 10
    end_pos += 10

    for instance in WordBox.instances:
        instance.inner_Button.destroy()
        instance.outer_frame.destroy()
        instance.inner_PicFrame.destroy()
        instance.inner_LabelContext.destroy()
        instance.inner_LabelTranslation.destroy()
        instance.inner_LabelWord.destroy()
    WordBox.button_container.clear()
    WordBox.instances.clear()
    for word in database_example[start_pos:end_pos]:
        WordBox(
            word=word[0],
            translation=word[1],
            context=word[2],
        )

Solution

  • You need to clear the two lists WordBox.button_container and WordBox.instances after deleting their items, otherwise .configure(...) will be called on those deleted items and raise the exception when switcher() is executed. Also you need to call switcher() to set the correct state of those newly created inner_buttons:

    def rightbutton():
        global start_pos
        global end_pos
        
        start_pos += 10
        end_pos += 10
    
        for item in WordBox.button_container:
            del item
        WordBox.button_container.clear() # clear the list
    
        for instance in WordBox.instances:
            instance.inner_Button.destroy()
            instance.outer_frame.destroy()
            instance.inner_PicFrame.destroy()
            instance.inner_LabelContext.destroy()
            instance.inner_LabelTranslation.destroy()
            instance.inner_LabelWord.destroy()
            del instance
        WordBox.instances.clear() # clear the list
    
        for word in database_example[start_pos:end_pos]:
            WordBox(
                word=word[0],
                translation=word[1],
                context=word[2],
            )
        switcher() # set the correct state of the buttons