0

Pardon me for my bad grammar or explanation, since I didn't know how to explain this properly.

I try to build some gui that could switch between frame, using script from this as base Switch between two frames in tkinter.

In this case, I will have a few frame that had similar design, but different function when the button is pressed. For example, I have 2 frames that have similar 2 entries and 1 button, but the button do different command (where at sub01 frame it will multiply and at sub02 frame will divide)

This is my code:

class SampleApp(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.grid(row=1,columnspan=4,sticky='nsew')
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in (sub01, sub02):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame
            frame.grid(row=1,sticky="nsew")
        
        self.choices = {'sub01','sub02'}
        self.tkvar = tk.StringVar()
        self.tkvar.set('sub01')
        self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
        self.popMenu.grid(row=0)
        self.show_frame()

        self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
        self.button1.grid(row=0, column=1)

    def show_frame(self):
        '''Show a frame for the given page name'''
        page_name = self.tkvar.get()
        frame = self.frames[page_name]
        frame.tkraise()

class sub01(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="This SubLayer 1")
        label.grid(row=0)
        self.entries=[]
        i = 0
        while i < 2:
            self.entries.append(tk.Entry(self,width=10))
            self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
            i += 1
        self.btn = tk.Button(self,text="multiply", command=lambda : self.multiply())
        self.btn.grid(row=i+1, columnspan=2,sticky='we')

    def multiply(self):
        pass

class sub02(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="This SubLayer 2")
        label.grid(row=0)
        self.entries=[]
        i = 0
        while i < 2:
            self.entries.append(tk.Entry(self,width=10))
            self.entries[i].grid(row=i+1,columnspan=2,sticky='w')
            i += 1
        self.btn = tk.Button(self,text="divide",command=lambda : self.divide())
        self.btn.grid(row=i+1, columnspan=2,sticky='we')

    def divide(self):
        pass

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

This code itself works, but when I need to create more of these frames, it becomes inconvenient. How could I make this code simpler? Like having that similar frame as a class, and the button as other class that do differ behaviour depend of the layer shown.

Thank you in advance

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

0

The canonical way to do this sort of thing is to create a class hierarchy for your Page classes and put common functionality in the base classes and derive subclasses from them that specify the behavior that differs between them. Below is how you could do that with the sample code in your question.

Since the things that are different between them are:

  1. The text displayed on the Label.
  2. The text displayed on the Button.
  3. The code in that's execute when the Button is clicked.

This means the derived classes only need to know what code to run in a generically named btn_func() method and what the text to displayed on the two widgets. The code below illustrates how to do that.

Note that I've changed the spelling of your class names to conform to the naming conventions describe in PEP 8 - Style Guide for Python Code.

import Tkinter as tk


class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.grid(row=1,columnspan=4,sticky='nsew')
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        for F in (Sub01, Sub02):
            page_name = F.__name__
            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame
            frame.grid(row=1,sticky="nsew")

        self.choices = {'Sub01','Sub02'}
        self.tkvar = tk.StringVar()
        self.tkvar.set('Sub01')
        self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
        self.popMenu.grid(row=0)
        self.show_frame()

        self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
        self.button1.grid(row=0, column=1)

    def show_frame(self):
        '''Show a frame for the given page name'''
        page_name = self.tkvar.get()
        frame = self.frames[page_name]
        frame.tkraise()


class BaseSubLayer(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text=self.lbl_text)
        label.grid(row=0)
        self.entries=[]
        i = 0
        while i < 2:
            self.entries.append(tk.Entry(self,width=10))
            self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
            i += 1
        self.btn = tk.Button(self,text=self.btn_func_name, command=self.btn_func)
        self.btn.grid(row=i+1, columnspan=2,sticky='we')

    def btn_func(self):
        raise NotImplementedError


class Sub01(BaseSubLayer):
    lbl_text = 'This SubLayer 1'
    btn_func_name = 'multiply'

    def btn_func(self):
        print('Running multiply() method.')


class Sub02(BaseSubLayer):
    lbl_text = 'This SubLayer 2'
    btn_func_name = 'divide'

    def btn_func(self):
        print('Running divide() method.')


if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

martineau
  • 119,623
  • 25
  • 170
  • 301
  • thankyou for the answer. but i cant run it with python 2.7, which say super() takes at least 1 argument (0 given). i've try to look for a clue bout this and found this, https://stackoverflow.com/questions/38963018/typeerror-super-takes-at-least-1-argument-0-given-error-is-specific-to-any. tried super(self).__init__(parent), or super(parent).__init__() and still not work – Shandy Yogaswara Surya Putra Jan 22 '21 at 01:19
  • the script is work when i change the BaseSub class like this : BaseSub(tk.Frame,object) and then change the super() like this : super(BaseSub,self).__init__(), but honestly idk what effect adding 'object' at BaseSub class to another class – Shandy Yogaswara Surya Putra Jan 22 '21 at 01:36
  • If you're using Python 2, you need to tag your questions with a "python-2.x". THe `super()` function doesn't work with `tkinter` in that version (it does in Python 3). I've modified my answer accordingly. – martineau Jan 22 '21 at 02:37
  • pardon me for that, and thank you for the answer, it's work perfectly – Shandy Yogaswara Surya Putra Jan 22 '21 at 03:13