2

I am writing a Python program with a GUI and have selected tkinter to do so. I have some experience with Python, but I am new to tkinter and GUI programming.

I have been reading a few tutorials about how to use tkinter. What I could find was very basic, such as "how to display a button". This leaves me struggling to identify a useful model for structuring the part of my program that defines the UI.

So far my searches only yielded 1 guide for structuring a python/tkinter GUI in an OOP style: pythonprogramming.net

Although this is a welcome example and very helpful in its specificity, it seems to me as though the inheritance of tkinter classes and adding new unrelated code to those new classes violates a strict separation of concerns. It looks very convenient on the short term, but I can't tell if it has undesirable consequences long-term.

As an alternative I created another example, in which I made similar classes, but avoided inheriting from tkinter classes, by composing various tkinter objects. This keeps functionality separated with only a couple of additional methods.

I would appreciate feedback on which approach is more useful as a UI grows in complexity. This could include specific suggestions on other models to use, links to information on the subject, source code examples of programs using tkinter, and so on.

Example of inheritance based on pythonprogramming.net:

import tkinter as tk


class AppMain(tk.Tk):

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

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        frame = Page(container, self)

        self.frames[Page] = frame

        frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(Page)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()


class Page(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = tk.Label(self, text="Start Page", font=("Verdana", 12))
        label.pack(padx=10, pady=10)


def main():
    application = AppMain()
    application.mainloop()


if __name__ == "__main__":
    main()

Alternative without inheritance:

EDIT 1: Add grid variable to Page init

import tkinter as tk


class AppMain(object):

    def __init__(self):
        self.root = tk.Tk()

        container = tk.Frame(self.root)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.pages = {}

        page = Page(container, self.root, {"row": 0, "column": 0, "sticky": "nsew"})

        self.pages[Page] = page

        self.show_page(Page)

    def show_page(self, container):
        page = self.pages[container]
        page.show()

    def run(self):
        self.root.mainloop()


class Page(object):

    def __init__(self, parent, controller, grid):
        self.frame = tk.Frame(parent)
        self.frame.grid(**grid)

        label = tk.Label(self.frame, text="Start Page", font=("Verdana", 12))
        label.pack(padx=10, pady=10)

    def show(self):
        self.frame.tkraise()


def main():
    application = AppMain()
    application.run()


if __name__ == "__main__":
    main()

1 Answer 1

3

The main advantage of inheriting from a tkinter widget (typically a Frame) is so that you can treat that object like any other widget when it comes to laying out your UI.

For example, a typical UI might be made of a toolbar, a side panel for navigation, a main work area, and perhaps a statusbar at the bottom. By creating a class for each of these which inherits from Frame, you can lay out your GUI like so:

toolbar = Toolbar(root)
sidebar = Sidebar(root)
main = WorkArea(root)
statusbar = Statusbar(root)

toolbar.pack(side="top", fill="x")
statusbar.pack(side="bottom", fill="x")
sidebar.pack(side="left", fill="y")
main.pack(side="right", fill="both", expand=True)

If you use composition instead of inheritance, then either your main program needs to know something about the internal structure of your objects, or your objects need to know something about the root window.

For example, you might have to name your inner frame of each section with a common name so that the main program can lay it out:

toolbar.inner_frame.pack(side="top", fill="x")
statusbar.inner_frame.pack(side="bottom", fill="x")
sidebar.inner_frame.pack(side="left", fill="y")
main.inner_frame.pack(side="right", fill="both", expand=True)

In the example in your question where you inherit from object, your Page class has to know that the root window is using grid, and further has to know that it need to place itself in a specific row and column. This tightly couples these two parts of your code together -- you can't modify one part of your code without having to modify other parts.

For example, let's say you have a dozen pages. After working on the code for a while you decide that AppMain needs to place an additional widget in row zero of the container. You now have to go in and modify all dozen page classes so that they can place themselves in row 1 instead of row 0.

Sign up to request clarification or add additional context in comments.

2 Comments

Isn't inheritance more helpful in situations where a modified version of the superclass is needed? Here inheritance from Frame is used for the Page class, indicates to me that it is not intended for use as a modified Frame, but rather a layouting class. So considering whether Page "is a" Frame, I would say no. Furthermore, the inheritance breaks encapsulation of Frame. If a class for containing or layouting is needed, then why not create that class? Inheriting from a widget that needs layouting and composing other widgets into it seems to me like blending together concerns.
"Furthermore, the inheritance breaks encapsulation of Frame. If a class for containing or layouting is needed, then why not create that class?" - because there is no way to do that in python. You would have to modify the underlying C implementation. If you want "a class for containing or layouting", inheriting from Frame is precisely how you do that - you inherit from Frame and then extend it with whatever additional features you need.

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.