Get AI summaries of any video or article — Sign up free
Python Tkinter Tutorial (Part 2): Using Classes for Functionality and Organization thumbnail

Python Tkinter Tutorial (Part 2): Using Classes for Functionality and Organization

Corey Schafer·
4 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Copy-pasting Tkinter frame code can create subtle bugs when event handlers depend on global widget names.

Briefing

Using Tkinter with classes fixes a common “wrong widget” bug that appears when event handlers rely on global variables and duplicated widget names. The tutorial starts with a two-panel app—two side-by-side input forms—built by copying the first frame’s code to create the second. That shortcut works for layout, but it breaks behavior: pressing Enter or clicking the button in one panel triggers logic tied to the other panel’s widgets. The root cause isn’t just reused variable names; the shared `add_to_list` handler reaches into the global namespace to find `entry` and `text_list`, so it ends up referencing whichever widgets were created last.

The solution is to move each frame’s widgets and its event handlers into a dedicated class. Instead of keeping `entry`, `text_list`, and the `add_to_list` function floating in global scope, the code wraps them as instance attributes (e.g., `self.entry`, `self.text_list`) inside an `InputForm` frame subclass. The handler becomes a method of that same class, so it naturally reads from the correct instance: `self.entry.get()` adds the right text to `self.text_list`. With two separate `InputForm` instances created inside the main `Application` class, each panel maintains its own state and bindings, eliminating the cross-wiring that caused the left form to fail.

From there, the tutorial walks through several ways to structure Tkinter classes, then recommends a cleaner pattern: inherit from `tk.Tk` for the main application class, call the parent constructor via `super().__init__()` inside `__init__`, and run `mainloop()` from a small `main()` function guarded by `if __name__ == "__main__":`. This keeps script startup logic at the top while still requiring class definitions before use.

The payoff goes beyond fixing the immediate bug. Once the frame logic lives inside a class, adding new functionality becomes localized and safer. A clear button is added only inside the `InputForm` class, along with a `clear_list` method that deletes the listbox contents for that specific instance. The result is two independent “clear” actions—clicking clear on the left affects only the left listbox, and the right button affects only the right listbox—without touching the main application code.

The tutorial closes by emphasizing maintainability: class-based GUI components can be split into separate files as the app grows (for example, moving `InputForm` into a `utilities.py` module and importing it). The overall message is practical: even for small Tkinter apps, classes prevent fragile global-state patterns, reduce duplicated code, and make future UI changes far less error-prone.

Cornell Notes

A two-panel Tkinter app breaks when both frames share event logic that pulls widgets from the global namespace. The handler ends up referencing the most recently created `entry` and `text_list`, so one panel’s Enter/button actions affect the other—or stop working entirely. The fix is to encapsulate each frame’s widgets and handlers inside an `InputForm` class, storing widgets as instance attributes like `self.entry` and `self.text_list` and making `add_to_list` a method of that class. With two separate `InputForm` instances, each panel operates on its own state. The tutorial also recommends structuring the main app via `tk.Tk` inheritance, a `main()` function, and an `if __name__ == "__main__":` guard for clean startup.

Why does the left input form stop working when the second frame is created by copying code?

The shared `add_to_list` handler reads `entry` and `text_list` from the global namespace. When the second frame recreates widgets (including an `entry` and a `text_list`), those global names now point to the second frame’s widgets. Even if variable names are changed, the handler still references whichever widgets are currently bound in the global scope, so the Enter key/button actions don’t target the intended panel.

How does moving `add_to_list` into the frame class fix the cross-panel bug?

When `add_to_list` becomes a method on `InputForm`, it uses instance attributes—`self.entry.get()` and `self.text_list.insert(...)`—instead of global variables. Each `InputForm` instance owns its own `entry` and `text_list`, so the handler always updates the correct listbox for that specific frame.

What are the recommended patterns for structuring a Tkinter app with classes in this tutorial?

For the main window, the tutorial recommends inheriting from `tk.Tk` and calling `super().__init__()` inside `__init__`. It also recommends placing startup code in a `main()` function and running it only under `if __name__ == "__main__":` so the script behaves correctly when imported.

Why does adding a “Clear” button become easier after refactoring into classes?

Because the clear behavior belongs to the frame that owns the listbox. Adding a clear button and a `clear_list` method inside `InputForm` lets each instance clear only its own `self.text_list` (via `delete(0, tk.END)`). The main `Application` class doesn’t need new event wiring or duplicated handlers.

What does the tutorial mean by “instance variables” like `self.entry` and `self.text_list`?

Variables prefixed with `self` are stored on the object instance, making them accessible across methods of that class. Without `self`, a widget created inside `__init__` would be a local variable and wouldn’t be reliably accessible from other methods like `add_to_list` or `clear_list`.

Review Questions

  1. In the original non-class approach, what mechanism caused the Enter key handler to update the wrong listbox?
  2. How do instance attributes (`self.entry`, `self.text_list`) change the way event handlers locate widgets?
  3. What benefits does the tutorial claim you gain when you add new UI features (like a clear button) after refactoring into frame classes?

Key Points

  1. 1

    Copy-pasting Tkinter frame code can create subtle bugs when event handlers depend on global widget names.

  2. 2

    A handler that reads widgets from the global namespace will often target the most recently created widgets, breaking earlier frames.

  3. 3

    Encapsulating each frame’s widgets and handlers inside a dedicated class (e.g., `InputForm`) prevents cross-panel interference.

  4. 4

    Using instance attributes (`self.entry`, `self.text_list`) ensures methods operate on the correct frame instance.

  5. 5

    Inheriting from `tk.Tk` for the main application and calling `super().__init__()` inside `__init__` leads to cleaner structure.

  6. 6

    A `main()` function plus `if __name__ == "__main__":` keeps startup logic organized and import-safe.

  7. 7

    Once GUI components live in classes, adding features like a clear button becomes localized and reduces duplicated wiring.

Highlights

The failure wasn’t just reused widget names—it was the `add_to_list` function reaching into global state, so it referenced the wrong frame’s widgets.
Refactoring into an `InputForm` frame class makes event handlers instance-based, so Enter/button actions always update the correct listbox.
A clear button added inside the frame class can delete only that frame’s listbox (`delete(0, tk.END)`), without touching the main application logic.
The tutorial’s preferred startup pattern uses `tk.Tk` inheritance, `super().__init__()`, a `main()` function, and an `if __name__ == "__main__":` guard.

Topics

  • Tkinter Classes
  • Event Handlers
  • Frame Encapsulation
  • Python OOP
  • GUI Organization

Mentioned