Widgets in einer separaten Klasse erstellen

Zu Tkinter habe ich bereits einige Artikel veröffentlicht. Bei einem dieser Beiträge ging es um die Verwendung des Grid-Layout-Managers. Der Code zu jenem Projekt sah folgendermaßen aus:

#!/usr/bin/env python3

import tkinter as tk
from tkinter import ttk


class MainWindow:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("Main Window")

        # Set width and height of the window
        window_width = 400
        window_height = 100

        # Get the screen dimension
        screen_width = self.window.winfo_screenwidth()
        screen_height = self.window.winfo_screenheight()

        # Find the center point
        center_x = int(screen_width / 2 - window_width / 2)
        center_y = int(screen_height / 2 - window_height / 2)

        # Set the position of the window to the center of the screen
        self.window.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")

        # Window is not resizable
        self.window.resizable(False, False)

        # All widgets will be part of this frame.
        self.content = ttk.Frame(self.window, padding=(10, 10, 10, 10))

        # Entry
        self.first_input_field = ttk.Entry(self.content)
        self.second_input_field = ttk.Entry(self.content)

        # Button
        self.calculate_button = ttk.Button(
            self.content,
            text="Calculate",
            command=self.calculate
        )

        # Label
        self.result_label = ttk.Label(
            self.content,
            text="Result label"
        )

        # Layout manager
        # Add frame and entries
        self.content.grid(column=0, row=0, sticky="nsew")
        self.first_input_field.grid(column=0, row=0)
        self.second_input_field.grid(column=1, row=0)

        # Add button
        self.calculate_button.grid(column=0, row=1, sticky="nsew")

        # Add label
        self.result_label.grid(column=0, row=2, sticky="nsew")

    def calculate():
        pass 

    def mainloop(self):
        self.window.mainloop()


if __name__ == "__main__":
    app_instance = MainWindow()
    app_instance.mainloop()

Sobald dieses Skript ausgeführt wird, erscheint folgendes Fenster:

Tkinter Main Window
Tkinter-Fenster

Es sind vier Widgets zu sehen: Zwei Eingabefelder, ein Button und ein Label. Erstellt werden sie in der Klasse MainWindow über diesen Code:

# Entry
self.first_input_field = ttk.Entry(self.content)
self.second_input_field = ttk.Entry(self.content)

# Button
self.calculate_button = ttk.Button(
    self.content,
    text="Calculate",
    command=self.calculate
)

# Label
self.result_label = ttk.Label(
    self.content,
    text="Result label"
)

Da dieses Programm nur vier Widgets hat, ist der Code noch relativ übersichtlich. Das mag sich aber schnell ändern, wenn noch mehr Widgets hinzukommen. Dann bietet es sich an, die Widgets in eine separate Datei bzw. Klasse auszulagern. Und genau darum geht es in diesem Blogbeitrag. Nachfolgend möchte ich den Code dieses kleinen Programms dementsprechend ändern.

Dazu erstelle ich eine neue Datei, die die Bezeichnung app_widgets.py erhält. Diese Datei hat die Klasse class CustomWidgets zum Inhalt, die den Code für das Erstellen der Widgets (Entry, Button, Label) erhalten soll.

from tkinter import ttk

class CustomWidgets:
    def __init__(self, parent, calculate_callback):
        # Entry fields
        self.first_input_field = ttk.Entry(parent)
        self.second_input_field = ttk.Entry(parent)

        # Button
        self.calculate_button = ttk.Button(
            parent, text="Calculate", command=calculate_callback
        )

        # Label
        self.result_label = ttk.Label(parent, text="Result label")

    def place_widgets(self):
        self.first_input_field.grid(column=0, row=0)
        self.second_input_field.grid(column=1, row=0)
        self.calculate_button.grid(column=0, row=1, sticky="nsew")
        self.result_label.grid(column=0, row=2, sticky="nsew")

Der Code, mit dem die Widgets erstellt werden, ist im Wesentlichen identisch mit dem ursprünglichen Code. Es ist wichtig zu beachten, dass die Widgets Bestandteil eines Frame-Widgets sind:

 # All widgets will be part of this frame.
self.content = ttk.Frame(self.window, padding=(10, 10, 10, 10))

Daher enthält die Init-Funktion den entsprechenden Parameter parent:

class CustomWidgets:
    def __init__(self, parent, calculate_callback):

Und damit nach einem Klick auf den Button auch der dafür hinterlegte Code ausgeführt werden kann, ist außerdem der Parameter calculate_callback vorhanden.

Zur Datei, die die Klasse MainWindow enthält, muss noch eine Import-Anweisung hinzugefügt werden:

from app_widgets import CustomWidgets

Dadurch ist es nun möglich, die Widget-Klasse zu initialisieren:

self.widgets = CustomWidgets(self.content, self.calculate)
self.widgets.place_widgets()

Der komplette Code dieser Datei sieht nun wie folgt aus:

import tkinter as tk
from tkinter import ttk

from app_widgets import CustomWidgets


class MainWindow:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("Main Window")

        # Set width and height of the window
        window_width = 400
        window_height = 100

        # Get the screen dimension
        screen_width = self.window.winfo_screenwidth()
        screen_height = self.window.winfo_screenheight()

        # Find the center point
        center_x = int(screen_width / 2 - window_width / 2)
        center_y = int(screen_height / 2 - window_height / 2)

        # Set the position of the window to the center of the screen
        self.window.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")

        # Window is not resizable
        self.window.resizable(False, False)

        # All widgets will be part of this frame.
        self.content = ttk.Frame(self.window, padding=(10, 10, 10, 10))
        self.content.grid(column=0, row=0, sticky="nsew")

        # Initialize custom widgets
        self.widgets = CustomWidgets(self.content, self.calculate)
        self.widgets.place_widgets()

    def calculate(self):
        pass

    def mainloop(self):
        self.window.mainloop()


if __name__ == "__main__":
    app_instance = MainWindow()
    app_instance.mainloop()