Python Forum
Made new instance - button still goes to old instance - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: Made new instance - button still goes to old instance (/thread-30636.html)



Made new instance - button still goes to old instance - nanok66 - Oct-29-2020

Hi,
I have a large python GUI program and I wanted to add a button that "resets all settings to default"

I have 3 pages of my program that I need to create new instances of these pages to "reset the settings".  

I made a function that does the work, the only issue is, my navigation buttons stop working.  That is because they lead to the previous instance of the page, after the new instances are created the navigation buttons simply don't do anything. 

Is there a standard way to get around this?  One random idea I had was using getters/setters in the format below, would that work for instances?

@property
def Temp(self):
    return self._temp

@Temp.setter
def Temp(self, value):
    self._temp = value
I tried to strip down my program, and I have included the code.  I doubt it actually runs, but at least y'all have something to go off. 
 
The function that does the work is called resetPages() 

import tkinter as tk
          
    
class Page(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

    def show(self):
        self.lift()


class MainPage(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        
        main_desc = tk.Label(text="stuff and things")
     


class FunctionsPage(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)

        self.device_desc_lbl = tk.Label(text="Devices", anchor=tk.CENTER)

        
class TempPage(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        
        temp_page_lbl = tk.Label(text="stuff and things")
        


class CycleTimesPage(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)

        cycle_times_lbl = tk.Label(frame.interior, font=page_title_font, text='Cycle Times')
        cycle_times_lbl.grid(row=0, column=0, columnspan=4)

        reset_pages_btn = tk.Button(command=main.resetPages)   # here is reset pages button
        reset_pages_btn.grid(column=0, row=1)


class OverridesPage(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)

        override_page_lbl = tk.Label(master=self, text='Sensor Overrides')
        override_page_lbl.grid(row=0, column=0, columnspan=2)

       

class MainView(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        main_bg = "#ffe499"
        
        self.container = tk.Frame(self)
        self.container.pack(side="left", fill="both", expand=True)
        
        self.p1 = MainPage(self.container)
        self.p2 = FunctionsPage(self.container)
        self.p3 = TempPage(self.container)
        self.p4 = CycleTimesPage(self.container)
        self.p5 = OverridesPage(self.container)

        self.p1.configure(bg=main_bg)
        self.p2.configure(bg=main_bg)
        self.p3.configure(bg=main_bg)
        self.p4.configure(bg=main_bg)
        self.p5.configure(bg=main_bg)

        buttonframe = tk.Frame(self)
        buttonframe.pack(side="right", fill="y", expand=False, anchor=tk.E)

        self.p1.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p2.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p3.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p4.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p5.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)

        self.v = tk.IntVar(value=1)

        radio_bg_select = "#ffae61"
        radio_bg = "#ffead6"
        nav_btn_width = 128
        nav_btn_font = font.Font(size=12, weight='bold')


        self.r1 = tk.Radiobutton(buttonframe, text="Home", font=nav_btn_font, bg=radio_bg,
                                 selectcolor=radio_bg_select, bd=3,
                                 indicatoron=0, width=nav_btn_width, height=80, variable=self.v,
                                 command=self.p1.lift, 
                                 value=1).pack(anchor=tk.E, expand=True, fill="both")
        self.r2 = tk.Radiobutton(buttonframe, text="Functions", font=nav_btn_font, bg=radio_bg, 
                                 selectcolor=radio_bg_select, bd=3,
                                 indicatoron=0, width=nav_btn_width, variable=self.v,
                                 command=self.p2.lift,
                                 value=2).pack(anchor=tk.E, expand=True, fill="both")
        self.r3 = tk.Radiobutton(buttonframe, text="Temperature", font=nav_btn_font, bg=radio_bg, 
                                 selectcolor=radio_bg_select, bd=3,
                                 indicatoron=0, width=nav_btn_width, variable=self.v,
                                 command=self.p3.lift,
                                 value=3).pack(anchor=tk.E, expand=True, fill="both")
        self.r4 = tk.Radiobutton(buttonframe, text="Cycle Times", font=nav_btn_font, bg=radio_bg, 
                                 selectcolor=radio_bg_select, bd=3,
                                 indicatoron=0, width=nav_btn_width, variable=self.v,
                                 command=self.p4.lift,
                                 value=4).pack(anchor=tk.E, expand=True, fill="both")
        self.r5 = tk.Radiobutton(buttonframe, text="Overrides", font=nav_btn_font, bg=radio_bg,
                                 selectcolor=radio_bg_select, bd=3,
                                 indicatoron=0, width=nav_btn_width, variable=self.v,
                                 command=self.p5.lift,
                                 value=5).pack(anchor=tk.E, expand=True, fill="both")

        self.p1.show()
        
    def resetPages(self):
        main_bg = "#ffe499"
        self.p3.destroy()
        self.p4.destroy()
        self.p5.destroy()
        self.p3 = TempPage(self.container)
        self.p4 = CycleTimesPage(self.container)
        self.p5 = OverridesPage(self.container)
        self.p3.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p4.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p5.place(in_=self.container, x=0, y=0, relwidth=1, relheight=1)
        self.p3.configure(bg=main_bg)
        self.p4.configure(bg=main_bg)
        self.p5.configure(bg=main_bg)
        self.p4.show()  # to bring user back to page p4 where the reset button was located
            
            

if __name__ == "__main__": 
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    mainP = main.p1
    root.wm_geometry("1024x600")
    root.configure(bg="#ffe499")
    root.mainloop()
Any help is appreciated.


RE: Made new instance - button still goes to old instance - deanhystad - Oct-29-2020

Why do you destroy the pages instead of updating the widgets? But if you want to continue with wonton destruction, you need to change your navigation callbacks. You cannot call self.p1.lift if you throw p1 away. You need a function that gets self.p1 and calls lift.

What is the difference? When you set the command for your radio button you are getting the function associated with self.p1.lift and saving that as the function to call when the button is pressed. When you press the radio button it calls the function. It does not ask self to return the attribute p1, then ask p1 to return the attribute lift, then call lift.

To use the new p1, you need a function that gets the new function and calls it. You could do it this way.
def raise_page_1(self)
    self.p1.lift()

self.r1 = tk.Radiobutton(buttonframe, bg=radio_bg, command=self.raise_page_1...)
You could also use a lambda, which is really the same thing except you don't have to write a function declaration.
self.r1 = tk.Radiobutton(buttonframe, bg=radio_bg,
    command=lambda x=self: x.p1.lift)
Your code would be greatly improved if you stopped using variables like p1 and r2 and used collections when you have collections of things. For example this is your code except I made an array of pages and use for loops where you use individual commands.
import tkinter as tk
from random import randint

class Page(tk.Frame):
    def __init__(self, parent, title):
        super().__init__(parent)
        # If something is done for every instance, do it here.
        cls = type(self)
        cls.instances += 1
        background_color="#ffe499"
        self.configure(bg=background_color)
        self.place(in_=parent, x=0, y=0, relwidth=1, relheight=1)
        self.title = title
        label = tk.Label(self, text=f'{title} {cls.instances}', bg=background_color)
        label.pack()
 
    def show(self):
        print('show', self.title)
        self.lift()

# If these are similar, make the interface similar so they can be
# treated interchangably.
class MainPage(Page):
    instances = 0
    def __init__(self, main, parent):
        super().__init__(parent, 'Home')
 
class FunctionsPage(Page):
    instances = 0
    def __init__(self, main, parent):
        super().__init__(parent, 'Functions')

class TempPage(Page):
    instances = 0
    def __init__(self, main, parent):
        super().__init__(parent, 'Temperature')
 
class CycleTimesPage(Page):
    instances = 0
    def __init__(self, main, parent):
        super().__init__(parent, 'Cycle Times')
 
class OverridesPage(Page):
    instances = 0
    def __init__(self, main, parent):
        super().__init__(parent, 'Overrides')
        self.reset_buton = tk.Button(self, text='Reset', command=main.reset_pages)
        self.reset_buton.pack()
 
class MainView(tk.Frame):
    page_types = [MainPage, FunctionsPage, TempPage, CycleTimesPage, OverridesPage]

    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
         
        self.page_frame = tk.Frame(self)
        self.page_frame.pack(side="left", fill="both", expand=True)
        
        button_bg = "#ffead6"
        button_frame = tk.Frame(self, bg=button_bg)
        button_frame.pack(side="right", fill="y", expand=False, anchor=tk.E)
        
        self.selected_page = tk.IntVar(value=0)

        # Use loops whenever possible.
        self.pages = [None]*len(self.page_types)
        for index in range(len(self.page_types)):
            page = self.new_page(index)

            # If you don't need to keep the handle don't make an
            # instance variable.
            btn = tk.Radiobutton(button_frame, text=page.title,
                    bg=button_bg, selectcolor="#ffae61", bd=3,
                    variable=self.selected_page, value=index,
                    command=lambda x=index: self.select_page(x))
            btn.grid(column=0,row=index,sticky=tk.W)

        # Be consistent.  select_page() is always used to raise a page
        self.select_page(0)

    def new_page(self, index):
        # Avoid repeating code.  This is used whenever a page is created
        if self.pages[index]:
            self.pages[index].destroy()
        self.pages[index] = self.page_types[index](self, self.page_frame)
        return self.pages[index]

    def select_page(self, index):
        self.pages[index].show()
        self.selected_page.set(index)

    def reset_pages(self):
        for index in (2, 3, 4):
            self.new_page(index)
        self.select_page(self.selected_page.get())
 
if __name__ == "__main__": 
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    root.wm_geometry("1024x600")
    root.mainloop()
One last and only slightly related thing. You need to be careful when stringing together Python commands.
This creates a button and creates a variable that references the button.
btn = tk.Radiobutton(frame, text='Text')
btn.pack()
This creates a button and creates a variable that is set to None.
btn = tk.Radiobutton(frame, text='Text').pack()



RE: Made new instance - button still goes to old instance - nanok66 - Oct-30-2020

Ok I'll definitely try the function. And wow that's a lot of other improvements! Will take me second to work that all in, but thanks!

I would have rather updated them but I don't know how to update the value for each since I created each setting with this one master function below. Creating the page in this way I just don't know how to reference back to each value to update.

        def new_setting(desc, value, setter, row):   
            title_lbl = tk.Label(master=frame.interior, text=desc)
            title_lbl.grid(row=row, column=3)
            value_lbl = tk.Label(master=frame.interior, text=value)  # trying to update this value
            value_lbl.grid(row=row, column=1)
            incr_btn = tk.Button(master=frame.interior, text="+", command=partial(incr, sanke, setter, value_lbl))
            incr_btn.grid(row=row, column=2)
            decr_btn = tk.Button(master=frame.interior, text="-", command=partial(decr, sanke, setter, value_lbl))
            decr_btn.grid(row=row, column=0)


        new_setting('Time of Rinse Shell', sanke.rinseShellTime, sankeFunctions.SankeProps.rinseShellTime, 3)
        new_setting('Time of Rinse Stem', sanke.rinseStemTime, sankeFunctions.SankeProps.rinseStemTime, 4)
        # many more settings..



RE: Made new instance - button still goes to old instance - deanhystad - Oct-30-2020

You are thinking about the problem wrong again. Your pages should know how to reset themselves. I would make reset a method sends a reset to every page and pages that reset will execute their reset method. Nowhere would there be code that knows how to reset all pages or even which pages need to be reset. Everything is treated generically as possible.

I call this kind of programming "blissful ignorance". The more ignorant parts are about each other the easier it is to modify and debug the program.
import tkinter as tk
 
class Page(tk.Frame):
    def __init__(self, parent, title):
        super().__init__(parent)
        background_color="#ffe499"
        self.configure(bg=background_color)
        self.place(in_=parent, x=0, y=0, relwidth=1, relheight=1)
        self.title = title
        self.label = tk.Label(self, text=title, bg=background_color)
        self.label.pack()

    def show(self):
        '''This method is shared (inherited) by all pages'''
        self.lift()

    def reset(self):
        '''This is an abstract method.  Panels that reset will override'''
        pass
 
class MainPage(Page):
    def __init__(self, main, parent):
        super().__init__(parent, 'Home')
        self.reset_buton = tk.Button(self, text='Reset', command=main.reset_pages)
        self.reset_buton.pack()
  
class OverridesPage(Page):
    def __init__(self, main, parent):
        super().__init__(parent, 'Overrides')
        self.reset_count = 0

    def reset(self):
        '''Not all panels know how to reset'''
        self.reset_count += 1
        self.label.configure(text=f'{self.title} {self.reset_count}')
  
class MainView(tk.Frame):
    page_types = [MainPage, OverridesPage]
 
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        self.pack(side="top", fill="both", expand=True)
          
        page_frame = tk.Frame(self)
        page_frame.pack(side="left", fill="both", expand=True)
         
        button_bg = "#ffead6"
        button_frame = tk.Frame(self, bg=button_bg)
        button_frame.pack(side="right", fill="y", expand=False, anchor=tk.E)
         
        self.selected_page = tk.IntVar(value=0)
 
        self.pages = []
        for index, cls in enumerate(self.page_types):
            page = cls(self, page_frame)
            self.pages.append(page)
 
            btn = tk.Radiobutton(button_frame, text=page.title,
                    bg=button_bg, selectcolor="#ffae61", bd=3,
                    variable=self.selected_page, value=index,
                    command=lambda x=index: self.select_page(x))
            btn.grid(column=0,row=index,sticky=tk.W)
 
        self.select_page(0)
 
    def select_page(self, index):
        self.pages[index].show()
        self.selected_page.set(index)
 
    def reset_pages(self):
        for page in self.pages:
            page.reset()
  
if __name__ == "__main__": 
    root = tk.Tk()
    main = MainView(root)
    root.wm_geometry("300x100")
    root.mainloop()



RE: Made new instance - button still goes to old instance - nanok66 - Nov-05-2020

Ok very cool, it took some playing around but I got some pages to update that way.

I am still stuck on the original issue of not knowing how to refer back to the value of the labels I created on the pages. This code below is how I CREATE the page not how I planned to update them. I added a reset method where I am not sure what to call the label to illustrate:

Also sorry the name of my function might have been misleading, I will rename it to setting_widget.

class CycleTimesPage(Page):
    def __init__(self, main, parent):
        super().__init__(parent, 'Cycle Times')

        def setting_widget(desc, value, setter, row):   # this method is how I create the page, not for updating
            title_lbl = tk.Label(master=frame.interior, text=desc)
            title_lbl.grid(row=row, column=3)
            value_lbl = tk.Label(master=frame.interior, text=value)  # trying to update this value
            value_lbl.grid(row=row, column=1)
            incr_btn = tk.Button(master=frame.interior, text="+", command=partial(incr, sanke, setter, value_lbl))
            incr_btn.grid(row=row, column=2)
            decr_btn = tk.Button(master=frame.interior, text="-", command=partial(decr, sanke, setter, value_lbl))
            decr_btn.grid(row=row, column=0)
 
 
        setting_widget('Time of Rinse Shell', sanke.rinseShellTime, sankeFunctions.SankeProps.rinseShellTime, 3)
        setting_widget('Time of Rinse Stem', sanke.rinseStemTime, sankeFunctions.SankeProps.rinseStemTime, 4)


    def reset(self):
        value_lbl.configure(text=sanke.rinseShellTime)  # how to refer to each value when I have given no ID?
        value_lbl.configure(text=sanke.rinseStemTime)   # ditto
   



RE: Made new instance - button still goes to old instance - nanok66 - Nov-05-2020

Gah I figured it out! Nevermind! My solution is below for anyone stuck with a similar question.

class CycleTimesPage(Page):
    def __init__(self, main, parent):
        super().__init__(parent, 'Cycle Times')
 
        def setting_widget(desc, value, setter, row):   # this method is how I create the page, not for updating
            title_lbl = tk.Label(master=frame.interior, text=desc)
            title_lbl.grid(row=row, column=3)
            value_lbl = tk.Label(master=frame.interior, text=value)  # trying to update this value
            value_lbl.grid(row=row, column=1)
            incr_btn = tk.Button(master=frame.interior, text="+", command=partial(incr, sanke, setter, value_lbl))
            incr_btn.grid(row=row, column=2)
            decr_btn = tk.Button(master=frame.interior, text="-", command=partial(decr, sanke, setter, value_lbl))
            decr_btn.grid(row=row, column=0)
            
            return value_lbl
  
        self.rinse_shell_value = setting_widget('Time of Rinse Shell', sanke.rinseShellTime, sankeFunctions.SankeProps.rinseShellTime, 3)
        self.rinse_stem_value = setting_widget('Time of Rinse Stem', sanke.rinseStemTime, sankeFunctions.SankeProps.rinseStemTime, 4)
 
 
    def reset(self):
        self.rinse_shell_value.configure(text=sanke.rinseShellTime) 
        self.rinse_stem_value.configure(text=sanke.rinseStemTime)   



RE: Made new instance - button still goes to old instance - deanhystad - Nov-06-2020

Another way to update the labels is to make a tkvariable and bind that to the label textvariable. I would do that instead of using the configure() method.
import tkinter as tk

counter = 0
def push_button():
    counter =label_number.get() + 1
    label_text.set(counter**2)
    label_number.set(counter)
    
root = tk.Tk()
label_text = tk.StringVar(root)
label_number = tk.IntVar(root)

label = tk.Label(root, width=10, textvariable=label_text)
label_text.set('0')
label.grid(row=0, column=0, padx=10, pady=10)

label = tk.Label(root, width=10, textvariable=label_number)
label_text.set(0)
label.grid(row=1, column=0, padx=10, pady=10)

button = tk.Button(root, text='Push Me', command=push_button)
button.grid(row=2, column=0, padx=10, pady=10)

tk.mainloop()