2

I'm currently working with tkinter for a school-assignment, creating a graphical user-interface for a user to enter their inputs. I have decided to split the inputs in various pages, in order to not overwhelm the user with questions, and not need to scroll.
Each page has a series of Label and Entry, separated on a right and a left divisor, and I've somehow managed to get this to work on each page, with some effort. This is a simplified version of my working code:

import tkinter as tk


class Layers(tk.Frame):
    def __init__(self, root):
        super().__init__(root)

        self.layers = []

        self.layers.append(Welcome_Page(self))
        self.layers.append(Form_1(self))
        self.layers.append(Form_2(self))

        for layer in self.layers:
            layer.add_form(self)
            layer.add_buttons(self)
            layer.grid(row=0, column=0, sticky="nsew")

        self.layers[0].tkraise()


class Welcome_Page(tk.Frame):
    def __init__(self, root):
        super().__init__(root, width=600, height=800, background="red")

    def add_buttons(self, root):
        self.next = tk.Button(self, text="Next page", width=25, height=5, command=self.master.layers[1].tkraise)
        self.next.place(relx=1, rely=1, anchor="se")

        self.prev = tk.Button(self, text="Quit", width=25, height=5, command=self.master.master.destroy)
        self.prev.place(relx=0, rely=1, anchor="sw")
        pass

    def add_form(self, root):
        self.text_label = tk.Label(self, text="Welcome to this program")
        self.text_label.place(relx=0.5, rely=0, anchor="n")
        pass



class Form_1(tk.Frame):
    def __init__(self, root):
        super().__init__(root, width=600, height=800, background="yellow")

    def add_buttons(self, root):
        self.next = tk.Button(self, text="Next page", width=25, height=5, command=self.master.layers[2].tkraise)
        self.next.place(relx=1, rely=1, anchor="se")

        self.prev = tk.Button(self, text="Back", width=25, height=5, command=self.master.layers[0].tkraise)
        self.prev.place(relx=0, rely=1, anchor="sw")
        pass

    def add_form(self, root):
        self.text_label = tk.Label(self, text="Personal data")
        self.text_label.place(relx=0.5, rely=0, anchor="n")

        self.container_left = tk.Frame(self, background="#BAFFCE")
        self.container_right = tk.Frame(self, background="#72FF9A")
        self.container_left.grid(row=0, column=0, sticky="nsew")
        self.container_right.grid(row=0, column=1, sticky="nsew")
        self.grid_columnconfigure(0, weight=1, uniform="group1")
        self.grid_columnconfigure(1, weight=1, uniform="group1")
        self.grid_rowconfigure(0, weight=1)

        self.last_name_label = tk.Label(self.container_right, text="Last name")
        self.last_name_space = tk.Entry(self.container_right, text="lastname")
        self.last_name_label.grid(row=0, column=0, padx=(10,0), pady=(10,0))
        self.last_name_space.grid(row=0, column=1, padx=(5, 0), pady=(10,0))

        pass


class Form_2(tk.Frame):
    def __init__(self, root):
        super().__init__(root, width=600, height=800, background="gray")

    def add_buttons(self, root):
        self.next = tk.Button(self, text="Next page", width=25, height=5)
        self.next.place(relx=1, rely=1, anchor="se")

        self.prev = tk.Button(self, text="Back", width=25, height=5, command=self.master.layers[1].tkraise)
        self.prev.place(relx=0, rely=1, anchor="sw")
        pass

    def add_form(self, root):
        self.text_label = tk.Label(self, text="Third page")
        self.text_label.place(relx=0.5, rely=0, anchor="n")
        pass


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("600x800")

    window = Layers(root)
    window.pack(expand=True, fill="both")
    root.mainloop()

Nevertheless, while splitting each page into two different Frame() containers I have stumbled across two issues:

  • Setting the internal padding of the frame with ipadx and ipady doesn't seem to do anything. I have manually set each element inside it at its position with padx and pady, though, and this works fine, but I believe I should be able to use the internal padding for this instead on container_left and container_right.
  • Setting the containers for each page is redundant, since they will all be split into two frames. I have tried the following, but it doesn't work as I'm expecting (not at all).
class Layers(tk.Frame):
    def __init__(self, root):
        super().__init__(root)

        self.layers = []

        self.layers.append(Welcome_Page(self))
        self.layers.append(Form_1(self))
        self.layers.append(Form_2(self))

        for layer in self.layers:
            layer.add_form(self)
            layer.add_buttons(self)
            layer.grid(row=0, column=0, sticky="nsew")

            layer.container_left = tk.Frame(layer, background="#BAFFCE")
            layer.container_right = tk.Frame(layer, background="#72FF9A")
            layer.container_left.grid(row=0, column=0, sticky="nsew")
            layer.container_right.grid(row=0, column=1, sticky="nsew")
            layer.grid_columnconfigure(0, weight=1, uniform="group1")
            layer.grid_columnconfigure(1, weight=1, uniform="group1")
            layer.grid_rowconfigure(0, weight=1)

            print(layer)

        self.layers[0].tkraise()

The error I'm getting is AttributeError: 'Form_1' object has no attribute 'container_right'. What I get from this is that I haven't created the variable inside the class, but at some other place instead, even though I'm using layer.. How can I create the variables inside the classes, without reusing the code?

Any other suggestion is appreciated too, as I'm fairly new to Python and Tkinter.

2 Answers 2

0

You can see the effect of ipadx and ipady in your first code by changing line 17 to:

layer.grid(row=0, column=0, sticky="nsew", ipadx=30, ipady=30,)
Sign up to request clarification or add additional context in comments.

Comments

0

I know you mentioned wanting to keep the class structure the same, but it might be worth it to introduce a superclass for your form pages. From there, you can define add_form and add_buttons instance methods, and just call them internally from the __init__ of the superclass, instead of looping through the pages. So, like:

class FormPage(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent, width=600, height=800, background="yellow")
        self.parent=parent
    
        self.add_buttons()
        self.add_form()

    def add_buttons(self):
        self.next = tk.Button(self, text="Next page", width=25, height=5)
        self.next.place(relx=1, rely=1, anchor="se")

        self.prev = tk.Button(self, text="Back", width=25, height=5)
        self.prev.place(relx=0, rely=1, anchor="sw")

    def add_form(self):
        self.text_label = tk.Label(self) #use the configure method in child classes to set the text
        self.text_label.place(relx=0.5, rely=0, anchor="n")

        self.container_left = tk.Frame(self, background="#BAFFCE")
        self.container_right = tk.Frame(self, background="#72FF9A")
        self.container_left.grid(row=0, column=0, sticky="nsew")
        self.container_right.grid(row=0, column=1, sticky="nsew")
        self.grid_columnconfigure(0, weight=1, uniform="group1")
        self.grid_columnconfigure(1, weight=1, uniform="group1")
        self.grid_rowconfigure(0, weight=1)

So, the FormPage's initialisation will call the add_buttons and add_form methods automatically when a form page is instantiated. Then for a specific form, you could do:

class Form_1(FormPage):
    def __init__(self, parent):
        tk.Frame.__init__(parent) #call FormPage's initialisation

    def add_buttons(self):
        self.next.configure(command=self.parent.layers[1].tkraise)
        ...#add the specific functionality of the buttons using configure
    
    def add_form(self):
        super().add_form()
        self.text_Label.configure(text="Personal Information")

        self.last_name_label = tk.Label(self.container_right, text="Last name")
        self.last_name_space = tk.Entry(self.container_right, text="lastname")
        self.last_name_label.grid(row=0, column=0, padx=(10,0), pady=(10,0))
        self.last_name_space.grid(row=0, column=1, padx=(5, 0), pady=(10,0))

So, any child of FormPage has the attributes container_left and container_right. Then, if certain forms need more button, you can just override the method add_buttons in that form's class. Likewise for any other method. Then, you just need a place to store all your pages, like your Layers class. IMO, you don't need to place all the layers on a grid, because you'll be calling tkraise from the navigation buttons anyway. I think that your Layers class could be reduced to:

class Layers(tk.Tk):
    def __init__(self):
        super().__init__(self)

        self.layers = []

        for F in {Welcome_Page, Form_1, Form_2}:
            self.layers.append(F(self))

        self.layers[0].tkraise()

In general, inheritance hierarchies are a great way to reduce code repetition. Hope this helps :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.